Skip to content

Commit 03d92c6

Browse files
committed
taproot: implement p2tr scriptpubkey generation
1 parent 5f07455 commit 03d92c6

File tree

10 files changed

+112
-9
lines changed

10 files changed

+112
-9
lines changed

include/wally.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,12 @@ inline int scriptpubkey_p2sh_from_bytes(const BYTES& bytes, uint32_t flags, BYTE
16711671
return detail::check_ret(__FUNCTION__, ret);
16721672
}
16731673

1674+
template <class BYTES, class BYTES_OUT>
1675+
inline int scriptpubkey_p2tr_from_bytes(const BYTES& bytes, uint32_t flags, BYTES_OUT& bytes_out, size_t* written) {
1676+
int ret = ::wally_scriptpubkey_p2tr_from_bytes(bytes.data(), bytes.size(), flags, bytes_out.data(), bytes_out.size(), written);
1677+
return detail::check_ret(__FUNCTION__, ret);
1678+
}
1679+
16741680
template <class SCRIPTPUBKEY>
16751681
inline int scriptpubkey_to_address(const SCRIPTPUBKEY& scriptpubkey, uint32_t network, char** output) {
16761682
int ret = ::wally_scriptpubkey_to_address(scriptpubkey.data(), scriptpubkey.size(), network, output);

include/wally_script.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,29 @@ WALLY_CORE_API int wally_witness_p2wpkh_from_der(
296296
struct wally_tx_witness_stack **witness);
297297

298298
/**
299-
* Create a P2TR keyspend witness from a BIP340 signature plus
300-
* optional sighash.
299+
* Create a P2TR scriptPubkey from a compressed or x-only public key.
300+
*
301+
* :param bytes: Compressed or x-only public key to create a scriptPubkey for.
302+
* :param bytes_len: The length of ``bytes`` in bytes. Must be ``EC_PUBLIC_KEY_LEN``
303+
*| or ``EC_XONLY_PUBLIC_KEY_LEN``.
304+
* :param flags: Must be 0.
305+
* :param bytes_out: Destination for the resulting scriptPubkey.
306+
* MAX_SIZED_OUTPUT(len, bytes_out, WALLY_SCRIPTPUBKEY_P2TR_LEN)
307+
* :param written: Destination for the number of bytes written to ``bytes_out``.
308+
*
309+
* .. note:: Compressed pubkeys are tweaked according to BIP341. X-only
310+
*| pubkeys are assumed to already be tweaked, and are used as-is.
311+
*/
312+
WALLY_CORE_API int wally_scriptpubkey_p2tr_from_bytes(
313+
const unsigned char *bytes,
314+
size_t bytes_len,
315+
uint32_t flags,
316+
unsigned char *bytes_out,
317+
size_t len,
318+
size_t *written);
319+
320+
/**
321+
* Create a P2TR keyspend witness from a BIP340 signature plus optional sighash.
301322
*
302323
* :param sig: The BIP340-encoded keyspend signature, including a sighash byte
303324
*| for non `WALLY_SIGHASH_DEFAULT` sighashes.

src/script.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,45 @@ int wally_witness_p2wpkh_from_sig(
12951295
return ret;
12961296
}
12971297

1298+
int wally_scriptpubkey_p2tr_from_bytes(const unsigned char *bytes, size_t bytes_len,
1299+
uint32_t flags,
1300+
unsigned char *bytes_out, size_t len,
1301+
size_t *written)
1302+
{
1303+
unsigned char tweaked[EC_PUBLIC_KEY_LEN];
1304+
1305+
if (written)
1306+
*written = 0;
1307+
1308+
/* FIXME: Support EC_FLAG_ELEMENTS for Elements P2TR */
1309+
if (!bytes || flags || !bytes_out || !written)
1310+
return WALLY_EINVAL;
1311+
1312+
if (len < WALLY_SCRIPTPUBKEY_P2TR_LEN) {
1313+
/* Tell the caller their buffer is too short */
1314+
*written = WALLY_SCRIPTPUBKEY_P2TR_LEN;
1315+
return WALLY_OK;
1316+
}
1317+
1318+
if (bytes_len == EC_PUBLIC_KEY_LEN) {
1319+
/* An untweaked public key, tweak it */
1320+
int ret = wally_ec_public_key_bip341_tweak(bytes, bytes_len, NULL, 0,
1321+
0, tweaked, sizeof(tweaked));
1322+
if (ret != WALLY_OK)
1323+
return ret;
1324+
bytes = tweaked + 1; /* Convert to x-only */
1325+
bytes_len = EC_XONLY_PUBLIC_KEY_LEN;
1326+
}
1327+
if (bytes_len != EC_XONLY_PUBLIC_KEY_LEN)
1328+
return WALLY_EINVAL; /* Not an x-only public key */
1329+
1330+
bytes_out[0] = OP_1;
1331+
bytes_out[1] = bytes_len;
1332+
memcpy(bytes_out + 2, bytes, bytes_len);
1333+
*written = WALLY_SCRIPTPUBKEY_P2TR_LEN;
1334+
return WALLY_OK;
1335+
}
1336+
12981337
int wally_witness_p2tr_from_sig(const unsigned char *sig, size_t sig_len,
12991338
struct wally_tx_witness_stack **witness)
13001339
{

src/swig_java/swig.i

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
950950
%returns_size_t(wally_scriptpubkey_op_return_from_bytes);
951951
%returns_size_t(wally_scriptpubkey_p2pkh_from_bytes);
952952
%returns_size_t(wally_scriptpubkey_p2sh_from_bytes);
953+
%returns_size_t(wally_scriptpubkey_p2tr_from_bytes);
953954
%returns_size_t(wally_scriptpubkey_multisig_from_bytes);
954955
%returns_size_t(wally_scriptsig_p2pkh_from_sig);
955956
%returns_size_t(wally_scriptsig_p2pkh_from_der);

src/swig_python/python_extra.py_in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ scriptpubkey_multisig_from_bytes = _wrap_bin(scriptpubkey_multisig_from_bytes, s
216216
scriptpubkey_op_return_from_bytes = _wrap_bin(scriptpubkey_op_return_from_bytes, WALLY_SCRIPTPUBKEY_OP_RETURN_MAX_LEN, resize=True)
217217
scriptpubkey_p2pkh_from_bytes = _wrap_bin(scriptpubkey_p2pkh_from_bytes, WALLY_SCRIPTPUBKEY_P2PKH_LEN, resize=True)
218218
scriptpubkey_p2sh_from_bytes = _wrap_bin(scriptpubkey_p2sh_from_bytes, WALLY_SCRIPTPUBKEY_P2SH_LEN, resize=True)
219+
scriptpubkey_p2tr_from_bytes = _wrap_bin(scriptpubkey_p2tr_from_bytes, WALLY_SCRIPTPUBKEY_P2TR_LEN, resize=True)
219220
scriptsig_multisig_from_bytes = _wrap_bin(scriptsig_multisig_from_bytes, scriptsig_multisig_from_bytes_len, resize=True)
220221
scriptsig_p2pkh_from_der = _wrap_bin(scriptsig_p2pkh_from_der, WALLY_SCRIPTSIG_P2PKH_MAX_LEN, resize=True)
221222
scriptsig_p2pkh_from_sig = _wrap_bin(scriptsig_p2pkh_from_sig, WALLY_SCRIPTSIG_P2PKH_MAX_LEN, resize=True)

src/test/test_script.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
SCRIPTPUBKEY_OP_RETURN_MAX_LEN = 83
2323
SCRIPTPUBKEY_P2PKH_LEN = 25
2424
SCRIPTPUBKEY_P2SH_LEN = 23
25+
SCRIPTPUBKEY_P2TR_LEN = 34
2526
HASH160_LEN = 20
2627
SCRIPTSIG_P2PKH_MAX_LEN = 140
2728

28-
PK, PK_LEN = make_cbuffer('11' * 33) # Fake compressed pubkey
29+
PK, PK_LEN = make_cbuffer('02' * 33) # Fake compressed pubkey
2930
PKU, PKU_LEN = make_cbuffer('11' * 65) # Fake uncompressed pubkey
31+
PKX, PKX_LEN = make_cbuffer('02' * 32) # Fake x-only pubkey
3032
SH, SH_LEN = make_cbuffer('11' * 20) # Fake script hash
3133
MPK_2, MPK_2_LEN = make_cbuffer('11' * 33 * 2) # Fake multiple (2) pubkeys
3234
MPK_3, MPK_3_LEN = make_cbuffer('11' * 33 * 3) # Fake multiple (3) pubkeys
@@ -124,15 +126,14 @@ def test_scriptpubkey_p2pkh_from_bytes(self):
124126

125127
# Valid cases
126128
valid_args = [
127-
[(PK, PK_LEN, SCRIPT_HASH160, out, out_len),'76a9148ec4cf3ee160b054e0abb6f5c8177b9ee56fa51e88ac'],
129+
[(PK, PK_LEN, SCRIPT_HASH160, out, out_len),'76a91451814f108670aced2d77c1805ddd6634bc9d473188ac'],
128130
[(PKU, PKU_LEN, SCRIPT_HASH160, out, out_len),'76a914e723a0f62396b8b03dbd9e48e9b9efe2eb704aab88ac'],
129131
[(PKU, HASH160_LEN, 0, out, out_len),'76a914111111111111111111111111111111111111111188ac'],
130132
]
131133
for args, exp_script in valid_args:
132134
ret = wally_scriptpubkey_p2pkh_from_bytes(*args)
133135
self.assertEqual(ret, (WALLY_OK, SCRIPTPUBKEY_P2PKH_LEN))
134-
exp_script, _ = make_cbuffer(exp_script)
135-
self.assertEqual(args[3], exp_script)
136+
self.assertEqual(h(args[3]), utf8(exp_script))
136137
ret = wally_scriptpubkey_get_type(out, SCRIPTPUBKEY_P2PKH_LEN)
137138
self.assertEqual(ret, (WALLY_OK, SCRIPT_TYPE_P2PKH))
138139

@@ -173,6 +174,37 @@ def test_scriptpubkey_p2sh_from_bytes(self):
173174
ret = wally_scriptpubkey_get_type(out, SCRIPTPUBKEY_P2SH_LEN)
174175
self.assertEqual(ret, (WALLY_OK, SCRIPT_TYPE_P2SH))
175176

177+
def test_scriptpubkey_p2tr_from_bytes(self):
178+
"""Tests for creating p2tr scriptPubKeys"""
179+
# Invalid args
180+
out, out_len = make_cbuffer('00' * SCRIPTPUBKEY_P2TR_LEN)
181+
invalid_args = [
182+
(None, PK_LEN, 0, out, out_len), # Null bytes
183+
(PK, 0, 0, out, out_len), # Empty bytes
184+
(PK, PK_LEN, 0x8, out, out_len), # Unsupported flags
185+
(PK, PK_LEN+1, 0, out, out_len), # Invalid pubkey len
186+
(PK, PK_LEN, 0, None, out_len), # Null output
187+
(PK, PK_LEN, 0, out, out_len-1), # Short output len
188+
]
189+
for args in invalid_args:
190+
ret = wally_scriptpubkey_p2tr_from_bytes(*args)
191+
if ret == (WALLY_OK, SCRIPTPUBKEY_P2TR_LEN):
192+
self.assertTrue(args[-1] < out_len)
193+
else:
194+
self.assertEqual(ret, (WALLY_EINVAL, 0))
195+
196+
# Valid cases
197+
valid_args = [
198+
[(PK, PK_LEN, 0, out, out_len), '51203b6ec3adc4917224b2da531904a1d12c2ad47cabaa88fa54adc55aa2d7d29571'],
199+
[(PKX, PKX_LEN, 0, out, out_len), '51200202020202020202020202020202020202020202020202020202020202020202'],
200+
]
201+
for args, exp_script in valid_args:
202+
ret = wally_scriptpubkey_p2tr_from_bytes(*args)
203+
self.assertEqual(ret, (WALLY_OK, SCRIPTPUBKEY_P2TR_LEN))
204+
self.assertEqual(h(args[3]), utf8(exp_script))
205+
ret = wally_scriptpubkey_get_type(out, SCRIPTPUBKEY_P2TR_LEN)
206+
self.assertEqual(ret, (WALLY_OK, SCRIPT_TYPE_P2TR))
207+
176208
def test_scriptpubkey_multisig_from_bytes(self):
177209
"""Tests for creating multisig scriptPubKeys"""
178210
# Invalid args
@@ -303,14 +335,13 @@ def test_scriptsig_p2pkh(self):
303335

304336
# Valid cases
305337
valid_args = [
306-
[(PK, PK_LEN, SIG_DER, SIG_DER_LEN, out, out_len), '4730450220'+'11'*32+'0220'+'11'*32+'0121'+'11'*33],
338+
[(PK, PK_LEN, SIG_DER, SIG_DER_LEN, out, out_len), '4730450220'+'11'*32+'0220'+'11'*32+'0121'+'02'*33],
307339
[(PKU, PKU_LEN, SIG_DER, SIG_DER_LEN, out, out_len), '4730450220'+'11'*32+'0220'+'11'*32+'0141'+'11'*65],
308340
]
309341
for args, exp_script in valid_args:
310342
ret = wally_scriptsig_p2pkh_from_der(*args)
311343
self.assertEqual(ret, (WALLY_OK, args[1] + args[3] + 2))
312-
exp_script, _ = make_cbuffer(exp_script)
313-
self.assertEqual(args[4][:(args[1] + args[3] + 2)], exp_script)
344+
self.assertEqual(h(args[4][:(args[1] + args[3] + 2)]), utf8(exp_script))
314345

315346
# From sig
316347
# Invalid args

src/test/util.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ class wally_psbt(Structure):
632632
('wally_scriptpubkey_op_return_from_bytes', c_int, [c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]),
633633
('wally_scriptpubkey_p2pkh_from_bytes', c_int, [c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]),
634634
('wally_scriptpubkey_p2sh_from_bytes', c_int, [c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]),
635+
('wally_scriptpubkey_p2tr_from_bytes', c_int, [c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]),
635636
('wally_scriptpubkey_to_address', c_int, [c_void_p, c_size_t, c_uint32, c_char_p_p]),
636637
('wally_scriptsig_multisig_from_bytes', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, POINTER(c_uint32), c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]),
637638
('wally_scriptsig_p2pkh_from_der', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_size_t_p]),

src/wasm_package/src/functions.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/wasm_package/src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ export function scriptpubkey_multisig_from_bytes(bytes: Buffer|Uint8Array, thres
562562
export function scriptpubkey_op_return_from_bytes(bytes: Buffer|Uint8Array, flags: number): Buffer;
563563
export function scriptpubkey_p2pkh_from_bytes(bytes: Buffer|Uint8Array, flags: number): Buffer;
564564
export function scriptpubkey_p2sh_from_bytes(bytes: Buffer|Uint8Array, flags: number): Buffer;
565+
export function scriptpubkey_p2tr_from_bytes(bytes: Buffer|Uint8Array, flags: number): Buffer;
565566
export function scriptpubkey_to_address(scriptpubkey: Buffer|Uint8Array, network: number): string;
566567
export function scriptsig_multisig_from_bytes(script: Buffer|Uint8Array, bytes: Buffer|Uint8Array, sighash: Uint32Array|number[], flags: number): Buffer;
567568
export function scriptsig_p2pkh_from_der(pub_key: Buffer|Uint8Array, sig: Buffer|Uint8Array): Buffer;

tools/wasm_exports.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \
361361
,'_wally_scriptpubkey_op_return_from_bytes' \
362362
,'_wally_scriptpubkey_p2pkh_from_bytes' \
363363
,'_wally_scriptpubkey_p2sh_from_bytes' \
364+
,'_wally_scriptpubkey_p2tr_from_bytes' \
364365
,'_wally_scriptpubkey_to_address' \
365366
,'_wally_scriptsig_multisig_from_bytes' \
366367
,'_wally_scriptsig_p2pkh_from_der' \

0 commit comments

Comments
 (0)