diff --git a/.github/workflows/wasm-package.yml b/.github/workflows/wasm-package.yml index 671e0e83c..5e2b8a80a 100644 --- a/.github/workflows/wasm-package.yml +++ b/.github/workflows/wasm-package.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 19c851b16..b7bf99bdb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,7 @@ test_with_valgrind: - for t in $(ls src/.libs/test_* | egrep -v '_clear|xml|json' | tr '\n' ' '); do valgrind-codequality --input-file $t.xml --output-file $t.json; done - for t in $(ls src/test/test_*.py | tr '\n' ' '); do WALLY_SKIP_EXPENSIVE_TESTS=1 PYTHONMALLOC=malloc PYTHONDEVMODE=1 MALLOC_CHECK_=3 valgrind --tool=memcheck --leak-check=no --verbose --xml=yes --xml-file=$t.xml python $t; done - for t in $(ls src/test/test_*.py | tr '\n' ' '); do valgrind-codequality --input-file $t.xml --output-file $t.json; done - - jq '[.[]|.[]]' -s ./src/.libs/test_*.json src/test/test_*.json > valgrind.json + - jq '[.[]|.[]]' -s ./src/.libs/test_*.json src/test/test_*.json > valgrind.json || true test_asan_ubsan_gcc: stage: test diff --git a/include/wally.hpp b/include/wally.hpp index e2f7d04a8..ef8d2e14d 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -689,6 +689,12 @@ inline int ec_public_key_negate(const PUB_KEY& pub_key, BYTES_OUT& bytes_out) { return detail::check_ret(__FUNCTION__, ret); } +template +inline int ec_public_key_tweak(const PUB_KEY& pub_key, const TWEAK& tweak, BYTES_OUT& bytes_out) { + int ret = ::wally_ec_public_key_tweak(pub_key.data(), pub_key.size(), tweak.data(), tweak.size(), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline bool ec_public_key_verify(const PUB_KEY& pub_key) { int ret = ::wally_ec_public_key_verify(pub_key.data(), pub_key.size()); @@ -2268,6 +2274,12 @@ inline int asset_blinding_key_to_ec_private_key(const BYTES& bytes, const SCRIPT return detail::check_ret(__FUNCTION__, ret); } +template +inline int asset_blinding_key_to_ec_public_key(const BYTES& bytes, const SCRIPT& script, BYTES_OUT& bytes_out) { + int ret = ::wally_asset_blinding_key_to_ec_public_key(bytes.data(), bytes.size(), script.data(), script.size(), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int asset_blinding_key_to_vbf(const BYTES& bytes, const HASH_PREVOUTS& hash_prevouts, uint32_t output_index, BYTES_OUT& bytes_out) { int ret = ::wally_asset_blinding_key_to_vbf(bytes.data(), bytes.size(), hash_prevouts.data(), hash_prevouts.size(), output_index, bytes_out.data(), bytes_out.size()); @@ -2426,6 +2438,24 @@ inline int elements_pegout_script_size(size_t genesis_blockhash_len, size_t main return detail::check_ret(__FUNCTION__, ret); } +template +inline int elip150_private_key_to_ec_private_key(const BYTES& bytes, const SCRIPT& script, BYTES_OUT& bytes_out) { + int ret = ::wally_elip150_private_key_to_ec_private_key(bytes.data(), bytes.size(), script.data(), script.size(), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int elip150_private_key_to_ec_public_key(const BYTES& bytes, const SCRIPT& script, BYTES_OUT& bytes_out) { + int ret = ::wally_elip150_private_key_to_ec_public_key(bytes.data(), bytes.size(), script.data(), script.size(), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + +template +inline int elip150_public_key_to_ec_public_key(const BYTES& bytes, const SCRIPT& script, BYTES_OUT& bytes_out) { + int ret = ::wally_elip150_public_key_to_ec_public_key(bytes.data(), bytes.size(), script.data(), script.size(), bytes_out.data(), bytes_out.size()); + return detail::check_ret(__FUNCTION__, ret); +} + template inline int explicit_rangeproof(uint64_t value, const NONCE& nonce, const VBF& vbf, const COMMITMENT& commitment, const GENERATOR& generator, BYTES_OUT& bytes_out, size_t* written) { int ret = ::wally_explicit_rangeproof(value, nonce.data(), nonce.size(), vbf.data(), vbf.size(), commitment.data(), commitment.size(), generator.data(), generator.size(), bytes_out.data(), bytes_out.size(), written); diff --git a/include/wally_crypto.h b/include/wally_crypto.h index 066603ddc..30df68019 100644 --- a/include/wally_crypto.h +++ b/include/wally_crypto.h @@ -454,6 +454,24 @@ WALLY_CORE_API int wally_ec_public_key_negate( unsigned char *bytes_out, size_t len); +/** + * Tweak a public key. + * + * :param pub_key: The public key to tweak. + * :param pub_key_len: The length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN`. + * :param tweak: The scalar/private key to tweak by. + * :param tweak_len: The length of ``tweak``. Must be `EC_PRIVATE_KEY_LEN`. + * :param bytes_out: Destination for the tweaked public key. + * FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_LEN) + */ +WALLY_CORE_API int wally_ec_public_key_tweak( + const unsigned char *pub_key, + size_t pub_key_len, + const unsigned char *tweak, + size_t tweak_len, + unsigned char *bytes_out, + size_t len); + /** * Tweak a compressed or x-only public key for taproot. * diff --git a/include/wally_descriptor.h b/include/wally_descriptor.h index c2d0deab2..7ab484174 100644 --- a/include/wally_descriptor.h +++ b/include/wally_descriptor.h @@ -22,19 +22,25 @@ struct wally_descriptor; #define WALLY_MINISCRIPT_DEPTH_SHIFT 16 /** Shift to convert maximum depth to flags */ /*** miniscript-features Miniscript/Descriptor feature flags */ -#define WALLY_MS_IS_RANGED 0x001 /** Allows key ranges via ``*`` */ -#define WALLY_MS_IS_MULTIPATH 0x002 /** Allows multiple paths via ```` */ -#define WALLY_MS_IS_PRIVATE 0x004 /** Contains at least one private key */ -#define WALLY_MS_IS_UNCOMPRESSED 0x008 /** Contains at least one uncompressed key */ -#define WALLY_MS_IS_RAW 0x010 /** Contains at least one raw key */ -#define WALLY_MS_IS_DESCRIPTOR 0x020 /** Contains only descriptor expressions (no miniscript) */ -#define WALLY_MS_IS_X_ONLY 0x040 /** Contains at least one x-only key */ -#define WALLY_MS_IS_PARENTED 0x080 /** Contains at least one key key with a parent key origin */ -#define WALLY_MS_IS_ELEMENTS 0x100 /** Contains Elements expressions or was parsed as Elements */ +#define WALLY_MS_IS_RANGED 0x001 /** Allows key ranges via ``*`` */ +#define WALLY_MS_IS_MULTIPATH 0x002 /** Allows multiple paths via ```` */ +#define WALLY_MS_IS_PRIVATE 0x004 /** Contains at least one private key */ +#define WALLY_MS_IS_UNCOMPRESSED 0x008 /** Contains at least one uncompressed key */ +#define WALLY_MS_IS_RAW 0x010 /** Contains at least one raw key */ +#define WALLY_MS_IS_DESCRIPTOR 0x020 /** Contains only descriptor expressions (no miniscript) */ +#define WALLY_MS_IS_X_ONLY 0x040 /** Contains at least one x-only key */ +#define WALLY_MS_IS_PARENTED 0x080 /** Contains at least one key key with a parent key origin */ +#define WALLY_MS_IS_ELEMENTS 0x100 /** Contains Elements expressions or was parsed as Elements */ +#define WALLY_MS_IS_SLIP77 0x200 /** A confidential ct() descriptor with SLIP-77 blinding */ +#define WALLY_MS_IS_ELIP150 0x400 /** A confidential ct() descriptor with ELIP-150 blinding */ +#define WALLY_MS_IS_ELIP151 0x800 /** A confidential ct() descriptor with ELIP-151 blinding */ +#define WALLY_MS_ANY_BLINDING_KEY 0xE00 /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ /*** ms-canonicalization-flags Miniscript/Descriptor canonicalization flags */ #define WALLY_MS_CANONICAL_NO_CHECKSUM 0x01 /** Do not include a checksum */ +#define WALLY_MS_BLINDING_KEY_INDEX 0xffffffff /* Key index for confidential blinding key */ + /** * Parse an output descriptor or miniscript expression. * @@ -192,7 +198,8 @@ WALLY_CORE_API int wally_descriptor_get_depth( * :param descriptor: Parsed output descriptor or miniscript expression. * :param value_out: Destination for the number of keys. * - * .. note:: Repeated keys are counted once for each time they appear. + * .. note:: Repeated keys are counted once for each time they appear, and any + *| blinding key within the descriptor is not included in the count. */ WALLY_CORE_API int wally_descriptor_get_num_keys( const struct wally_descriptor *descriptor, @@ -202,13 +209,16 @@ WALLY_CORE_API int wally_descriptor_get_num_keys( * Get the string representation of a key in a parsed output descriptor or miniscript expression. * * :param descriptor: Parsed output descriptor or miniscript expression. - * :param index: The zero-based index of the key to get. + * :param index: The zero-based index of the key to get, or `WALLY_MS_BLINDING_KEY_INDEX` + *| to fetch the descriptors blinding key representaton (if any). * :param output: Destination for the resulting string representation. *| The string returned should be freed using `wally_free_string`. * * .. note:: Keys may be BIP32 xpub/xpriv, WIF or hex pubkeys, and may be *| x-only. The caller can use `wally_descriptor_get_key_features` to *| determine the type of a given key. + * + * .. note:: Raw private blinding keys are returned as hex, not WIF. */ WALLY_CORE_API int wally_descriptor_get_key( const struct wally_descriptor *descriptor, @@ -219,7 +229,8 @@ WALLY_CORE_API int wally_descriptor_get_key( * Get the features of a key in a parsed output descriptor or miniscript expression. * * :param descriptor: Parsed output descriptor or miniscript expression. - * :param index: The zero-based index of the key to get. + * :param index: The zero-based index of the key to get, or `WALLY_MS_BLINDING_KEY_INDEX` + *| to fetch the descriptors blinding key features (if any). * :param value_out: Destination for the resulting :ref:`miniscript-features`. */ WALLY_CORE_API int wally_descriptor_get_key_features( diff --git a/include/wally_elements.h b/include/wally_elements.h index 85a0c641d..d1e4f30b2 100644 --- a/include/wally_elements.h +++ b/include/wally_elements.h @@ -495,7 +495,7 @@ WALLY_CORE_API int wally_asset_blinding_key_from_seed( size_t len); /** - * Generate a blinding private key for a scriptPubkey. + * Generate a blinding private key for a scriptPubkey from a SLIP-0077 master blinding key. * * :param bytes: A full master blinding key, e.g. from `wally_asset_blinding_key_from_seed`, *| or a partial key of length `SHA256_LEN`, typically from the last half of the full key. @@ -513,6 +513,70 @@ WALLY_CORE_API int wally_asset_blinding_key_to_ec_private_key( unsigned char *bytes_out, size_t len); +/** + * Generate a blinding public key for a scriptPubkey from a SLIP-0077 master blinding key. + * + * :param bytes: A full master blinding key, e.g. from `wally_asset_blinding_key_from_seed`, + *| or a partial key of length `SHA256_LEN`, typically from the last half of the full key. + * :param bytes_len: Length of ``bytes``. Must be `HMAC_SHA512_LEN` or `SHA256_LEN`. + * :param script: The scriptPubkey for the confidential output address. + * :param script_len: Length of ``script``. + * :param bytes_out: Destination for the resulting blinding public key. + * FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_LEN) + */ +WALLY_CORE_API int wally_asset_blinding_key_to_ec_public_key( + const unsigned char *bytes, + size_t bytes_len, + const unsigned char *script, + size_t script_len, + unsigned char *bytes_out, + size_t len); + +/** + * Generate a blinding private key for a scriptPubkey from an ELIP-0150 blinding private key. + * + * :param bytes: An ELIP-0150 blinding private key ("View Key"), e.g. from a ct() descriptor. + * :param bytes_len: Length of ``bytes``. Must be `EC_PRIVATE_KEY_LEN`. + * :param script: The scriptPubkey for the confidential output address. + * :param script_len: Length of ``script``. + * :param bytes_out: Destination for the resulting blinding private key. + * FIXED_SIZED_OUTPUT(len, bytes_out, EC_PRIVATE_KEY_LEN) + */ +WALLY_CORE_API int wally_elip150_private_key_to_ec_private_key( + const unsigned char *bytes, size_t bytes_len, + const unsigned char *script, size_t script_len, + unsigned char *bytes_out, size_t len); + +/** + * Generate a blinding public key for a scriptPubkey from an ELIP-0150 blinding private key. + * + * :param bytes: An ELIP-0150 blinding private key ("View Key"), e.g. from a ct() descriptor. + * :param bytes_len: Length of ``bytes``. Must be `EC_PRIVATE_KEY_LEN`. + * :param script: The scriptPubkey for the confidential output address. + * :param script_len: Length of ``script``. + * :param bytes_out: Destination for the resulting blinding public key. + * FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_LEN) + */ +WALLY_CORE_API int wally_elip150_private_key_to_ec_public_key( + const unsigned char *bytes, size_t bytes_len, + const unsigned char *script, size_t script_len, + unsigned char *bytes_out, size_t len); + +/** + * Generate a blinding public key for a scriptPubkey from an ELIP-0150 blinding public key. + * + * :param bytes: An ELIP-0150 blinding public key, e.g. from a ct() descriptor. + * :param bytes_len: Length of ``bytes``. Must be `EC_PUBLIC_KEY_LEN`. + * :param script: The scriptPubkey for the confidential output address. + * :param script_len: Length of ``script``. + * :param bytes_out: Destination for the resulting blinding public key. + * FIXED_SIZED_OUTPUT(len, bytes_out, EC_PUBLIC_KEY_LEN) + */ +WALLY_CORE_API int wally_elip150_public_key_to_ec_public_key( + const unsigned char *bytes, size_t bytes_len, + const unsigned char *script, size_t script_len, + unsigned char *bytes_out, size_t len); + #define WALLY_ABF_VBF_LEN 64 /** diff --git a/src/bip32.c b/src/bip32.c index dfc907ecb..55b08eee5 100644 --- a/src/bip32.c +++ b/src/bip32.c @@ -639,7 +639,6 @@ int bip32_key_from_parent(const struct ext_key *hdkey, uint32_t child_num, uint32_t flags, struct ext_key *key_out) { struct sha512 sha; - const secp256k1_context *ctx; const bool we_are_private = hdkey && key_is_private(hdkey); const bool derive_private = !(flags & BIP32_FLAG_KEY_PUBLIC); const bool hardened = child_is_hardened(child_num); @@ -650,9 +649,6 @@ int bip32_key_from_parent(const struct ext_key *hdkey, uint32_t child_num, if (!hdkey || !key_out) return WALLY_EINVAL; - if (!(ctx = secp_ctx())) - return WALLY_ENOMEM; - if (!we_are_private && (derive_private || hardened)) return wipe_key_fail(key_out); /* Unsupported derivation */ @@ -710,20 +706,14 @@ int bip32_key_from_parent(const struct ext_key *hdkey, uint32_t child_num, } else { /* The returned child key ki is point(parse256(IL) + kpar) * In case parse256(IL) ≥ n or Ki is the point at infinity, the - * resulting key is invalid (NOTE: pubkey_tweak_add checks both - * conditions) + * resulting key is invalid (NOTE: wally_ec_public_key_tweak checks + * both conditions) */ - secp256k1_pubkey pub_key; - size_t len = sizeof(key_out->pub_key); - - /* FIXME: Out of bounds on pubkey_tweak_add */ - if (!pubkey_parse(&pub_key, hdkey->pub_key, sizeof(hdkey->pub_key)) || - !pubkey_tweak_add(ctx, &pub_key, sha.u.u8) || - !pubkey_serialize(key_out->pub_key, &len, &pub_key, - PUBKEY_COMPRESSED) || - len != sizeof(key_out->pub_key)) { + if (wally_ec_public_key_tweak(hdkey->pub_key, sizeof(hdkey->pub_key), + sha.u.u8, EC_PRIVATE_KEY_LEN, + key_out->pub_key, sizeof(key_out->pub_key)) + != WALLY_OK) goto fail; - } } #ifndef WALLY_ABI_NO_ELEMENTS memset(key_out->pub_key_tweak_sum, 0, @@ -862,27 +852,22 @@ int bip32_key_with_tweak_from_parent_path(const struct ext_key *hdkey, #ifndef BUILD_ELEMENTS return WALLY_ERROR; #else - const secp256k1_context *ctx; - secp256k1_pubkey pub_key; - size_t len = EC_PUBLIC_KEY_LEN; int ret; - if (!(ctx = secp_ctx())) - return WALLY_ENOMEM; - if (!(flags & (BIP32_FLAG_KEY_TWEAK_SUM | BIP32_FLAG_KEY_PUBLIC))) return WALLY_EINVAL; - if ((ret = bip32_key_from_parent_path(hdkey, child_path, - child_path_len, flags, output)) != WALLY_OK) - return ret; - - if (!pubkey_parse(&pub_key, hdkey->pub_key, sizeof(hdkey->pub_key)) || - !pubkey_tweak_add(ctx, &pub_key, output->pub_key_tweak_sum) || - !pubkey_serialize(output->pub_key, &len, &pub_key, PUBKEY_COMPRESSED)) - return wipe_key_fail(output); - - return WALLY_OK; + ret = bip32_key_from_parent_path(hdkey, child_path, + child_path_len, flags, output); + if (ret == WALLY_OK) { + ret = wally_ec_public_key_tweak(hdkey->pub_key, sizeof(hdkey->pub_key), + output->pub_key_tweak_sum, + sizeof(output->pub_key_tweak_sum), + output->pub_key, sizeof(output->pub_key)); + if (ret != WALLY_OK) + wipe_key_fail(output); + } + return ret; #endif /* BUILD_ELEMENTS */ } diff --git a/src/blech32.c b/src/blech32.c index 6baae2a52..2ea189152 100644 --- a/src/blech32.c +++ b/src/blech32.c @@ -220,7 +220,7 @@ int wally_confidential_addr_to_addr_segwit( return WALLY_ERROR; #else unsigned char buf[WALLY_BLECH32_MAXLEN]; - unsigned char *hash_bytes_p = &buf[EC_PUBLIC_KEY_LEN - 2]; + unsigned char *p = &buf[EC_PUBLIC_KEY_LEN - 2]; size_t written = 0; int ret; uint8_t witver; @@ -237,9 +237,9 @@ int wally_confidential_addr_to_addr_segwit( ret = WALLY_EINVAL; else { written = written - EC_PUBLIC_KEY_LEN + 2; - hash_bytes_p[0] = value_to_op_n(witver); - hash_bytes_p[1] = (unsigned char) (written - 2); - ret = wally_addr_segwit_from_bytes(hash_bytes_p, written, + p[0] = value_to_op_n(witver); + p[1] = (unsigned char) (written - 2); + ret = wally_addr_segwit_from_bytes(p, written, addr_family, 0, output); } @@ -290,7 +290,7 @@ int wally_confidential_addr_from_addr_segwit( #else char result[WALLY_BLECH32_MAXLEN + 1]; unsigned char buf[EC_PUBLIC_KEY_LEN + SHA256_LEN]; - unsigned char *hash_bytes_p = &buf[EC_PUBLIC_KEY_LEN - 2]; + unsigned char *p = &buf[EC_PUBLIC_KEY_LEN - 2]; size_t written = SHA256_LEN + 2; int ret; size_t witver; @@ -305,14 +305,14 @@ int wally_confidential_addr_from_addr_segwit( /* get witness program's script */ ret = wally_addr_segwit_to_bytes(address, addr_family, 0, - hash_bytes_p, written, &written); + p, written, &written); if (ret == WALLY_OK) { if ((written != (HASH160_LEN + 2)) && (written != (SHA256_LEN + 2))) { ret = WALLY_EINVAL; goto done; } - if (!script_is_op_n(hash_bytes_p[0], true, &witver)) { + if (!script_is_op_n(p[0], true, &witver)) { ret = WALLY_EINVAL; goto done; } diff --git a/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c index 65199eed3..6829c7eb3 100644 --- a/src/ctest/test_descriptor.c +++ b/src/ctest/test_descriptor.c @@ -30,6 +30,7 @@ static struct wally_map_item g_key_map_items[] = { { B("key_1"), B("038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, { B("key_2"), B("03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, { B("key_3"), B("03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284") }, + { B("key_4"), B("xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH") }, { B("key_likely"), B("038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, { B("key_unlikely"), B("03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7") }, { B("key_user"), B("038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048") }, @@ -45,7 +46,8 @@ static struct wally_map_item g_key_map_items[] = { { B("x_only"), B("b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e") }, { B("non_x_only"), B("03b71aa79cab0ae2d83b82d44cbdc23f5dcca3797e8ba622c4e45a8f7dce28ba0e") }, /* The taproot singlesig xpriv corresponding to Jades test_jade.py test script */ - { B("jade_ss_tr_xpriv"), B("tprv8gTfWnFCND72oJZfZTokBBXcS1FzQhrtd5wNFu3FgBE76yErH49cev2Zn3Wws3o6ZwKZVZaQP1UWKVNotpPg8U6tCgGrjMfaRQJvV1Vdbi7") } + { B("jade_ss_tr_xpriv"), B("tprv8gTfWnFCND72oJZfZTokBBXcS1FzQhrtd5wNFu3FgBE76yErH49cev2Zn3Wws3o6ZwKZVZaQP1UWKVNotpPg8U6tCgGrjMfaRQJvV1Vdbi7") }, + { B("slip77_key"), B("b2396b3ee20509cdb64fe24180a14a72dbd671728eaa49bac69d2bdecb5f5a04") } }; static const struct wally_map g_key_map = { @@ -315,13 +317,13 @@ static const struct descriptor_test { "e0pf8z74" },{ "descriptor - p2wsh-multi-xpub", - "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,key_4/0/0/*))", WALLY_NETWORK_NONE, 0, 0, 0, &g_miniscript_index_16, 0, "00204616bb4e66d0b540b480c5b26c619385c4c2b83ed79f4f3eab09b01745443a55", "t2zpj2eu" },{ "descriptor - p2wsh-sortedmulti-xpub", - "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,key_4/0/0/*))", WALLY_NETWORK_NONE, 0, 0, 0, &g_miniscript_index_16, 0, "002002aeee9c3773dfecfe6215f2eea2908776b1232513a700e1ee516b634883ecb0", "v66cvalc" @@ -399,12 +401,25 @@ static const struct descriptor_test { "tp2ky708" }, #ifdef BUILD_ELEMENTS + /* Elements/Confidential descriptors */ { - "descriptor - Elements tr", + "descriptor - tr() interpreted as Elements", "tr([59d1f3b0/86h/1h/0h]jade_ss_tr_xpriv/0/*)", WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_AS_ELEMENTS, "5120900d1d75269396d4220c4529527dbcb746a6093c7209cea2d76a87c8ab9447fc", "3d4maj53" + }, { + "descriptor - Elements eltr()", + "eltr([59d1f3b0/86h/1h/0h]jade_ss_tr_xpriv/0/*)", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "5120900d1d75269396d4220c4529527dbcb746a6093c7209cea2d76a87c8ab9447fc", + "nldl65wt" + }, { + "descriptor - slip77 (ELIP150 Valid Descriptor 5)", + "ct(slip77(slip77_key),elpkh(key_4))#hw2glz99", + WALLY_NETWORK_NONE, 0, 0, 0, NULL, 0, + "76a9145a61ff8eb7aaca3010db97ebda76121610b7809688ac", + "hw2glz99" }, #endif { @@ -445,7 +460,7 @@ static const struct descriptor_test { "8re62ejc" },{ "descriptor - derive key index 0", - "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,key_4/0/0/*))", WALLY_NETWORK_NONE, 0, 0, 0, &g_miniscript_index_0, 0, "002064969d8cdca2aa0bb72cfe88427612878db98a5f07f9a7ec6ec87b85e9f9208b", "t2zpj2eu" @@ -961,7 +976,7 @@ static const struct descriptor_test { "" }, { "miniscript - taproot bip32 key", - "c:pk_k([bd16bee5/0]xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/1)", + "c:pk_k([bd16bee5/0]key_4/0/0/1)", WALLY_NETWORK_NONE, 0, 0, 0, NULL, WALLY_MINISCRIPT_ONLY | WALLY_MINISCRIPT_TAPSCRIPT, "208c6f5956c3cc7251d483fc683fa06b22d4e2ddc7496a2590acee36c4a313f816ac", "" @@ -1588,6 +1603,70 @@ static const struct descriptor_test { WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" } +#ifdef BUILD_ELEMENTS + /* Elements/Confidential descriptor error cases */ + , { + "descriptor errchk - empty ct()", + "ct()", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - wrapper on ct()", + "v:ct(slip77(slip77_key),elpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - single child ct()", + "ct(slip77(slip77_key))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - multiple blinding keys in ct()", + "ct(slip77(slip77_key),slip77(slip77_key))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - missing blinding key in ct()", + "ct(elpkh(key_4),elpkh(mainnet_xpub))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - multiple descriptors in ct()", + "ct(slip77(slip77_key),elpkh(key_4),elpkh(mainnet_xpub))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - no args to slip77()", + "ct(slip77,elpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - empty args to slip77()", + "ct(slip77(),elpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - invalid blinding key in slip77()", + "ct(slip77(010101010101010101),elpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - wrapper on slip77()", + "ct(v:slip77(slip77_key),elpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - ELIP 150 WIF private key", + "ct(L3jXxwef3fpB7hcrFozcWgHeJCPSAFiZ1Ji2YJMPxceaGvy3PC1q,elwpkh(03774eec7a3d550d18e9f89414152025b3b0ad6a342b19481f702d843cff06dfc4))#gcy6hcfz", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - ELIP 150 blinding key with wildcard", + "ct(mainnet_xpub/0/*,elpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - ELIP 150 blinding key with multipath", + "ct(mainnet_xpub/<0;1>,elpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - ELIP 150 short hex key", + "ct(fc9a38e765d955e9b0bcc18fa9ae81b0c893e2dd1ef5542a9c73780a086b90,elwpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + }, { + "descriptor errchk - ELIP 150 long hex key", + "ct(0202fc9a38e765d955e9b0bcc18fa9ae81b0c893e2dd1ef5542a9c73780a086b90,elwpkh(key_4))", + WALLY_NETWORK_LIQUID, 0, 0, 0, NULL, 0, NULL, "" + } +#endif /* BUILD_ELEMENTS */ }; #define ADDR(a) 1, { a, "", "", "", "", "", "", "", "", "", "", "", "", "", \ @@ -1728,13 +1807,13 @@ static const struct address_test { ADDR("14qCH92HCyDDBFFZdhDt1WMfrMDYnBFYMF") },{ "address - p2wsh-multi-xpub", - "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,key_4/0/0/*))", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, ADDR("bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c") },{ "address - p2wsh-sortedmulti-xpub", - "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))", + "wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,key_4/0/0/*))", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, ADDR("bc1qvjtfmrxu524qhdevl6yyyasjs7xmnzjlqlu60mrwepact60eyz9s9xjw0c") @@ -1840,7 +1919,7 @@ static const struct address_test { ADDR("mn9rm3FtHUHANae2p5jURy9GXJGDM1ox43") }, /* - * Taproot + * Taproot (Bitcoin) */ { "address - rawtr (x-only)", @@ -1900,7 +1979,7 @@ static const struct address_test { */ { "address list - p2wsh multisig (0-29)", - "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))#t2zpj2eu", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,key_4/0/0/*))#t2zpj2eu", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, 30, { @@ -1937,7 +2016,7 @@ static const struct address_test { } }, { "address list - p2wsh multisig (30-40)", - "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))#t2zpj2eu", + "wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,key_4/0/0/*))#t2zpj2eu", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 30, 11, { @@ -1955,6 +2034,82 @@ static const struct address_test { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" } }, + /* Elements/Confidential descriptors */ +#ifdef BUILD_ELEMENTS + { + "address - ELIP 150 Valid Descriptor 1", + "ct(mainnet_xpub,elpkh(key_4))#y0lg3d5y", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("VTpvZZYdbhbyVF3Wa99eMjgXhfvu4LS26dR2FwMfNXq7FDX73HZEsZr3VvgH9EDgQnYK7sP6ACKSuMGw") + }, { + "address - ELIP 150 Valid Descriptor 2", + "ct(mainnet_xpub,elwpkh(key_4))#kt4e25qt", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qqg5s7xj7upzl7h4q2k2wj4vq63nvaktn0egqu09nqcr6d44p4evaqknpl78t02k2xqgdh9ltmfmpy9ssk7qfvghdsfr4mvr9c") + }, { + "address - ELIP 150 Valid Descriptor 3", + "ct(mainnet_xpub,elsh(wpkh(key_4)))#xg9r4jej", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("VJL8znN4XjXEUKzDaYsqdzRASGLY2KHxC4N6g5b5QvrNjXfeKp83Ci9AW2a8QzbZjpEffoy4PEywpLAZ") + }, { + "address - ELIP 150 Valid Descriptor 4", + "ct(mainnet_xpub,eltr(key_4))#c0pjjxyw", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1pq0nsl8du3gsuk7r90sgm78259mmv6mt9d4yvj30zr3u052ufs5meuc2tuvwx7k7g9kvhhpux07vqpm3qjj8uwdj94650265ustv0xy8zrdxdfgp8g9pl") + }, { + "address - ELIP 150 View Descriptor", + "ct(xprv9s21ZrQH143K28NgQ7bHCF61hy9VzwquBZvpzTwXLsbmQLRJ6iV9k2hUBRt5qzmBaSpeMj5LdcsHaXJvM7iFEivPryRcL8irN7Na9p65UUb,elwpkh(key_4))#j95xktq7", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qq2r0pdvcknjpwev96qu9975alzqs78cvsut5ju82t7tv8d645dgmwknpl78t02k2xqgdh9ltmfmpy9ssk7qfvtk83xqzx62q4") + }, { + "address - ELIP 150 Non-View Descriptor", + "ct(xpub661MyMwAqRbcEcT9W98HZP2kFzyzQQZkYnrRnrM8uD8kH8kSeFoQHq1x2iihLgC6PXGy5LrjCL66uSNhJ8pwjfx2rMUTLWuRMns2EG9xnjs,elwpkh(key_4))#elmfpmp9", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qq2r0pdvcknjpwev96qu9975alzqs78cvsut5ju82t7tv8d645dgmwknpl78t02k2xqgdh9ltmfmpy9ssk7qfvtk83xqzx62q4") + }, { + "address - ELIP 150 View Descriptor 2", + "ct(c25deb86fa11e49d651d7eae27c220ef930fbd86ea023eebfa73e54875647963,elwpkh(021a8fb6bd5a653b021b98a2a785725b8ddacfe3687bc043aa7f4d25d3a48d40b5))#c2kx9zll", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qq265u4g3k3m3qpyxjwpdrtnm293wuxgvs9xzmzcs2ck0mv5rx23w4d7xfsednsmmxrszfe7s9rs0c6cvf3dfytxax3utlmm46") + }, { + "address - ELIP 150 Non-View Descriptor 2", + "ct(0286fc9a38e765d955e9b0bcc18fa9ae81b0c893e2dd1ef5542a9c73780a086b90,elwpkh(021a8fb6bd5a653b021b98a2a785725b8ddacfe3687bc043aa7f4d25d3a48d40b5))#m5mvyh29", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qq265u4g3k3m3qpyxjwpdrtnm293wuxgvs9xzmzcs2ck0mv5rx23w4d7xfsednsmmxrszfe7s9rs0c6cvf3dfytxax3utlmm46") + }, { + "address - slip77 (ELIP 150 Valid Descriptor 5)", + "ct(slip77(slip77_key),elpkh(key_4))#hw2glz99", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("VTq585ahVjWarEwg2nKQ9yYirmYs5F5j74CeYYA9cq1EZD9obm7hwpx6xqq3J1AY9YRaSavEMzYfr6t7") + }, { + "address - slip77 (ELIP 150 Valid Descriptor 6)", + "ct(slip77(slip77_key),elwpkh(key_4))#545pl285", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1qqdx5wnttttzulcs6ujlg9pfts6mp3r4sdwg5ekdej566n5wxzk88vknpl78t02k2xqgdh9ltmfmpy9ssk7qfvr33xa22hpw23") + }, { + "address - slip77 (ELIP 150 Valid Descriptor 7)", + "ct(slip77(slip77_key),elsh(wpkh(key_4)))#m30vswxr", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("VJLFGQ17aGa3WSVEVyxzDktD9SFixJjfSmqVq8xaWmR9X6gFbiF95KFwKA41PBhu3jNTxJFKTUphHL8J") + }, { + "address - slip77 (ELIP 150 Valid Descriptor 8)", + "ct(slip77(slip77_key),eltr(key_4))#n3v4t5cs", + WALLY_NETWORK_LIQUID, + 0, 0, 0, + ADDR("lq1pq26fndnz8ef6umlz6e2755sm6j5jwxv3tdt2295mr4mx6ux0uf8vcc2tuvwx7k7g9kvhhpux07vqpm3qjj8uwdj94650265ustv0xy8z8wfacw9e5a5t") + }, +#endif /* BUILD_ELEMENTS */ /* * Address error cases */ @@ -2098,7 +2253,7 @@ static const struct address_test { static bool check_descriptor_to_script(const struct descriptor_test* test) { struct wally_descriptor *descriptor; - size_t written, computed_written, max_written; + size_t written, upper_limit, max_written; const size_t default_script_len = 520; char *checksum, *canonical; int expected_ret, ret, len_ret; @@ -2107,13 +2262,13 @@ static bool check_descriptor_to_script(const struct descriptor_test* test) const bool is_policy = test->flags & WALLY_MINISCRIPT_POLICY_TEMPLATE; const struct wally_map *keys = is_policy ? &g_policy_maps[0] : &g_key_map; + /* Parse the descriptor. For policy tests, use the policy keys */ expected_ret = test->script ? WALLY_OK : WALLY_EINVAL; if (is_policy && expected_ret == WALLY_OK) { const char *p = strchr(test->descriptor, '@'); if (p && strchr(p + 1, '@')) keys = &g_policy_maps[1]; /* 2-Element policy key map */ } - ret = wally_descriptor_parse(test->descriptor, keys, test->network, test->flags, &descriptor); if (is_policy && expected_ret == WALLY_OK) { @@ -2133,24 +2288,45 @@ static bool check_descriptor_to_script(const struct descriptor_test* test) return true; } - computed_written = default_script_len; + /* Test script generation */ + upper_limit = default_script_len; if (expected_ret == WALLY_OK) { - /* Try the call with a too-short buffer. - * This returns a more exact required size for generation, although - * it may still overestimate by a few bytes for some descriptors. + /* Try the call with too-short buffers, up to the returned required + * length. This returns a more exact required size for generation + * than wally_descriptor_to_script_get_maximum_length(), although it + * may still overestimate by a few bytes for some descriptors which + * have a variable size based on the generated contents. */ - unsigned char *short_script = malloc(1); - ret = wally_descriptor_to_script(descriptor, - test->depth, test->index, - test->variant, multi_index, - child_num, 0, - short_script, 1, &computed_written); - free(short_script); - if (!check_ret("descriptor_to_script(short buffer)\n", ret, expected_ret)) - return false; + for (size_t i = 1; i <= upper_limit; ++i) { + unsigned char *short_script = malloc(i); + + ret = wally_descriptor_to_script(descriptor, + test->depth, test->index, + test->variant, multi_index, + child_num, 0, + short_script, i, &written); + free(short_script); + if (!check_ret("descriptor_to_script(short buffer)\n", ret, expected_ret)) { + wally_descriptor_free(descriptor); + return false; + } + if (i == 1) { + /* First call: store the the returned required size */ + upper_limit = written; + } else { + /* Subsequent calls with an output buffer up up to the + * required size must return at least the required size */ + if (written > upper_limit) { + printf("%s: %d > %d!\n", test->name, + (int)written, (int)upper_limit); + wally_descriptor_free(descriptor); + return false; + } + } + } } - const size_t script_len = computed_written ? computed_written : 1; + const size_t script_len = upper_limit ? upper_limit : 1; unsigned char *script = malloc(script_len); ret = wally_descriptor_to_script(descriptor, test->depth, test->index, @@ -2158,48 +2334,61 @@ static bool check_descriptor_to_script(const struct descriptor_test* test) child_num, 0, script, script_len, &written); if (ret == WALLY_OK && written > script_len) { - printf("descriptor_to_script: wrote more than computed length!\n"); - return false; + printf("descriptor_to_script: wrote more than computed length:\n"); + printf("%s: %d vs %d!\n", test->name, (int)written, (int)script_len); + ret = false; + goto end; + } + if (!check_ret("descriptor_to_script", ret, expected_ret)) { + ret = false; + goto end; } - if (!check_ret("descriptor_to_script", ret, expected_ret)) - return false; - if (expected_ret != WALLY_OK) { /* Failure case: stop testing here */ - wally_descriptor_free(descriptor); - free(script); - return true; + ret = true; + goto end; } - if (computed_written < written) { + if (upper_limit < written) { printf("descriptor_to_script: computed < written\n"); - return false; + ret = false; + goto end; } ret = wally_descriptor_get_features(descriptor, &features); - if (!check_ret("descriptor_get_features", ret, WALLY_OK)) - return false; + if (!check_ret("descriptor_get_features", ret, WALLY_OK)) { + ret = false; + goto end; + } len_ret = wally_descriptor_to_script_get_maximum_length(descriptor, 0, 0, 0, 0, 0, 0, &max_written); if (!check_ret("descriptor_to_script_get_maximum_length", len_ret, WALLY_OK) || - max_written < written) - return false; + max_written < written) { + ret = false; + goto end; + } - if (computed_written > max_written) { - printf("descriptor_to_script: computed > max written\n"); - return false; + if (upper_limit > max_written) { + printf("descriptor_to_script: computed %d > max written %d\n", + (int)upper_limit, (int)max_written); + ret = false; + goto end; } ret = wally_descriptor_get_checksum(descriptor, 0, &checksum); - if (!check_ret("descriptor_get_checksum", ret, WALLY_OK)) - return false; + if (!check_ret("descriptor_get_checksum", ret, WALLY_OK)) { + ret = false; + goto end; + } ret = wally_descriptor_canonicalize(descriptor, 0, &canonical); wally_free_string(canonical); - if (!check_ret("descriptor_canonicalize", ret, WALLY_OK)) - return false; + if (!check_ret("descriptor_canonicalize", ret, WALLY_OK)) { + ret = false; + goto end; + } ret = check_varbuff("descriptor_to_script", script, written, test->script) && (!*test->checksum || !strcmp(checksum, test->checksum)); @@ -2208,6 +2397,7 @@ static bool check_descriptor_to_script(const struct descriptor_test* test) test->checksum, checksum); wally_free_string(checksum); +end: wally_descriptor_free(descriptor); free(script); return !!ret; diff --git a/src/descriptor.c b/src/descriptor.c index 0e68a8301..59f1a248d 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -9,6 +9,9 @@ #include #include #include +#ifdef BUILD_ELEMENTS +#include +#endif #include #include @@ -89,6 +92,9 @@ #define KIND_DESCRIPTOR_RAW (0x00050000 | KIND_DESCRIPTOR) #define KIND_DESCRIPTOR_RAW_TR (0x00100000 | KIND_DESCRIPTOR) #define KIND_DESCRIPTOR_TR (0x00200000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_CT (0x00300000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_SLIP77 (0x00400000 | KIND_DESCRIPTOR) +#define KIND_DESCRIPTOR_ELIP151 (0x00500000 | KIND_DESCRIPTOR) /* miniscript */ #define KIND_MINISCRIPT_PK (0x00000100 | KIND_MINISCRIPT) @@ -117,7 +123,9 @@ struct addr_ver_t { const unsigned char version_p2pkh; const unsigned char version_p2sh; const unsigned char version_wif; - const char family[8]; + const unsigned char elements_prefix; + const char bech32[5]; /* bech32 prefix */ + const char blech32[4]; /* blech32 prefix */ }; static const struct addr_ver_t g_address_versions[] = { @@ -126,42 +134,54 @@ static const struct addr_ver_t g_address_versions[] = { WALLY_ADDRESS_VERSION_P2PKH_MAINNET, WALLY_ADDRESS_VERSION_P2SH_MAINNET, WALLY_ADDRESS_VERSION_WIF_MAINNET, - { 'b', 'c', '\0', '\0', '\0', '\0', '\0', '\0' } + 0, + { 'b', 'c', '\0', '\0', '\0' }, + { '\0', '\0', '\0', '\0' } }, { WALLY_NETWORK_BITCOIN_TESTNET, WALLY_ADDRESS_VERSION_P2PKH_TESTNET, WALLY_ADDRESS_VERSION_P2SH_TESTNET, WALLY_ADDRESS_VERSION_WIF_TESTNET, - { 't', 'b', '\0', '\0', '\0', '\0', '\0', '\0' } + 0, + { 't', 'b', '\0', '\0', '\0' }, + { '\0', '\0', '\0', '\0' } }, { /* Bitcoin regtest. This must remain immediately after WALLY_NETWORK_BITCOIN_TESTNET */ WALLY_NETWORK_BITCOIN_REGTEST, WALLY_ADDRESS_VERSION_P2PKH_TESTNET, WALLY_ADDRESS_VERSION_P2SH_TESTNET, WALLY_ADDRESS_VERSION_WIF_TESTNET, - { 'b', 'c', 'r', 't', '\0', '\0', '\0', '\0' } + 0, + { 'b', 'c', 'r', 't', '\0' }, + { '\0', '\0', '\0', '\0' } }, { WALLY_NETWORK_LIQUID, WALLY_ADDRESS_VERSION_P2PKH_LIQUID, WALLY_ADDRESS_VERSION_P2SH_LIQUID, WALLY_ADDRESS_VERSION_WIF_MAINNET, - { 'e', 'x', '\0', '\0', '\0', '\0', '\0', '\0' } + 12, + { 'e', 'x', '\0', '\0', '\0' }, + { 'l', 'q', '\0', '\0' } }, { WALLY_NETWORK_LIQUID_TESTNET, WALLY_ADDRESS_VERSION_P2PKH_LIQUID_TESTNET, WALLY_ADDRESS_VERSION_P2SH_LIQUID_TESTNET, WALLY_ADDRESS_VERSION_WIF_TESTNET, - { 't', 'e', 'x', '\0', '\0', '\0', '\0', '\0' } + 23, + { 't', 'e', 'x', '\0', '\0' }, + { 't', 'l', 'q', '\0' } }, { WALLY_NETWORK_LIQUID_REGTEST, WALLY_ADDRESS_VERSION_P2PKH_LIQUID_REGTEST, WALLY_ADDRESS_VERSION_P2SH_LIQUID_REGTEST, WALLY_ADDRESS_VERSION_WIF_TESTNET, - { 'e', 'r', 't', '\0', '\0', '\0', '\0', '\0' } + 4, + { 'e', 'r', 't', '\0', '\0' }, + { 'e', 'l', '\0', '\0' } }, }; @@ -271,8 +291,8 @@ static const struct addr_ver_t *addr_ver_from_family( const char *family, size_t family_len, uint32_t network) { const struct addr_ver_t *addr_ver = addr_ver_from_network(network); - if (!addr_ver || !family || strlen(addr_ver->family) != family_len || - memcmp(family, addr_ver->family, family_len)) + if (!addr_ver || !family || strlen(addr_ver->bech32) != family_len || + memcmp(family, addr_ver->bech32, family_len)) return NULL; /* Not found or mismatched address version */ return addr_ver; /* Found */ } @@ -531,12 +551,31 @@ static bool node_has_uncompressed_key(const ms_ctx *ctx, const ms_node *node) return false; } +static int node_is_top(const ms_node *node) +{ + /* True if this is the top node in the descriptor + * (disregarding any ct() parent for Elements). + */ +#ifdef BUILD_ELEMENTS + return !node->parent || node->parent->kind == KIND_DESCRIPTOR_CT; +#else + return !node->parent; +#endif +} + static bool node_is_root(const ms_node *node) { /* True if this is a (possibly temporary) top level node, or an argument of a builtin */ return !node->parent || node->parent->builtin; } +#ifdef BUILD_ELEMENTS +static bool node_is_ct(const ms_node *node) +{ + return !node->parent && node->kind == KIND_DESCRIPTOR_CT; +} +#endif + static void node_free(ms_node *node) { if (node) { @@ -546,7 +585,8 @@ static void node_free(ms_node *node) node_free(child); child = next; } - if (node->kind & (KIND_RAW | KIND_ADDRESS) || node->kind == KIND_PUBLIC_KEY || node->kind == KIND_PRIVATE_KEY) + if (node->kind & (KIND_RAW | KIND_ADDRESS) || + node->kind == KIND_PUBLIC_KEY || node->kind == KIND_PRIVATE_KEY) clear_and_free((void*)node->data, node->data_len); clear_and_free(node, sizeof(*node)); } @@ -577,7 +617,7 @@ int wally_descriptor_free(ms_ctx *ctx) static int verify_sh(ms_ctx *ctx, ms_node *node) { (void)ctx; - if (node->parent || !node->child->builtin) + if (!node_is_top(node) || !node->child->builtin) return WALLY_EINVAL; node->type_properties = node->child->type_properties; @@ -587,7 +627,8 @@ static int verify_sh(ms_ctx *ctx, ms_node *node) static int verify_wsh(ms_ctx *ctx, ms_node *node) { (void)ctx; - if (node->parent && node->parent->kind != KIND_DESCRIPTOR_SH) + if (node->parent && node->parent->kind != KIND_DESCRIPTOR_SH && + node->parent->kind != KIND_DESCRIPTOR_CT) return WALLY_EINVAL; if (!node->child->builtin || node_has_uncompressed_key(ctx, node)) return WALLY_EINVAL; @@ -630,7 +671,7 @@ static int verify_combo(ms_ctx *ctx, ms_node *node) const bool has_uncompressed_key = node_has_uncompressed_key(ctx, node); int ret; - if (node->parent) + if (!node_is_top(node)) return WALLY_EINVAL; if (has_uncompressed_key) { @@ -709,7 +750,7 @@ static int verify_tr(ms_ctx *ctx, ms_node *node) const uint32_t child_count = node_get_child_count(node); if (child_count != 1u) return WALLY_EINVAL; /* FIXME: Support script paths */ - if (node->parent || node->child->builtin || !(node->child->kind & KIND_KEY) || + if (!node_is_top(node) || node->child->builtin || !(node->child->kind & KIND_KEY) || node_has_uncompressed_key(ctx, node)) return WALLY_EINVAL; node->type_properties = builtin_get(node)->type_properties; @@ -1003,6 +1044,52 @@ static int verify_thresh(ms_ctx *ctx, ms_node *node) return WALLY_OK; } +#ifdef BUILD_ELEMENTS +static int verify_ct(ms_ctx *ctx, ms_node *node) +{ + (void)ctx; + if (node->parent || node->wrapper_str[0]) + return WALLY_EINVAL; + if (node->child->kind != KIND_DESCRIPTOR_SLIP77 && + !(node->child->kind & KIND_KEY) && + node->child->kind != KIND_DESCRIPTOR_ELIP151) + return WALLY_EINVAL; + if (node->child->kind & KIND_KEY) { + if (node_has_uncompressed_key(ctx, node) || + (node->flags & WALLY_MS_IS_X_ONLY)) + return WALLY_EINVAL; /* Blinding keys must be compressed non-x-only */ + } + /* Ensure the second child is a valid top level node */ + switch (node->child->next->kind) { + case KIND_DESCRIPTOR_PK | KIND_MINISCRIPT_PK: + case KIND_DESCRIPTOR_PKH | KIND_MINISCRIPT_PKH: + case KIND_DESCRIPTOR_MULTI | KIND_MINISCRIPT_MULTI: + case KIND_DESCRIPTOR_MULTI_S: + case KIND_DESCRIPTOR_SH: + case KIND_DESCRIPTOR_WPKH: + case KIND_DESCRIPTOR_WSH: + case KIND_DESCRIPTOR_COMBO: + case KIND_DESCRIPTOR_TR: + case KIND_MINISCRIPT_PK_K: + case KIND_MINISCRIPT_PK_H: + return WALLY_OK; + } + return WALLY_EINVAL; +} + +static int verify_slip77(ms_ctx *ctx, ms_node *node) +{ + (void)ctx; + if (!node->parent || node->parent->kind != KIND_DESCRIPTOR_CT) + return WALLY_EINVAL; + if (node->child->builtin || !(node->child->kind & KIND_RAW) || + node->child->data_len != 32 || node->wrapper_str[0]) + + return WALLY_EINVAL; + return WALLY_OK; +} +#endif /* ifdef BUILD_ELEMENTS */ + static int node_verify_wrappers(ms_node *node) { uint32_t *properties = &node->type_properties; @@ -1230,7 +1317,8 @@ static int generate_sh_wsh(ms_ctx *ctx, ms_node *node, return ret; } -static int generate_checksig(unsigned char *script, size_t script_len, size_t *written) +static int generate_inplace_checksig(unsigned char *script, size_t script_len, + size_t *written) { if (!*written || (*written + 1 > WITNESS_SCRIPT_MAX_SIZE)) return WALLY_EINVAL; @@ -1245,14 +1333,18 @@ static int generate_pk(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { int ret = generate_pk_k(ctx, node, script, script_len, written); - return ret == WALLY_OK ? generate_checksig(script, script_len, written) : ret; + if (ret == WALLY_OK) + ret = generate_inplace_checksig(script, script_len, written); + return ret; } static int generate_pkh(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { int ret = generate_pk_h(ctx, node, script, script_len, written); - return ret == WALLY_OK ? generate_checksig(script, script_len, written) : ret; + if (ret == WALLY_OK) + ret = generate_inplace_checksig(script, script_len, written); + return ret; } static int generate_wpkh(ms_ctx *ctx, ms_node *node, @@ -1275,7 +1367,8 @@ static int generate_wpkh(ms_ctx *ctx, ms_node *node, if (script_len < required) { *written = required; /* To generate, not for the final script */ } else { - ret = wally_witness_program_from_bytes(script, output_len, WALLY_SCRIPT_HASH160, + ret = wally_witness_program_from_bytes(script, output_len, + WALLY_SCRIPT_HASH160, output, final_len, written); if (ret == WALLY_OK && *written <= script_len) memcpy(script, output, *written); @@ -1301,8 +1394,8 @@ static int generate_sh_wpkh(ms_ctx *ctx, ms_node *node, memcpy(&fake_ctx, ctx, sizeof(fake_ctx)); fake_ctx.variant = 2; /* Generate wpkh from the combo node */ return builtin_get(&sh_node)->generate_fn(&fake_ctx, &sh_node, - script, script_len, - written); + script, script_len, + written); } static int generate_combo(ms_ctx *ctx, ms_node *node, @@ -1349,7 +1442,7 @@ static int generate_multi(ms_ctx *ctx, ms_node *node, ret = generate_script(ctx, child, item->pubkey, sizeof(item->pubkey), &item->pubkey_len); if (ret == WALLY_OK && item->pubkey_len > sizeof(item->pubkey)) - ret = WALLY_EINVAL; /* FIXME: check for valid pubkey lengths */ + ret = WALLY_ERROR; /* FIXME: check for valid pubkey lengths */ child = child->next; } @@ -1435,7 +1528,7 @@ static int generate_tr(ms_ctx *ctx, ms_node *node, /* Tweak it into a compressed pubkey */ #ifdef BUILD_ELEMENTS - if (node->flags & WALLY_MS_IS_ELEMENTS) + if (ctx->features & WALLY_MS_IS_ELEMENTS) tweak_flags = EC_FLAG_ELEMENTS; #endif ret = wally_ec_public_key_bip341_tweak(pubkey + 1, pubkey_len - 1, @@ -1456,31 +1549,31 @@ static int generate_delay(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { int ret; - size_t output_len = *written; + size_t output_len; if (!node->child || !node_is_root(node) || !node->builtin) return WALLY_EINVAL; ret = generate_script(ctx, node->child, script, script_len, &output_len); - if (ret != WALLY_OK) - return ret; - - *written = output_len + 1; - if (*written <= script_len) { - if (node->kind == KIND_MINISCRIPT_OLDER) - script[output_len] = OP_CHECKSEQUENCEVERIFY; - else if (node->kind == KIND_MINISCRIPT_AFTER) - script[output_len] = OP_CHECKLOCKTIMEVERIFY; - else - ret = WALLY_ERROR; /* Shouldn't happen */ + if (ret == WALLY_OK) { + *written = output_len + 1; + if (*written <= script_len) { + if (node->kind == KIND_MINISCRIPT_OLDER) + script[output_len] = OP_CHECKSEQUENCEVERIFY; + else if (node->kind == KIND_MINISCRIPT_AFTER) + script[output_len] = OP_CHECKLOCKTIMEVERIFY; + else + ret = WALLY_ERROR; /* Shouldn't happen */ + } } return ret; } static int generate_hash_type(ms_ctx *ctx, ms_node *node, - unsigned char *script, size_t script_len, size_t *written) + unsigned char *script, size_t script_len, + size_t *written) { int ret; - size_t hash_size, output_len = *written, remaining_len = 0; + size_t hash_size, remaining_len = 0; unsigned char op_code; if (!node->child || !node_is_root(node) || !node->builtin) @@ -1503,18 +1596,18 @@ static int generate_hash_type(ms_ctx *ctx, ms_node *node, if (script_len >= 7) remaining_len = script_len - 7; - ret = generate_script(ctx, node->child, script + 6, remaining_len, &output_len); + ret = generate_script(ctx, node->child, script + 6, remaining_len, written); if (ret == WALLY_OK) { - *written = output_len + 7; - if (*written <= script_len) { + if (*written + 7 <= script_len) { script[0] = OP_SIZE; script[1] = 0x01; script[2] = 0x20; script[3] = OP_EQUALVERIFY; script[4] = op_code; script[5] = hash_size; - script[6 + output_len] = OP_EQUAL; + script[6 + *written] = OP_EQUAL; } + *written += 7; } return ret; } @@ -1542,7 +1635,7 @@ static int generate_concat(ms_ctx *ctx, ms_node *node, size_t target_num, } for (i = 0; i < target_num; ++i) { - size_t output_len = 0, remaining_len = 0; + size_t output_len, remaining_len = 0; if (insert_len[i] && offset + insert_len[i] <= script_len) memcpy(script + offset, insert[i], insert_len[i]); @@ -1704,12 +1797,13 @@ static int generate_thresh(ms_ctx *ctx, ms_node *node, return ret; } -static int generate_wrappers(ms_node *node, - unsigned char *script, size_t script_len, size_t *written) +static int generate_inplace_wrappers(ms_node *node, + unsigned char *script, size_t script_len, + size_t *written) { - size_t i; + size_t i = strlen(node->wrapper_str); - if (node->wrapper_str[0] == '\0') + if (!i) return WALLY_OK; /* No wrappers */ if (!*written) @@ -1721,9 +1815,9 @@ static int generate_wrappers(ms_node *node, #define WRAP_REQUIRE_END } break /* Generate the nodes wrappers in reserve order */ - for (i = strlen(node->wrapper_str); i != 0; --i) { + while (i--) { size_t output_len = 0; - switch(node->wrapper_str[i - 1]) { + switch(node->wrapper_str[i]) { case 'a': WRAP_REQUIRE(2, 1); script[0] = OP_TOALTSTACK; @@ -1757,6 +1851,10 @@ static int generate_wrappers(ms_node *node, output_len = 1; } else { unsigned char *last = script + *written - 1; + /* The following check cannot happen, but scan-build believes + * it can - check for it to avoid false positives */ + if (last < script) + return WALLY_ERROR; if (*last == OP_EQUAL) *last = OP_EQUALVERIFY; else if (*last == OP_NUMEQUAL) @@ -1956,12 +2054,40 @@ static const struct ms_builtin_t g_builtins[] = { KIND_MINISCRIPT_THRESH, TYPE_B | PROP_D | PROP_U, 0xffffffff, verify_thresh, generate_thresh } + /* Elements confidential descriptors */ +#ifdef BUILD_ELEMENTS + , { + I_NAME("ct"), + KIND_DESCRIPTOR_CT, + TYPE_NONE, + 2, verify_ct, NULL /* Generation is skipped for this node type */ + }, { + I_NAME("slip77"), + KIND_DESCRIPTOR_SLIP77, + TYPE_NONE, + 1, verify_slip77, NULL /* Generation is skipped for this node type */ + } +#endif /* ifdef BUILD_ELEMENTS */ }; #undef I_NAME +#ifdef BUILD_ELEMENTS +static inline bool builtin_is_elements(const char *name, size_t name_len) +{ + /* Elements descriptor builtins are prefixed with "el" */ + return name_len > 2 && name[0] == 'e' && name[1] == 'l'; +} +#endif /* ifdef BUILD_ELEMENTS */ + static unsigned char builtin_lookup(const char *name, size_t name_len, uint32_t kind) { unsigned char i; +#ifdef BUILD_ELEMENTS + if (builtin_is_elements(name, name_len)) { + name += 2; /* Look up without matching the prefix */ + name_len -= 2; + } +#endif /* ifdef BUILD_ELEMENTS */ for (i = 0; i < NUM_ELEMS(g_builtins); ++i) { if ((g_builtins[i].kind & kind) && @@ -1981,7 +2107,8 @@ static int generate_script(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written) { int ret = WALLY_EINVAL; - size_t output_len = *written; + size_t output_len = 0; + *written = 0; if (node->builtin) { ret = builtin_get(node)->generate_fn(ctx, node, script, script_len, &output_len); @@ -2050,7 +2177,7 @@ static int generate_script(ms_ctx *ctx, ms_node *node, } } if (ret == WALLY_OK) { - ret = generate_wrappers(node, script, script_len, &output_len); + ret = generate_inplace_wrappers(node, script, script_len, &output_len); if (ret == WALLY_OK) *written = output_len; } @@ -2121,58 +2248,80 @@ static int analyze_address(ms_ctx *ctx, const char *str, size_t str_len, return ret; } -/* take the possible hex data in node->data, if it is a valid pubkey +/* take the possible hex data in node->data, if it is a valid key then * convert it to an allocated binary buffer and make this node a key node */ -static int analyze_pubkey_hex(ms_ctx *ctx, ms_node *node, - uint32_t flags, bool *is_hex) -{ - unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN]; - size_t pubkey_len; - bool allow_xonly, make_xonly = false; - - *is_hex = wally_hex_n_to_bytes(node->data, node->data_len, - pubkey, sizeof(pubkey), &pubkey_len) == WALLY_OK; - if (!*is_hex || pubkey_len > sizeof(pubkey)) - return WALLY_OK; /* Not hex, or too long */ - - if (wally_ec_public_key_verify(pubkey, pubkey_len) != WALLY_OK && - wally_ec_xonly_public_key_verify(pubkey, pubkey_len) != WALLY_OK) - return WALLY_OK; /* Not a valid pubkey */ - - make_xonly = node->parent && - (node->parent->kind == KIND_DESCRIPTOR_RAW_TR || - node->parent->kind == KIND_DESCRIPTOR_TR); - allow_xonly = make_xonly || flags & WALLY_MINISCRIPT_TAPSCRIPT; - if (pubkey_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN && allow_xonly) - return WALLY_OK; /* Uncompressed key not allowed here */ - if (pubkey_len == EC_XONLY_PUBLIC_KEY_LEN && !allow_xonly) - return WALLY_OK; /* X-only not allowed here */ - if (pubkey_len != EC_XONLY_PUBLIC_KEY_LEN) { - if (flags & WALLY_MINISCRIPT_TAPSCRIPT) - return WALLY_OK; /* Only X-only pubkeys allowed under tapscript */ - if (make_xonly) { - /* Convert to x-only */ - --pubkey_len; - memmove(pubkey, pubkey + 1, pubkey_len); +static int analyze_key_hex(ms_ctx *ctx, ms_node *node, + uint32_t flags, bool is_ct_key, bool *is_hex) +{ + unsigned char key[EC_PUBLIC_KEY_UNCOMPRESSED_LEN], *key_p = key; + size_t key_len; + bool allow_xonly, make_xonly = false, is_private = false; + + *is_hex = wally_hex_n_to_bytes(node->data, node->data_len, + key, sizeof(key), &key_len) == WALLY_OK; + if (!*is_hex) + return WALLY_OK; /* Not a hex string */ + + if (key_len == EC_PRIVATE_KEY_LEN && is_ct_key) { + if (wally_ec_private_key_verify(key, key_len) != WALLY_OK) + return WALLY_OK; /* Not a valid private key */ + is_private = true; + } else if (key_len == EC_XONLY_PUBLIC_KEY_LEN) { + if (wally_ec_xonly_public_key_verify(key, key_len) != WALLY_OK) + return WALLY_OK; /* Not a valid x-only key */ + } else if (key_len == EC_PUBLIC_KEY_LEN || + key_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN) { + if (wally_ec_public_key_verify(key, key_len) != WALLY_OK) + return WALLY_OK; /* Not a valid compressed/uncompressed pubkey */ + } else + return WALLY_OK; /* Not a pubkey */ + + if (!is_private) { + /* Ensure the pubkey is allowed in this context/convert as needed */ + make_xonly = node->parent && + (node->parent->kind == KIND_DESCRIPTOR_RAW_TR || + node->parent->kind == KIND_DESCRIPTOR_TR); + allow_xonly = make_xonly || flags & WALLY_MINISCRIPT_TAPSCRIPT; + if (key_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN && allow_xonly) + return WALLY_OK; /* Uncompressed key not allowed here */ + if (key_len == EC_XONLY_PUBLIC_KEY_LEN && !allow_xonly) + return WALLY_OK; /* X-only not allowed here */ + if (key_len != EC_XONLY_PUBLIC_KEY_LEN) { + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) + return WALLY_OK; /* Only X-only pubkeys allowed under tapscript */ + if (make_xonly) { + /* Convert to x-only */ + --key_len; + ++key_p; + } } } - if (!clone_bytes((unsigned char **)&node->data, pubkey, pubkey_len)) + if (!clone_bytes((unsigned char **)&node->data, key_p, key_len)) return WALLY_ENOMEM; - node->data_len = pubkey_len; + node->data_len = key_len; - if (pubkey_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN) { + if (is_ct_key) + ctx->features |= WALLY_MS_IS_ELIP150; + if (is_private) { + node->kind = KIND_PRIVATE_KEY; + node->flags |= (WALLY_MS_IS_PRIVATE | WALLY_MS_IS_RAW); + return WALLY_OK; + } + node->kind = KIND_PUBLIC_KEY; + if (is_ct_key) + return WALLY_OK; + if (key_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN) { node->flags |= WALLY_MS_IS_UNCOMPRESSED; ctx->features |= WALLY_MS_IS_UNCOMPRESSED; } - if (pubkey_len == EC_XONLY_PUBLIC_KEY_LEN) { + if (key_len == EC_XONLY_PUBLIC_KEY_LEN) { node->flags |= WALLY_MS_IS_X_ONLY; ctx->features |= WALLY_MS_IS_X_ONLY; } - ctx->features |= WALLY_MS_IS_RAW; - node->kind = KIND_PUBLIC_KEY; node->flags |= WALLY_MS_IS_RAW; + ctx->features |= WALLY_MS_IS_RAW; return ctx_add_key_node(ctx, node); } @@ -2181,9 +2330,16 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, { unsigned char privkey[2 + EC_PRIVATE_KEY_LEN + BASE58_CHECKSUM_LEN]; struct ext_key extkey; - size_t privkey_len, size; + size_t privkey_len = 0, size; int ret; bool is_hex; +#ifdef BUILD_ELEMENTS + /* Whether we are the blinding key child of a ct() expression */ + const bool is_ct_key = parent && parent->kind == KIND_DESCRIPTOR_CT && + !parent->child; /* If no child, we are the first child */ +#else + const bool is_ct_key = false; +#endif if (!node || (parent && !parent->builtin)) return WALLY_EINVAL; @@ -2222,15 +2378,16 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, node->data_len -= size; } - /* check key (public key) */ - ret = analyze_pubkey_hex(ctx, node, flags, &is_hex); + /* Check for a hex public key (hex private keys allowed for ct() only) */ + ret = analyze_key_hex(ctx, node, flags, is_ct_key, &is_hex); if (ret == WALLY_OK && is_hex) return WALLY_OK; - /* check key (private key(wif)) */ - ret = wally_base58_n_to_bytes(node->data, node->data_len, BASE58_FLAG_CHECKSUM, - privkey, sizeof(privkey), &privkey_len); - if (ret == WALLY_OK && privkey_len <= EC_PRIVATE_KEY_LEN + 2) { + /* Check for a WIF private key (not allowed for ct() blinding keys) */ + if (!is_ct_key) + ret = wally_base58_n_to_bytes(node->data, node->data_len, BASE58_FLAG_CHECKSUM, + privkey, sizeof(privkey), &privkey_len); + if (ret == WALLY_OK && privkey_len && privkey_len <= EC_PRIVATE_KEY_LEN + 2) { if (ctx->addr_ver && ctx->addr_ver->version_wif != privkey[0]) return WALLY_EINVAL; if (privkey_len == EC_PRIVATE_KEY_LEN + 1) { @@ -2257,7 +2414,7 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, return ret; } - /* check bip32 key */ + /* Check for a bip32 key */ if ((node->child_path = memchr(node->data, '/', node->data_len))) { node->child_path_len = node->data_len - (node->child_path - node->data); node->data_len = node->child_path - node->data; /* Trim to bip32 key */ @@ -2274,6 +2431,11 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, num_elems = (features & BIP32_PATH_LEN_MASK) >> BIP32_PATH_LEN_SHIFT; /* TODO: Check length of key origin plus our length < 255 */ num_multi = (features & BIP32_PATH_MULTI_MASK) >> BIP32_PATH_MULTI_SHIFT; + if (is_ct_key && + (features & (BIP32_PATH_IS_WILDCARD | BIP32_PATH_IS_MULTIPATH))) { + /* ct() blinding keys must resolve to a single key */ + return WALLY_EINVAL; + } if (num_multi) { if (ctx->num_multipaths != 1 && ctx->num_multipaths != num_multi) return WALLY_EINVAL; /* Different multi-path lengths */ @@ -2301,8 +2463,11 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, if (extkey.priv_key[0] == BIP32_FLAG_KEY_PRIVATE) { node->kind = KIND_BIP32_PRIVATE_KEY; - ctx->features |= WALLY_MS_IS_PRIVATE; node->flags |= WALLY_MS_IS_PRIVATE; + if (!is_ct_key) { + /* MS_IS_PRIVATE refers only to signing keys - not blinding keys */ + ctx->features |= WALLY_MS_IS_PRIVATE; + } } else node->kind = KIND_BIP32_PUBLIC_KEY; @@ -2316,11 +2481,15 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, } if (ret == WALLY_OK) { - if (flags & WALLY_MINISCRIPT_TAPSCRIPT) { - node->flags |= WALLY_MS_IS_X_ONLY; - ctx->features |= WALLY_MS_IS_X_ONLY; + if (is_ct_key) { + ctx->features |= WALLY_MS_IS_ELIP150; + } else { + if (flags & WALLY_MINISCRIPT_TAPSCRIPT) { + node->flags |= WALLY_MS_IS_X_ONLY; + ctx->features |= WALLY_MS_IS_X_ONLY; + } + ret = ctx_add_key_node(ctx, node); } - ret = ctx_add_key_node(ctx, node); } wally_clear(&extkey, sizeof(extkey)); return ret; @@ -2340,9 +2509,11 @@ static int analyze_miniscript_value(ms_ctx *ctx, const char *str, size_t str_len const uint32_t kind = parent->kind; if (kind == KIND_DESCRIPTOR_RAW || kind == KIND_MINISCRIPT_SHA256 || kind == KIND_MINISCRIPT_HASH256 || kind == KIND_MINISCRIPT_RIPEMD160 || - kind == KIND_MINISCRIPT_HASH160) { - int ret = wally_hex_n_verify(str, str_len); - if (ret == WALLY_OK) { + kind == KIND_MINISCRIPT_HASH160 || kind == KIND_DESCRIPTOR_SLIP77) { + int ret; + if (kind == KIND_DESCRIPTOR_SLIP77 && str_len != 64) + ret = WALLY_EINVAL; /* slip77 blinding keys must be 32 bytes */ + else if ((ret = wally_hex_n_verify(str, str_len)) == WALLY_OK) { if (!(node->data = wally_malloc(str_len / 2))) ret = WALLY_ENOMEM; else { @@ -2352,6 +2523,9 @@ static int analyze_miniscript_value(ms_ctx *ctx, const char *str, size_t str_len &written); node->data_len = written; node->kind = KIND_RAW; + if (kind == KIND_DESCRIPTOR_SLIP77) { + ctx->features |= (WALLY_MS_IS_ELEMENTS | WALLY_MS_IS_SLIP77); + } } } return ret; @@ -2384,11 +2558,6 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, return WALLY_ENOMEM; node->parent = parent; -#ifdef BUILD_ELEMENTS - if (ctx->features & WALLY_MS_IS_ELEMENTS) { - node->flags |= WALLY_MS_IS_ELEMENTS; /* Treat this node as an elements node */ - } -#endif for (i = 0; i < str_len; ++i) { if (!node->builtin && str[i] == ':') { @@ -2414,6 +2583,11 @@ static int analyze_miniscript(ms_ctx *ctx, const char *str, size_t str_len, /* Not a pure descriptor */ ctx->features &= ~WALLY_MS_IS_DESCRIPTOR; } +#ifdef BUILD_ELEMENTS + if (builtin_is_elements(str + offset, i - offset)) { + ctx->features |= WALLY_MS_IS_ELEMENTS; + } +#endif /* ifdef BUILD_ELEMENTS */ offset = i + 1; child_offset = offset; } @@ -2606,6 +2780,13 @@ static int node_generation_size(const ms_node *node, size_t *total) case KIND_MINISCRIPT_AND_V: /* no-op */ break; +#ifdef BUILD_ELEMENTS + case KIND_DESCRIPTOR_CT: + case KIND_DESCRIPTOR_SLIP77: + case KIND_DESCRIPTOR_ELIP151: + /* Confidential blinding nodes don't change the script */ + break; +#endif default: return WALLY_ERROR; /* Should not happen! */ } @@ -2655,6 +2836,13 @@ static int node_generate_script(ms_ctx *ctx, uint32_t depth, uint32_t index, *written = 0; +#ifdef BUILD_ELEMENTS + if (node->kind == KIND_DESCRIPTOR_CT) { + /* Generate using the actual descriptor as the root */ + node = node->child->next; + } +#endif + for (i = 0; i < depth; ++i) { if (!node->child) return WALLY_EINVAL; @@ -2858,6 +3046,75 @@ int wally_descriptor_to_script_get_maximum_length( return WALLY_OK; } +static int descriptor_get_addr(struct wally_descriptor *descriptor, + const unsigned char* script, size_t script_len, + char **output) +{ + const struct addr_ver_t *addr_ver = descriptor->addr_ver; + char *address = NULL; + bool is_segwit = false; + int ret = wally_scriptpubkey_to_address(script, script_len, + addr_ver->network, &address); + if (ret == WALLY_EINVAL) { + /* Try a segwit address */ + ret = wally_addr_segwit_from_bytes(script, script_len, + addr_ver->bech32, 0, &address); + is_segwit = true; + } + if (ret != WALLY_OK) + return ret; + +#ifndef BUILD_ELEMENTS + (void)is_segwit; +#else + if (descriptor->features & WALLY_MS_IS_ELEMENTS) { + /* Elements: compute the blinding key and blind the address */ + unsigned char pubkey[EC_PUBLIC_KEY_LEN]; + const ms_node *blinding_node = descriptor->top_node->child->child; + if (descriptor->features & WALLY_MS_IS_SLIP77) { + /* SLIP77 blinding key */ + const unsigned char* seed; + seed = (const unsigned char*)blinding_node->data; + ret = wally_asset_blinding_key_to_ec_public_key(seed, 32, + script, script_len, + pubkey, sizeof(pubkey)); + } else if (descriptor->features & WALLY_MS_IS_ELIP150) { + /* ELIP-150 blinding key */ + size_t written; + ret = generate_script(descriptor, descriptor->top_node->child, + pubkey, sizeof(pubkey), &written); + if (ret == WALLY_OK && written != sizeof(pubkey)) + ret = WALLY_ERROR; /* Unsupported pubkey - should not happen! */ + if (ret == WALLY_OK) + ret = wally_elip150_public_key_to_ec_public_key(pubkey, sizeof(pubkey), + script, script_len, + pubkey, sizeof(pubkey)); + } else + ret = WALLY_ERROR; /* FIXME: Support ELIP 151 */ + + if (ret == WALLY_OK) { + char *conf_addr = NULL; + if (is_segwit) + ret = wally_confidential_addr_from_addr_segwit(address, + addr_ver->bech32, + addr_ver->blech32, + pubkey, sizeof(pubkey), + &conf_addr); + else + ret = wally_confidential_addr_from_addr(address, + addr_ver->elements_prefix, + pubkey, sizeof(pubkey), + &conf_addr); + wally_free_string(address); + address = conf_addr; + wally_clear(pubkey, sizeof(pubkey)); + } + } +#endif /* BUILD_ELEMENTS */ + *output = address; + return ret; +} + int wally_descriptor_to_addresses(const struct wally_descriptor *descriptor, uint32_t variant, uint32_t multi_index, uint32_t child_num, uint32_t flags, @@ -2881,13 +3138,14 @@ int wally_descriptor_to_addresses(const struct wally_descriptor *descriptor, if (!(p = wally_malloc(descriptor->script_len))) return WALLY_ENOMEM; - if (descriptor->features & WALLY_MS_IS_ELEMENTS) { - /* Disable Elements address generation until: - * - It is reconciled with Elements-core, and - * - We support blinded addresses - */ +#ifdef BUILD_ELEMENTS + if (descriptor->features & WALLY_MS_IS_ELEMENTS && + !(descriptor->features & WALLY_MS_ANY_BLINDING_KEY)) { + // Elements requires a blinding key to generate addresses return WALLY_ERROR; } +#endif + memcpy(&ctx, descriptor, sizeof(ctx)); ctx.variant = variant; if (ctx.max_path_elems && @@ -2903,13 +3161,7 @@ int wally_descriptor_to_addresses(const struct wally_descriptor *descriptor, ret = WALLY_ERROR; /* Not enough room - should not happen! */ else { /* Generate the address corresponding to this script */ - ret = wally_scriptpubkey_to_address(p, written, - ctx.addr_ver->network, - &addresses[i]); - if (ret == WALLY_EINVAL) - ret = wally_addr_segwit_from_bytes(p, written, - ctx.addr_ver->family, - 0, &addresses[i]); + ret = descriptor_get_addr(&ctx, p, written, &addresses[i]); } } } @@ -3080,14 +3332,33 @@ static const ms_node *descriptor_get_key(const struct wally_descriptor *descript int wally_descriptor_get_key(const struct wally_descriptor *descriptor, size_t index, char **output) { - const ms_node *node = descriptor_get_key(descriptor, index); + const ms_node *node = NULL; +#ifdef BUILD_ELEMENTS + if (index == WALLY_MS_BLINDING_KEY_INDEX) { + if (descriptor && node_is_ct(descriptor->top_node)) { + node = descriptor->top_node->child; + if (node && node->kind == KIND_DESCRIPTOR_SLIP77) + node = node->child; + } + } else +#endif + node = descriptor_get_key(descriptor, index); if (output) *output = 0; if (!node || !output) return WALLY_EINVAL; +#ifdef BUILD_ELEMENTS + if (index == WALLY_MS_BLINDING_KEY_INDEX) { + if (node->kind == KIND_PRIVATE_KEY || node->kind == KIND_RAW) + goto return_hex; + } +#endif if (node->kind == KIND_PUBLIC_KEY) { +#ifdef BUILD_ELEMENTS +return_hex: +#endif return wally_hex_from_bytes((const unsigned char *)node->data, node->data_len, output); } @@ -3109,7 +3380,17 @@ int wally_descriptor_get_key(const struct wally_descriptor *descriptor, int wally_descriptor_get_key_features(const struct wally_descriptor *descriptor, size_t index, uint32_t *value_out) { - const ms_node *node = descriptor_get_key(descriptor, index); + const ms_node *node = NULL; +#ifdef BUILD_ELEMENTS + if (index == WALLY_MS_BLINDING_KEY_INDEX) { + if (descriptor && node_is_ct(descriptor->top_node)) { + node = descriptor->top_node->child; + if (node && node->kind == KIND_DESCRIPTOR_SLIP77) + node = node->child; + } + } else +#endif + node = descriptor_get_key(descriptor, index); if (value_out) *value_out = 0; diff --git a/src/elements.c b/src/elements.c index cbe39ba1b..07566330d 100644 --- a/src/elements.c +++ b/src/elements.c @@ -1,6 +1,7 @@ #include "internal.h" #ifdef BUILD_ELEMENTS #include "script_int.h" +#include "tx_io.h" #include #include #include @@ -13,10 +14,16 @@ #include -static const unsigned char LABEL_STR[] = { +static const unsigned char SLIP77_LABEL[] = { 'S', 'L', 'I', 'P', '-', '0', '0', '7', '7' }; +/* SHA256(CT-Blinding-Key/1.0) */ +static const unsigned char CT_BLINDING_KEY_1_0_SHA256[SHA256_LEN] = { + 0x02, 0xe0, 0xc2, 0x24, 0x76, 0xf8, 0xc5, 0xfd, 0xb7, 0x30, 0x5d, 0x9f, 0xd0, 0xe0, 0xa3, 0x56, + 0xb5, 0x88, 0x77, 0x69, 0x24, 0x8e, 0x04, 0xc8, 0x6f, 0xda, 0xad, 0x35, 0x11, 0x37, 0x85, 0xb4 +}; + static int parse_generator(const secp256k1_context *ctx, const unsigned char *generator, size_t generator_len, secp256k1_generator *dest) @@ -808,7 +815,8 @@ int wally_asset_blinding_key_from_seed( ret = wally_symmetric_key_from_seed(bytes, bytes_len, root, sizeof(root)); if (ret == WALLY_OK) { - ret = wally_symmetric_key_from_parent(root, sizeof(root), 0, LABEL_STR, sizeof(LABEL_STR), + ret = wally_symmetric_key_from_parent(root, sizeof(root), 0, + SLIP77_LABEL, sizeof(SLIP77_LABEL), bytes_out, len); wally_clear(root, sizeof(root)); } @@ -853,6 +861,114 @@ int wally_asset_blinding_key_to_ec_private_key( #endif /* BUILD_ELEMENTS */ } +int wally_asset_blinding_key_to_ec_public_key( + const unsigned char *bytes, size_t bytes_len, + const unsigned char *script, size_t script_len, + unsigned char *bytes_out, size_t len) +{ +#ifndef BUILD_ELEMENTS + return WALLY_ERROR; +#else + unsigned char priv_key[EC_PRIVATE_KEY_LEN]; + int ret; + ret = wally_asset_blinding_key_to_ec_private_key(bytes, bytes_len, + script, script_len, + priv_key, sizeof(priv_key)); + if (ret == WALLY_OK) + ret = wally_ec_public_key_from_private_key(priv_key, sizeof(priv_key), + bytes_out, len); + wally_clear(priv_key, sizeof(priv_key)); + return ret; +#endif /* BUILD_ELEMENTS */ +} + +#ifdef BUILD_ELEMENTS +static void elip150_tagged_hash(const unsigned char *pubkey, size_t pubkey_len, + const unsigned char *script, size_t script_len, + struct sha256 *sha_out) +{ + struct sha256_ctx ctx; + tagged_hash_init(&ctx, CT_BLINDING_KEY_1_0_SHA256, SHA256_LEN); + sha256_update(&ctx, pubkey, pubkey_len); + hash_varbuff(&ctx, script, script_len); /* Consensus encoding */ + sha256_done(&ctx, sha_out); +} +#endif /* BUILD_ELEMENTS */ + +int wally_elip150_private_key_to_ec_private_key( + const unsigned char *bytes, size_t bytes_len, + const unsigned char *script, size_t script_len, + unsigned char *bytes_out, size_t len) +{ +#ifndef BUILD_ELEMENTS + return WALLY_ERROR; +#else + unsigned char pubkey[EC_PUBLIC_KEY_LEN]; + int ret; + + if (!bytes || bytes_len != EC_PRIVATE_KEY_LEN || !script || !script_len || + !bytes_out || len != EC_PRIVATE_KEY_LEN) + return WALLY_EINVAL; + + ret = wally_ec_public_key_from_private_key(bytes, bytes_len, + pubkey, sizeof(pubkey)); + if (ret == WALLY_OK) { + struct sha256 sha; + unsigned char tweaked[EC_PRIVATE_KEY_LEN]; + elip150_tagged_hash(pubkey, sizeof(pubkey), script, script_len, &sha); + ret = wally_ec_scalar_add(bytes, bytes_len, sha.u.u8, sizeof(sha), + tweaked, sizeof(tweaked)); + if (ret == WALLY_OK) + memcpy(bytes_out, tweaked, sizeof(tweaked)); + } + return ret; +#endif /* BUILD_ELEMENTS */ +} + +int wally_elip150_private_key_to_ec_public_key( + const unsigned char *bytes, size_t bytes_len, + const unsigned char *script, size_t script_len, + unsigned char *bytes_out, size_t len) +{ +#ifndef BUILD_ELEMENTS + return WALLY_ERROR; +#else + unsigned char pubkey[EC_PUBLIC_KEY_LEN]; + int ret = wally_ec_public_key_from_private_key(bytes, bytes_len, + pubkey, sizeof(pubkey)); + if (ret == WALLY_OK) + ret = wally_elip150_public_key_to_ec_public_key(pubkey, sizeof(pubkey), + script, script_len, + bytes_out, len); + return ret; +#endif /* BUILD_ELEMENTS */ +} + +int wally_elip150_public_key_to_ec_public_key( + const unsigned char *bytes, size_t bytes_len, + const unsigned char *script, size_t script_len, + unsigned char *bytes_out, size_t len) +{ +#ifndef BUILD_ELEMENTS + return WALLY_ERROR; +#else + struct sha256 sha; + unsigned char tweaked[EC_PUBLIC_KEY_LEN]; + int ret; + + if (!bytes || bytes_len != EC_PUBLIC_KEY_LEN || !script || !script_len || + !bytes_out || len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + + elip150_tagged_hash(bytes, bytes_len, script, script_len, &sha); + ret = wally_ec_public_key_tweak(bytes, bytes_len, sha.u.u8, sizeof(sha), + tweaked, sizeof(tweaked)); + if (ret == WALLY_OK) + memcpy(bytes_out, tweaked, sizeof(tweaked)); + return ret; +#endif /* BUILD_ELEMENTS */ +} + #define BK_ABF 0x1 #define BK_VBF 0x2 diff --git a/src/sign.c b/src/sign.c index 7b8a103bb..f4a35a548 100644 --- a/src/sign.c +++ b/src/sign.c @@ -132,6 +132,26 @@ int wally_ec_public_key_negate(const unsigned char *pub_key, size_t pub_key_len, return ok ? WALLY_OK : WALLY_EINVAL; } +int wally_ec_public_key_tweak(const unsigned char *pub_key, size_t pub_key_len, + const unsigned char *tweak, size_t tweak_len, + unsigned char *bytes_out, size_t len) +{ + const secp256k1_context *ctx = secp256k1_context_static; + secp256k1_pubkey pk; + + if (!pub_key || pub_key_len != EC_PUBLIC_KEY_LEN || + !tweak || tweak_len != EC_PRIVATE_KEY_LEN || + !bytes_out || len != EC_PUBLIC_KEY_LEN) + return WALLY_EINVAL; + + if (!pubkey_parse(&pk, pub_key, pub_key_len) || + !pubkey_tweak_add(ctx, &pk, tweak) || + !pubkey_serialize(bytes_out, &len, &pk, PUBKEY_COMPRESSED) || + len != EC_PUBLIC_KEY_LEN) + return WALLY_ERROR; /* Out of bounds/invalid public key/tweak */ + return WALLY_OK; +} + static int get_bip341_tweak(const unsigned char *pub_key, size_t pub_key_len, const unsigned char *merkle_root, uint32_t flags, unsigned char *tweak, size_t tweak_len) diff --git a/src/swig_java/jni_elements_extra.java_in b/src/swig_java/jni_elements_extra.java_in index 285095f0c..f09680f90 100644 --- a/src/swig_java/jni_elements_extra.java_in +++ b/src/swig_java/jni_elements_extra.java_in @@ -38,8 +38,28 @@ } public final static byte[] asset_blinding_key_to_ec_private_key(byte[] asset_blinding_key, - byte[] script_pub_key) { - return asset_blinding_key_to_ec_private_key(asset_blinding_key, script_pub_key, null); + byte[] scriptpubkey) { + return asset_blinding_key_to_ec_private_key(asset_blinding_key, scriptpubkey, null); + } + + public final static byte[] asset_blinding_key_to_ec_public_key(byte[] asset_blinding_key, + byte[] scriptpubkey) { + return asset_blinding_key_to_ec_public_key(asset_blinding_key, scriptpubkey, null); + } + + public final static byte[] elip150_private_key_to_ec_private_key(byte[] priv_key, + byte[] scriptpubkey) { + return elip150_private_key_to_ec_private_key(priv_key, scriptpubkey, null); + } + + public final static byte[] elip150_private_key_to_ec_public_key(byte[] priv_key, + byte[] scriptpubkey) { + return elip150_private_key_to_ec_public_key(priv_key, scriptpubkey, null); + } + + public final static byte[] elip150_public_key_to_ec_public_key(byte[] pub_key, + byte[] scriptpubkey) { + return elip150_public_key_to_ec_public_key(pub_key, scriptpubkey, null); } public final static long asset_unblind(byte[] pub_key, byte[] priv_key, byte[] proof, diff --git a/src/swig_java/jni_extra.java_in b/src/swig_java/jni_extra.java_in index 8c1ae2ad3..9be288598 100644 --- a/src/swig_java/jni_extra.java_in +++ b/src/swig_java/jni_extra.java_in @@ -175,6 +175,10 @@ return ec_public_key_negate(jarg1, null); } + public final static byte[] ec_public_key_tweak(byte[] jarg1, byte[] jarg2) { + return ec_public_key_tweak(jarg1, jarg2, null); + } + public final static byte[] ec_sig_from_bytes(byte[] jarg1, byte[] jarg2, long jarg3) { return ec_sig_from_bytes(jarg1, jarg2, jarg3, null); } diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index ac130a78c..4fc6d93e3 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -314,6 +314,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %apply(char *STRING, size_t LENGTH) { (const unsigned char* tap_sig, size_t tap_sig_len) }; %apply(char *STRING, size_t LENGTH) { (const unsigned char* tapleaf_hashes, size_t tapleaf_hashes_len) }; %apply(char *STRING, size_t LENGTH) { (const unsigned char* tapleaf_script, size_t tapleaf_script_len) }; +%apply(char *STRING, size_t LENGTH) { (const unsigned char* tweak, size_t tweak_len) }; %apply(char *STRING, size_t LENGTH) { (const unsigned char* txhash, size_t txhash_len) }; %apply(char *STRING, size_t LENGTH) { (const unsigned char* txhashes, size_t txhashes_len) }; %apply(char *STRING, size_t LENGTH) { (const unsigned char* txout_proof, size_t txout_proof_len) }; @@ -548,6 +549,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_array_(wally_asset_blinding_key_to_abf_vbf, 6, 7, WALLY_ABF_VBF_LEN); %returns_array_(wally_asset_blinding_key_to_vbf, 6, 7, BLINDING_FACTOR_LEN); %returns_array_(wally_asset_blinding_key_to_ec_private_key, 5, 6, EC_PRIVATE_KEY_LEN); +%returns_array_(wally_asset_blinding_key_to_ec_public_key, 5, 6, EC_PUBLIC_KEY_LEN); %returns_array_(wally_asset_value_commitment, 6, 7, ASSET_COMMITMENT_LEN); %returns_string(wally_base58_from_bytes); %returns_size_t(wally_base58_to_bytes); @@ -594,10 +596,11 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_array_(wally_ec_private_key_bip341_tweak, 6, 7, EC_PRIVATE_KEY_LEN); %returns_void__(wally_ec_private_key_verify); %returns_array_(wally_ec_public_key_bip341_tweak, 6, 7, EC_PUBLIC_KEY_LEN); -%returns_void__(wally_ec_public_key_verify); %returns_array_(wally_ec_public_key_decompress, 3, 4, EC_PUBLIC_KEY_UNCOMPRESSED_LEN); -%returns_array_(wally_ec_public_key_negate, 3, 4, EC_PUBLIC_KEY_LEN); %returns_array_(wally_ec_public_key_from_private_key, 3, 4, EC_PUBLIC_KEY_LEN); +%returns_array_(wally_ec_public_key_negate, 3, 4, EC_PUBLIC_KEY_LEN); +%returns_array_(wally_ec_public_key_tweak, 5, 6, EC_PUBLIC_KEY_LEN); +%returns_void__(wally_ec_public_key_verify); %returns_size_t(wally_ec_sig_from_bytes_aux_len); %returns_size_t(wally_ec_sig_from_bytes_len); %returns_array_check_flag(wally_ec_sig_from_bytes_aux, 8, 9, jarg7, 10, EC_SIGNATURE_RECOVERABLE_LEN, EC_SIGNATURE_LEN); @@ -614,6 +617,9 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_void__(wally_ec_xonly_public_key_verify); %returns_array_(wally_ecdh, 5, 6, SHA256_LEN); %returns_array_(wally_ecdh_nonce_hash, 5, 6, SHA256_LEN); +%returns_array_(wally_elip150_private_key_to_ec_private_key, 5, 6, EC_PUBLIC_KEY_LEN); +%returns_array_(wally_elip150_private_key_to_ec_public_key, 5, 6, EC_PUBLIC_KEY_LEN); +%returns_array_(wally_elip150_public_key_to_ec_public_key, 5, 6, EC_PUBLIC_KEY_LEN); %returns_size_t(wally_explicit_rangeproof); %returns_void__(wally_explicit_rangeproof_verify); %returns_array_(wally_explicit_surjectionproof, 7, 8, ASSET_EXPLICIT_SURJECTIONPROOF_LEN); diff --git a/src/swig_python/python_extra.py_in b/src/swig_python/python_extra.py_in index bd433b43a..566d10a1e 100644 --- a/src/swig_python/python_extra.py_in +++ b/src/swig_python/python_extra.py_in @@ -155,6 +155,7 @@ ec_public_key_bip341_tweak = _wrap_bin(ec_public_key_bip341_tweak, EC_PUBLIC_KEY ec_public_key_decompress = _wrap_bin(ec_public_key_decompress, EC_PUBLIC_KEY_UNCOMPRESSED_LEN) ec_public_key_from_private_key = _wrap_bin(ec_public_key_from_private_key, EC_PUBLIC_KEY_LEN) ec_public_key_negate = _wrap_bin(ec_public_key_negate, EC_PUBLIC_KEY_LEN) +ec_public_key_tweak = _wrap_bin(ec_public_key_tweak, EC_PUBLIC_KEY_LEN) ec_scalar_add = _wrap_bin(ec_scalar_add, EC_SCALAR_LEN) ec_scalar_multiply = _wrap_bin(ec_scalar_multiply, EC_SCALAR_LEN) ec_scalar_subtract = _wrap_bin(ec_scalar_subtract, EC_SCALAR_LEN) @@ -266,6 +267,7 @@ if is_elements_build(): asset_blinding_key_to_abf = _wrap_bin(asset_blinding_key_to_abf, BLINDING_FACTOR_LEN) asset_blinding_key_to_abf_vbf = _wrap_bin(asset_blinding_key_to_abf_vbf, WALLY_ABF_VBF_LEN) asset_blinding_key_to_ec_private_key = _wrap_bin(asset_blinding_key_to_ec_private_key, EC_PRIVATE_KEY_LEN) + asset_blinding_key_to_ec_public_key = _wrap_bin(asset_blinding_key_to_ec_public_key, EC_PUBLIC_KEY_LEN) asset_blinding_key_to_vbf = _wrap_bin(asset_blinding_key_to_vbf, BLINDING_FACTOR_LEN) asset_final_vbf = _wrap_bin(asset_final_vbf, BLINDING_FACTOR_LEN) asset_generator_from_bytes = _wrap_bin(asset_generator_from_bytes, ASSET_GENERATOR_LEN) @@ -283,6 +285,9 @@ if is_elements_build(): ecdh_nonce_hash = _wrap_bin(ecdh_nonce_hash, SHA256_LEN) elements_pegin_contract_script_from_bytes = _wrap_bin(elements_pegin_contract_script_from_bytes, elements_pegin_contract_script_from_bytes_len, resize=True) elements_pegout_script_from_bytes = _wrap_bin(elements_pegout_script_from_bytes, elements_pegout_script_from_bytes_len, resize=True) + elip150_private_key_to_ec_private_key = _wrap_bin(elip150_private_key_to_ec_private_key, EC_PRIVATE_KEY_LEN) + elip150_private_key_to_ec_public_key = _wrap_bin(elip150_private_key_to_ec_public_key, EC_PUBLIC_KEY_LEN) + elip150_public_key_to_ec_public_key = _wrap_bin(elip150_public_key_to_ec_public_key, EC_PUBLIC_KEY_LEN) explicit_rangeproof = _wrap_bin(explicit_rangeproof, ASSET_EXPLICIT_RANGEPROOF_MAX_LEN, resize=True) explicit_surjectionproof = _wrap_bin(explicit_surjectionproof, ASSET_EXPLICIT_SURJECTIONPROOF_LEN) psbt_blind = psbt_blind_alloc diff --git a/src/swig_python/swig.i b/src/swig_python/swig.i index 6310dd9d6..262077a4f 100644 --- a/src/swig_python/swig.i +++ b/src/swig_python/swig.i @@ -387,6 +387,7 @@ static void destroy_words(PyObject *obj) { (void)obj; } %pybuffer_nullable_binary(const unsigned char* tap_sig, size_t tap_sig_len); %pybuffer_nullable_binary(const unsigned char* tapleaf_hashes, size_t tapleaf_hashes_len); %pybuffer_nullable_binary(const unsigned char* tapleaf_script, size_t tapleaf_script_len); +%pybuffer_nullable_binary(const unsigned char* tweak, size_t tweak_len); %pybuffer_nullable_binary(const unsigned char* txhash, size_t txhash_len); %pybuffer_nullable_binary(const unsigned char* txhashes, size_t txhashes_len); %pybuffer_nullable_binary(const unsigned char* txout_proof, size_t txout_proof_len); diff --git a/src/test/test_descriptor.py b/src/test/test_descriptor.py index e9f5b0bdb..9ae38a59b 100644 --- a/src/test/test_descriptor.py +++ b/src/test/test_descriptor.py @@ -26,9 +26,14 @@ MS_IS_X_ONLY = 0x40 MS_IS_PARENTED = 0x80 MS_IS_ELEMENTS = 0x100 +MS_IS_SLIP77 = 0x200 +MS_IS_ELIP150 = 0x400 +MS_IS_ELIP151 = 0x800 NO_CHECKSUM = 0x1 # WALLY_MS_CANONICAL_NO_CHECKSUM +BLINDING_KEY_INDEX = 0xffffffff + def wally_map_from_dict(d): m = pointer(wally_map()) assert(wally_map_init_alloc(len(d.keys()), None, m) == WALLY_OK) @@ -235,55 +240,90 @@ def test_features_and_depth(self): k1 = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB' k2 = 'xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU' # Valid args + # descriptor, flags, expected_features, expected_depth, expected keys cases = [ # Bip32 xpub (f'pkh({k1})', - 0, MS_IS_DESCRIPTOR, 2), + 0, MS_IS_DESCRIPTOR, 2, 1), # Bip32 xpub with range (f'pkh({k1}/*)', - 0, MS_IS_RANGED|MS_IS_DESCRIPTOR, 2), + 0, MS_IS_RANGED|MS_IS_DESCRIPTOR, 2, 1), # BIP32 xprv (f'pkh({k2}/*)', - 0, MS_IS_PRIVATE|MS_IS_RANGED|MS_IS_DESCRIPTOR, 2), + 0, MS_IS_PRIVATE|MS_IS_RANGED|MS_IS_DESCRIPTOR, 2, 1), # WIF ('pkh(L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM)', - 0, MS_IS_PRIVATE|MS_IS_RAW|MS_IS_DESCRIPTOR, 2), + 0, MS_IS_PRIVATE|MS_IS_RAW|MS_IS_DESCRIPTOR, 2, 1), # Hex pubkey, compressed ('pk(03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284)', - 0, MS_IS_RAW|MS_IS_DESCRIPTOR, 2), + 0, MS_IS_RAW|MS_IS_DESCRIPTOR, 2, 1), # Hex pubkey, uncompressed ('pk(0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf)', - 0, MS_IS_UNCOMPRESSED|MS_IS_RAW|MS_IS_DESCRIPTOR, 2), + 0, MS_IS_UNCOMPRESSED|MS_IS_RAW|MS_IS_DESCRIPTOR, 2, 1), # Miniscript ('j:and_v(vdv:after(1567547623),older(2016))', - MS_ONLY, 0, 3), + MS_ONLY, 0, 3, 0), # pk() is both descriptor and miniscript valid and should parse as each (f'or_d(thresh(1,pk({k1})),and_v(v:thresh(1,pk({k2}/)),older(30)))', - 0, MS_IS_PRIVATE, 5), + 0, MS_IS_PRIVATE, 5, 2), (f'or_d(thresh(1,pk({k1})),and_v(v:thresh(1,pk({k2}/)),older(30)))', - MS_ONLY, MS_IS_PRIVATE, 5), + MS_ONLY, MS_IS_PRIVATE, 5, 2), ] if is_elements_build: + slip77 = 'ct(slip77(b2396b3ee20509cdb64fe24180a14a72dbd671728eaa49bac69d2bdecb5f5a04),elpkh(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))' cases.extend([ # Parsing a descriptor as elements returns elements in its features (f'tr({k1})', - AS_ELEMENTS, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS, 2), + AS_ELEMENTS, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS, 2, 1), + # el-prefixed builtins return elements in their features + (f'eltr({k1})', + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS, 2, 1), + # Note that ct() blinding keys aren't returned in the key count. + # slip77 builtins return elements and slip77 in their features, + # and the ct() parent wrapper is included in their depth. + (slip77, + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_SLIP77, 3, 1), + # An xpub ELIP-150 key + (f'ct({k1},elpkh({k1}))', + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), + # A hex public ELIP-150 key. + (f'ct(0286fc9a38e765d955e9b0bcc18fa9ae81b0c893e2dd1ef5542a9c73780a086b90,elpkh({k1}))', + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), + # An xpriv ELIP-150 key. Note that MS_IS_PRIVATE is not + # returned because the blinding key is not included in the + # key count. + (f'ct({k2},elpkh({k1}))', + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), + # A hex private ELIP-150 key. As above MS_IS_PRIVATE is not + # returned. + (f'ct(c25deb86fa11e49d651d7eae27c220ef930fbd86ea023eebfa73e54875647963,elpkh({k1}))', + 0, MS_IS_DESCRIPTOR|MS_IS_ELEMENTS|MS_IS_ELIP150, 3, 1), ]) - for descriptor, flags, expected, expected_depth in cases: + for descriptor, flags, expected_features, expected_depth, expected_keys in cases: d = c_void_p() ret = wally_descriptor_parse(descriptor, None, NETWORK_NONE, flags, d) ret, features = wally_descriptor_get_features(d) - self.assertEqual((ret, features), (WALLY_OK, expected)) + self.assertEqual((ret, features), (WALLY_OK, expected_features)) ret, depth = wally_descriptor_get_depth(d) self.assertEqual((ret, depth), (WALLY_OK, expected_depth)) + ret, num_keys = wally_descriptor_get_num_keys(d) + self.assertEqual((ret, num_keys), (WALLY_OK, expected_keys)) + ret, key_info = wally_descriptor_get_key(d, BLINDING_KEY_INDEX) + if descriptor.startswith('ct'): + self.assertEqual(ret, WALLY_OK) + expected_key_info = descriptor.split(',')[0][3:] + if expected_key_info.startswith('slip77'): + expected_key_info = expected_key_info[7:-1] + self.assertEqual(key_info, expected_key_info) + else: + self.assertEqual(ret, WALLY_EINVAL) wally_descriptor_free(d) # Check the maximum depth parsing limit for limit, expected in [(depth-1, WALLY_EINVAL), (depth, WALLY_OK)]: ret = wally_descriptor_parse(descriptor, None, NETWORK_NONE, flags | (limit << 16), d) self.assertEqual(ret, expected) - wally_descriptor_free(d) # Invalid args ret, features = wally_descriptor_get_features(None) # NULL descriptor diff --git a/src/test/test_elements.py b/src/test/test_elements.py index d29f4b435..3583e69f2 100755 --- a/src/test/test_elements.py +++ b/src/test/test_elements.py @@ -256,6 +256,41 @@ def test_deterministic_blinding_factors(self): continue # Skip final VBF self.assertEqual(h(out[:o_len]), utf8(expected)) + def test_elip150_blinding_keys(self): + if not wally_is_elements_build()[1]: + self.skipTest('Elements support not enabled') + + # Ensure tweaking private and public keys results in the same key + priv_key, priv_key_len = make_cbuffer('66'*32) + pub_key, pub_key_len = make_cbuffer('00'*33) + ret = wally_ec_public_key_from_private_key(priv_key, priv_key_len, + pub_key, pub_key_len) + self.assertEqual(WALLY_OK, ret) + script, script_len = make_cbuffer('11'*40) + out_priv, out_priv_len = make_cbuffer('00'*32) + out_pub, out_pub_len = make_cbuffer('00'*33) + ret = wally_elip150_private_key_to_ec_private_key(priv_key, priv_key_len, + script, script_len, + out_priv, out_priv_len) + self.assertEqual(WALLY_OK, ret) + ret = wally_ec_public_key_from_private_key(out_priv, out_priv_len, + out_pub, out_pub_len) + self.assertEqual(WALLY_OK, ret) + expected_pubkey = h(out_pub) + + ret = wally_elip150_private_key_to_ec_public_key(priv_key, priv_key_len, + script, script_len, + out_pub, out_pub_len) + self.assertEqual(WALLY_OK, ret) + self.assertEqual(expected_pubkey, h(out_pub)) + + ret = wally_elip150_public_key_to_ec_public_key(pub_key, pub_key_len, + script, script_len, + out_pub, out_pub_len) + self.assertEqual(WALLY_OK, ret) + self.assertEqual(expected_pubkey, h(out_pub)) + + def test_elements_tx_weights(self): if not wally_is_elements_build()[1]: self.skipTest('Elements support not enabled') diff --git a/src/test/util.py b/src/test/util.py index 0c4a0b05e..c463e4488 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -280,6 +280,7 @@ class wally_psbt(Structure): ('wally_asset_blinding_key_to_abf', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t]), ('wally_asset_blinding_key_to_abf_vbf', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t]), ('wally_asset_blinding_key_to_ec_private_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), + ('wally_asset_blinding_key_to_ec_public_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_asset_blinding_key_to_vbf', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t]), ('wally_asset_final_vbf', c_int, [POINTER(c_uint64), c_size_t, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_asset_generator_from_bytes', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), @@ -346,6 +347,7 @@ class wally_psbt(Structure): ('wally_ec_public_key_decompress', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_ec_public_key_from_private_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_ec_public_key_negate', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), + ('wally_ec_public_key_tweak', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_ec_public_key_verify', c_int, [c_void_p, c_size_t]), ('wally_ec_scalar_add', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_ec_scalar_add_to', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), @@ -369,6 +371,9 @@ class wally_psbt(Structure): ('wally_elements_pegin_contract_script_from_bytes', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]), ('wally_elements_pegout_script_from_bytes', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]), ('wally_elements_pegout_script_size', c_int, [c_size_t, c_size_t, c_size_t, c_size_t, c_size_t_p]), + ('wally_elip150_private_key_to_ec_private_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), + ('wally_elip150_private_key_to_ec_public_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), + ('wally_elip150_public_key_to_ec_public_key', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_explicit_rangeproof', c_int, [c_uint64, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_size_t_p]), ('wally_explicit_rangeproof_verify', c_int, [c_void_p, c_size_t, c_uint64, c_void_p, c_size_t, c_void_p, c_size_t]), ('wally_explicit_surjectionproof', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t]), diff --git a/src/tx_io.c b/src/tx_io.c index 83eb9d2ad..aea93e988 100644 --- a/src/tx_io.c +++ b/src/tx_io.c @@ -158,8 +158,8 @@ static void hash_varint(struct sha256_ctx *ctx, hash_bytes(ctx, buff, n); } -static void hash_varbuff(struct sha256_ctx *ctx, - const unsigned char *bytes, size_t bytes_len) +void hash_varbuff(struct sha256_ctx *ctx, + const unsigned char *bytes, size_t bytes_len) { hash_varint(ctx, bytes_len); hash_bytes(ctx, bytes, bytes_len); @@ -214,8 +214,8 @@ static int txio_done(cursor_io *io, uint32_t flags) /* Initialize a sha256 context for bip340 tagged hashing. * 'hash' must be SHA256(tag), e.g. 'TapSighash', 'TapLeaf' etc. */ -static void tagged_hash_init(struct sha256_ctx *ctx, - const unsigned char *hash, size_t hash_len) +void tagged_hash_init(struct sha256_ctx *ctx, + const unsigned char *hash, size_t hash_len) { sha256_init(ctx); hash_bytes(ctx, hash, hash_len); diff --git a/src/tx_io.h b/src/tx_io.h index da888168c..41e3b323c 100644 --- a/src/tx_io.h +++ b/src/tx_io.h @@ -16,4 +16,11 @@ typedef struct cursor_io size_t max; } cursor_io; +/* Hash helpers */ +void tagged_hash_init(struct sha256_ctx *ctx, + const unsigned char *hash, size_t hash_len); + +void hash_varbuff(struct sha256_ctx *ctx, + const unsigned char *bytes, size_t bytes_len); + #endif /* LIBWALLY_CORE_TX_IO_H */ diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index cb9f2d242..8b10c1929 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -129,14 +129,19 @@ export const WALLY_MINISCRIPT_REQUIRE_CHECKSUM = 0x04; /** Require a checksum to export const WALLY_MINISCRIPT_TAPSCRIPT = 0x01; /** Tapscript, use x-only pubkeys */ export const WALLY_MINISCRIPT_UNIQUE_KEYPATHS = 0x10; /** For policy templates, ensure BIP32 derivation paths differ for identical keys */ export const WALLY_MINOR_VER = 4; +export const WALLY_MS_ANY_BLINDING_KEY = 0xE00; /** SLIP-77, ELIP-150 or ELIP-151 blinding key present */ +export const WALLY_MS_BLINDING_KEY_INDEX = 0xffffffff; /* Key index for confidential blinding key */ export const WALLY_MS_CANONICAL_NO_CHECKSUM = 0x01; /** Do not include a checksum */ export const WALLY_MS_IS_DESCRIPTOR = 0x020; /** Contains only descriptor expressions (no miniscript) */ export const WALLY_MS_IS_ELEMENTS = 0x100; /** Contains Elements expressions or was parsed as Elements */ +export const WALLY_MS_IS_ELIP150 = 0x400; /** A confidential ct() descriptor with ELIP-150 blinding */ +export const WALLY_MS_IS_ELIP151 = 0x800; /** A confidential ct() descriptor with ELIP-151 blinding */ export const WALLY_MS_IS_MULTIPATH = 0x002; /** Allows multiple paths via ```` */ export const WALLY_MS_IS_PARENTED = 0x080; /** Contains at least one key key with a parent key origin */ export const WALLY_MS_IS_PRIVATE = 0x004; /** Contains at least one private key */ export const WALLY_MS_IS_RANGED = 0x001; /** Allows key ranges via ``*`` */ export const WALLY_MS_IS_RAW = 0x010; /** Contains at least one raw key */ +export const WALLY_MS_IS_SLIP77 = 0x200; /** A confidential ct() descriptor with SLIP-77 blinding */ export const WALLY_MS_IS_UNCOMPRESSED = 0x008; /** Contains at least one uncompressed key */ export const WALLY_MS_IS_X_ONLY = 0x040; /** Contains at least one x-only key */ export const WALLY_NETWORK_BITCOIN_MAINNET = 0x01; /** Bitcoin mainnet */ diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index d6ff0291e..f14d0aedc 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -77,6 +77,7 @@ export const asset_blinding_key_from_seed = wrap('wally_asset_blinding_key_from_ export const asset_blinding_key_to_abf = wrap('wally_asset_blinding_key_to_abf', [T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, C.BLINDING_FACTOR_LEN)]); export const asset_blinding_key_to_abf_vbf = wrap('wally_asset_blinding_key_to_abf_vbf', [T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, C.WALLY_ABF_VBF_LEN)]); export const asset_blinding_key_to_ec_private_key = wrap('wally_asset_blinding_key_to_ec_private_key', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PRIVATE_KEY_LEN)]); +export const asset_blinding_key_to_ec_public_key = wrap('wally_asset_blinding_key_to_ec_public_key', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); export const asset_blinding_key_to_vbf = wrap('wally_asset_blinding_key_to_vbf', [T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, C.BLINDING_FACTOR_LEN)]); export const asset_final_vbf = wrap('wally_asset_final_vbf', [T.Uint64Array, T.Int32, T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.BLINDING_FACTOR_LEN)]); export const asset_generator_from_bytes = wrap('wally_asset_generator_from_bytes', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.ASSET_GENERATOR_LEN)]); @@ -186,6 +187,7 @@ export const ec_public_key_bip341_tweak = wrap('wally_ec_public_key_bip341_tweak export const ec_public_key_decompress = wrap('wally_ec_public_key_decompress', [T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_UNCOMPRESSED_LEN)]); export const ec_public_key_from_private_key = wrap('wally_ec_public_key_from_private_key', [T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); export const ec_public_key_negate = wrap('wally_ec_public_key_negate', [T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); +export const ec_public_key_tweak = wrap('wally_ec_public_key_tweak', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); export const ec_public_key_verify = wrap('wally_ec_public_key_verify', [T.Bytes]); export const ec_scalar_add = wrap('wally_ec_scalar_add', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_SCALAR_LEN)]); export const ec_scalar_multiply = wrap('wally_ec_scalar_multiply', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_SCALAR_LEN)]); @@ -204,6 +206,9 @@ export const ecdh_nonce_hash = wrap('wally_ecdh_nonce_hash', [T.Bytes, T.Bytes, export const elements_pegin_contract_script_from_bytes = wrap('wally_elements_pegin_contract_script_from_bytes', [T.Bytes, T.Bytes, T.Int32, T.DestPtrVarLen(T.Bytes, elements_pegin_contract_script_from_bytes_len, true)]); export const elements_pegout_script_from_bytes = wrap('wally_elements_pegout_script_from_bytes', [T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrVarLen(T.Bytes, elements_pegout_script_from_bytes_len, true)]); export const elements_pegout_script_size = wrap('wally_elements_pegout_script_size', [T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtr(T.Int32)]); +export const elip150_private_key_to_ec_private_key = wrap('wally_elip150_private_key_to_ec_private_key', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PRIVATE_KEY_LEN)]); +export const elip150_private_key_to_ec_public_key = wrap('wally_elip150_private_key_to_ec_public_key', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); +export const elip150_public_key_to_ec_public_key = wrap('wally_elip150_public_key_to_ec_public_key', [T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.EC_PUBLIC_KEY_LEN)]); export const explicit_rangeproof = wrap('wally_explicit_rangeproof', [T.Int64, T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.DestPtrVarLen(T.Bytes, C.ASSET_EXPLICIT_RANGEPROOF_MAX_LEN, true)]); export const explicit_rangeproof_verify = wrap('wally_explicit_rangeproof_verify', [T.Bytes, T.Int64, T.Bytes, T.Bytes]); export const explicit_surjectionproof = wrap('wally_explicit_surjectionproof', [T.Bytes, T.Bytes, T.Bytes, T.DestPtrSized(T.Bytes, C.ASSET_EXPLICIT_SURJECTIONPROOF_LEN)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 3e9d60efb..31268af00 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -37,6 +37,7 @@ export function asset_blinding_key_from_seed(bytes: Buffer|Uint8Array): Buffer; export function asset_blinding_key_to_abf(bytes: Buffer|Uint8Array, hash_prevouts: Buffer|Uint8Array, output_index: number): Buffer; export function asset_blinding_key_to_abf_vbf(bytes: Buffer|Uint8Array, hash_prevouts: Buffer|Uint8Array, output_index: number): Buffer; export function asset_blinding_key_to_ec_private_key(bytes: Buffer|Uint8Array, script: Buffer|Uint8Array): Buffer; +export function asset_blinding_key_to_ec_public_key(bytes: Buffer|Uint8Array, script: Buffer|Uint8Array): Buffer; export function asset_blinding_key_to_vbf(bytes: Buffer|Uint8Array, hash_prevouts: Buffer|Uint8Array, output_index: number): Buffer; export function asset_final_vbf(values: BigUint64Array|Array, num_inputs: number, abf: Buffer|Uint8Array, vbf: Buffer|Uint8Array): Buffer; export function asset_generator_from_bytes(asset: Buffer|Uint8Array, abf: Buffer|Uint8Array): Buffer; @@ -146,6 +147,7 @@ export function ec_public_key_bip341_tweak(pub_key: Buffer|Uint8Array, merkle_ro export function ec_public_key_decompress(pub_key: Buffer|Uint8Array): Buffer; export function ec_public_key_from_private_key(priv_key: Buffer|Uint8Array): Buffer; export function ec_public_key_negate(pub_key: Buffer|Uint8Array): Buffer; +export function ec_public_key_tweak(pub_key: Buffer|Uint8Array, tweak: Buffer|Uint8Array): Buffer; export function ec_public_key_verify(pub_key: Buffer|Uint8Array): void; export function ec_scalar_add(scalar: Buffer|Uint8Array, operand: Buffer|Uint8Array): Buffer; export function ec_scalar_multiply(scalar: Buffer|Uint8Array, operand: Buffer|Uint8Array): Buffer; @@ -164,6 +166,9 @@ export function ecdh_nonce_hash(pub_key: Buffer|Uint8Array, priv_key: Buffer|Uin export function elements_pegin_contract_script_from_bytes(redeem_script: Buffer|Uint8Array, script: Buffer|Uint8Array, flags: number): Buffer; export function elements_pegout_script_from_bytes(genesis_blockhash: Buffer|Uint8Array, mainchain_script: Buffer|Uint8Array, sub_pubkey: Buffer|Uint8Array, whitelistproof: Buffer|Uint8Array, flags: number): Buffer; export function elements_pegout_script_size(genesis_blockhash_len: number, mainchain_script_len: number, sub_pubkey_len: number, whitelistproof_len: number): number; +export function elip150_private_key_to_ec_private_key(bytes: Buffer|Uint8Array, script: Buffer|Uint8Array): Buffer; +export function elip150_private_key_to_ec_public_key(bytes: Buffer|Uint8Array, script: Buffer|Uint8Array): Buffer; +export function elip150_public_key_to_ec_public_key(bytes: Buffer|Uint8Array, script: Buffer|Uint8Array): Buffer; export function explicit_rangeproof(value: bigint, nonce: Buffer|Uint8Array, vbf: Buffer|Uint8Array, commitment: Buffer|Uint8Array, generator: Buffer|Uint8Array): Buffer; export function explicit_rangeproof_verify(rangeproof: Buffer|Uint8Array, value: bigint, commitment: Buffer|Uint8Array, generator: Buffer|Uint8Array): void; export function explicit_surjectionproof(output_asset: Buffer|Uint8Array, output_abf: Buffer|Uint8Array, output_generator: Buffer|Uint8Array): Buffer; diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index a69642922..320396f54 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -113,6 +113,7 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_ec_public_key_decompress' \ ,'_wally_ec_public_key_from_private_key' \ ,'_wally_ec_public_key_negate' \ +,'_wally_ec_public_key_tweak' \ ,'_wally_ec_public_key_verify' \ ,'_wally_ec_scalar_add' \ ,'_wally_ec_scalar_multiply' \ @@ -509,6 +510,7 @@ if [ -z "$DISABLE_ELEMENTS" ]; then ,'_wally_asset_blinding_key_to_abf' \ ,'_wally_asset_blinding_key_to_abf_vbf' \ ,'_wally_asset_blinding_key_to_ec_private_key' \ +,'_wally_asset_blinding_key_to_ec_public_key' \ ,'_wally_asset_blinding_key_to_vbf' \ ,'_wally_asset_final_vbf' \ ,'_wally_asset_generator_from_bytes' \ @@ -536,6 +538,9 @@ if [ -z "$DISABLE_ELEMENTS" ]; then ,'_wally_elements_pegin_contract_script_from_bytes' \ ,'_wally_elements_pegout_script_from_bytes' \ ,'_wally_elements_pegout_script_size' \ +,'_wally_elip150_private_key_to_ec_private_key' \ +,'_wally_elip150_private_key_to_ec_public_key' \ +,'_wally_elip150_public_key_to_ec_public_key' \ ,'_wally_explicit_rangeproof' \ ,'_wally_explicit_rangeproof_verify' \ ,'_wally_explicit_surjectionproof' \