Skip to content

Commit 58aa502

Browse files
authored
Merge pull request #46 from coderofstuff/support-p2sh
Support P2SH send address
2 parents 6475f5f + 18d2b1f commit 58aa502

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+228
-28
lines changed

doc/COMMANDS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Transactions signed with ECDSA are currently not supported.
8383
| P1 Value | Usage | CData |
8484
| --- | --- | --- |
8585
| 0x00 | Sending transaction metadata | `version (2)` \|\| `output_len (1)` \|\| `input_len (1)` |
86-
| 0x01 | Sending a tx output | `value (8)` \|\| `script_public_key (32/33)` |
86+
| 0x01 | Sending a tx output | `value (8)` \|\| `script_public_key (34/35)` |
8787
| 0x02 | Sending a tx input | `value (8)` \|\| `tx_id (32)` \|\| `address_type (1)` \|\| `address_index (4)` \|\| `outpoint_index (1)` |
8888
| 0x03 | Requesting for next signature | - |
8989

doc/TRANSACTION.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The base unit in Kaspa is the KAS and the smallest unit used in raw transaction
1010

1111
## Address format
1212

13-
Kaspa addresses begin with `kaspa:` followed by 61 base32 characters for a total of `67` bytes for Schnorr-signed addresses.
13+
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.
1414

1515
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.
1616

@@ -54,7 +54,7 @@ Total bytes: 43 (max)
5454
| Field | Size (bytes) | Description |
5555
| --- | --- | --- |
5656
| `value` | 8 | The amount of KAS in sompi that will go send to the address |
57-
| `script_public_key` | 35 | Schnorr: `20` + public_key (32 bytes) + `ac` <br/> ECDSA: `20` + public_key (33 bytes) + `ab` |
57+
| `script_public_key` | 35 | Schnorr: `20` + public_key (32 bytes) + `ac` <br/> ECDSA: `20` + public_key (33 bytes) + `ab` <br/> P2SH: `aa20` + public_key (32 bytes) + `87` |
5858

5959
### Transaction Requirements
6060
- Fee = (total inputs amount) - (total outputs amount)

src/address.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,17 @@ size_t compress_public_key(const uint8_t public_key[static 64],
3535
address_type_e address_type,
3636
uint8_t *out) {
3737
size_t compressed_pub_size = 0;
38-
if (address_type == SCHNORR) {
38+
if (address_type == SCHNORR || address_type == P2SH) {
3939
compressed_pub_size = 32;
4040
memmove(out, public_key, 32);
41-
} else {
41+
} else if (address_type == ECDSA) {
4242
compressed_pub_size = 33;
4343
// If Y coord is even, first byte is 0x02. if odd then 0x03
4444
out[0] = public_key[63] % 2 == 0 ? 0x02 : 0x03;
4545
// We copy starting from the 2nd byte
4646
memmove(out + 1, public_key, 32);
47+
} else {
48+
return 0;
4749
}
4850

4951
return compressed_pub_size;
@@ -61,6 +63,9 @@ bool address_from_pubkey(const uint8_t public_key[static 64],
6163
if (address_type == ECDSA) {
6264
address_len = ECDSA_ADDRESS_LEN;
6365
version = 1;
66+
} else if (address_type == P2SH) {
67+
address_len = SCHNORR_ADDRESS_LEN;
68+
version = 8;
6469
}
6570

6671
if (out_len < address_len) {
@@ -79,6 +84,10 @@ bool address_from_pubkey(const uint8_t public_key[static 64],
7984
size_t compressed_pub_size =
8085
compress_public_key(public_key, address_type, compressed_public_key);
8186

87+
if (compressed_pub_size == 0) {
88+
return false;
89+
}
90+
8291
// First part of the address is "kaspa:"
8392
memmove(address, hrp, sizeof(hrp));
8493
address[5] = ':';

src/sighash.c

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,21 @@ static void calc_outputs_hash(transaction_t* tx, uint8_t* out_hash) {
122122
inner_buffer,
123123
2); // Write the output script version, assume 0
124124

125-
// First byte is always the length of the following public key
126-
// Last byte is always 0xac (op code for normal transactions)
127-
uint8_t script_len = tx->tx_outputs[i].script_public_key[0] + 2;
128-
write_u64_le(inner_buffer,
129-
0,
130-
script_len); // Write the number of bytes of the script public key
125+
uint8_t script_len = 0;
126+
if (tx->tx_outputs[i].script_public_key[0] == 0xaa) {
127+
// P2SH script public key is always 35 bytes,
128+
// always begins with 0xaa and ends with 0x87
129+
script_len = 35;
130+
write_u64_le(inner_buffer, 0, 35);
131+
} else {
132+
// First byte is always the length of the following public key
133+
// Last byte is always 0xac (op code for normal transactions)
134+
script_len = tx->tx_outputs[i].script_public_key[0] + 2;
135+
write_u64_le(inner_buffer,
136+
0,
137+
script_len); // Write the number of bytes of the script public key
138+
}
139+
131140
hash_update(&inner_hash_writer, inner_buffer, 8);
132141
hash_update(&inner_hash_writer, tx->tx_outputs[i].script_public_key, script_len);
133142
}

src/transaction/deserialize.c

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,30 @@ parser_status_e transaction_output_deserialize(buffer_t *buf, transaction_output
3434
}
3535

3636
size_t script_len = (size_t) * (buf->ptr + buf->offset);
37-
// Can only be length 32 or 33. Fail it otherwise:
38-
if (script_len == 0x20 || script_len == 0x21) {
37+
38+
if (script_len == OP_BLAKE2B) {
39+
// P2SH = 0xaa + 0x20 + (pubkey) + 0x87
40+
// script len is actually the second byte if the first one is 0xaa
41+
script_len = (size_t) * (buf->ptr + buf->offset + 1);
42+
43+
if (!buffer_can_read(buf, script_len + 3)) {
44+
return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR;
45+
}
46+
47+
uint8_t sig_op_code = *(buf->ptr + buf->offset + script_len + 2);
48+
49+
if (script_len == 0x20 && sig_op_code != OP_EQUAL) {
50+
return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR;
51+
}
52+
53+
memcpy(txout->script_public_key, buf->ptr + buf->offset, script_len + 3);
54+
55+
if (!buffer_seek_cur(buf, script_len + 3)) {
56+
return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR;
57+
}
58+
} else if (script_len == 0x20 || script_len == 0x21) {
59+
// P2PK
60+
// Can only be length 32 or 33. Fail it otherwise:
3961
if (!buffer_can_read(buf, script_len + 2)) {
4062
return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR;
4163
}
@@ -48,11 +70,11 @@ parser_status_e transaction_output_deserialize(buffer_t *buf, transaction_output
4870
}
4971

5072
memcpy(txout->script_public_key, buf->ptr + buf->offset, script_len + 2);
51-
} else {
52-
return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR;
53-
}
5473

55-
if (!buffer_seek_cur(buf, script_len + 2)) {
74+
if (!buffer_seek_cur(buf, script_len + 2)) {
75+
return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR;
76+
}
77+
} else {
5678
return OUTPUT_SCRIPT_PUBKEY_PARSING_ERROR;
5779
}
5880

src/transaction/serialize.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,19 @@ int transaction_output_serialize(const transaction_output_t *txout, uint8_t *out
8282
write_u64_be(out, offset, txout->value);
8383
offset += 8;
8484

85-
size_t script_len = txout->script_public_key[0];
85+
if (txout->script_public_key[0] == OP_BLAKE2B) {
86+
size_t script_len = txout->script_public_key[1];
8687

87-
memcpy(out + offset, txout->script_public_key, script_len + 2);
88+
memcpy(out + offset, txout->script_public_key, script_len + 3);
8889

89-
offset += script_len + 2;
90+
offset += script_len + 3;
91+
} else {
92+
size_t script_len = txout->script_public_key[0];
93+
94+
memcpy(out + offset, txout->script_public_key, script_len + 2);
95+
96+
offset += script_len + 2;
97+
}
9098

9199
return (int) offset;
92100
}

src/transaction/types.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ typedef enum {
6060
* Enumeration of op codes
6161
*/
6262
typedef enum {
63-
OP_CHECKSIG = 0xac, // Used for SCHNORR output
64-
OP_CHECKSIGECDSA = 0xab // Used for ECDSA output
63+
OP_CHECKSIG = 0xac, // Used for SCHNORR output
64+
OP_CHECKSIGECDSA = 0xab, // Used for ECDSA output
65+
OP_BLAKE2B = 0xaa, // Used for P2SH (start)
66+
OP_EQUAL = 0x87 // Used for P2SH (end)
6567
} op_code_e;
6668

6769
typedef struct {
@@ -75,7 +77,7 @@ typedef struct {
7577

7678
typedef struct {
7779
uint64_t value;
78-
uint8_t script_public_key[35]; // In hex: 20 + public_key_hex + ac (34/35 bytes total)
80+
uint8_t script_public_key[40]; // In hex: 20 + public_key_hex + ac (34/35 bytes total)
7981
} transaction_output_t;
8082

8183
typedef struct {

src/transaction/utils.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,15 @@ bool transaction_utils_check_encoding(const uint8_t* memo, uint64_t memo_len) {
4141
void script_public_key_to_address(uint8_t* out_address, uint8_t* in_script_public_key) {
4242
uint8_t public_key[64] = {0};
4343
// public script keys begin with the length, followed by the amount of data
44-
size_t data_len = (size_t) in_script_public_key[0];
44+
size_t first_byte = (size_t) in_script_public_key[0];
4545
address_type_e type = SCHNORR;
4646
size_t address_len = SCHNORR_ADDRESS_LEN;
4747

48-
if (data_len == 0x21) {
48+
if (first_byte == 0xaa) {
49+
type = P2SH;
50+
address_len = SCHNORR_ADDRESS_LEN;
51+
memmove(public_key, in_script_public_key + 2, 32);
52+
} else if (first_byte == 0x21) {
4953
// We're dealing with ECDSA instead:
5054
type = ECDSA;
5155
address_len = ECDSA_ADDRESS_LEN;

src/types.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ typedef enum {
7575

7676
typedef enum {
7777
SCHNORR, // Display the 61 byte address for schnorr
78-
ECDSA // Display the 63 byte address for ecdsa
78+
ECDSA, // Display the 63 byte address for ecdsa
79+
P2SH // Display the 61 byte address for p2sh (also schnorr)
7980
} address_type_e;
8081

8182
/**

tests/application_client/kaspa_transaction.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,11 @@ def _calc_outputs_hash(self) -> bytes:
179179
for txout in self.tx.outputs:
180180
inner_hash.update(txout.value.to_bytes(8, "little"))
181181
inner_hash.update((0).to_bytes(2, "little")) # assume script version 0
182-
inner_hash.update((txout.script_public_key[0] + 2).to_bytes(8, "little"))
182+
if txout.script_public_key[0] == 0xaa:
183+
inner_hash.update((35).to_bytes(8, "little"))
184+
else:
185+
inner_hash.update((txout.script_public_key[0] + 2).to_bytes(8, "little"))
186+
183187
inner_hash.update(txout.script_public_key)
184188

185189
return inner_hash.digest()

0 commit comments

Comments
 (0)