diff --git a/doc/COMMANDS.md b/doc/COMMANDS.md index 175c8a2..334ad62 100644 --- a/doc/COMMANDS.md +++ b/doc/COMMANDS.md @@ -83,7 +83,7 @@ Transactions signed with ECDSA are currently not supported. | P1 Value | Usage | CData | | --- | --- | --- | | 0x00 | Sending transaction metadata | `version (2)` \|\| `output_len (1)` \|\| `input_len (1)` | -| 0x01 | Sending a tx output | `value (8)` \|\| `script_public_key (32/33)` | +| 0x01 | Sending a tx output | `value (8)` \|\| `script_public_key (34/35)` | | 0x02 | Sending a tx input | `value (8)` \|\| `tx_id (32)` \|\| `address_type (1)` \|\| `address_index (4)` \|\| `outpoint_index (1)` | | 0x03 | Requesting for next signature | - | diff --git a/doc/TRANSACTION.md b/doc/TRANSACTION.md index e2415e4..9bc76bc 100644 --- a/doc/TRANSACTION.md +++ b/doc/TRANSACTION.md @@ -10,7 +10,7 @@ The base unit in Kaspa is the KAS and the smallest unit used in raw transaction ## Address format -Kaspa addresses begin with `kaspa:` followed by 61 base32 characters for a total of `67` bytes for Schnorr-signed addresses. +Kaspa addresses begin with `kaspa:` followed by 61 base32 characters for a total of `67` bytes for Schnorr-signed and P2SH addresses. P2SH addresses are supported only as a send address by this app. For ECDSA-signed addresses (supported by this app only as a send address), it begins with `kaspa:` followed by 63 bytes for a total of `69` bytes. @@ -54,7 +54,7 @@ Total bytes: 43 (max) | Field | Size (bytes) | Description | | --- | --- | --- | | `value` | 8 | The amount of KAS in sompi that will go send to the address | -| `script_public_key` | 35 | Schnorr: `20` + public_key (32 bytes) + `ac`
ECDSA: `20` + public_key (33 bytes) + `ab` | +| `script_public_key` | 35 | Schnorr: `20` + public_key (32 bytes) + `ac`
ECDSA: `20` + public_key (33 bytes) + `ab`
P2SH: `aa20` + public_key (32 bytes) + `87` | ### Transaction Requirements - Fee = (total inputs amount) - (total outputs amount) diff --git a/src/address.c b/src/address.c index e587ecb..55c41ab 100644 --- a/src/address.c +++ b/src/address.c @@ -35,15 +35,17 @@ size_t compress_public_key(const uint8_t public_key[static 64], address_type_e address_type, uint8_t *out) { size_t compressed_pub_size = 0; - if (address_type == SCHNORR) { + if (address_type == SCHNORR || address_type == P2SH) { compressed_pub_size = 32; memmove(out, public_key, 32); - } else { + } else if (address_type == ECDSA) { compressed_pub_size = 33; // If Y coord is even, first byte is 0x02. if odd then 0x03 out[0] = public_key[63] % 2 == 0 ? 0x02 : 0x03; // We copy starting from the 2nd byte memmove(out + 1, public_key, 32); + } else { + return 0; } return compressed_pub_size; @@ -61,6 +63,9 @@ bool address_from_pubkey(const uint8_t public_key[static 64], if (address_type == ECDSA) { address_len = ECDSA_ADDRESS_LEN; version = 1; + } else if (address_type == P2SH) { + address_len = SCHNORR_ADDRESS_LEN; + version = 8; } if (out_len < address_len) { @@ -79,6 +84,10 @@ bool address_from_pubkey(const uint8_t public_key[static 64], size_t compressed_pub_size = compress_public_key(public_key, address_type, compressed_public_key); + if (compressed_pub_size == 0) { + return false; + } + // First part of the address is "kaspa:" memmove(address, hrp, sizeof(hrp)); address[5] = ':'; diff --git a/src/sighash.c b/src/sighash.c index 9e464c9..f7153d3 100644 --- a/src/sighash.c +++ b/src/sighash.c @@ -122,12 +122,21 @@ static void calc_outputs_hash(transaction_t* tx, uint8_t* out_hash) { inner_buffer, 2); // Write the output script version, assume 0 - // First byte is always the length of the following public key - // Last byte is always 0xac (op code for normal transactions) - uint8_t script_len = tx->tx_outputs[i].script_public_key[0] + 2; - write_u64_le(inner_buffer, - 0, - script_len); // Write the number of bytes of the script public key + uint8_t script_len = 0; + if (tx->tx_outputs[i].script_public_key[0] == 0xaa) { + // P2SH script public key is always 35 bytes, + // always begins with 0xaa and ends with 0x87 + script_len = 35; + write_u64_le(inner_buffer, 0, 35); + } else { + // First byte is always the length of the following public key + // Last byte is always 0xac (op code for normal transactions) + script_len = tx->tx_outputs[i].script_public_key[0] + 2; + write_u64_le(inner_buffer, + 0, + script_len); // Write the number of bytes of the script public key + } + hash_update(&inner_hash_writer, inner_buffer, 8); hash_update(&inner_hash_writer, tx->tx_outputs[i].script_public_key, script_len); } diff --git a/src/transaction/deserialize.c b/src/transaction/deserialize.c index 687a548..8c8303b 100644 --- a/src/transaction/deserialize.c +++ b/src/transaction/deserialize.c @@ -34,8 +34,30 @@ parser_status_e transaction_output_deserialize(buffer_t *buf, transaction_output } size_t script_len = (size_t) * (buf->ptr + buf->offset); - // Can only be length 32 or 33. Fail it otherwise: - if (script_len == 0x20 || script_len == 0x21) { + + if (script_len == OP_BLAKE2B) { + // P2SH = 0xaa + 0x20 + (pubkey) + 0x87 + // script len is actually the second byte if the first one is 0xaa + script_len = (size_t) * (buf->ptr + buf->offset + 1); + + if (!buffer_can_read(buf, script_len + 3)) { + return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR; + } + + uint8_t sig_op_code = *(buf->ptr + buf->offset + script_len + 2); + + if (script_len == 0x20 && sig_op_code != OP_EQUAL) { + return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR; + } + + memcpy(txout->script_public_key, buf->ptr + buf->offset, script_len + 3); + + if (!buffer_seek_cur(buf, script_len + 3)) { + return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR; + } + } else if (script_len == 0x20 || script_len == 0x21) { + // P2PK + // Can only be length 32 or 33. Fail it otherwise: if (!buffer_can_read(buf, script_len + 2)) { return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR; } @@ -48,11 +70,11 @@ parser_status_e transaction_output_deserialize(buffer_t *buf, transaction_output } memcpy(txout->script_public_key, buf->ptr + buf->offset, script_len + 2); - } else { - return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR; - } - if (!buffer_seek_cur(buf, script_len + 2)) { + if (!buffer_seek_cur(buf, script_len + 2)) { + return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR; + } + } else { return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR; } diff --git a/src/transaction/serialize.c b/src/transaction/serialize.c index da30e60..7d414f0 100644 --- a/src/transaction/serialize.c +++ b/src/transaction/serialize.c @@ -82,11 +82,19 @@ int transaction_output_serialize(const transaction_output_t *txout, uint8_t *out write_u64_be(out, offset, txout->value); offset += 8; - size_t script_len = txout->script_public_key[0]; + if (txout->script_public_key[0] == OP_BLAKE2B) { + size_t script_len = txout->script_public_key[1]; - memcpy(out + offset, txout->script_public_key, script_len + 2); + memcpy(out + offset, txout->script_public_key, script_len + 3); - offset += script_len + 2; + offset += script_len + 3; + } else { + size_t script_len = txout->script_public_key[0]; + + memcpy(out + offset, txout->script_public_key, script_len + 2); + + offset += script_len + 2; + } return (int) offset; } diff --git a/src/transaction/types.h b/src/transaction/types.h index f7048ff..49eaed2 100644 --- a/src/transaction/types.h +++ b/src/transaction/types.h @@ -60,8 +60,10 @@ typedef enum { * Enumeration of op codes */ typedef enum { - OP_CHECKSIG = 0xac, // Used for SCHNORR output - OP_CHECKSIGECDSA = 0xab // Used for ECDSA output + OP_CHECKSIG = 0xac, // Used for SCHNORR output + OP_CHECKSIGECDSA = 0xab, // Used for ECDSA output + OP_BLAKE2B = 0xaa, // Used for P2SH (start) + OP_EQUAL = 0x87 // Used for P2SH (end) } op_code_e; typedef struct { @@ -75,7 +77,7 @@ typedef struct { typedef struct { uint64_t value; - uint8_t script_public_key[35]; // In hex: 20 + public_key_hex + ac (34/35 bytes total) + uint8_t script_public_key[40]; // In hex: 20 + public_key_hex + ac (34/35 bytes total) } transaction_output_t; typedef struct { diff --git a/src/transaction/utils.c b/src/transaction/utils.c index 7fe4798..4a56eff 100644 --- a/src/transaction/utils.c +++ b/src/transaction/utils.c @@ -41,11 +41,15 @@ bool transaction_utils_check_encoding(const uint8_t* memo, uint64_t memo_len) { void script_public_key_to_address(uint8_t* out_address, uint8_t* in_script_public_key) { uint8_t public_key[64] = {0}; // public script keys begin with the length, followed by the amount of data - size_t data_len = (size_t) in_script_public_key[0]; + size_t first_byte = (size_t) in_script_public_key[0]; address_type_e type = SCHNORR; size_t address_len = SCHNORR_ADDRESS_LEN; - if (data_len == 0x21) { + if (first_byte == 0xaa) { + type = P2SH; + address_len = SCHNORR_ADDRESS_LEN; + memmove(public_key, in_script_public_key + 2, 32); + } else if (first_byte == 0x21) { // We're dealing with ECDSA instead: type = ECDSA; address_len = ECDSA_ADDRESS_LEN; diff --git a/src/types.h b/src/types.h index 7f9a56e..245e128 100644 --- a/src/types.h +++ b/src/types.h @@ -75,7 +75,8 @@ typedef enum { typedef enum { SCHNORR, // Display the 61 byte address for schnorr - ECDSA // Display the 63 byte address for ecdsa + ECDSA, // Display the 63 byte address for ecdsa + P2SH // Display the 61 byte address for p2sh (also schnorr) } address_type_e; /** diff --git a/tests/application_client/kaspa_transaction.py b/tests/application_client/kaspa_transaction.py index 07a5fa0..3a031a4 100644 --- a/tests/application_client/kaspa_transaction.py +++ b/tests/application_client/kaspa_transaction.py @@ -179,7 +179,11 @@ def _calc_outputs_hash(self) -> bytes: for txout in self.tx.outputs: inner_hash.update(txout.value.to_bytes(8, "little")) inner_hash.update((0).to_bytes(2, "little")) # assume script version 0 - inner_hash.update((txout.script_public_key[0] + 2).to_bytes(8, "little")) + if txout.script_public_key[0] == 0xaa: + inner_hash.update((35).to_bytes(8, "little")) + else: + inner_hash.update((txout.script_public_key[0] + 2).to_bytes(8, "little")) + inner_hash.update(txout.script_public_key) return inner_hash.digest() diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00000.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00000.png new file mode 100644 index 0000000..8842989 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00000.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00001.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00001.png new file mode 100644 index 0000000..1321e8b Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00001.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00002.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00002.png new file mode 100644 index 0000000..8ada473 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00002.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00003.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00003.png new file mode 100644 index 0000000..b5655aa Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00003.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00004.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00004.png new file mode 100644 index 0000000..4ba0ac3 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00004.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00005.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00005.png new file mode 100644 index 0000000..2f595a3 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00005.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00006.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00006.png new file mode 100644 index 0000000..1267831 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00006.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00007.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00007.png new file mode 100644 index 0000000..66c411c Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00007.png differ diff --git a/tests/snapshots/nanos/test_sign_tx_p2sh/00008.png b/tests/snapshots/nanos/test_sign_tx_p2sh/00008.png new file mode 100644 index 0000000..9ab6248 Binary files /dev/null and b/tests/snapshots/nanos/test_sign_tx_p2sh/00008.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_p2sh/00000.png b/tests/snapshots/nanosp/test_sign_tx_p2sh/00000.png new file mode 100644 index 0000000..df51419 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_p2sh/00000.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_p2sh/00001.png b/tests/snapshots/nanosp/test_sign_tx_p2sh/00001.png new file mode 100644 index 0000000..52c8036 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_p2sh/00001.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_p2sh/00002.png b/tests/snapshots/nanosp/test_sign_tx_p2sh/00002.png new file mode 100644 index 0000000..17aa5fd Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_p2sh/00002.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_p2sh/00003.png b/tests/snapshots/nanosp/test_sign_tx_p2sh/00003.png new file mode 100644 index 0000000..596ee5f Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_p2sh/00003.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_p2sh/00004.png b/tests/snapshots/nanosp/test_sign_tx_p2sh/00004.png new file mode 100644 index 0000000..848a5ce Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_p2sh/00004.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_p2sh/00005.png b/tests/snapshots/nanosp/test_sign_tx_p2sh/00005.png new file mode 100644 index 0000000..53ae651 Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_p2sh/00005.png differ diff --git a/tests/snapshots/nanosp/test_sign_tx_p2sh/00006.png b/tests/snapshots/nanosp/test_sign_tx_p2sh/00006.png new file mode 100644 index 0000000..63b43ce Binary files /dev/null and b/tests/snapshots/nanosp/test_sign_tx_p2sh/00006.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_p2sh/00000.png b/tests/snapshots/nanox/test_sign_tx_p2sh/00000.png new file mode 100644 index 0000000..df51419 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_p2sh/00000.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_p2sh/00001.png b/tests/snapshots/nanox/test_sign_tx_p2sh/00001.png new file mode 100644 index 0000000..52c8036 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_p2sh/00001.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_p2sh/00002.png b/tests/snapshots/nanox/test_sign_tx_p2sh/00002.png new file mode 100644 index 0000000..17aa5fd Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_p2sh/00002.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_p2sh/00003.png b/tests/snapshots/nanox/test_sign_tx_p2sh/00003.png new file mode 100644 index 0000000..596ee5f Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_p2sh/00003.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_p2sh/00004.png b/tests/snapshots/nanox/test_sign_tx_p2sh/00004.png new file mode 100644 index 0000000..848a5ce Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_p2sh/00004.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_p2sh/00005.png b/tests/snapshots/nanox/test_sign_tx_p2sh/00005.png new file mode 100644 index 0000000..53ae651 Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_p2sh/00005.png differ diff --git a/tests/snapshots/nanox/test_sign_tx_p2sh/00006.png b/tests/snapshots/nanox/test_sign_tx_p2sh/00006.png new file mode 100644 index 0000000..63b43ce Binary files /dev/null and b/tests/snapshots/nanox/test_sign_tx_p2sh/00006.png differ diff --git a/tests/snapshots/stax/test_sign_tx_p2sh/00000.png b/tests/snapshots/stax/test_sign_tx_p2sh/00000.png new file mode 100644 index 0000000..401fea2 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_p2sh/00000.png differ diff --git a/tests/snapshots/stax/test_sign_tx_p2sh/00001.png b/tests/snapshots/stax/test_sign_tx_p2sh/00001.png new file mode 100644 index 0000000..dd3466a Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_p2sh/00001.png differ diff --git a/tests/snapshots/stax/test_sign_tx_p2sh/00002.png b/tests/snapshots/stax/test_sign_tx_p2sh/00002.png new file mode 100644 index 0000000..578eec7 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_p2sh/00002.png differ diff --git a/tests/snapshots/stax/test_sign_tx_p2sh/00003.png b/tests/snapshots/stax/test_sign_tx_p2sh/00003.png new file mode 100644 index 0000000..7a05bf7 Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_p2sh/00003.png differ diff --git a/tests/snapshots/stax/test_sign_tx_p2sh/00004.png b/tests/snapshots/stax/test_sign_tx_p2sh/00004.png new file mode 100644 index 0000000..275ed2d Binary files /dev/null and b/tests/snapshots/stax/test_sign_tx_p2sh/00004.png differ diff --git a/tests/test_sign_cmd.py b/tests/test_sign_cmd.py index 669f0e4..9a09cba 100644 --- a/tests/test_sign_cmd.py +++ b/tests/test_sign_cmd.py @@ -66,6 +66,62 @@ def test_sign_tx_simple(firmware, backend, navigator, test_name): assert transaction.get_sighash(0) == sighash assert check_signature_validity(public_key, der_sig, sighash, transaction) +def test_sign_tx_p2sh(firmware, backend, navigator, test_name): + # Use the app interface instead of raw interface + client = KaspaCommandSender(backend) + # The path used for this entire test + path: str = "m/44'/111111'/0'/0/0" + + # First we need to get the public key of the device in order to build the transaction + rapdu = client.get_public_key(path=path) + _, public_key, _, _ = unpack_get_public_key_response(rapdu.data) + + # Create the transaction that will be sent to the device for signing + transaction = Transaction( + version=0, + inputs=[ + TransactionInput( + value=1100000, + tx_id="40b022362f1a303518e2b49f86f87a317c87b514ca0f3d08ad2e7cf49d08cc70", + address_type=0, + address_index=0, + index=0, + public_key=public_key[1:33] + ) + ], + outputs=[ + TransactionOutput( + value=1090000, + script_public_key="aa20f38031f61ca23d70844f63a477d07f0b2c2decab907c2e096e548b0e08721c7987" + ) + ] + ) + + # Send the sign device instruction. + # As it requires on-screen validation, the function is asynchronous. + # It will yield the result when the navigation is done + with client.sign_tx(transaction=transaction): + # Validate the on-screen request by performing the navigation appropriate for this device + if firmware.device.startswith("nano"): + navigator.navigate_until_text_and_compare(NavInsID.RIGHT_CLICK, + [NavInsID.BOTH_CLICK], + "Approve", + ROOT_SCREENSHOT_PATH, + test_name) + else: + navigator.navigate_until_text_and_compare(NavInsID.USE_CASE_REVIEW_TAP, + [NavInsID.USE_CASE_REVIEW_CONFIRM, + NavInsID.USE_CASE_STATUS_DISMISS], + "Hold to sign", + ROOT_SCREENSHOT_PATH, + test_name) + + # The device as yielded the result, parse it and ensure that the signature is correct + response = client.get_async_response().data + _, _, _, der_sig, _, sighash = unpack_sign_tx_response(response) + assert transaction.get_sighash(0) == sighash + assert check_signature_validity(public_key, der_sig, sighash, transaction) + def test_sign_tx_simple_sendint(firmware, backend, navigator, test_name): # Use the app interface instead of raw interface client = KaspaCommandSender(backend) diff --git a/unit-tests/test_address.c b/unit-tests/test_address.c index e0c0ad6..4b4c2c4 100644 --- a/unit-tests/test_address.c +++ b/unit-tests/test_address.c @@ -60,6 +60,23 @@ static void test_schnorr_address_from_public_key(void **state) { assert_string_equal((char *) address2, "kaspa:qrazhptjkcvrv23xz2xm8z8sfmg6jhxvmrscn7wph4k9we5tzxedwfxf0v6f8"); } +static void test_p2sh_address_from_public_key(void **state) { + uint8_t public_key[] = { + // OP_BLAKE2B, 0x20, + 0xF3, 0x80, 0x31, 0xF6, 0x1C, 0xA2, 0x3D, 0x70, 0x84, 0x4F, 0x63, 0xA4, 0x77, 0xD0, 0x7F, 0x0B, + 0x2C, 0x2D, 0xEC, 0xAB, 0x90, 0x7C, 0x2E, 0x09, 0x6E, 0x54, 0x8B, 0x0E, 0x08, 0x72, 0x1C, 0x79, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + // OP_EQUAL + }; + + uint8_t address[SCHNORR_ADDRESS_LEN + 1] = {0}; + + address_from_pubkey(public_key, P2SH, address, SCHNORR_ADDRESS_LEN); + + assert_string_equal((char *) address, "kaspa:precqv0krj3r6uyyfa36ga7s0u9jct0v4wg8ctsfde2gkrsgwgw8jgxfzfc98"); +} + static void test_ecdsa_address_from_public_key(void **state) { // Even Y-coord test case uint8_t public_key_even[] = { @@ -90,9 +107,22 @@ static void test_ecdsa_address_from_public_key(void **state) { assert_string_equal((char *) address_odd, "kaspa:qyp7xyqdshh6aylqct7x2je0pse4snep8glallgz8jppyaajz7y7qeq4x79fq4z"); } +static void test_invalid_type(void **state) { + // Even Y-coord test case + uint8_t public_key[1] = {0}; + + uint8_t address[1] = {0}; + + bool res = address_from_pubkey(public_key, -1, address, 1); + + assert_int_equal(res, 0); +} + int main() { const struct CMUnitTest tests[] = {cmocka_unit_test(test_schnorr_address_from_public_key), - cmocka_unit_test(test_ecdsa_address_from_public_key)}; + cmocka_unit_test(test_ecdsa_address_from_public_key), + cmocka_unit_test(test_p2sh_address_from_public_key), + cmocka_unit_test(test_invalid_type)}; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/unit-tests/test_tx_parser.c b/unit-tests/test_tx_parser.c index d8c5d63..1c23655 100644 --- a/unit-tests/test_tx_parser.c +++ b/unit-tests/test_tx_parser.c @@ -259,6 +259,35 @@ static void test_tx_output_serialization_33_bytes(void **state) { assert_memory_equal(raw_tx, output, sizeof(raw_tx)); } +static void test_tx_output_serialization_p2sh(void **state) { + (void) state; + + transaction_output_t txout; + + // clang-format off + uint8_t raw_tx[] = { + // Output + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xa0, + OP_BLAKE2B, 0x20, + 0xe1, 0x19, 0xd5, 0x35, 0x14, 0xc1, 0xb0, 0xe2, + 0xef, 0xce, 0x7a, 0x89, 0xe3, 0xd1, 0xd5, 0xd6, + 0xcd, 0x73, 0x58, 0x2e, 0xa2, 0x06, 0x87, 0x64, + 0x1c, 0x8f, 0xdc, 0xcb, 0x60, 0x60, 0xa9, 0xad, + OP_EQUAL + }; + + buffer_t buf = {.ptr = raw_tx, .size = sizeof(raw_tx), .offset = 0}; + + parser_status_e status = transaction_output_deserialize(&buf, &txout); + + assert_int_equal(status, PARSING_OK); + + uint8_t output[350]; + int length = transaction_output_serialize(&txout, output, sizeof(output)); + assert_int_equal(length, sizeof(raw_tx)); + assert_memory_equal(raw_tx, output, sizeof(raw_tx)); +} + static int run_test_tx_output_serialize(uint8_t* raw_tx, size_t raw_tx_len) { transaction_output_t txout; @@ -320,6 +349,17 @@ static void test_tx_output_deserialization_fail(void **state) { 0xcd, 0x73, 0x58, 0x2e, 0xa2, 0x06, 0x87, 0x64, }; + uint8_t invalid_p2sh_opcode[] = { + // Output + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xa0, + OP_BLAKE2B, 0x20, + 0xe1, 0x19, 0xd5, 0x35, 0x14, 0xc1, 0xb0, 0xe2, + 0xef, 0xce, 0x7a, 0x89, 0xe3, 0xd1, 0xd5, 0xd6, + 0xcd, 0x73, 0x58, 0x2e, 0xa2, 0x06, 0x87, 0x64, + 0x1c, 0x8f, 0xdc, 0xcb, 0x60, 0x60, 0xa9, 0xad, + 0x00, OP_CHECKSIG + }; + uint8_t raw_tx[] = { // Output 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xa0, @@ -335,6 +375,7 @@ static void test_tx_output_deserialization_fail(void **state) { assert_int_equal(run_test_tx_output_serialize(invalid_script_start, sizeof(invalid_script_start)), OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR); assert_int_equal(run_test_tx_output_serialize(invalid_script_end_ecdsa, sizeof(invalid_script_end_ecdsa)), OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR); assert_int_equal(run_test_tx_output_serialize(invalid_script_len, sizeof(invalid_script_len)), OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR); + assert_int_equal(run_test_tx_output_serialize(invalid_p2sh_opcode, sizeof(invalid_p2sh_opcode)), OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR); } static void test_serialization_fail(void **state) { @@ -357,6 +398,7 @@ int main() { cmocka_unit_test(test_tx_input_deserialization_fail), cmocka_unit_test(test_tx_output_serialization_32_bytes), cmocka_unit_test(test_tx_output_serialization_33_bytes), + cmocka_unit_test(test_tx_output_serialization_p2sh), cmocka_unit_test(test_tx_output_deserialization_fail), cmocka_unit_test(test_serialization_fail)}; diff --git a/unit-tests/test_tx_utils.c b/unit-tests/test_tx_utils.c index cf47771..0ef0b46 100644 --- a/unit-tests/test_tx_utils.c +++ b/unit-tests/test_tx_utils.c @@ -61,6 +61,19 @@ static void test_script_public_key_to_address(void **state) { assert_string_equal((char *) schnorr_address, "kaspa:qrazhptjkcvrv23xz2xm8z8sfmg6jhxvmrscn7wph4k9we5tzxedwfxf0v6f8"); + uint8_t p2sh_spk[35] = { + OP_BLAKE2B, 0x20, + 0xF3, 0x80, 0x31, 0xF6, 0x1C, 0xA2, 0x3D, 0x70, 0x84, 0x4F, 0x63, 0xA4, 0x77, 0xD0, 0x7F, 0x0B, + 0x2C, 0x2D, 0xEC, 0xAB, 0x90, 0x7C, 0x2E, 0x09, 0x6E, 0x54, 0x8B, 0x0E, 0x08, 0x72, 0x1C, 0x79, + OP_EQUAL + }; + + uint8_t p2sh_address[68] = {0}; + + script_public_key_to_address(p2sh_address, p2sh_spk); + + assert_string_equal((char *) p2sh_address, "kaspa:precqv0krj3r6uyyfa36ga7s0u9jct0v4wg8ctsfde2gkrsgwgw8jgxfzfc98"); + uint8_t ecdsa_even_spk[35] = { 0x21, 0x02, 0xd5, 0xfd, 0xc7, 0xad, 0x11, 0xa6, 0x5d, 0x0b,