Skip to content

Commit 9d76cb4

Browse files
committed
psbt: add support for elements p2tr signing
Use the new signature hash function for both btc and elements. For signing via PSBT input instead of via the PSBT, this requires that EC_FLAG_ELEMENTS is passed in flags, in order to correctly use the Elements-specific tagged hashes. This is because the input alone does not know if it belongs to a PSET.
1 parent ca0870f commit 9d76cb4

File tree

2 files changed

+109
-77
lines changed

2 files changed

+109
-77
lines changed

include/wally_psbt.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,7 +2614,9 @@ WALLY_CORE_API int wally_psbt_sign(
26142614
*
26152615
* :param psbt: PSBT to sign. Directly modifies this PSBT.
26162616
* :param hdkey: The parent extended key to derive signing keys from.
2617-
* :param flags: Flags controlling signing. Must be 0 or EC_FLAG_GRIND_R.
2617+
* :param flags: Flags controlling signing. Must be 0 or `EC_FLAG_GRIND_R`.
2618+
*| Note that unlike `wally_psbt_sign_input_bip32`, `EC_FLAG_ELEMENTS`
2619+
*| is determined automatically and should not included in ``flags``.
26182620
*
26192621
* .. note:: See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#simple-signer-algorithm
26202622
*| for a description of the signing algorithm.
@@ -2633,7 +2635,8 @@ WALLY_CORE_API int wally_psbt_sign_bip32(
26332635
* :param txhash: The signature hash to sign, from `wally_psbt_get_input_signature_hash`.
26342636
* :param txhash_len: Size of ``txhash`` in bytes. Must be `WALLY_TXHASH_LEN`.
26352637
* :param hdkey: The derived extended key to sign with.
2636-
* :param flags: Flags controlling signing. Must be 0 or EC_FLAG_GRIND_R.
2638+
* :param flags: Flags controlling signing. Must be 0 or `EC_FLAG_GRIND_R`,
2639+
*| logical or-d with `EC_FLAG_ELEMENTS` if ``psbt`` is a PSET.
26372640
*/
26382641
WALLY_CORE_API int wally_psbt_sign_input_bip32(
26392642
struct wally_psbt *psbt,

src/psbt.c

Lines changed: 104 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4422,47 +4422,63 @@ int wally_psbt_get_input_scriptcode(const struct wally_psbt *psbt, size_t index,
44224422
return ret;
44234423
}
44244424

4425-
/* Get the input scripts and values for taproot signing.
4426-
* Creates a non-value-owning map, avoiding allocating/copying the scripts.
4425+
static void append_signing_data(struct wally_map *m, size_t index,
4426+
unsigned char* bytes, size_t len)
4427+
{
4428+
if (bytes && len) {
4429+
m->items[m->num_items].key = NULL;
4430+
m->items[m->num_items].key_len = index;
4431+
m->items[m->num_items].value = bytes;
4432+
m->items[m->num_items].value_len = len;
4433+
++m->num_items;
4434+
}
4435+
}
4436+
4437+
/* Get input scripts, assets (if applicable) and values for tx signing.
4438+
* Creates non-owning maps, avoiding allocations/copying.
44274439
*/
4428-
static int get_scripts_and_values(const struct wally_psbt *psbt,
4429-
struct wally_map *scripts,
4430-
uint64_t **values)
4440+
static int get_signing_data(const struct wally_psbt *psbt,
4441+
struct wally_map *scripts,
4442+
struct wally_map *assets,
4443+
struct wally_map *values)
44314444
{
4432-
size_t num_inputs = psbt->num_inputs, i;
4433-
int ret = WALLY_OK;
4445+
int ret;
44344446

4435-
wally_clear(scripts, sizeof(scripts));
4447+
memset(scripts, 0, sizeof(*scripts));
4448+
memset(values, 0, sizeof(*values));
4449+
if (assets)
4450+
memset(assets, 0, sizeof(*assets));
44364451

4437-
if (!(*values = wally_malloc(num_inputs * sizeof(uint64_t))))
4438-
return WALLY_ENOMEM;
4439-
if (!(scripts->items = wally_calloc(num_inputs * sizeof(struct wally_map_item)))) {
4440-
ret = WALLY_ENOMEM;
4441-
goto fail;
4442-
}
4443-
scripts->items_allocation_len = num_inputs;
4452+
ret = wally_map_init(psbt->num_inputs, NULL, scripts);
4453+
if (ret == WALLY_OK)
4454+
ret = wally_map_init(psbt->num_inputs, NULL, values);
4455+
if (ret == WALLY_OK && assets)
4456+
ret = wally_map_init(psbt->num_inputs, NULL, assets);
44444457

4445-
for (i = 0; i < num_inputs && ret == WALLY_OK; ++i) {
4458+
/* We add all the data we have and let the signing code
4459+
* validate that it is sufficient, since the required data
4460+
* depends on things like the sighash type being signed with.
4461+
*/
4462+
for (size_t i = 0; i < psbt->num_inputs && ret == WALLY_OK; ++i) {
44464463
const struct wally_psbt_input *p = psbt->inputs + i;
44474464
const struct wally_tx_output *utxo = utxo_from_input(psbt, p);
4448-
if (!utxo || !utxo->script)
4449-
ret = WALLY_EINVAL;
4450-
else {
4451-
(*values)[i] = utxo->satoshi; /* FIXME: Support for Elements */
4452-
/* Add the script to the map without allocating/copying */
4453-
scripts->items[i].key_len = i;
4454-
scripts->items[i].value = utxo->script;
4455-
scripts->items[i].value_len = utxo->script_len;
4465+
if (utxo) {
4466+
/* Add items to maps without allocating/copying */
4467+
append_signing_data(scripts, i, utxo->script, utxo->script_len);
4468+
if (assets) {
4469+
append_signing_data(assets, i, utxo->asset, utxo->asset_len);
4470+
append_signing_data(values, i, utxo->value, utxo->value_len);
4471+
} else {
4472+
append_signing_data(values, i, (unsigned char*)&utxo->satoshi,
4473+
sizeof(utxo->satoshi));
4474+
}
44564475
}
44574476
}
4458-
if (ret == WALLY_OK)
4459-
scripts->num_items = num_inputs;
4460-
else {
4477+
if (ret != WALLY_OK) {
44614478
wally_free(scripts->items); /* No need to clear the value pointers */
4462-
wally_clear(scripts, sizeof(scripts));
4463-
fail:
4464-
wally_free(*values);
4465-
*values = NULL;
4479+
wally_free(values->items);
4480+
if (assets)
4481+
wally_free(assets->items);
44664482
}
44674483
return ret;
44684484
}
@@ -4473,61 +4489,67 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index,
44734489
uint32_t flags,
44744490
unsigned char *bytes_out, size_t len)
44754491
{
4476-
struct wally_map scripts;
44774492
const struct wally_psbt_input *inp = psbt_get_input(psbt, index);
4478-
const bool is_taproot = is_taproot_input(psbt, inp);
4479-
uint64_t satoshi, *values = NULL;
4480-
uint32_t sighash, sig_flags;
4493+
const struct wally_tx_output *utxo = utxo_from_input(psbt, inp);
44814494
size_t is_pset;
4495+
uint32_t sighash, sig_flags;
4496+
const bool is_taproot = is_taproot_input(psbt, inp);
44824497
int ret;
44834498

4484-
if (!inp || !tx || flags)
4499+
if (!tx || !inp || !utxo || flags)
44854500
return WALLY_EINVAL;
44864501

44874502
if ((ret = wally_psbt_is_elements(psbt, &is_pset)) != WALLY_OK)
44884503
return ret;
4504+
#ifndef BUILD_ELEMENTS
4505+
if (is_pset)
4506+
return WALLY_EINVAL; /* Unsupported */
4507+
#endif
44894508

44904509
sighash = inp->sighash;
44914510
if (!sighash)
44924511
sighash = is_taproot ? WALLY_SIGHASH_DEFAULT : WALLY_SIGHASH_ALL;
44934512
else if (sighash & 0xffffff00)
44944513
return WALLY_EINVAL;
44954514

4515+
if (is_taproot) {
4516+
struct wally_map scripts, assets, values;
4517+
struct wally_map *assets_p = is_pset ? &assets : NULL;
4518+
4519+
#ifdef BUILD_ELEMENTS
4520+
if (is_pset && mem_is_zero(psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash)))
4521+
return WALLY_EINVAL; /* Genesis blockhash is required for taproot */
4522+
#endif
4523+
ret = get_signing_data(psbt, &scripts, assets_p, &values);
4524+
if (ret == WALLY_OK)
4525+
ret = wally_tx_get_input_signature_hash(tx, index,
4526+
&scripts, assets_p, &values,
4527+
NULL, 0, 0, WALLY_NO_CODESEPARATOR,
4528+
NULL, 0,
4529+
psbt->genesis_blockhash, sizeof(psbt->genesis_blockhash),
4530+
sighash, WALLY_SIGTYPE_SW_V1,
4531+
NULL, bytes_out, len);
4532+
4533+
wally_free(scripts.items); /* No need to clear the value pointers */
4534+
wally_free(values.items);
4535+
if (assets_p)
4536+
wally_free(assets_p->items);
4537+
return ret;
4538+
}
4539+
44964540
sig_flags = inp->witness_utxo ? WALLY_TX_FLAG_USE_WITNESS : 0;
44974541

4498-
if (is_pset) {
4499-
const struct wally_tx_output *utxo = utxo_from_input(psbt, inp);
4500-
if (!utxo)
4501-
return WALLY_EINVAL; /* Prevout is required */
45024542
#ifdef BUILD_ELEMENTS
4543+
if (is_pset)
45034544
return wally_tx_get_elements_signature_hash(tx, index,
45044545
script, script_len,
45054546
utxo->value, utxo->value_len,
45064547
sighash, sig_flags, bytes_out,
45074548
len);
4508-
#else
4509-
return WALLY_EINVAL; /* Unsupported */
4510-
#endif /* BUILD_ELEMENTS */
4511-
}
4512-
4513-
if (!is_taproot) {
4514-
satoshi = inp->witness_utxo ? inp->witness_utxo->satoshi : 0;
4515-
return wally_tx_get_btc_signature_hash(tx, index, script, script_len,
4516-
satoshi, sighash, sig_flags,
4517-
bytes_out, len);
4518-
}
4519-
4520-
/* Taproot */
4521-
if ((ret = get_scripts_and_values(psbt, &scripts, &values)) == WALLY_OK) {
4522-
ret = wally_tx_get_btc_taproot_signature_hash(tx, index, &scripts,
4523-
values, psbt->num_inputs,
4524-
NULL, 0, 0, 0xFFFFFFFF,
4525-
NULL, 0, sighash, 0,
4526-
bytes_out, len);
4527-
wally_free(values);
4528-
wally_free(scripts.items); /* No need to clear the value pointers */
4529-
}
4530-
return ret;
4549+
#endif
4550+
return wally_tx_get_btc_signature_hash(tx, index, script, script_len,
4551+
utxo->satoshi, sighash, sig_flags,
4552+
bytes_out, len);
45314553
}
45324554

45334555
int wally_psbt_sign_input_bip32(struct wally_psbt *psbt,
@@ -4545,7 +4567,7 @@ int wally_psbt_sign_input_bip32(struct wally_psbt *psbt,
45454567
int ret;
45464568

45474569
if (!inp || !hdkey || hdkey->priv_key[0] != BIP32_FLAG_KEY_PRIVATE ||
4548-
(flags & ~EC_FLAGS_ALL))
4570+
(flags & ~(EC_FLAG_GRIND_R|EC_FLAG_ELEMENTS)))
45494571
return WALLY_EINVAL;
45504572

45514573
/* Find the public key this signature is for */
@@ -4561,24 +4583,25 @@ int wally_psbt_sign_input_bip32(struct wally_psbt *psbt,
45614583
if (ret != WALLY_OK || !pubkey_idx)
45624584
return WALLY_EINVAL; /* Signing pubkey key not found */
45634585

4564-
/* Copy signing key so we can tweak it if needed */
4565-
memcpy(signing_key, hdkey->priv_key + 1, EC_PRIVATE_KEY_LEN);
4566-
4567-
if (is_taproot) {
4586+
if (!is_taproot) {
4587+
/* ECDSA: Use untweaked private key. Only grinding flag is relevant */
4588+
memcpy(signing_key, hdkey->priv_key + 1, EC_PRIVATE_KEY_LEN);
4589+
flags = EC_FLAG_ECDSA | (flags & EC_FLAG_GRIND_R);
4590+
} else {
45684591
/* Schnorr BIP340: Tweak the private key */
45694592
const struct wally_map_item *p = wally_map_get_integer(&inp->psbt_fields,
45704593
PSBT_IN_TAP_MERKLE_ROOT);
45714594
const unsigned char *merkle_root = p ? p->value : NULL;
45724595
const size_t merkle_root_len = p ? p->value_len : 0;
4573-
ret = wally_ec_private_key_bip341_tweak(signing_key, sizeof(signing_key),
4596+
4597+
ret = wally_ec_private_key_bip341_tweak(hdkey->priv_key + 1, EC_PRIVATE_KEY_LEN,
45744598
merkle_root, merkle_root_len,
4575-
0, signing_key, sizeof(signing_key));
4599+
flags & EC_FLAG_ELEMENTS,
4600+
signing_key, sizeof(signing_key));
45764601
if (ret != WALLY_OK)
45774602
goto done;
4578-
flags = EC_FLAG_SCHNORR;
4579-
} else {
4580-
/* ECDSA: Only grinding flag is relevant */
4581-
flags = EC_FLAG_ECDSA | (flags & EC_FLAG_GRIND_R);
4603+
/* Only Elements flag is relevant */
4604+
flags = EC_FLAG_SCHNORR | (flags & EC_FLAG_ELEMENTS);
45824605
}
45834606

45844607
sighash = inp->sighash;
@@ -4626,12 +4649,18 @@ int wally_psbt_sign_bip32(struct wally_psbt *psbt,
46264649
struct wally_tx *tx;
46274650

46284651
if (!hdkey || hdkey->priv_key[0] != BIP32_FLAG_KEY_PRIVATE ||
4629-
(flags & ~EC_FLAGS_ALL))
4652+
(flags & ~EC_FLAG_GRIND_R))
46304653
return WALLY_EINVAL;
46314654

46324655
if ((ret = psbt_build_tx(psbt, &tx, &is_pset, false)) != WALLY_OK)
46334656
return ret;
46344657

4658+
#ifdef BUILD_ELEMENTS
4659+
if (is_pset) {
4660+
flags |= EC_FLAG_ELEMENTS;
4661+
}
4662+
#endif
4663+
46354664
/* Go through each of the inputs */
46364665
for (i = 0; ret == WALLY_OK && i < psbt->num_inputs; ++i) {
46374666
unsigned char txhash[WALLY_TXHASH_LEN];

0 commit comments

Comments
 (0)