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,