From 26438ebb873cef5a198f866fbc7a744902e412d2 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Mon, 30 Jun 2025 22:38:55 +1200 Subject: [PATCH 1/3] docs: clarify signature type values --- include/wally_transaction.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/wally_transaction.h b/include/wally_transaction.h index 614024aca..03749216b 100644 --- a/include/wally_transaction.h +++ b/include/wally_transaction.h @@ -51,11 +51,11 @@ extern "C" { #define WALLY_SIGHASH_MASK 0x1f /* Mask for determining ALL/NONE/SINGLE */ #define WALLY_SIGHASH_TR_IN_MASK 0xc0 /* Taproot mask for determining input hash type */ -/*** tx-sighash-type Transaction signature hash flags */ +/*** tx-sig-type Transaction signature type flags */ #define WALLY_SIGTYPE_PRE_SW 0x1 /* Pre-segwit signature hash */ #define WALLY_SIGTYPE_SW_V0 0x2 /* Segwit v0 signature hash */ #define WALLY_SIGTYPE_SW_V1 0x3 /* Segwit v1 (taproot) signature hash */ -#define WALLY_SIGTYPE_MASK 0xf /* Mask for signature hash in signature hash flags */ +#define WALLY_SIGTYPE_MASK 0xf /* Mask for signature type in flags */ #define WALLY_TX_ASSET_CT_EMPTY_PREFIX 0x00 #define WALLY_TX_ASSET_CT_EXPLICIT_PREFIX 0x01 @@ -910,7 +910,7 @@ WALLY_CORE_API int wally_tx_get_signature_hash( *| be `SHA256_LEN` or 0. * :param sighash: ``WALLY_SIGHASH_`` flags specifying the sighash flags *| to sign with. - * :param flags: :ref:`tx-sighash-type` controlling signature hash generation. + * :param flags: :ref:`tx-sig-type` controlling signature hash generation. * :param cache: An opaque cache for faster generation, or NULL to disable *| caching. Must be empty on the first call to this function for a given *| transaction, and only used for signing the inputs of the same ``tx``. From c3a5aee434670c29e8a2dc90d22faf04649f9f02 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Tue, 1 Jul 2025 08:19:11 +1200 Subject: [PATCH 2/3] psbt: add psbt_get_input_signature_type to get the type of sig required for an input As a first cut, this uses the pre-existing logic for differentiating segwit and non-segwit inputs (the presence of a witness UTXO). --- include/wally_psbt_members.h | 9 ++++ src/psbt.c | 68 ++++++++++++++----------------- src/swig_java/swig.i | 1 + src/test/util.py | 1 + src/wasm_package/src/const.js | 2 +- src/wasm_package/src/functions.js | 1 + src/wasm_package/src/index.d.ts | 1 + tools/wasm_exports.sh | 1 + 8 files changed, 45 insertions(+), 39 deletions(-) diff --git a/include/wally_psbt_members.h b/include/wally_psbt_members.h index 6e3965f2f..45651a685 100644 --- a/include/wally_psbt_members.h +++ b/include/wally_psbt_members.h @@ -60,6 +60,15 @@ WALLY_CORE_API int wally_psbt_get_input_unknown(const struct wally_psbt *psbt, s WALLY_CORE_API int wally_psbt_get_input_unknown_len(const struct wally_psbt *psbt, size_t index, size_t subindex, size_t *written); WALLY_CORE_API int wally_psbt_get_input_sighash(const struct wally_psbt *psbt, size_t index, size_t *written); +/** + * Return the signature type of a PSBT input. + * + * :param psbt: The PSBT to get the signature type from. + * :param index: The zero-based index of the input to get the signature type from. + * :param value_out: Destination for the :ref:`tx-sig-type` of the input. + */ +WALLY_CORE_API int wally_psbt_get_input_signature_type(const struct wally_psbt *psbt, size_t index, uint32_t *value_out); + /** * FIXED_SIZED_OUTPUT(len, bytes_out, WALLY_TXHASH_LEN) */ diff --git a/src/psbt.c b/src/psbt.c index ccb6f15e5..f670cb242 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -155,34 +155,32 @@ static const struct wally_tx_output *utxo_from_input(const struct wally_psbt *ps return NULL; } -/* Try to determine if a PSBT input is taproot. - * TODO: We could verify that the script and field checks are in sync - * here, i.e. that an input with taproot fields has a taproot script, - * and return an error otherwise. - */ -static bool is_taproot_input(const struct wally_psbt *psbt, - const struct wally_psbt_input *inp) +struct wally_psbt_input *psbt_get_input_signature_type(const struct wally_psbt *psbt, + size_t index, uint32_t *value_out) { - if (!inp) - return false; - else { - const struct wally_tx_output *utxo = utxo_from_input(psbt, inp); - if (utxo) { - /* Determine from the scriptpubkey if possible */ - size_t script_type; - int ret = wally_scriptpubkey_get_type(utxo->script, utxo->script_len, - &script_type); - if (ret == WALLY_OK) - return script_type == WALLY_SCRIPT_TYPE_P2TR; - } - /* No usable UTXO/script for this input, check for taproot fields */ - return inp->taproot_leaf_hashes.num_items || - inp->taproot_leaf_scripts.num_items || - inp->taproot_leaf_signatures.num_items || - wally_map_get_integer(&inp->psbt_fields, PSBT_IN_TAP_INTERNAL_KEY) || - wally_map_get_integer(&inp->psbt_fields, PSBT_IN_TAP_MERKLE_ROOT) || - wally_map_get_integer(&inp->psbt_fields, PSBT_IN_TAP_KEY_SIG); + struct wally_psbt_input *inp = psbt_get_input(psbt, index); + const struct wally_tx_output *utxo = utxo_from_input(psbt, inp); + + if (value_out) + *value_out = 0; + if (!utxo || !value_out) + return NULL; + + if (scriptpubkey_is_p2tr(utxo->script, utxo->script_len)) { + *value_out = WALLY_SIGTYPE_SW_V1; + return inp; } + + /* Otherwise, follow core and use whether a witness utxo is present */ + *value_out = inp->witness_utxo ? WALLY_SIGTYPE_SW_V0 : WALLY_SIGTYPE_PRE_SW; + return inp; +} + +int wally_psbt_get_input_signature_type(const struct wally_psbt *psbt, + size_t index, uint32_t *value_out) +{ + struct wally_psbt_input *inp = psbt_get_input_signature_type(psbt, index, value_out); + return inp ? WALLY_OK : WALLY_EINVAL; } /* Set a struct member on a parent struct */ @@ -4579,12 +4577,10 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, unsigned char *bytes_out, size_t len) { struct wally_map scripts, assets, values, *assets_p; - const struct wally_psbt_input *inp = psbt_get_input(psbt, index); size_t is_pset; uint32_t sighash, sighash_type; - const bool is_taproot = is_taproot_input(psbt, inp); - /* FIXME: Determine segwitness in a smarter way (e.g. prevout script */ - const bool is_segwit = inp && inp->witness_utxo != NULL; + const struct wally_psbt_input *inp = psbt_get_input_signature_type(psbt, index, &sighash_type); + const bool is_taproot = sighash_type == WALLY_SIGTYPE_SW_V1; int ret; if (!tx || !inp || flags) @@ -4608,11 +4604,7 @@ int wally_psbt_get_input_signature_hash(struct wally_psbt *psbt, size_t index, /* FIXME: Support script path spends */ script = NULL; script_len = 0; - sighash_type = WALLY_SIGTYPE_SW_V1; - } else if (is_segwit) - sighash_type = WALLY_SIGTYPE_SW_V0; - else - sighash_type = WALLY_SIGTYPE_PRE_SW; + } ret = get_signing_data(psbt, &scripts, assets_p, &values); if (ret == WALLY_OK) @@ -4640,9 +4632,9 @@ int wally_psbt_sign_input_bip32(struct wally_psbt *psbt, unsigned char sig[EC_SIGNATURE_LEN + 1], der[EC_SIGNATURE_DER_MAX_LEN + 1]; unsigned char signing_key[EC_PRIVATE_KEY_LEN]; size_t sig_len = EC_SIGNATURE_LEN, der_len, pubkey_idx; - uint32_t sighash; - struct wally_psbt_input *inp = psbt_get_input(psbt, index); - const bool is_taproot = is_taproot_input(psbt, inp); + uint32_t sighash, sighash_type; + struct wally_psbt_input *inp = psbt_get_input_signature_type(psbt, index, &sighash_type); + const bool is_taproot = sighash_type == WALLY_SIGTYPE_SW_V1; int ret; if (!inp || !hdkey || hdkey->priv_key[0] != BIP32_FLAG_KEY_PRIVATE || diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index 4fc6d93e3..14d7f9f6d 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -809,6 +809,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_size_t(wally_psbt_get_input_required_locktime); %returns_size_t(wally_psbt_get_input_scriptcode); %returns_size_t(wally_psbt_get_input_scriptcode_len); +%returns_size_t(wally_psbt_get_input_signature_type); %returns_size_t(wally_psbt_get_input_signing_script); %returns_size_t(wally_psbt_get_input_signing_script_len); %returns_size_t(wally_psbt_get_input_sequence); diff --git a/src/test/util.py b/src/test/util.py index c463e4488..cdca1d9c4 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -859,6 +859,7 @@ class wally_psbt(Structure): ('wally_psbt_get_input_sighash', c_int, [POINTER(wally_psbt), c_size_t, c_size_t_p]), ('wally_psbt_get_input_signature', c_int, [POINTER(wally_psbt), c_size_t, c_size_t, c_void_p, c_size_t, c_size_t_p]), ('wally_psbt_get_input_signature_len', c_int, [POINTER(wally_psbt), c_size_t, c_size_t, c_size_t_p]), + ('wally_psbt_get_input_signature_type', c_int, [POINTER(wally_psbt), c_size_t, c_uint32_p]), ('wally_psbt_get_input_signatures_size', c_int, [POINTER(wally_psbt), c_size_t, c_size_t_p]), ('wally_psbt_get_input_taproot_internal_key', c_int, [POINTER(wally_psbt), c_size_t, c_void_p, c_size_t, c_size_t_p]), ('wally_psbt_get_input_taproot_internal_key_len', c_int, [POINTER(wally_psbt), c_size_t, c_size_t_p]), diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index 8b10c1929..24a7d1aef 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -223,7 +223,7 @@ export const WALLY_SIGHASH_NONE = 0x02; export const WALLY_SIGHASH_RANGEPROOF = 0x40 ; /* Liquid/Elements only */ export const WALLY_SIGHASH_SINGLE = 0x03; export const WALLY_SIGHASH_TR_IN_MASK = 0xc0; /* Taproot mask for determining input hash type */ -export const WALLY_SIGTYPE_MASK = 0xf; /* Mask for signature hash in signature hash flags */ +export const WALLY_SIGTYPE_MASK = 0xf; /* Mask for signature type in in flags */ export const WALLY_SIGTYPE_PRE_SW = 0x1; /* Pre-segwit signature hash */ export const WALLY_SIGTYPE_SW_V0 = 0x2; /* Segwit v0 signature hash */ export const WALLY_SIGTYPE_SW_V1 = 0x3; /* Segwit v1 (taproot) signature hash */ diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index f14d0aedc..41ae6e7e3 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -373,6 +373,7 @@ export const psbt_get_input_sequence = wrap('wally_psbt_get_input_sequence', [T. export const psbt_get_input_sighash = wrap('wally_psbt_get_input_sighash', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const psbt_get_input_signature_hash = wrap('wally_psbt_get_input_signature_hash', [T.OpaqueRef, T.Int32, T.OpaqueRef, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, C.SHA256_LEN)]); export const psbt_get_input_signature_len = wrap('wally_psbt_get_input_signature_len', [T.OpaqueRef, T.Int32, T.Int32, T.DestPtr(T.Int32)]); +export const psbt_get_input_signature_type = wrap('wally_psbt_get_input_signature_type', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const psbt_get_input_signatures_size = wrap('wally_psbt_get_input_signatures_size', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const psbt_get_input_signing_script_len = wrap('wally_psbt_get_input_signing_script_len', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const psbt_get_input_taproot_internal_key_len = wrap('wally_psbt_get_input_taproot_internal_key_len', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 31268af00..de2fed74c 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -333,6 +333,7 @@ export function psbt_get_input_sequence(psbt: Ref_wally_psbt, index: number): nu export function psbt_get_input_sighash(psbt: Ref_wally_psbt, index: number): number; export function psbt_get_input_signature_hash(psbt: Ref_wally_psbt, index: number, tx: Ref_wally_tx, script: Buffer|Uint8Array, flags: number): Buffer; export function psbt_get_input_signature_len(psbt: Ref_wally_psbt, index: number, subindex: number): number; +export function psbt_get_input_signature_type(psbt: Ref_wally_psbt, index: number): number; export function psbt_get_input_signatures_size(psbt: Ref_wally_psbt, index: number): number; export function psbt_get_input_signing_script_len(psbt: Ref_wally_psbt, index: number): number; export function psbt_get_input_taproot_internal_key_len(psbt: Ref_wally_psbt, index: number): number; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index 320396f54..6af704488 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -247,6 +247,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_psbt_get_input_signature' \ ,'_wally_psbt_get_input_signature_hash' \ ,'_wally_psbt_get_input_signature_len' \ +,'_wally_psbt_get_input_signature_type' \ ,'_wally_psbt_get_input_signatures_size' \ ,'_wally_psbt_get_input_signing_script' \ ,'_wally_psbt_get_input_signing_script_len' \ From 91707854a5f2e08a1c3f6b6ba6079dcc8c278911 Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Tue, 1 Jul 2025 10:07:16 +1200 Subject: [PATCH 3/3] psbt: avoid a redundant keypath search for p2tr input signing --- src/psbt.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/psbt.c b/src/psbt.c index f670cb242..8e0ab3f82 100644 --- a/src/psbt.c +++ b/src/psbt.c @@ -4642,19 +4642,14 @@ int wally_psbt_sign_input_bip32(struct wally_psbt *psbt, return WALLY_EINVAL; /* Find the public key this signature is for */ - ret = wally_map_find_bip32_public_key_from(&inp->keypaths, subindex, - hdkey, &pubkey_idx); - if (ret != WALLY_OK || !pubkey_idx) { - /* Try again with the taproot public key */ + if (is_taproot) ret = wally_map_find_bip32_public_key_from(&inp->taproot_leaf_hashes, subindex, hdkey, &pubkey_idx); - if (ret == WALLY_OK && pubkey_idx && !is_taproot) - return WALLY_EINVAL; /* Must be tr if we have a tr key */ - } else if (ret == WALLY_OK && pubkey_idx && is_taproot) { - return WALLY_EINVAL; /* Must not be tr if we have a non-tr key */ - } - + else + ret = wally_map_find_bip32_public_key_from(&inp->keypaths, + subindex, hdkey, + &pubkey_idx); if (ret != WALLY_OK || !pubkey_idx) return WALLY_EINVAL; /* Signing pubkey key not found */