Skip to content

Commit

Permalink
Add flag to sort multisig keys (BIP67)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sjors authored and greenaddress committed Dec 9, 2019
1 parent 9fd3bd1 commit f4364a9
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 8 deletions.
9 changes: 5 additions & 4 deletions include/wally_script.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ extern "C" {
#define WALLY_WITNESSSCRIPT_MAX_LEN 35 /** (PUSH OF)0 [SHA256] */

/* Script creation flags */
#define WALLY_SCRIPT_HASH160 0x1 /** hash160 input bytes before using them */
#define WALLY_SCRIPT_SHA256 0x2 /** sha256 input bytes before using them */
#define WALLY_SCRIPT_AS_PUSH 0x4 /** Return a push of the generated script */
#define WALLY_SCRIPT_HASH160 0x1 /** hash160 input bytes before using them */
#define WALLY_SCRIPT_SHA256 0x2 /** sha256 input bytes before using them */
#define WALLY_SCRIPT_AS_PUSH 0x4 /** Return a push of the generated script */
#define WALLY_SCRIPT_MULTISIG_SORTED 0x8 /** Sort public keys (BIP67) */

/* Script opcodes */
#define OP_0 0x00
Expand Down Expand Up @@ -326,7 +327,7 @@ WALLY_CORE_API int wally_scriptpubkey_p2sh_from_bytes(
* :param bytes: Compressed public keys to create a scriptPubkey from.
* :param bytes_len: Length of ``bytes`` in bytes. Must be a multiple of ``EC_PUBLIC_KEY_LEN``.
* :param threshold: The number of signatures that must match to satisfy the script.
* :param flags: Must be zero.
* :param flags: Must be ``WALLY_SCRIPT_MULTISIG_SORTED`` for BIP67 sorting or 0.
* :param bytes_out: Destination for the resulting scriptPubkey.
* :param len: The length of ``bytes_out`` in bytes.
* :param written: Destination for the number of bytes written to ``bytes_out``.
Expand Down
18 changes: 15 additions & 3 deletions src/script.c
Original file line number Diff line number Diff line change
Expand Up @@ -541,34 +541,46 @@ int wally_scriptpubkey_p2sh_from_bytes(
return ret;
}

static int pubkey_compare(const void *a, const void *b)
{
return memcmp(a, b, EC_PUBLIC_KEY_LEN);
}

int wally_scriptpubkey_multisig_from_bytes(
const unsigned char *bytes, size_t bytes_len, uint32_t threshold,
uint32_t flags, unsigned char *bytes_out, size_t len, size_t *written)
{
size_t n_pubkeys = bytes_len / EC_PUBLIC_KEY_LEN;
size_t script_len = 3 + (n_pubkeys * (EC_PUBLIC_KEY_LEN + 1));
size_t i;
unsigned char pubkey_bytes[16 * EC_PUBLIC_KEY_LEN];

if (written)
*written = 0;

if (!bytes || !bytes_len || bytes_len % EC_PUBLIC_KEY_LEN ||
n_pubkeys < 1 || n_pubkeys > 16 || threshold < 1 || threshold > 16 ||
threshold > n_pubkeys || flags || !bytes_out || !written)
threshold > n_pubkeys || (flags & ~WALLY_SCRIPT_MULTISIG_SORTED) ||
!bytes_out || !written)
return WALLY_EINVAL;

if (len < script_len) {
*written = script_len;
return WALLY_OK;
}

memcpy(pubkey_bytes, bytes, bytes_len);
if (flags & WALLY_SCRIPT_MULTISIG_SORTED) {
qsort(pubkey_bytes, n_pubkeys, EC_PUBLIC_KEY_LEN, pubkey_compare);
}

*bytes_out++ = v_to_op_n(threshold);
for (i = 0; i < n_pubkeys; ++i) {
*bytes_out++ = EC_PUBLIC_KEY_LEN;
memcpy(bytes_out, bytes, EC_PUBLIC_KEY_LEN);
memcpy(bytes_out, pubkey_bytes + i * EC_PUBLIC_KEY_LEN, EC_PUBLIC_KEY_LEN);
bytes_out += EC_PUBLIC_KEY_LEN;
bytes += EC_PUBLIC_KEY_LEN;
}
wally_clear(pubkey_bytes, sizeof(pubkey_bytes));
*bytes_out++ = v_to_op_n(n_pubkeys);
*bytes_out = OP_CHECKMULTISIG;
*written = script_len;
Expand Down
10 changes: 9 additions & 1 deletion src/test/test_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
SCRIPT_TYPE_P2SH = 0x4
SCRIPT_TYPE_MULTISIG = 0x20

WALLY_SCRIPT_MULTISIG_SORTED = 0x8

SCRIPT_HASH160 = 0x1
SCRIPT_SHA256 = 0x2

Expand All @@ -21,7 +23,10 @@
PKU, PKU_LEN = make_cbuffer('11' * 65) # Fake uncompressed pubkey
SH, SH_LEN = make_cbuffer('11' * 20) # Fake script hash
MPK_2, MPK_2_LEN = make_cbuffer('11' * 33 * 2) # Fake multiple (2) pubkeys
MPK_3, MPK_3_LEN = make_cbuffer('11' * 33 * 3) # Fake multiple (3) pubkeys
MPK_3, MPK_3_LEN = make_cbuffer('11' * 33 * 3) # Fake multiple (3) pubkeys
MPK_3_SORTED, MPK_3_SORTED_LEN = make_cbuffer('01'*33 + '10'*33 + '11'*33) # Fake multiple (3) sorted pubkeys
MPK_3_SORTED_REVERSE, MPK_3_SORTED_LEN = make_cbuffer('11'*33 + '10'*33 + '01'*33) # Fake multiple (3) sorted pubkeys, in reverse order
MPK_3_SORTED_KEY_PUSH = '21'+'01'*33 + '21'+'10'*33 + '21'+'11'*33
MPK_17, MPK_17_LEN = make_cbuffer('11' * 33 * 17) # Fake multiple (17) pubkeys

SIG, SIG_LEN = make_cbuffer('11' * 64) # Fake signature
Expand Down Expand Up @@ -156,6 +161,9 @@ def test_scriptpubkey_multisig_from_bytes(self):
[(MPK_3, MPK_3_LEN, 1, 0, out, out_len), '51'+('21'+'11'*33)*3+'53ae'], # 1of3
[(MPK_3, MPK_3_LEN, 2, 0, out, out_len), '52'+('21'+'11'*33)*3+'53ae'], # 2of3
[(MPK_3, MPK_3_LEN, 3, 0, out, out_len), '53'+('21'+'11'*33)*3+'53ae'], # 3of3
[(MPK_3_SORTED, MPK_3_SORTED_LEN, 1, 0, out, out_len), '51'+ MPK_3_SORTED_KEY_PUSH + '53ae'], # 1of3 sorted
[(MPK_3_SORTED, MPK_3_SORTED_LEN, 1, WALLY_SCRIPT_MULTISIG_SORTED, out, out_len), '51'+ MPK_3_SORTED_KEY_PUSH + '53ae'], # 1of3 sorted (WALLY_SCRIPT_MULTISIG_SORTED should have no effect)
[(MPK_3_SORTED_REVERSE, MPK_3_SORTED_LEN, 1, WALLY_SCRIPT_MULTISIG_SORTED, out, out_len), '51'+ MPK_3_SORTED_KEY_PUSH + '53ae'], # 1of3 sorted reverse (BIP67)
]
for args, exp_script in valid_args:
script_len = 3 + (args[1] // 33 * (33 + 1))
Expand Down

0 comments on commit f4364a9

Please sign in to comment.