diff --git a/README.md b/README.md index 0855aba61..ad53c42a7 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ $ brew install swig ### configure options - `--enable-debug`. Enables debugging information and disables compiler - optimisations (default: no). + optimizations (default: no). - `--enable-minimal`. Minimises library size and memory requirements to target embedded or resource-constrained environments (default: no). - `--enable-asm`. Enables fast assembly language implementations where available. diff --git a/include/wally.hpp b/include/wally.hpp index 193bf4705..26524e61f 100644 --- a/include/wally.hpp +++ b/include/wally.hpp @@ -395,6 +395,31 @@ inline int aes_cbc(const KEY& key, const IV& iv, const BYTES& bytes, uint32_t fl return written || ret != WALLY_OK ? ret : n == static_cast(bytes_out.size()) ? WALLY_OK : WALLY_EINVAL; } +template +inline int aes_cbc_get_maximum_length(const KEY& key, const IV& iv, const BYTES& bytes, uint32_t flags, size_t* written) { + int ret = ::wally_aes_cbc_get_maximum_length(key.data(), key.size(), iv.data(), iv.size(), bytes.data(), bytes.size(), flags, written); + return ret; +} + +template +inline int aes_cbc_with_ecdh_key(const PRIV_KEY& priv_key, const IV& iv, const BYTES& bytes, const PUB_KEY& pub_key, const LABEL& label, uint32_t flags, BYTES_OUT& bytes_out, size_t* written = 0) { + size_t n; + int ret = ::wally_aes_cbc_with_ecdh_key(priv_key.data(), priv_key.size(), iv.data(), iv.size(), bytes.data(), bytes.size(), pub_key.data(), pub_key.size(), label.data(), label.size(), flags, bytes_out.data(), bytes_out.size(), written ? written : &n); + return written || ret != WALLY_OK ? ret : n == static_cast(bytes_out.size()) ? WALLY_OK : WALLY_EINVAL; +} + +template +inline int aes_cbc_with_ecdh_key_get_maximum_length(const PRIV_KEY& priv_key, const IV& iv, const BYTES& bytes, const PUB_KEY& pub_key, const LABEL& label, uint32_t flags, size_t* written) { + int ret = ::wally_aes_cbc_with_ecdh_key_get_maximum_length(priv_key.data(), priv_key.size(), iv.data(), iv.size(), bytes.data(), bytes.size(), pub_key.data(), pub_key.size(), label.data(), label.size(), flags, written); + return ret; +} + +template +inline int aes_len(const KEY& key, const BYTES& bytes, uint32_t flags, size_t* written) { + int ret = ::wally_aes_len(key.data(), key.size(), bytes.data(), bytes.size(), flags, written); + return ret; +} + template inline int base58_from_bytes(const BYTES& bytes, uint32_t flags, char** output) { int ret = ::wally_base58_from_bytes(bytes.data(), bytes.size(), flags, output); @@ -504,12 +529,60 @@ inline int descriptor_get_features(const DESCRIPTOR& descriptor, uint32_t* value return ret; } +template +inline int descriptor_get_key(const DESCRIPTOR& descriptor, size_t index, char** output) { + int ret = ::wally_descriptor_get_key(detail::get_p(descriptor), index, output); + return ret; +} + +template +inline int descriptor_get_key_child_path_str(const DESCRIPTOR& descriptor, size_t index, char** output) { + int ret = ::wally_descriptor_get_key_child_path_str(detail::get_p(descriptor), index, output); + return ret; +} + +template +inline int descriptor_get_key_child_path_str_len(const DESCRIPTOR& descriptor, size_t index, size_t* written) { + int ret = ::wally_descriptor_get_key_child_path_str_len(detail::get_p(descriptor), index, written); + return ret; +} + +template +inline int descriptor_get_key_features(const DESCRIPTOR& descriptor, size_t index, uint32_t* value_out) { + int ret = ::wally_descriptor_get_key_features(detail::get_p(descriptor), index, value_out); + return ret; +} + +template +inline int descriptor_get_key_origin_fingerprint(const DESCRIPTOR& descriptor, size_t index, BYTES_OUT& bytes_out) { + int ret = ::wally_descriptor_get_key_origin_fingerprint(detail::get_p(descriptor), index, bytes_out.data(), bytes_out.size()); + return ret; +} + +template +inline int descriptor_get_key_origin_path_str(const DESCRIPTOR& descriptor, size_t index, char** output) { + int ret = ::wally_descriptor_get_key_origin_path_str(detail::get_p(descriptor), index, output); + return ret; +} + +template +inline int descriptor_get_key_origin_path_str_len(const DESCRIPTOR& descriptor, size_t index, size_t* written) { + int ret = ::wally_descriptor_get_key_origin_path_str_len(detail::get_p(descriptor), index, written); + return ret; +} + template inline int descriptor_get_network(const DESCRIPTOR& descriptor, uint32_t* value_out) { int ret = ::wally_descriptor_get_network(detail::get_p(descriptor), value_out); return ret; } +template +inline int descriptor_get_num_keys(const DESCRIPTOR& descriptor, uint32_t* value_out) { + int ret = ::wally_descriptor_get_num_keys(detail::get_p(descriptor), value_out); + return ret; +} + template inline int descriptor_get_num_paths(const DESCRIPTOR& descriptor, uint32_t* value_out) { int ret = ::wally_descriptor_get_num_paths(detail::get_p(descriptor), value_out); diff --git a/include/wally_crypto.h b/include/wally_crypto.h index 8cdcacac4..ccf03bcce 100644 --- a/include/wally_crypto.h +++ b/include/wally_crypto.h @@ -46,10 +46,30 @@ WALLY_CORE_API int wally_scrypt( #define AES_FLAG_ENCRYPT 1 /** Encrypt */ #define AES_FLAG_DECRYPT 2 /** Decrypt */ +/** + * Get the length of encrypted/decrypted data using AES (ECB mode, no padding). + * + * :param key: Encryption/decryption key. + * :param key_len: Length of ``key`` in bytes. Must be one of the :ref:`aes-key-length`. + * :param bytes: Bytes to encrypt/decrypt. + * :param bytes_len: Length of ``bytes`` in bytes. Must be a multiple of `AES_BLOCK_LEN`. + * :param flags: :ref:`aes-operation-flag` indicating the desired behavior. + * :param written: Destination for the length of the encrypted/decrypted data. + * + * This function returns ``bytes_len`` assuming its arguments are valid. + */ +WALLY_CORE_API int wally_aes_len( + const unsigned char *key, + size_t key_len, + const unsigned char *bytes, + size_t bytes_len, + uint32_t flags, + size_t *written); + /** * Encrypt/decrypt data using AES (ECB mode, no padding). * - * :param key: Key material for initialisation. + * :param key: Encryption/decryption key. * :param key_len: Length of ``key`` in bytes. Must be one of the :ref:`aes-key-length`. * :param bytes: Bytes to encrypt/decrypt. * :param bytes_len: Length of ``bytes`` in bytes. Must be a multiple of `AES_BLOCK_LEN`. @@ -66,12 +86,36 @@ WALLY_CORE_API int wally_aes( unsigned char *bytes_out, size_t len); +/** + * Get the maximum length of encrypted/decrypted data using AES (CBC mode, PKCS#7 padding). + * + * :param key: Encryption/decryption key. + * :param key_len: Length of ``key`` in bytes. Must be one of the :ref:`aes-key-length`. + * :param iv: Initialization vector. For encryption this should be secure entropy. For + *| decryption the bytes used when encrypting must be given. + * :param iv_len: Length of ``iv`` in bytes. Must be `AES_BLOCK_LEN`. + * :param bytes: Bytes to encrypt/decrypt. + * :param bytes_len: Length of ``bytes`` in bytes. Can be of any length for encryption, must be a multiple of `AES_BLOCK_LEN` for decryption. + * :param flags: :ref:`aes-operation-flag` indicating the desired behavior. + * :param written: Destination for the maximum length of the encrypted/decrypted data. + */ +WALLY_CORE_API int wally_aes_cbc_get_maximum_length( + const unsigned char *key, + size_t key_len, + const unsigned char *iv, + size_t iv_len, + const unsigned char *bytes, + size_t bytes_len, + uint32_t flags, + size_t *written); + /** * Encrypt/decrypt data using AES (CBC mode, PKCS#7 padding). * - * :param key: Key material for initialisation. + * :param key: Encryption/decryption key. * :param key_len: Length of ``key`` in bytes. Must be one of the :ref:`aes-key-length`. - * :param iv: Initialisation vector. + * :param iv: Initialization vector. For encryption this should be secure entropy. For + *| decryption the bytes used when encrypting must be given. * :param iv_len: Length of ``iv`` in bytes. Must be `AES_BLOCK_LEN`. * :param bytes: Bytes to encrypt/decrypt. * :param bytes_len: Length of ``bytes`` in bytes. Can be of any length for encryption, must be a multiple of `AES_BLOCK_LEN` for decryption. @@ -849,6 +893,81 @@ WALLY_CORE_API int wally_s2c_commitment_verify( size_t s2c_opening_len, uint32_t flags); +/** + * Get the maximum length of data encrypted/decrypted using `wally_aes_cbc_with_ecdh_key`. + * + * :param priv_key: The callers private key used for Diffie-Helman exchange. + * :param priv_key_len: The length of ``priv_key`` in bytes. Must be `EC_PRIVATE_KEY_LEN`. + * :param iv: Initialization vector. Only required when encrypting, otherwise pass NULL. + * :param iv_len: Length of ``iv`` in bytes. Must be `AES_BLOCK_LEN`. + * :param bytes: Bytes to encrypt/decrypt. + * :param bytes_len: Length of ``bytes`` in bytes. + * :param pub_key: The other parties public key used for Diffie-Helman exchange. + * :param pub_key_len: Length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN`. + * :param label: A non-empty array of bytes for internal key generation. Must + *| be the same (fixed) value when encrypting and decrypting. + * :param label_len: Length of ``label`` in bytes. + * :param flags: :ref:`aes-operation-flag` indicating the desired behavior. + * :param written: Destination for the maximum length of the encrypted/decrypted data. + */ +WALLY_CORE_API int wally_aes_cbc_with_ecdh_key_get_maximum_length( + const unsigned char *priv_key, + size_t priv_key_len, + const unsigned char *iv, + size_t iv_len, + const unsigned char *bytes, + size_t bytes_len, + const unsigned char *pub_key, + size_t pub_key_len, + const unsigned char *label, + size_t label_len, + uint32_t flags, + size_t *written); + +/** + * Encrypt/decrypt data using AES-256 (CBC mode, PKCS#7 padding) and a shared Diffie-Helman secret. + * + * :param priv_key: The callers private key used for Diffie-Helman exchange. + * :param priv_key_len: The length of ``priv_key`` in bytes. Must be `EC_PRIVATE_KEY_LEN`. + * :param iv: Initialization vector. Only required when encrypting, otherwise pass NULL. + * :param iv_len: Length of ``iv`` in bytes. Must be `AES_BLOCK_LEN` if encrypting otherwise 0. + * :param bytes: Bytes to encrypt/decrypt. + * :param bytes_len: Length of ``bytes`` in bytes. + * :param pub_key: The other parties public key used for Diffie-Helman exchange. + * :param pub_key_len: Length of ``pub_key`` in bytes. Must be `EC_PUBLIC_KEY_LEN`. + * :param label: A non-empty array of bytes for internal key generation. Must + *| be the same (fixed) value when encrypting and decrypting. + * :param label_len: Length of ``label`` in bytes. + * :param flags: :ref:`aes-operation-flag` indicating the desired behavior. + * :param bytes_out: Destination for the encrypted/decrypted data. + * :param len: The length of ``bytes_out`` in bytes. + * :param written: Destination for the number of bytes written to ``bytes_out``. + * + * This function implements a scheme for sharing data using a derived secret. + * Alice creates an ephemeral key pair and sends her public key to Bob along + * with any request details. Bob creates an ephemeral key pair and calls this + * function with his private key and Alices public key to encrypt ``bytes`` + * (the request payload). Bob returns his public key and the encrypted data to + * Alice, who calls this function with her private key and Bobs public key + * to decrypt and authenticate the payload. The ``label`` parameter must be + * be the same for both Alice and Bob for a given request/response. + */ +WALLY_CORE_API int wally_aes_cbc_with_ecdh_key( + const unsigned char *priv_key, + size_t priv_key_len, + const unsigned char *iv, + size_t iv_len, + const unsigned char *bytes, + size_t bytes_len, + const unsigned char *pub_key, + size_t pub_key_len, + const unsigned char *label, + size_t label_len, + uint32_t flags, + unsigned char *bytes_out, + size_t len, + size_t *written); + #ifdef __cplusplus } #endif diff --git a/include/wally_descriptor.h b/include/wally_descriptor.h index d98abf32a..9d16300c0 100644 --- a/include/wally_descriptor.h +++ b/include/wally_descriptor.h @@ -15,6 +15,8 @@ struct wally_descriptor; #define WALLY_MINISCRIPT_TAPSCRIPT 0x01 /** Tapscript, use x-only pubkeys */ #define WALLY_MINISCRIPT_ONLY 0x02 /** Only allow miniscript (not descriptor) expressions */ #define WALLY_MINISCRIPT_REQUIRE_CHECKSUM 0x04 /** Require a checksum to be present */ +#define WALLY_MINISCRIPT_POLICY_TEMPLATE 0x08 /** Only allow policy templates with @n BIP32 keys */ +#define WALLY_MINISCRIPT_UNIQUE_KEYPATHS 0x10 /** For policy templates, ensure BIP32 derivation paths differ for identical keys */ #define WALLY_MINISCRIPT_DEPTH_MASK 0xffff0000 /** Mask for limiting maximum depth */ #define WALLY_MINISCRIPT_DEPTH_SHIFT 16 /** Shift to convert maximum depth to flags */ @@ -25,6 +27,8 @@ struct wally_descriptor; #define WALLY_MS_IS_UNCOMPRESSED 0x08 /** Contains at least one uncompressed key */ #define WALLY_MS_IS_RAW 0x10 /** Contains at least one raw key */ #define WALLY_MS_IS_DESCRIPTOR 0x20 /** Contains only descriptor expressions (no miniscript) */ +#define WALLY_MS_IS_X_ONLY 0x40 /** Contains at least one x-only key */ +#define WALLY_MS_IS_PARENTED 0x80 /** Contains at least one key key with a parent key origin */ /*** ms-canonicalization-flags Miniscript/Descriptor canonicalization flags */ #define WALLY_MS_CANONICAL_NO_CHECKSUM 0x01 /** Do not include a checksum */ @@ -180,6 +184,115 @@ WALLY_CORE_API int wally_descriptor_get_depth( const struct wally_descriptor *descriptor, uint32_t *value_out); +/** + * Get the number of keys in a parsed output descriptor or miniscript expression. + * + * :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. + */ +WALLY_CORE_API int wally_descriptor_get_num_keys( + const struct wally_descriptor *descriptor, + uint32_t *value_out); + +/** + * 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 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. + */ +WALLY_CORE_API int wally_descriptor_get_key( + const struct wally_descriptor *descriptor, + size_t index, + char **output); + +/** + * 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 value_out: Destination for the resulting :ref:`miniscript-features`. + */ +WALLY_CORE_API int wally_descriptor_get_key_features( + const struct wally_descriptor *descriptor, + size_t index, + uint32_t *value_out); + +/** + * Get the length of a keys child path string 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 whose child path to get. + * :param written: Destination for the length of the keys child path string, + *| excluding the NUL terminator (zero if not present). + */ +WALLY_CORE_API int wally_descriptor_get_key_child_path_str_len( + const struct wally_descriptor *descriptor, + size_t index, + size_t *written); + +/** + * Get the keys child path string 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 whose child path to get. + * :param output: Destination for the resulting path string (empty if not present). + *| The string returned should be freed using `wally_free_string`. + */ +WALLY_CORE_API int wally_descriptor_get_key_child_path_str( + const struct wally_descriptor *descriptor, + size_t index, + char **output); + +/** + * Get the keys parent BIP32 fingerprint 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 whose parent fingerprint to get. + * :param bytes_out: Destination for the fingerprint. + * FIXED_SIZED_OUTPUT(len, bytes_out, BIP32_KEY_FINGERPRINT_LEN) + * + * If the key does not contain key origin information then `WALLY_EINVAL` is returned. + */ +WALLY_CORE_API int wally_descriptor_get_key_origin_fingerprint( + const struct wally_descriptor *descriptor, + size_t index, + unsigned char *bytes_out, + size_t len); + +/** + * Get the length of a keys parent path string 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 whose parent path to get. + * :param written: Destination for the length of the keys parent path string, + *| excluding the NUL terminator (zero if not present). + */ +WALLY_CORE_API int wally_descriptor_get_key_origin_path_str_len( + const struct wally_descriptor *descriptor, + size_t index, + size_t *written); + +/** + * Get the keys parent path string 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 whose parent path to get. + * :param output: Destination for the resulting path string (empty if not present). + *| The string returned should be freed using `wally_free_string`. + */ +WALLY_CORE_API int wally_descriptor_get_key_origin_path_str( + const struct wally_descriptor *descriptor, + size_t index, + char **output); + /** * Get the maximum length of a script corresponding to an output descriptor. * diff --git a/include/wally_transaction.h b/include/wally_transaction.h index 118a9f109..6dfa65af0 100644 --- a/include/wally_transaction.h +++ b/include/wally_transaction.h @@ -26,7 +26,7 @@ extern "C" { #define WALLY_TX_FLAG_ALLOW_PARTIAL 0x4 /* Allow partially complete transactions */ /* Note: This flag encodes/parses transactions that are ambiguous to decode. Unless you have a good reason to do so you will most likely not need it */ -#define WALLY_TX_FLAG_PRE_BIP144 0x8 /* Encode/Decode using pre-BIP 144 serialisation */ +#define WALLY_TX_FLAG_PRE_BIP144 0x8 /* Encode/Decode using pre-BIP 144 serialization */ #define WALLY_TX_FLAG_BLINDED_INITIAL_ISSUANCE 0x1 diff --git a/src/Makefile.am b/src/Makefile.am index d9ff95759..9933955a4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -319,6 +319,7 @@ endif if USE_SWIG_PYTHON check-swig-python: $(SWIG_PYTHON_TEST_DEPS) + $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/aes.py $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/bip32.py $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/coinselection.py $(AM_V_at)$(PYTHON_SWIGTEST) swig_python/contrib/descriptor.py diff --git a/src/aes.c b/src/aes.c index a9a761d85..0f3245767 100644 --- a/src/aes.c +++ b/src/aes.c @@ -1,6 +1,7 @@ #include "internal.h" #include +#include "ccan/ccan/build_assert/build_assert.h" #include "ctaes/ctaes.h" #include "ctaes/ctaes.c" @@ -17,7 +18,7 @@ static bool are_valid_args(const unsigned char *key, size_t key_len, { return key && is_valid_key_len(key_len) && (bytes != NULL || (bytes == NULL && bytes_len == 0 && (flags & AES_FLAG_ENCRYPT))) && - (flags & ALL_OPS) != ALL_OPS; + (flags == AES_FLAG_ENCRYPT || flags == AES_FLAG_DECRYPT); } static void aes_enc(AES256_ctx *ctx, @@ -70,6 +71,19 @@ static void aes_dec(AES256_ctx *ctx, } } +int wally_aes_len(const unsigned char *key, size_t key_len, + const unsigned char *bytes, size_t bytes_len, + uint32_t flags, size_t *written) +{ + if (!written) + *written = 0; + if (!are_valid_args(key, key_len, bytes, bytes_len, flags) || + !bytes_len || bytes_len % AES_BLOCK_LEN || !written) + return WALLY_EINVAL; + *written = bytes_len; + return WALLY_OK; +} + int wally_aes(const unsigned char *key, size_t key_len, const unsigned char *bytes, size_t bytes_len, uint32_t flags, @@ -79,7 +93,7 @@ int wally_aes(const unsigned char *key, size_t key_len, if (!are_valid_args(key, key_len, bytes, bytes_len, flags) || len % AES_BLOCK_LEN || !bytes_len || bytes_len % AES_BLOCK_LEN || - flags & ~ALL_OPS || !bytes_out || !len) + !bytes_out || !len) return WALLY_EINVAL; if (flags & AES_FLAG_ENCRYPT) @@ -91,6 +105,24 @@ int wally_aes(const unsigned char *key, size_t key_len, return WALLY_OK; } +int wally_aes_cbc_get_maximum_length(const unsigned char *key, size_t key_len, + const unsigned char *iv, size_t iv_len, + const unsigned char *bytes, size_t bytes_len, + uint32_t flags, + size_t *written) +{ + if (written) + *written = 0; + + if (!are_valid_args(key, key_len, bytes, bytes_len, flags) || + ((flags & AES_FLAG_DECRYPT) && (bytes_len % AES_BLOCK_LEN)) || + !iv || iv_len != AES_BLOCK_LEN || !written) + return WALLY_EINVAL; + + *written = ((bytes_len / AES_BLOCK_LEN) + 1) * AES_BLOCK_LEN; + return WALLY_OK; +} + int wally_aes_cbc(const unsigned char *key, size_t key_len, const unsigned char *iv, size_t iv_len, const unsigned char *bytes, size_t bytes_len, @@ -109,7 +141,7 @@ int wally_aes_cbc(const unsigned char *key, size_t key_len, if (!are_valid_args(key, key_len, bytes, bytes_len, flags) || ((flags & AES_FLAG_ENCRYPT) && (len % AES_BLOCK_LEN)) || ((flags & AES_FLAG_DECRYPT) && (bytes_len % AES_BLOCK_LEN)) || - !iv || iv_len != AES_BLOCK_LEN || flags & ~ALL_OPS || !written) + !iv || iv_len != AES_BLOCK_LEN || !written) return WALLY_EINVAL; blocks = bytes_len / AES_BLOCK_LEN; @@ -177,3 +209,132 @@ int wally_aes_cbc(const unsigned char *key, size_t key_len, wally_clear_2(buf, sizeof(buf), &ctx, sizeof(ctx)); return WALLY_OK; } + +int wally_aes_cbc_with_ecdh_key_get_maximum_length( + const unsigned char *priv_key, size_t priv_key_len, + const unsigned char *iv, size_t iv_len, + const unsigned char *bytes, size_t bytes_len, + const unsigned char *pub_key, size_t pub_key_len, + const unsigned char *label, size_t label_len, uint32_t flags, + size_t *written) +{ + if (written) + *written = 0; + + if (!priv_key || priv_key_len != EC_PRIVATE_KEY_LEN || !bytes || + !pub_key || pub_key_len != EC_PUBLIC_KEY_LEN || !label || !label_len || + (flags != AES_FLAG_ENCRYPT && flags != AES_FLAG_DECRYPT) || !written) + return WALLY_EINVAL; + + if (flags & AES_FLAG_ENCRYPT) { + /* Must provide IV + minimum 1 byte of payload for encryption */ + if (!iv || iv_len != AES_BLOCK_LEN || !bytes_len) + return WALLY_EINVAL; + /* Output is IV + encrypted payload + HMAC */ + *written = AES_BLOCK_LEN + HMAC_SHA256_LEN + + ((bytes_len / AES_BLOCK_LEN) + 1) * AES_BLOCK_LEN; + } else { + /* Must not provide an IV for decryption */ + if (iv || iv_len) + return WALLY_EINVAL; + /* Payload must contain IV, payload and the HMAC for decryption */ + if (bytes_len < AES_BLOCK_LEN + AES_BLOCK_LEN + HMAC_SHA256_LEN) + return WALLY_EINVAL; + /* Output is the decrypted payload without the IV and HMAC */ + bytes_len = bytes_len - AES_BLOCK_LEN - HMAC_SHA256_LEN; + if (bytes_len % AES_BLOCK_LEN) + return WALLY_EINVAL; /* Payload isn't a block size multiple */ + /* Actual bytes written may be less due to padding, but the + * caller must pass a buffer of the padded size. */ + *written = bytes_len; + } + return WALLY_OK; +} + +int wally_aes_cbc_with_ecdh_key( + const unsigned char *priv_key, size_t priv_key_len, + const unsigned char *iv, size_t iv_len, + const unsigned char *bytes, size_t bytes_len, + const unsigned char *pub_key, size_t pub_key_len, + const unsigned char *label, size_t label_len, uint32_t flags, + unsigned char *bytes_out, size_t len, size_t *written) +{ + unsigned char secret[SHA256_LEN], keys[HMAC_SHA512_LEN]; + const unsigned char *enc_key = keys, *hmac_key = keys + AES_KEY_LEN_256; + size_t expected_len; + const bool is_encrypt = flags & AES_FLAG_ENCRYPT; + int ret; + + /* Derived key sizes must match the derived keys buffer */ + BUILD_ASSERT(sizeof(keys) == AES_KEY_LEN_256 + SHA256_LEN); + + if (written) + *written = 0; + + if (!bytes_out || !len || !written) + return WALLY_EINVAL; + ret = wally_aes_cbc_with_ecdh_key_get_maximum_length( + priv_key, priv_key_len, iv, iv_len, bytes, bytes_len, + pub_key, pub_key_len, label, label_len, flags, + &expected_len); + if (ret == WALLY_OK && expected_len > len) { + *written = expected_len; + return ret; /* Tell the caller how much space is needed */ + } + if (ret != WALLY_OK) + return ret; + + if (is_encrypt) { + /* Copy the IV to the start of the encrypted output */ + memcpy(bytes_out, iv, iv_len); + } else { + /* The IV is the first AES_BLOCK_LEN bytes of the payload */ + iv = bytes; + iv_len = AES_BLOCK_LEN; + } + + /* Shared secret is ECDH(their pubkey, our private key) */ + ret = wally_ecdh(pub_key, pub_key_len, priv_key, priv_key_len, + secret, sizeof(secret)); + /* Generate encryption/HMAC keys using the shared secret and label */ + if (ret == WALLY_OK) + ret = wally_hmac_sha512(secret, sizeof(secret), label, label_len, keys, sizeof(keys)); + if (ret == WALLY_OK && !is_encrypt) { + /* Verify the IV + encrypted data's HMAC */ + unsigned char hmac[HMAC_SHA256_LEN]; + ret = wally_hmac_sha256(hmac_key, SHA256_LEN, + bytes, bytes_len - sizeof(hmac), + hmac, sizeof(hmac)); + if (ret == WALLY_OK && + memcmp(hmac, bytes + bytes_len - sizeof(hmac), sizeof(hmac))) + ret = WALLY_EINVAL; /* Invalid HMAC */ + } + + /* Encrypt/decrypt the payload */ + if (ret == WALLY_OK) { + /* Trim our output length to a block size multiple for aes_cbc */ + size_t out_len = (len - (is_encrypt ? (iv_len + HMAC_SHA256_LEN) : 0)) & ~((size_t)0xf); + if (is_encrypt) + ret = wally_aes_cbc(enc_key, AES_KEY_LEN_256, iv, iv_len, + bytes, bytes_len, flags, + bytes_out + iv_len, out_len, written); + else + ret = wally_aes_cbc(enc_key, AES_KEY_LEN_256, iv, iv_len, + bytes + iv_len, bytes_len - iv_len - HMAC_SHA256_LEN, + flags, bytes_out, out_len, written); + } + + if (ret == WALLY_OK && is_encrypt) { + /* append the HMAC of the IV + encrypted data */ + *written += AES_BLOCK_LEN; /* Include the IV */ + ret = wally_hmac_sha256(hmac_key, SHA256_LEN, + bytes_out, *written, + bytes_out + *written, HMAC_SHA256_LEN); + *written = ret == WALLY_OK ? *written + HMAC_SHA256_LEN : 0; + } + + if (ret == WALLY_OK && *written > expected_len) + ret = WALLY_ERROR; /* Should never happen! */ + wally_clear_2(secret, sizeof(secret), keys, sizeof(keys)); + return ret; +} diff --git a/src/coins.c b/src/coins.c index b65d58dc4..988b54cf9 100644 --- a/src/coins.c +++ b/src/coins.c @@ -53,12 +53,12 @@ typedef struct value_remaining { * the io_ratio passed in, which increases the search space and prefers to * use more inputs if a changeless solution is found. * - * We implement the core optimisations: + * We implement the core optimizations: * - Cut the search branch when it cannot reach the target * - Cut the search branch when it exceeds the target (after scoring it) * - Do not test equivalent combinations * - * We add two additional optimisations. First, we return immediately if we find + * We add two additional optimizations. First, we return immediately if we find * a single input exact match, since that represents the best match possible. * Second, we cut the search branch when the number of selected inputs * is enough that even an exact match would not score better than the current diff --git a/src/ctest/test_clear.c b/src/ctest/test_clear.c index 15bacdc56..0432396c5 100644 --- a/src/ctest/test_clear.c +++ b/src/ctest/test_clear.c @@ -36,7 +36,7 @@ extern void __asan_unpoison_memory_region(void const volatile *addr, size_t size * determine that the memory is not read afterwards. There are reports * that tricks designed to work around this including making data volatile, * calling through function pointers, dummy asm contraints etc are - * not always effective as optimisation continues to improve. + * not always effective as optimization continues to improve. * * Here we try to ensure that the wally_clear/wally_clear_n() functions work as advertised * by: diff --git a/src/ctest/test_descriptor.c b/src/ctest/test_descriptor.c index 3445e1c80..0ceb1da72 100644 --- a/src/ctest/test_descriptor.c +++ b/src/ctest/test_descriptor.c @@ -51,6 +51,19 @@ static const struct wally_map g_key_map = { NULL }; +static struct wally_map_item g_policy_map_items[] = { + { B("@0"), B("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL") }, + { B("@1"), B("xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU") } +}; + +/* 1 and 2 element key variable maps for policy testing. + * The bip refers to these as "key information vectors". + */ +static const struct wally_map g_policy_maps[2] = { + { g_policy_map_items, 1, 1, NULL }, + { g_policy_map_items, NUM_ELEMS(g_policy_map_items), NUM_ELEMS(g_policy_map_items), NULL } +}; + static const uint32_t g_miniscript_index_0 = 0; static const uint32_t g_miniscript_index_16 = 0x10; @@ -968,6 +981,38 @@ static const struct descriptor_test { "5221038145454b87fc9ec3557478d6eadc2aea290b50f3c469b828abeb542ae8f8849d2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f052ae", "y5pky4r2" }, + /* Wallet policies https://github.com/bitcoin/bips/pull/1389 */ + { + "policy - single asterisk reconciliation", + "pkh(mainnet_xpub/*)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, + "76a914bb57ca9e62c7084081edc68d2cbc9524a523784288ac", + "cp8r8rlg" + }, { + "policy - single asterisk", + "pkh(@0/*)", // Becomes "pkh(mainnet_xpub/*)" i.e. the test case above this + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, WALLY_MINISCRIPT_POLICY_TEMPLATE, + "76a914bb57ca9e62c7084081edc68d2cbc9524a523784288ac", + "cp8r8rlg" + }, { + "policy - double asterisk", + "pkh(@0/**)", // Becomes "pkh(mainnet_xpub/<0;1>/*)" + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, WALLY_MINISCRIPT_POLICY_TEMPLATE, + "76a9143099ad49dfdd021bf3748f7f858e0d1fa0b4f6f888ac", + "ydnzkve4" + }, { + "policy - multi-path", + "pkh(@0/<0;1>/*)", // Becomes "pkh(mainnet_xpub/<0;1>/*)" + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, WALLY_MINISCRIPT_POLICY_TEMPLATE, + "76a9143099ad49dfdd021bf3748f7f858e0d1fa0b4f6f888ac", + "ydnzkve4" + }, { + "policy - previous key reference", + "sh(multi(1,@0/**,@1/**,@0/<2;3>/*))", /* For testing: expresssion isn't sensible */ + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, WALLY_MINISCRIPT_POLICY_TEMPLATE, + "a9146cc69bc5443e97b8a30918cb6297ba4efb3d6dc387", + "45w36ywr" + }, /* * Misc error cases (code coverage) */ @@ -1375,6 +1420,63 @@ static const struct descriptor_test { "descriptor - hardened xpub multi-path", /* TODO: Allow setting an xpriv into the descriptor */ "pkh(mainnet_xpub/<0';1>)", WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, 0, NULL, "" + }, + /* Wallet policy error cases */ + { + "policy errchk - no key expression", + "raw()", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - key with path", + "pkh(@0/0/*)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - missing key postfix", + "pkh(@0)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - terminal key postfix", + "@0", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - missing key number", + "@", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - mis-ordered keys", + "sh(multi(1,@1/**,@0/**))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - non-policy keys", + "sh(multi(1,@0/**,xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - mismatched key cardinalities (1)", + "sh(multi(1,@0/**,@1/*))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - mismatched key cardinalities (2)", + "sh(multi(1,@0/<0;1>/*,@1/*))", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - invalid key cardinality (key path)", + "pkh(@0/<0;1;2>/*)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" + }, { + "policy errchk - invalid key cardinality (variant)", + "combo(@0/**)", + WALLY_NETWORK_BITCOIN_MAINNET, 0, 0, 0, NULL, + WALLY_MINISCRIPT_POLICY_TEMPLATE, NULL, "" } }; @@ -1823,11 +1925,25 @@ static bool check_descriptor_to_script(const struct descriptor_test* test) int expected_ret, ret, len_ret; uint32_t multi_index = 0; uint32_t child_num = test->child_num ? *test->child_num : 0, features; + const bool is_policy = test->flags & WALLY_MINISCRIPT_POLICY_TEMPLATE; + const struct wally_map *keys = is_policy ? &g_policy_maps[0] : &g_key_map; 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, &g_key_map, test->network, + ret = wally_descriptor_parse(test->descriptor, keys, test->network, test->flags, &descriptor); + if (is_policy && expected_ret == WALLY_OK) { + /* Re-parse with strict key checking */ + wally_descriptor_free(descriptor); + ret = wally_descriptor_parse(test->descriptor, keys, test->network, + test->flags | WALLY_MINISCRIPT_UNIQUE_KEYPATHS, + &descriptor); + } if (expected_ret == WALLY_OK || ret == expected_ret) { /* For failure cases, we may fail when generating instead of parsing, * we catch those cases below */ diff --git a/src/descriptor.c b/src/descriptor.c index 7fedb5c95..8a6f10b0e 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -17,7 +17,11 @@ #define NUM_ELEMS(a) (sizeof(a) / sizeof(a[0])) #define MS_FLAGS_ALL (WALLY_MINISCRIPT_TAPSCRIPT | \ WALLY_MINISCRIPT_ONLY | \ - WALLY_MINISCRIPT_REQUIRE_CHECKSUM) + WALLY_MINISCRIPT_REQUIRE_CHECKSUM | \ + WALLY_MINISCRIPT_POLICY_TEMPLATE | \ + WALLY_MINISCRIPT_UNIQUE_KEYPATHS) +#define MS_FLAGS_CANONICALIZE (WALLY_MINISCRIPT_REQUIRE_CHECKSUM | \ + WALLY_MINISCRIPT_POLICY_TEMPLATE) /* Properties and expressions definition */ #define TYPE_NONE 0x00 @@ -159,11 +163,6 @@ static const struct addr_ver_t g_address_versions[] = { }, }; -#define NF_IS_UNCOMPRESSED 0x01 -#define NF_IS_XONLY 0x02 -#define NF_IS_RANGED 0x04 -#define NF_IS_MULTI 0x08 - /* A node in a parsed miniscript expression */ typedef struct ms_node_t { struct ms_node_t *next; @@ -178,7 +177,7 @@ typedef struct ms_node_t { uint32_t child_path_len; char wrapper_str[12]; unsigned char builtin; - unsigned char flags; /* NF_ flags */ + unsigned char flags; /* WALLY_MS_IS_ flags */ } ms_node; typedef struct wally_descriptor { @@ -196,8 +195,18 @@ typedef struct wally_descriptor { uint32_t multi_index; /* Multi-path index for derivation */ uint32_t *path_buff; /* Path buffer for deriving keys */ uint32_t max_path_elems; /* Max path length seen in the descriptor */ + struct wally_map keys; } ms_ctx; +static int ctx_add_key_node(ms_ctx *ctx, ms_node *node) +{ + const char *v = (char *)node; + return map_add(&ctx->keys, NULL, ctx->keys.num_items, + (unsigned char *)v, 1, true, false); +} + +static int ensure_unique_policy_keys(const ms_ctx *ctx); + /* Built-in miniscript expressions */ typedef int (*node_verify_fn_t)(ms_ctx *ctx, ms_node *node); typedef int (*node_gen_fn_t)(ms_ctx *ctx, ms_node *node, @@ -270,6 +279,7 @@ static const struct addr_ver_t *addr_ver_from_family( static const struct ms_builtin_t *builtin_get(const ms_node *node); static int generate_script(ms_ctx *ctx, ms_node *node, unsigned char *script, size_t script_len, size_t *written); +static bool is_valid_policy_map(const struct wally_map *map_in); /* Wrapper for strtoll */ static bool strtoll_n(const char *str, size_t str_len, int64_t *v) @@ -355,34 +365,49 @@ static int generate_checksum(const char *str, size_t str_len, char *checksum_out return WALLY_OK; } -static inline bool is_identifer_char(char c) +typedef bool (*is_identifer_fn)(char c); + +static bool is_identifer_char(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } +static bool is_policy_start_char(char c) { return c == '@'; } +static bool is_policy_identifer_char(char c) { return c >= '0' && c <= '9'; } static int canonicalize(const char *descriptor, const struct wally_map *vars_in, uint32_t flags, - char **output) + char **output, size_t *num_substitutions) { const size_t VAR_MAX_NAME_LEN = 16; + is_identifer_fn is_id_start = is_identifer_char, is_id_char = is_identifer_char; size_t required_len = 0; + int key_index_hwm = -1; const char *p = descriptor, *start; char *out; + bool found_policy_single = false, found_policy_multi = false;; - if (output) - *output = NULL; - - if (!descriptor || (flags & ~WALLY_MINISCRIPT_REQUIRE_CHECKSUM) || !output) + *output = NULL; + *num_substitutions = 0; + if (!descriptor || (flags & ~MS_FLAGS_CANONICALIZE)) return WALLY_EINVAL; + if (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE) { + if (!is_valid_policy_map(vars_in)) + return WALLY_EINVAL; /* Invalid policy variables given */ + is_id_start = is_policy_start_char; + is_id_char = is_policy_identifer_char; + } + /* First, find the length of the canonicalized descriptor */ while (*p && *p != '#') { - while (*p && *p != '#' && !is_identifer_char(*p)) { + while (*p && *p != '#' && !is_id_start(*p)) { ++required_len; ++p; } - start = p; - while (is_identifer_char(*p)) + if (!is_id_start(*p)) + break; + start = p++; + while (is_id_char(*p)) ++p; if (p != start) { const bool starts_with_digit = *start >= '0' && *start <= '9'; @@ -393,37 +418,81 @@ static int canonicalize(const char *descriptor, /* Lookup the potential identifier */ const struct wally_map_item *item; item = wally_map_get(vars_in, (unsigned char*)start, lookup_len); - required_len += item ? item->value_len : lookup_len; + if (!item) { + required_len += lookup_len; + continue; + } + required_len += item->value_len; + ++*num_substitutions; + if (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE) { + int key_index = (int)(item - vars_in->items); + if (key_index > key_index_hwm + 1) + return WALLY_EINVAL; /* Must be ordered with no gaps */ + if (key_index > key_index_hwm) + key_index_hwm = key_index; + if (*p++ != '/') + return WALLY_EINVAL; + ++required_len; + if (*p == '<') { + found_policy_multi = true; + continue; + } + if (*p++ != '*') + return WALLY_EINVAL; + if (*p == '*') { + found_policy_multi = true; + ++p; + required_len += strlen("<0;1>/*"); + } else { + found_policy_single = true; + required_len += 1; + } + } } } } if (!*p && (flags & WALLY_MINISCRIPT_REQUIRE_CHECKSUM)) return WALLY_EINVAL; /* Checksum required but not present */ - + if (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE) { + if (found_policy_single && found_policy_multi) + return WALLY_EINVAL; /* Cannot mix cardinality of policy keys */ + if (key_index_hwm == -1 || key_index_hwm != (int)vars_in->num_items - 1) + return WALLY_EINVAL; /* One or more keys wasn't substituted */ + } if (!(*output = wally_malloc(required_len + 1 + DESCRIPTOR_CHECKSUM_LENGTH + 1))) return WALLY_ENOMEM; p = descriptor; out = *output; while (*p && *p != '#') { - while (*p && *p != '#' && !is_identifer_char(*p)) { + while (*p && *p != '#' && !is_id_start(*p)) { *out++ = *p++; } - start = p; - while (is_identifer_char(*p)) + if (!is_id_start(*p)) + break; + start = p++; + while (is_id_char(*p)) ++p; if (p != start) { const bool is_number = *start >= '0' && *start <= '9'; size_t lookup_len = p - start; - if (!vars_in || lookup_len > VAR_MAX_NAME_LEN || is_number) { + if (!vars_in || lookup_len > VAR_MAX_NAME_LEN || is_number) memcpy(out, start, lookup_len); - } else { + else { /* Lookup the potential identifier */ const struct wally_map_item *item; item = wally_map_get(vars_in, (unsigned char*)start, lookup_len); lookup_len = item ? item->value_len : lookup_len; memcpy(out, item ? (char *)item->value : start, lookup_len); + if (item && flags & WALLY_MINISCRIPT_POLICY_TEMPLATE) { + if (p[1] == '*' && p[2] == '*') { + out += lookup_len; + lookup_len = strlen("/<0;1>/*"); + memcpy(out, "/<0;1>/*", lookup_len); + p += strlen("/**"); + } + } } out += lookup_len; } @@ -454,7 +523,7 @@ static bool node_has_uncompressed_key(const ms_ctx *ctx, const ms_node *node) if (ctx->features & WALLY_MS_IS_UNCOMPRESSED) { const ms_node *child; for (child = node->child; child; child = child->next) - if ((child->flags & NF_IS_UNCOMPRESSED) || node_has_uncompressed_key(ctx, child)) + if ((child->flags & WALLY_MS_IS_UNCOMPRESSED) || node_has_uncompressed_key(ctx, child)) return true; } return false; @@ -492,6 +561,10 @@ static bool has_two_different_lock_states(uint32_t primary, uint32_t secondary) int wally_descriptor_free(ms_ctx *ctx) { if (ctx) { + /* Just clear the item storage, the actual items are owned by + * the tree of nodes */ + clear_and_free(ctx->keys.items, + ctx->keys.num_items * sizeof(*ctx->keys.items)); wally_free_string(ctx->src); node_free(ctx->top_node); clear_and_free(ctx, sizeof(*ctx)); @@ -1072,7 +1145,7 @@ static int generate_pk_h(ms_ctx *ctx, ms_node *node, if (script_len >= WALLY_SCRIPTPUBKEY_P2PKH_LEN - 1) { ret = generate_pk_k(ctx, node, buff+3, sizeof(buff)-3, written); if (ret == WALLY_OK) { - if (node->child->flags & NF_IS_XONLY) + if (node->child->flags & WALLY_MS_IS_X_ONLY) return WALLY_EINVAL; script[0] = OP_DUP; script[1] = OP_HASH160; @@ -1826,13 +1899,13 @@ static int generate_script(ms_ctx *ctx, ms_node *node, ret = wally_ec_public_key_from_private_key((const unsigned char*)node->data, node->data_len, pubkey, sizeof(pubkey)); if (ret == WALLY_OK) { - if (node->flags & NF_IS_UNCOMPRESSED) { + if (node->flags & WALLY_MS_IS_UNCOMPRESSED) { output_len = EC_PUBLIC_KEY_UNCOMPRESSED_LEN; if (output_len <= script_len) ret = wally_ec_public_key_decompress(pubkey, sizeof(pubkey), script, EC_PUBLIC_KEY_UNCOMPRESSED_LEN); } else { - if (node->flags & NF_IS_XONLY) { + if (node->flags & WALLY_MS_IS_X_ONLY) { output_len = EC_XONLY_PUBLIC_KEY_LEN; if (output_len <= script_len) memcpy(script, &pubkey[1], EC_XONLY_PUBLIC_KEY_LEN); @@ -1844,7 +1917,7 @@ static int generate_script(ms_ctx *ctx, ms_node *node, } } } else if ((node->kind & KIND_BIP32) == KIND_BIP32) { - output_len = node->flags & NF_IS_XONLY ? EC_XONLY_PUBLIC_KEY_LEN : EC_PUBLIC_KEY_LEN; + output_len = node->flags & WALLY_MS_IS_X_ONLY ? EC_XONLY_PUBLIC_KEY_LEN : EC_PUBLIC_KEY_LEN; if (output_len > script_len) { ret = WALLY_OK; /* Return required length without writing */ } else { @@ -1858,8 +1931,8 @@ static int generate_script(ms_ctx *ctx, ms_node *node, BIP32_FLAG_STR_MULTIPATH; const uint32_t derive_flags = BIP32_FLAG_SKIP_HASH | BIP32_FLAG_KEY_PUBLIC; - const bool is_ranged = node->flags & NF_IS_RANGED; - const bool is_multi = node->flags & NF_IS_MULTI; + const bool is_ranged = node->flags & WALLY_MS_IS_RANGED; + const bool is_multi = node->flags & WALLY_MS_IS_MULTIPATH; struct ext_key derived; ret = bip32_path_from_str_n(node->child_path, node->child_path_len, @@ -1874,7 +1947,7 @@ static int generate_script(ms_ctx *ctx, ms_node *node, memcpy(&master, &derived, sizeof(master)); } if (ret == WALLY_OK) - memcpy(script, master.pub_key + ((node->flags & NF_IS_XONLY) ? 1 : 0), output_len); + memcpy(script, master.pub_key + ((node->flags & WALLY_MS_IS_X_ONLY) ? 1 : 0), output_len); wally_clear(&master, sizeof(master)); } } @@ -1950,39 +2023,43 @@ static int analyze_address(ms_ctx *ctx, const char *str, size_t str_len, return ret; } -static bool analyze_pubkey_hex(ms_ctx *ctx, const char *str, size_t str_len, - uint32_t flags, ms_node *node) +static int analyze_pubkey_hex(ms_ctx *ctx, const char *str, size_t str_len, + uint32_t flags, ms_node *node, bool *is_hex) { unsigned char pubkey[EC_PUBLIC_KEY_UNCOMPRESSED_LEN + 1]; size_t offset = flags & WALLY_MINISCRIPT_TAPSCRIPT ? 1 : 0; size_t written; - (void)ctx; + *is_hex = false; if (offset) { if (str_len != EC_XONLY_PUBLIC_KEY_LEN * 2) - return false; /* Only X-only pubkeys allowed under tapscript */ + return WALLY_OK; /* Only X-only pubkeys allowed under tapscript */ pubkey[0] = 2; /* Non-X-only pubkey prefix, for validation below */ } else { if (str_len != EC_PUBLIC_KEY_LEN * 2 && str_len != EC_PUBLIC_KEY_UNCOMPRESSED_LEN * 2) - return false; /* Unknown public key size */ + return WALLY_OK; /* Unknown public key size */ } if (wally_hex_n_to_bytes(str, str_len, pubkey + offset, sizeof(pubkey) - offset, &written) != WALLY_OK || wally_ec_public_key_verify(pubkey, written + offset) != WALLY_OK) - return false; + return WALLY_OK; /* Not hex, or not a pubkey */ if (!clone_bytes((unsigned char **)&node->data, pubkey + offset, written)) - return false; /* FIXME: This needs to return ENOMEM, not continue checking */ + return WALLY_ENOMEM; node->data_len = str_len / 2; if (str_len == EC_PUBLIC_KEY_UNCOMPRESSED_LEN * 2) { - node->flags |= NF_IS_UNCOMPRESSED; + node->flags |= WALLY_MS_IS_UNCOMPRESSED; ctx->features |= WALLY_MS_IS_UNCOMPRESSED; } - if (str_len == EC_XONLY_PUBLIC_KEY_LEN * 2) - node->flags |= NF_IS_XONLY; - node->kind = KIND_PUBLIC_KEY; + if (str_len == EC_XONLY_PUBLIC_KEY_LEN * 2) { + node->flags |= WALLY_MS_IS_X_ONLY; + ctx->features |= WALLY_MS_IS_X_ONLY; + } ctx->features |= WALLY_MS_IS_RAW; - return true; + node->kind = KIND_PUBLIC_KEY; + node->flags |= WALLY_MS_IS_RAW; + *is_hex = true; + return ctx_add_key_node(ctx, node); } static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, @@ -1992,6 +2069,7 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, struct ext_key extkey; size_t privkey_len, size; int ret; + bool is_hex; if (!node || (parent && !parent->builtin)) return WALLY_EINVAL; @@ -2007,13 +2085,19 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, (node->data[9] != ']' && node->data[9] != '/')) return WALLY_EINVAL; /* Invalid key origin fingerprint */ size = end - node->data + 1; - /* cut parent path */ + /* Store offset and length of any origin info in the number field */ + node->number = (uint64_t)(node->data - ctx->src) << 32u; + node->number |= size; + ctx->features |= WALLY_MS_IS_PARENTED; + node->flags |= WALLY_MS_IS_PARENTED; + /* Remove the key origin info from the key data */ node->data = end + 1; node->data_len -= size; } /* check key (public key) */ - if (analyze_pubkey_hex(ctx, node->data, node->data_len, flags, node)) + ret = analyze_pubkey_hex(ctx, node->data, node->data_len, flags, node, &is_hex); + if (ret == WALLY_OK && is_hex) return WALLY_OK; /* check key (private key(wif)) */ @@ -2025,13 +2109,13 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, if (privkey_len == EC_PRIVATE_KEY_LEN + 1) { if (flags & WALLY_MINISCRIPT_TAPSCRIPT) return WALLY_EINVAL; /* Tapscript only allows x-only keys */ - node->flags |= NF_IS_UNCOMPRESSED; + node->flags |= WALLY_MS_IS_UNCOMPRESSED; ctx->features |= WALLY_MS_IS_UNCOMPRESSED; } else if (privkey_len != EC_PRIVATE_KEY_LEN + 2 || privkey[EC_PRIVATE_KEY_LEN + 1] != 1) return WALLY_EINVAL; /* Unknown WIF format */ - node->flags |= (flags & WALLY_MINISCRIPT_TAPSCRIPT) ? NF_IS_XONLY : 0; + node->flags |= (flags & WALLY_MINISCRIPT_TAPSCRIPT) ? WALLY_MS_IS_X_ONLY : 0; ret = wally_ec_private_key_verify(&privkey[1], EC_PRIVATE_KEY_LEN); if (ret == WALLY_OK && !clone_bytes((unsigned char **)&node->data, &privkey[1], EC_PRIVATE_KEY_LEN)) ret = WALLY_EINVAL; @@ -2039,6 +2123,8 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, node->data_len = EC_PRIVATE_KEY_LEN; node->kind = KIND_PRIVATE_KEY; ctx->features |= (WALLY_MS_IS_PRIVATE | WALLY_MS_IS_RAW); + node->flags |= (WALLY_MS_IS_PRIVATE | WALLY_MS_IS_RAW); + ret = ctx_add_key_node(ctx, node); } wally_clear(privkey, sizeof(privkey)); return ret; @@ -2066,14 +2152,14 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, return WALLY_EINVAL; /* Different multi-path lengths */ ctx->num_multipaths = num_multi; ctx->features |= WALLY_MS_IS_MULTIPATH; - node->flags |= NF_IS_MULTI; + node->flags |= WALLY_MS_IS_MULTIPATH; } if (features & BIP32_PATH_IS_WILDCARD) { wildcard_pos = (features & BIP32_PATH_WILDCARD_MASK) >> BIP32_PATH_WILDCARD_SHIFT; if (wildcard_pos != num_elems - 1) return WALLY_EINVAL; /* Must be the last element */ ctx->features |= WALLY_MS_IS_RANGED; - node->flags |= NF_IS_RANGED; + node->flags |= WALLY_MS_IS_RANGED; } if (num_elems > ctx->max_path_elems) ctx->max_path_elems = num_elems; @@ -2089,6 +2175,7 @@ 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; } else node->kind = KIND_BIP32_PUBLIC_KEY; @@ -2101,8 +2188,13 @@ static int analyze_miniscript_key(ms_ctx *ctx, uint32_t flags, ret = WALLY_EINVAL; /* Mismatched main/test network */ } - if (ret == WALLY_OK && (flags & WALLY_MINISCRIPT_TAPSCRIPT)) - node->flags |= NF_IS_XONLY; + if (ret == WALLY_OK) { + 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); + } wally_clear(&extkey, sizeof(extkey)); return ret; } @@ -2385,9 +2477,9 @@ static int node_generation_size(const ms_node *node, size_t *total) } else if (node->kind & (KIND_RAW | KIND_ADDRESS) || node->kind == KIND_PUBLIC_KEY) { *total += node->data_len; } else if (node->kind == KIND_PRIVATE_KEY || (node->kind & KIND_BIP32) == KIND_BIP32) { - if (node->flags & NF_IS_UNCOMPRESSED) + if (node->flags & WALLY_MS_IS_UNCOMPRESSED) *total += EC_PUBLIC_KEY_UNCOMPRESSED_LEN; - else if (node->flags & NF_IS_XONLY) + else if (node->flags & WALLY_MS_IS_X_ONLY) *total += EC_XONLY_PUBLIC_KEY_LEN; else *total += EC_PUBLIC_KEY_LEN; @@ -2455,12 +2547,63 @@ static uint32_t get_max_depth(const char *miniscript, size_t miniscript_len) return depth == 1 ? max_depth : 0xffffffff; } +static bool is_valid_policy_map(const struct wally_map *map_in) +{ + struct wally_map keys; + ms_ctx ctx; + ms_node* node; + int64_t v; + size_t i; + int ret = WALLY_OK; + + if (!map_in || !map_in->num_items) + return WALLY_EINVAL; /* Must contain at least one key expression */ + + memset(&ctx, 0, sizeof(ctx)); + ret = wally_map_init(map_in->num_items, NULL, &keys); + + for (i = 0; ret == WALLY_OK && i < map_in->num_items; ++i) { + const struct wally_map_item *item = &map_in->items[i]; + if (!item->key || item->key_len < 2 || item->key[0] != '@' || + !strtoll_n((const char *)item->key + 1, item->key_len - 1, &v) || v < 0) + ret = WALLY_EINVAL; /* Policy keys can only be @n */ + else if ((size_t)v != i) + ret = WALLY_EINVAL; /* Must be sorted in order from 0-n */ + else if (!item->value || !item->value_len) + ret = WALLY_EINVAL; /* No key value */ + else if (!(node = wally_calloc(sizeof(*node)))) + ret = WALLY_EINVAL; + else { + node->data = (const char*)item->value; + node->data_len = item->value_len; + if (analyze_miniscript_key(&ctx, 0, node, NULL) != WALLY_OK || + node->kind != KIND_BIP32_PUBLIC_KEY || + node->child_path_len) { + ret = WALLY_EINVAL; /* Only BIP32 xpubs are allowed */ + } else if (ctx.features & (WALLY_MS_IS_MULTIPATH | WALLY_MS_IS_RANGED)) { + /* Range or multipath must be part of the expression, not the key */ + ret = WALLY_EINVAL; + } else { + ret = wally_map_add(&keys, item->value, item->value_len, NULL, 0); + } + node_free(node); + } + } + if (ret == WALLY_OK && keys.num_items != map_in->num_items) + ret = WALLY_EINVAL; /* One of more keys is not unique */ + clear_and_free(ctx.keys.items, + ctx.keys.num_items * sizeof(*ctx.keys.items)); + wally_map_clear(&keys); + return ret == WALLY_OK; +} + int wally_descriptor_parse(const char *miniscript, const struct wally_map *vars_in, uint32_t network, uint32_t flags, ms_ctx **output) { const struct addr_ver_t *addr_ver = addr_ver_from_network(network); + size_t num_substitutions; uint32_t kind = KIND_MINISCRIPT | (flags & WALLY_MINISCRIPT_ONLY ? 0 : KIND_DESCRIPTOR); uint32_t max_depth = flags >> WALLY_MINISCRIPT_DEPTH_SHIFT; ms_ctx *ctx; @@ -2480,9 +2623,10 @@ int wally_descriptor_parse(const char *miniscript, ctx->addr_ver = addr_ver; ctx->num_variants = 1; ctx->num_multipaths = 1; - ret = canonicalize(miniscript, vars_in, - flags & WALLY_MINISCRIPT_REQUIRE_CHECKSUM, - &ctx->src); + ret = wally_map_init(vars_in ? vars_in->num_items : 1, NULL, &ctx->keys); + if (ret == WALLY_OK) + ret = canonicalize(miniscript, vars_in, flags & MS_FLAGS_CANONICALIZE, + &ctx->src, &num_substitutions); if (ret == WALLY_OK) { ctx->src_len = strlen(ctx->src); ctx->features = WALLY_MS_IS_DESCRIPTOR; /* Un-set if miniscript found */ @@ -2494,6 +2638,16 @@ int wally_descriptor_parse(const char *miniscript, flags, NULL, NULL, &ctx->top_node); if (ret == WALLY_OK) ret = node_generation_size(ctx->top_node, &ctx->script_len); + if (ret == WALLY_OK && (flags & WALLY_MINISCRIPT_POLICY_TEMPLATE)) { + if (ctx->keys.num_items != num_substitutions) + ret = WALLY_EINVAL; /* non-substituted key in the expression */ + else if (vars_in && ctx->keys.num_items < vars_in->num_items) + ret = WALLY_EINVAL; /* non-substituted key in substitutions */ + else if (ctx->num_variants > 1 || ctx->num_multipaths > 2) + ret = WALLY_EINVAL; /* Solved cardinality must be 1 or 2 */ + else if (flags & WALLY_MINISCRIPT_UNIQUE_KEYPATHS) + ret = ensure_unique_policy_keys(ctx); + } } if (ret != WALLY_OK) { wally_descriptor_free(ctx); @@ -2683,37 +2837,36 @@ int wally_descriptor_set_network(struct wally_descriptor *descriptor, return descriptor->addr_ver ? WALLY_OK : WALLY_EINVAL; } -int wally_descriptor_get_features(const struct wally_descriptor *descriptor, - uint32_t *value_out) +static int descriptor_uint32(const void *descriptor, + uint32_t *value_out, size_t offset) { if (value_out) *value_out = 0; if (!descriptor || !value_out) return WALLY_EINVAL; - *value_out = descriptor->features; + memcpy(value_out, (char*)descriptor + offset, sizeof(uint32_t)); return WALLY_OK; } +int wally_descriptor_get_features(const struct wally_descriptor *descriptor, + uint32_t *value_out) +{ + return descriptor_uint32(descriptor, value_out, + offsetof(struct wally_descriptor, features)); +} + int wally_descriptor_get_num_variants(const struct wally_descriptor *descriptor, uint32_t *value_out) { - if (value_out) - *value_out = 0; - if (!descriptor || !value_out) - return WALLY_EINVAL; - *value_out = descriptor->num_variants; - return WALLY_OK; + return descriptor_uint32(descriptor, value_out, + offsetof(struct wally_descriptor, num_variants)); } int wally_descriptor_get_num_paths(const struct wally_descriptor *descriptor, uint32_t *value_out) { - if (value_out) - *value_out = 0; - if (!descriptor || !value_out) - return WALLY_EINVAL; - *value_out = descriptor->num_multipaths; - return WALLY_OK; + return descriptor_uint32(descriptor, value_out, + offsetof(struct wally_descriptor, num_multipaths)); } static uint32_t node_get_depth(const ms_node *node) @@ -2738,3 +2891,208 @@ int wally_descriptor_get_depth(const struct wally_descriptor *descriptor, *value_out = node_get_depth(descriptor->top_node) - 1; return WALLY_OK; } + +int wally_descriptor_get_num_keys(const struct wally_descriptor *descriptor, + uint32_t *value_out) +{ + if (value_out) + *value_out = 0; + if (!descriptor || !value_out) + return WALLY_EINVAL; + *value_out = (uint32_t)descriptor->keys.num_items; + return WALLY_OK; +} + +/* Ignore incorrect warnings from the ms_node cast below */ +#pragma GCC diagnostic ignored "-Wcast-align" +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wcast-align" +#endif +static const ms_node *descriptor_get_key(const struct wally_descriptor *descriptor, + size_t index) +{ + if (!descriptor || index >= descriptor->keys.num_items) + return NULL; + return (ms_node *)descriptor->keys.items[index].value; +} + +int wally_descriptor_get_key(const struct wally_descriptor *descriptor, + size_t index, char **output) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (output) + *output = 0; + if (!node || !output) + return WALLY_EINVAL; + + if (node->kind == KIND_PUBLIC_KEY) { + return wally_hex_from_bytes((const unsigned char *)node->data, + node->data_len, output); + } + if (node->kind == KIND_PRIVATE_KEY) { + uint32_t flags = node->flags & WALLY_MS_IS_UNCOMPRESSED ? WALLY_WIF_FLAG_UNCOMPRESSED : 0; + if (!descriptor->addr_ver) + return WALLY_EINVAL; /* Must have a network to fetch private keys */ + return wally_wif_from_bytes((const unsigned char *)node->data, node->data_len, + descriptor->addr_ver->version_wif, + flags, output); + } + if ((node->kind & KIND_BIP32) != KIND_BIP32) + return WALLY_ERROR; /* Unknown key type, should not happen */ + if (!(*output = wally_strdup_n(node->data, node->data_len))) + return WALLY_ENOMEM; + return WALLY_OK; +} + +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); + + if (value_out) + *value_out = 0; + if (!node || !value_out) + return WALLY_EINVAL; + *value_out = node->flags; + return WALLY_OK; +} + +int wally_descriptor_get_key_child_path_str_len( + const struct wally_descriptor *descriptor, size_t index, size_t *written) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (written) + *written = 0; + if (!node || !written) + return WALLY_EINVAL; + *written = node->child_path_len; + return WALLY_OK; +} + +int wally_descriptor_get_key_child_path_str( + const struct wally_descriptor *descriptor, size_t index, char **output) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (output) + *output = 0; + if (!node || !output) + return WALLY_EINVAL; + if (!(*output = wally_strdup_n(node->child_path, node->child_path_len))) + return WALLY_ENOMEM; + return WALLY_OK; +} + +int wally_descriptor_get_key_origin_fingerprint( + const struct wally_descriptor *descriptor, size_t index, + unsigned char *bytes_out, size_t len) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + const char *fingerprint; + size_t written; + int ret; + + if (!node || !bytes_out || len != BIP32_KEY_FINGERPRINT_LEN || + !(node->flags & WALLY_MS_IS_PARENTED)) + return WALLY_EINVAL; + fingerprint = descriptor->src + (((uint64_t)node->number) >> 32u) + 1; + ret = wally_hex_n_to_bytes(fingerprint, BIP32_KEY_FINGERPRINT_LEN * 2, + bytes_out, len, &written); + return ret == WALLY_OK && written != BIP32_KEY_FINGERPRINT_LEN ? WALLY_EINVAL : ret; +} + +int wally_descriptor_get_key_origin_path_str_len( + const struct wally_descriptor *descriptor, size_t index, size_t *written) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + + if (written) + *written = 0; + if (!node || !written) + return WALLY_EINVAL; + *written = node->flags & WALLY_MS_IS_PARENTED ? node->number & 0xffffffff : 0; + *written = *written < 11u ? 0 : *written - 11u; + return WALLY_OK; +} + +int wally_descriptor_get_key_origin_path_str( + const struct wally_descriptor *descriptor, size_t index, char **output) +{ + const ms_node *node = descriptor_get_key(descriptor, index); + const char *path; + size_t path_len; + + if (output) + *output = NULL; + if (!node || !output) + return WALLY_EINVAL; + path_len = node->flags & WALLY_MS_IS_PARENTED ? node->number & 0xffffffff : 0; + path_len = path_len < 11u ? 0 : path_len - 11u; + path = descriptor->src + (((uint64_t)node->number) >> 32u) + 10u; + if (!(*output = wally_strdup_n(path, path_len))) + return WALLY_ENOMEM; + return WALLY_OK; +} + +static const char *get_multipath_child(const char* p, uint32_t *v) +{ + *v = 0; + if (*p != '<' && *p != ';') + return NULL; + else { + ++p; + while (*p >= '0' && *p <= '9') { + *v *= 10; + *v += (*p++ - '0'); + } + if (*p == '\'' || *p == 'h' || *p == 'H') { + *v |= BIP32_INITIAL_HARDENED_CHILD; + ++p; + } + } + return p; +} + +static int are_keys_overlapped(const ms_ctx *ctx, + const ms_node *lhs, const ms_node *rhs) +{ + const char *p; + uint32_t l1, l2, r1, r2; + + if (lhs->data_len != rhs->data_len || + memcmp(lhs->data, rhs->data, lhs->data_len)) + return WALLY_OK; /* Different root keys */ + if (lhs->child_path_len == rhs->child_path_len && + !memcmp(lhs->child_path, rhs->child_path, lhs->child_path_len)) + return WALLY_EINVAL; /* Identical paths */ + if (!(lhs->flags & WALLY_MS_IS_MULTIPATH)) + return WALLY_OK; /* Non-identical ranged, non-multipath keys */ + if (ctx->max_path_elems != 2 || !(rhs->flags & WALLY_MS_IS_MULTIPATH)) + return WALLY_ERROR; /* Should never happen! */ + /* Check the set of multi-path indices is disjoint */ + if (!(p = get_multipath_child(strchr(lhs->child_path, '<'), &l1)) || + !get_multipath_child(p, &l2) || + !(p = get_multipath_child(strchr(rhs->child_path, '<'), &r1)) || + !get_multipath_child(p, &r2)) + return WALLY_ERROR; /* Should never happen! */ + if (l1 == r1 || l1 == r2 || l2 == r1 || l2 == r2) + return WALLY_EINVAL; /* indices are not disjoint */ + return WALLY_OK; +} + +static int ensure_unique_policy_keys(const ms_ctx *ctx) +{ + size_t i, j; + + for (i = 0; i < ctx->keys.num_items; ++i) { + const ms_node *node = descriptor_get_key(ctx, i); + for (j = i + 1; j < ctx->keys.num_items; ++j) { + int ret = are_keys_overlapped(ctx, node, descriptor_get_key(ctx, j)); + if (ret != WALLY_OK) + return ret; + } + } + return WALLY_OK; +} diff --git a/src/swig_java/src/com/blockstream/test/test_bip32.java b/src/swig_java/src/com/blockstream/test/test_bip32.java index c17fa9d9f..f4c412786 100644 --- a/src/swig_java/src/com/blockstream/test/test_bip32.java +++ b/src/swig_java/src/com/blockstream/test/test_bip32.java @@ -48,7 +48,7 @@ public void test() { final byte[] initSerialized = Wally.bip32_key_serialize(initKey, BIP32_FLAG_KEY_PRIVATE); if (!h(initSerialized).equals(h(derivedSerialized))) - throw new RuntimeException("BIP32 initialisation by member failed"); + throw new RuntimeException("BIP32 initialization by member failed"); final byte[] message = Wally.bip32_key_get_chain_code(derivedKey); final byte[] signature = Wally.ec_sig_from_bytes(Wally.bip32_key_get_priv_key(derivedKey), diff --git a/src/swig_java/swig.i b/src/swig_java/swig.i index b36c2cbbd..1b0fd73fa 100644 --- a/src/swig_java/swig.i +++ b/src/swig_java/swig.i @@ -520,7 +520,11 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_size_t(wally_addr_segwit_to_bytes); %returns_size_t(wally_address_to_scriptpubkey); %returns_array_(wally_aes, 6, 7, AES_BLOCK_LEN); +%returns_size_t(wally_aes_len); %returns_size_t(wally_aes_cbc); +%returns_size_t(wally_aes_cbc_get_maximum_length); +%returns_size_t(wally_aes_cbc_with_ecdh_key); +%returns_size_t(wally_aes_cbc_with_ecdh_key_get_maximum_length); %returns_array_(wally_asset_final_vbf, 8, 9, ASSET_TAG_LEN); %returns_array_(wally_asset_generator_from_bytes, 5, 6, ASSET_GENERATOR_LEN); %returns_size_t(wally_asset_rangeproof_get_maximum_len); @@ -561,6 +565,14 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) { %returns_size_t(wally_descriptor_get_depth); %returns_size_t(wally_descriptor_get_features); %returns_size_t(wally_descriptor_get_network); +%returns_string(wally_descriptor_get_key); +%returns_string(wally_descriptor_get_key_child_path_str); +%returns_size_t(wally_descriptor_get_key_child_path_str_len); +%returns_size_t(wally_descriptor_get_key_features); +%returns_array_(wally_descriptor_get_key_origin_fingerprint, 3, 4, BIP32_KEY_FINGERPRINT_LEN); +%returns_string(wally_descriptor_get_key_origin_path_str); +%returns_size_t(wally_descriptor_get_key_origin_path_str_len); +%returns_size_t(wally_descriptor_get_num_keys); %returns_size_t(wally_descriptor_get_num_paths); %returns_size_t(wally_descriptor_get_num_variants); %returns_void__(wally_descriptor_set_network); diff --git a/src/swig_python/contrib/aes.py b/src/swig_python/contrib/aes.py new file mode 100644 index 000000000..50280f70f --- /dev/null +++ b/src/swig_python/contrib/aes.py @@ -0,0 +1,43 @@ +import unittest +from wallycore import init, cleanup, \ + ec_public_key_from_private_key, aes_cbc_with_ecdh_key, \ + AES_FLAG_ENCRYPT, AES_FLAG_DECRYPT + + +class AESTests(unittest.TestCase): + + def test_aes_cbc_ecdh(self): + """Test python wrappers for aes_cbc_with_ecdh_key """ + # Alice generates an ephemeral keypair for her request. + alice_priv = bytes.fromhex('1c6a837d1ac663fdc7f1002327ca38452766eaf4fe3b80ce620bf7cd3f584cf6') + alice_pub = ec_public_key_from_private_key(alice_priv) + # Bob generates an ephemeral keypair for his response. + bob_priv = bytes.fromhex('0b6b3dc90d203d854100110788ac87d43aa00620c9cdb361b281b09022ef4b53') + bob_pub = ec_public_key_from_private_key(bob_priv) + # Bob also generates a secure random IV for encrypting the response. + iv = bytes.fromhex('bd5d4724243880738e7e8b0c02658700') + # Both parties must agree on a shared label to use. + label = 'a sample label'.encode() + # This is the example payload we are using. + payload = 'This is an example response/payload to encrypt'.encode() + + # Test we handle messages up to and over AES_BLOCK_LEN boundaries + for i in range(1, len(payload) + 1): + # The protocol: + # 1) Alice requests some data using her pubkey. + # 2) Bob encrypts the response (payload) with his private key, + # a random IV, a shared label and Alice's pubkey. + encrypted = aes_cbc_with_ecdh_key(bob_priv, iv, payload[0:i], + alice_pub, label, AES_FLAG_ENCRYPT) + # 3) Bob sends the encrypted data and his pubkey to Alice. + # 4) Alice decrypts the payload with her private key and Bobs pubkey. + decrypted = aes_cbc_with_ecdh_key(alice_priv, None, encrypted, + bob_pub, label, AES_FLAG_DECRYPT) + # Alice now has the unencrypted payload. + self.assertEqual(decrypted.hex(), payload[0:i].hex()) + + +if __name__ == '__main__': + init(0) + unittest.main() + cleanup(0) diff --git a/src/swig_python/contrib/sha.py b/src/swig_python/contrib/sha.py index cc897890e..2285602a9 100644 --- a/src/swig_python/contrib/sha.py +++ b/src/swig_python/contrib/sha.py @@ -1,62 +1,39 @@ -"""Tests for shas incl wrong types passed""" -import sys import unittest -from wallycore import * - -b2h = hex_from_bytes -h2b = hex_to_bytes - -class SHA_tests(unittest.TestCase): - - def test_sha256(self): - self.assertEqual(b2h(sha256("This is a test message to hash".encode())), "726ca2c10e9d8b76e5b79f2961c3069a09fdd0a3b9bf8650e091e39b3c6c35be") - - self.assertEqual(b2h(sha256(h2b("3e8379862d658e168c71f083bc05169b3b58ca3212e11c838b08629c5ca48a42"))), "2f7d292595788655c5288b6e1dc698440d9c12559e3bc1e3cc38005a4add132f") - - - def test_sha256d(self): - self.assertEqual(b2h(sha256d("This is a test message to hash".encode())), "29e04e90a1075caaa06573ea701913148d99fb0b7d6928e33f1aabe6032761a0") - - self.assertEqual(b2h(sha256d(h2b("3e8379862d658e168c71f083bc05169b3b58ca3212e11c838b08629c5ca48a42"))), "26e30f19dc2b29d8c220766fd5835d8256c87c32804d19b8307e21d6685c9d3e") - - - def test_sha512(self): - self.assertEqual(b2h(sha512("This is a test message to hash".encode())), "2ed34644ddfcf76ca4de13e4632aa61376fbce813fecc5a043a479daaab17b2f8c3f376468d4637cb2e7c9e2b99ad08b8cb56fe6e724e476826f2aa210872c32") - - self.assertEqual(b2h(sha512(h2b("3e8379862d658e168c71f083bc05169b3b58ca3212e11c838b08629c5ca48a42"))), "d51342efcb114c11045c12f7fede6f9a5fdb11051032bd520a99d79023423f4ac3ab706ce5fa88c0aac46bbbf15bde720cf49eae5be0def3b39e6d3abb29a67b") - - - def _test_wrong_types_py2(self): - # Python2 implicitly converts/decodes - self.assertEqual(b2h(sha256('not bytes')), "b6cb5f25b258630497a18528fb8f73a64034e94e1ead857a8151e3f30a9835ae") - - self.assertEqual(b2h(sha256d('not bytes')), "878eb992aeb736646ecf2c76f562c5d411a487d62ac172d098a83afb023d1b53") - - self.assertEqual(b2h(sha512('not bytes')), "981e82b6ccc079c455cd3fd37b9e04f52f084ffb268a07c47b0447910e2d6280ccbaa5be3f8f062e3e284c98f52039bbddee150a06183ff8d9cb243ef35e3f57") - - - def _test_wrong_types_py3(self): - # Python3 raises TypeError - for shaX in [sha256, sha256d, sha512]: +from wallycore import init, cleanup, sha256, sha256d, sha512 + + +class SHATests(unittest.TestCase): + + def test_sha_functions(self): + """Test python wrappers for SHA hash functions""" + # Ensure empty and valid buffers work correctly + msg1 = 'This is a test message to hash'.encode() + msg2 = bytes.fromhex('3e8379862d658e168c71f083bc05169b3b58ca3212e11c838b08629c5ca48a42') + cases = [ + (sha256, None, 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), + (sha256, msg1, '726ca2c10e9d8b76e5b79f2961c3069a09fdd0a3b9bf8650e091e39b3c6c35be'), + (sha256, msg2, '2f7d292595788655c5288b6e1dc698440d9c12559e3bc1e3cc38005a4add132f'), + (sha256d, None, '5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456'), + (sha256d, msg1, '29e04e90a1075caaa06573ea701913148d99fb0b7d6928e33f1aabe6032761a0'), + (sha256d, msg2, '26e30f19dc2b29d8c220766fd5835d8256c87c32804d19b8307e21d6685c9d3e'), + (sha512, None, 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce' + '47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'), + (sha512, msg1, '2ed34644ddfcf76ca4de13e4632aa61376fbce813fecc5a043a479daaab17b2f' + '8c3f376468d4637cb2e7c9e2b99ad08b8cb56fe6e724e476826f2aa210872c32'), + (sha512, msg2, 'd51342efcb114c11045c12f7fede6f9a5fdb11051032bd520a99d79023423f4a' + 'c3ab706ce5fa88c0aac46bbbf15bde720cf49eae5be0def3b39e6d3abb29a67b'), + ] + for sha_fn, src, expected in cases: + result = sha_fn(src) + self.assertEqual(result.hex(), expected) + + # Ensure passing an incorrect type (a) throws and (b) doesn't crash + for sha_fn in [sha256, sha256d, sha512]: with self.assertRaises(TypeError): - shaX('not bytes') - - - def test_wrong_types(self): - if sys.version_info.major < 3: - # Python2 implicitly converts/decodes - self._test_wrong_types_py2() - else: - # Python3 raises TypeError - self._test_wrong_types_py3() - - - def test_pass_none(self): - self.assertEqual(b2h(sha256(None)), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - self.assertEqual(b2h(sha256d(None)), "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456") - self.assertEqual(b2h(sha512(None)), "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e") + sha_fn('not bytes') if __name__ == '__main__': + init(0) unittest.main() - + cleanup(0) diff --git a/src/swig_python/python_extra.py_in b/src/swig_python/python_extra.py_in index 26d2894b6..01e0e44b8 100644 --- a/src/swig_python/python_extra.py_in +++ b/src/swig_python/python_extra.py_in @@ -9,7 +9,7 @@ def _wrap_bin(fn, length, resize=False): if resize: # Truncate buf to bytes written if needed. Also assert the # wrapper allocated enough space for the returned value to fit. - assert ret <= n + assert ret <= n, f'{fn.__name__}: {ret} > {n}' return buf[0:ret] if ret != n else buf if ret is not None: assert ret == n @@ -92,13 +92,6 @@ def scriptsig_multisig_from_bytes_len(script, sigs, sighashes, flags): def wif_to_public_key_len(wif, prefix): return EC_PUBLIC_KEY_UNCOMPRESSED_LEN if wif_is_uncompressed(wif) else EC_PUBLIC_KEY_LEN -def aes_len(key, src_bytes, flags): - return len(src_bytes) - -def aes_cbc_len(key, iv, src_bytes, flags): - bytes_len = len(src_bytes) - return (bytes_len // AES_BLOCK_LEN + 1) * AES_BLOCK_LEN - if is_elements_build(): def elements_pegout_script_from_bytes_len(bh, mcs, pk, whl, flags): return elements_pegout_script_size(len(bh), len(mcs), len(pk), len(whl)) @@ -122,7 +115,8 @@ ae_host_commit_from_bytes = _wrap_bin(ae_host_commit_from_bytes, WALLY_HOST_COMM ae_sig_from_bytes = _wrap_bin(ae_sig_from_bytes, EC_SIGNATURE_LEN) ae_signer_commit_from_bytes = _wrap_bin(ae_signer_commit_from_bytes, WALLY_S2C_OPENING_LEN) aes = _wrap_bin(aes, aes_len) -aes_cbc = _wrap_bin(aes_cbc, aes_cbc_len, resize=True) +aes_cbc = _wrap_bin(aes_cbc, aes_cbc_get_maximum_length, resize=True) +aes_cbc_with_ecdh_key = _wrap_bin(aes_cbc_with_ecdh_key, aes_cbc_with_ecdh_key_get_maximum_length, resize=True) base58_n_to_bytes = _wrap_bin(base58_n_to_bytes, base58_n_to_bytes_len, resize=True) base58_to_bytes = _wrap_bin(base58_to_bytes, base58_to_bytes_len, resize=True) base64_to_bytes = _wrap_bin(base64_to_bytes, base64_get_maximum_length, resize=True) @@ -150,6 +144,7 @@ bip38_to_private_key = _wrap_bin(bip38_to_private_key, EC_PRIVATE_KEY_LEN) bip39_mnemonic_to_bytes = _wrap_bin(bip39_mnemonic_to_bytes, BIP39_ENTROPY_MAX_LEN, resize=True) bip39_mnemonic_to_seed512 = _wrap_bin(bip39_mnemonic_to_seed512, BIP39_SEED_LEN_512) bip85_get_bip39_entropy = _wrap_bin(bip85_get_bip39_entropy, HMAC_SHA512_LEN, resize=True) +descriptor_get_key_origin_fingerprint = _wrap_bin(descriptor_get_key_origin_fingerprint, BIP32_KEY_FINGERPRINT_LEN) descriptor_to_script = _wrap_bin(descriptor_to_script, descriptor_to_script_get_maximum_length, resize=True) ec_private_key_bip341_tweak = _wrap_bin(ec_private_key_bip341_tweak, EC_PRIVATE_KEY_LEN) ec_public_key_bip341_tweak = _wrap_bin(ec_public_key_bip341_tweak, EC_PUBLIC_KEY_LEN) diff --git a/src/test/test_aes.py b/src/test/test_aes.py index 2a4a376a5..dd75de47c 100755 --- a/src/test/test_aes.py +++ b/src/test/test_aes.py @@ -92,6 +92,85 @@ def test_aes_cbc(self): self.assertEqual((ret, written), (0, len(o))) self.assertEqual(h(out_buf), h(o)) + def test_aes_cbc_with_ecdh_key(self): + ENCRYPT, DECRYPT, PUBKEY_LEN, _ = 1, 2, 33, True + a_priv = make_cbuffer('1c6a837d1ac663fdc7f1002327ca38452766eaf4fe3b80ce620bf7cd3f584cf6')[0] + a_pub = make_cbuffer('03e581be89d1ef8ce11d60746d08e4f8aedf934d1d861dd436042ee2e3b16db918')[0] + b_priv = make_cbuffer('0b6b3dc90d203d854100110788ac87d43aa00620c9cdb361b281b09022ef4b53')[0] + b_pub = make_cbuffer('03ff06999ad61c0f3a733b93fc1e6b75ecfb1439b326e840de590a56454f0eeb0d')[0] + iv = make_cbuffer('bd5d4724243880738e7e8b0c02658700')[0] + label = 'a sample label'.encode() + payload = 'This is an example response/payload to encrypt'.encode() + buf = make_cbuffer('00' * 256)[0] + + # Encryption + good_args = [b_priv, len(b_priv), iv, len(iv), payload, len(payload), + a_pub, len(a_pub), label, len(label), ENCRYPT, buf, len(buf)] + + ret, written = wally_aes_cbc_with_ecdh_key(*good_args) + self.assertEqual(ret, WALLY_OK) # Make sure good args work + encrypted = make_cbuffer(buf[:written].hex())[0] + + invalid_cases = [ + (None, _, _, _, _, _, _, _, _, _, _, _, _), # NULL privkey + (_, 0, _, _, _, _, _, _, _, _, _, _, _), # Empty privkey + (_, 9, _, _, _, _, _, _, _, _, _, _, _), # Wrong privkey length + (_, _, None, _, _, _, _, _, _, _, _, _, _), # NULL IV (enc) + (_, _, _, 0, _, _, _, _, _, _, _, _, _), # Empty IV (enc) + (_, _, _, 9, _, _, _, _, _, _, _, _, _), # Wrong IV length (enc) + (_, _, _, _, None, _, _, _, _, _, _, _, _), # NULL payload + (_, _, _, _, _, 0, _, _, _, _, _, _, _), # Empty payload + (_, _, _, _, _, _, None, _, _, _, _, _, _), # NULL pubkey + (_, _, _, _, _, _, _, 0, _, _, _, _, _), # Empty pubkey + (_, _, _, _, _, _, _, 9, _, _, _, _, _), # Wrong pubkey length + (_, _, _, _, _, _, _, _, None, _, _, _, _), # NULL label + (_, _, _, _, _, _, _, _, _, 0, _, _, _), # Empty label + (_, _, _, _, _, _, _, _, _, _, 3, _, _), # Encrypt+Decrypt flags + (_, _, _, _, _, _, _, _, _, _, 5, _, _), # Unknown flag + (_, _, _, _, _, _, _, _, _, _, _, None, _), # NULL output + (_, _, _, _, _, _, _, _, _, _, _, _, 0), # Zero-length output + ] + for case in invalid_cases: + args = [good_args[i] if a == _ else a for i, a in enumerate(case)] + self.assertEqual(wally_aes_cbc_with_ecdh_key(*args), (WALLY_EINVAL, 0)) + + # Test writing up to/beyond the output buffer size + for out_len in range(1, len(encrypted) + 16): + args = [a for a in good_args] + args[-1] = out_len + ret = wally_aes_cbc_with_ecdh_key(*args) + self.assertEqual(ret, (WALLY_OK, written)) # returns required length + + # Decryption + good_args = [a_priv, len(a_priv), None, 0, encrypted, len(encrypted), + b_pub, len(b_pub), label, len(label), DECRYPT, buf, len(buf)] + + ret, written = wally_aes_cbc_with_ecdh_key(*good_args) + self.assertEqual(ret, WALLY_OK) # Make sure good args work + self.assertEqual(buf[:written], payload) + + bad = make_cbuffer((encrypted[:-1] + b'?').hex())[0] # Corrupt the HMAC + bad_len = len(encrypted) - 1 + invalid_cases = [ + (_, _, iv, _, _, _, _, _, _, _, _, _, _), # Non-NULL IV (dec) + (_, _, _, len(iv), _, _, _, _, _, _, _, _, _), # Non-zero IV length (dec) + (_, _, _, _, bad, _, _, _, _, _, _, _, _), # Corrupt HMAC + (_, _, _, _, _, bad_len, _, _, _, _, _, _, _), # Truncated encrypted data + ] + for case in invalid_cases: + args = [good_args[i] if a == _ else a for i, a in enumerate(case)] + self.assertEqual(wally_aes_cbc_with_ecdh_key(*args), (WALLY_EINVAL, 0)) + + # Test writing up to/beyond the output buffer size + for out_len in range(1, len(payload) + 16): + args = [a for a in good_args] + args[-1] = out_len + ret, written = wally_aes_cbc_with_ecdh_key(*args) + self.assertEqual(ret, WALLY_OK) + # The output size required includes final padding which is + # stripped if the payload isn't a multiple of the AES block size. + self.assertLessEqual(len(payload), written) + if __name__ == '__main__': unittest.main() diff --git a/src/test/test_descriptor.py b/src/test/test_descriptor.py index f4e28dbd1..3d92bbd48 100644 --- a/src/test/test_descriptor.py +++ b/src/test/test_descriptor.py @@ -10,15 +10,20 @@ NETWORK_LIQUID = 0x03 NETWORK_LIQUID_REG = 0x04 -MS_TAP = 0x1 # WALLY_MINISCRIPT_TAPSCRIPT -MS_ONLY = 0x2 # WALLY_MINISCRIPT_ONLY +MS_TAP = 0x1 # WALLY_MINISCRIPT_TAPSCRIPT +MS_ONLY = 0x2 # WALLY_MINISCRIPT_ONLY +REQUIRE_CHECKSUM = 0x4 # WALLY_MINISCRIPT_REQUIRE_CHECKSUM +POLICY = 0x08 # WALLY_MINISCRIPT_POLICY_TEMPLATE +UNIQUE_KEYPATHS = 0x10 # WALLY_MINISCRIPT_UNIQUE_KEYPATHS -MS_IS_RANGED = 0x1 -MS_IS_MULTIPATH = 0x2 -MS_IS_PRIVATE = 0x4 +MS_IS_RANGED = 0x1 +MS_IS_MULTIPATH = 0x2 +MS_IS_PRIVATE = 0x4 MS_IS_UNCOMPRESSED = 0x08 -MS_IS_RAW = 0x010 -MS_IS_DESCRIPTOR = 0x20 +MS_IS_RAW = 0x010 +MS_IS_DESCRIPTOR = 0x20 +MS_IS_X_ONLY = 0x40 +MS_IS_PARENTED = 0x80 NO_CHECKSUM = 0x1 # WALLY_MS_CANONICAL_NO_CHECKSUM @@ -26,7 +31,7 @@ def wally_map_from_dict(d): m = pointer(wally_map()) assert(wally_map_init_alloc(len(d.keys()), None, m) == WALLY_OK) for k,v in d.items(): - assert(wally_map_add(m, k, len(k), v, len(v)) == WALLY_OK) + assert(wally_map_add(m, utf8(k), len(k), utf8(v), len(v)) == WALLY_OK) return m @@ -35,10 +40,10 @@ class DescriptorTests(unittest.TestCase): def test_parse_and_to_script(self): """Test parsing and script generation""" keys = wally_map_from_dict({ - utf8('key_local'): utf8('038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048'), - utf8('key_remote'): utf8('03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7'), - utf8('key_revocation'): utf8('03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284'), - utf8('H'): utf8('d0721279e70d39fb4aa409b52839a0056454e3b5'), # HASH160(key_local) + 'key_local': '038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048', + 'key_remote': '03a22745365f673e658f0d25eb0afa9aaece858c6a48dfe37a67210c2e23da8ce7', + 'key_revocation': '03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284', + 'H': 'd0721279e70d39fb4aa409b52839a0056454e3b5', # HASH160(key_local) }) script, script_len = make_cbuffer('00' * 256 * 2) @@ -278,5 +283,122 @@ def test_features_and_depth(self): flags | (5 << 16), d) self.assertEqual(ret, WALLY_EINVAL) + def test_policy(self): + """Test policy parsing""" + # Substitution variables + xpriv = 'xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU' + xpub1 = 'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL' + xpub2 = 'xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU' + + def make_keys(xpubs): + keys = {f'@{i}': xpub for i,xpub in enumerate(xpubs)} + return wally_map_from_dict(keys) + + P, K = POLICY, UNIQUE_KEYPATHS + bad_args = [ + # Raw pubkey + [P, 'pkh(@0/*)', ['038bc7431d9285a064b0328b6333f3a20b86664437b6de8f4e26e6bbdee258f048']], + # Bip32 private key + [P, 'pkh(@0/*)', [xpriv]], + # Keys must be in the form of @N + [P, 'pkh(@0/*)', {'foo': xpub1}], + # Keys must start from 0 + [P, 'pkh(@0/*)', {'@1': xpub1}], + # Keys must be successive integers + [P, 'pkh(@0/*)', [xpub1, xpub2]], + # Keys must all be substituted + [P, 'pkh(@0/*)', {'@0': xpub1, '@1': xpub2}], + # Keys cannot have child paths + [P, 'pkh(@0/*)', {'@0': f'{xpub1}/0'}], + # Keys must be unique in the substitution list (always) + [P, 'sh(multi(1, @0/*,@1/*))', [xpub1, xpub1]], + # Keys must be unique in the final expression (with flag) + [P|K, 'sh(multi(1,@0/*,@0/*))', [xpub1]], + [P|K, 'sh(multi(1,@0/**,@0/**))', [xpub1]], + # Key multi-paths must be disjoint sets + [P|K, 'sh(multi(1,@0/<0;1>/*,@0/<1;2>/*))', [xpub1]], + [P|K, 'sh(multi(1,@0/<1;0>/*,@0/<2;1>/*))', [xpub1]], + ] + d = c_void_p() + for flags, policy, key_items in bad_args: + keys = wally_map_from_dict(key_items) if type(key_items) is dict else make_keys(key_items) + ret = wally_descriptor_parse(policy, keys, NETWORK_BTC_MAIN, flags, d) + self.assertEqual(ret, WALLY_EINVAL) + wally_map_free(keys) + + def test_key_iteration(self): + """Test iterating descriptor keys""" + origin_fp = 'd34db33f' + origin_path = "44'/0'/0'" + origin = f'[{origin_fp}/{origin_path}]' + k1 = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB' + k2 = 'xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU' + wif = 'L1AAHuEC7XuDM7pJ7yHLEqYK1QspMo8n1kgxyZVdgvEpVC1rkUrM' + pk = '03b428da420cd337c7208ed42c5331ebb407bb59ffbe3dc27936a227c619804284' + pk_u = '0414fc03b8df87cd7b872996810db8458d61da8448e531569c8517b469a119d267be5645686309c6e6736dbd93940707cc9143d3cf29f1b877ff340e2cb2d259cf' + policy_keys = wally_map_from_dict({f'@{i}': xpub for i,xpub in enumerate([k1])}) + policy_keys_with_origins = wally_map_from_dict({f'@{i}': f'{origin}{xpub}' for i,xpub in enumerate([k1])}) + P = POLICY + + # Valid args + for flags, descriptor, expected, child_path, expected_features in [ + # Bip32 xpub + (0, f'pkh({k1})', k1, '', 0), + (0, f'pkh({k1}/*)', k1, '*', MS_IS_RANGED), + (0, f'pkh({k1}/0/1/2/*)', k1, '0/1/2/*', MS_IS_RANGED), + (0, f'pkh({k1}/<0;1>/*)', k1, '<0;1>/*', MS_IS_RANGED|MS_IS_MULTIPATH), + # Bip32 xpub (as policy) + (P, 'pkh(@0/*)', k1, '*', MS_IS_RANGED), + (P, 'pkh(@0/**)', k1, '<0;1>/*', MS_IS_RANGED|MS_IS_MULTIPATH), + (P, 'pkh(@0/<0;1>/*)', k1, '<0;1>/*', MS_IS_RANGED|MS_IS_MULTIPATH), + # BIP32 xprv + (0, f'pkh({k2})', k2, '', MS_IS_PRIVATE), + # WIF + (0, f'pkh({wif})', wif, '', MS_IS_RAW|MS_IS_PRIVATE), + # Hex pubkey, compressed + (0, f'pk({pk})', pk, '', MS_IS_RAW), + # Hex pubkey, uncompressed + (0, f'pk({pk_u})', pk_u, '', MS_IS_RAW|MS_IS_UNCOMPRESSED), + ]: + d = c_void_p() + buf, buf_len = make_cbuffer('0' * 8) + + for with_origin in [False, True] if expected == k1 else [False]: + keys = None + if flags & P: + keys = policy_keys_with_origins if with_origin else policy_keys + elif with_origin: + continue + ret = wally_descriptor_parse(descriptor, keys, NETWORK_BTC_MAIN, flags, d) + self.assertEqual(ret, WALLY_OK) + ret, num_keys = wally_descriptor_get_num_keys(d) + self.assertEqual((ret, num_keys), (WALLY_OK, 1)) + ret, key_str = wally_descriptor_get_key(d, 0) + self.assertEqual((ret, key_str), (WALLY_OK, expected)) + ret, path_len = wally_descriptor_get_key_child_path_str_len(d, 0) + self.assertEqual((ret, path_len), (WALLY_OK, len(child_path))) + ret, path_str = wally_descriptor_get_key_child_path_str(d, 0) + self.assertEqual((ret, path_str), (WALLY_OK, child_path)) + ret, features = wally_descriptor_get_key_features(d, 0) + if with_origin: + expected_features |= MS_IS_PARENTED + self.assertEqual((ret, features), (WALLY_OK, expected_features)) + ret = wally_descriptor_get_key_origin_fingerprint(d, 0, buf, buf_len) + # Ensure the key origin matches if present + if with_origin: + self.assertEqual(ret, WALLY_OK) + ret, fp = wally_hex_from_bytes(buf, buf_len) + self.assertEqual((ret, fp), (WALLY_OK, origin_fp)) + else: + self.assertEqual(ret, WALLY_EINVAL) + ret, path_len = wally_descriptor_get_key_origin_path_str_len(d, 0) + expected_len = len(origin_path) if with_origin else 0 + self.assertEqual((ret, path_len), (WALLY_OK, expected_len)) + ret, path_str = wally_descriptor_get_key_origin_path_str(d, 0) + expected_path = origin_path if with_origin else '' + self.assertEqual((ret, path_str), (WALLY_OK, expected_path)) + wally_descriptor_free(d) + + if __name__ == '__main__': unittest.main() diff --git a/src/test/util.py b/src/test/util.py index edfe9ff5b..22ae6afc9 100755 --- a/src/test/util.py +++ b/src/test/util.py @@ -268,6 +268,10 @@ class wally_psbt(Structure): ('wally_ae_verify', 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]), ('wally_aes', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_void_p, c_size_t]), ('wally_aes_cbc', c_int, [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_aes_cbc_get_maximum_length', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_size_t_p]), + ('wally_aes_cbc_with_ecdh_key', 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_void_p, c_size_t, c_uint32, c_void_p, c_size_t, c_size_t_p]), + ('wally_aes_cbc_with_ecdh_key_get_maximum_length', 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_void_p, c_size_t, c_uint32, c_size_t_p]), + ('wally_aes_len', c_int, [c_void_p, c_size_t, c_void_p, c_size_t, c_uint32, c_size_t_p]), ('wally_asset_blinding_key_from_seed', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), ('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]), @@ -313,7 +317,15 @@ class wally_psbt(Structure): ('wally_descriptor_get_checksum', c_int, [c_void_p, c_uint32, c_char_p_p]), ('wally_descriptor_get_depth', c_int, [c_void_p, c_uint32_p]), ('wally_descriptor_get_features', c_int, [c_void_p, c_uint32_p]), + ('wally_descriptor_get_key', c_int, [c_void_p, c_size_t, c_char_p_p]), + ('wally_descriptor_get_key_child_path_str', c_int, [c_void_p, c_size_t, c_char_p_p]), + ('wally_descriptor_get_key_child_path_str_len', c_int, [c_void_p, c_size_t, c_size_t_p]), + ('wally_descriptor_get_key_features', c_int, [c_void_p, c_size_t, c_uint32_p]), + ('wally_descriptor_get_key_origin_fingerprint', c_int, [c_void_p, c_size_t, c_void_p, c_size_t]), + ('wally_descriptor_get_key_origin_path_str', c_int, [c_void_p, c_size_t, c_char_p_p]), + ('wally_descriptor_get_key_origin_path_str_len', c_int, [c_void_p, c_size_t, c_size_t_p]), ('wally_descriptor_get_network', c_int, [c_void_p, c_uint32_p]), + ('wally_descriptor_get_num_keys', c_int, [c_void_p, c_uint32_p]), ('wally_descriptor_get_num_paths', c_int, [c_void_p, c_uint32_p]), ('wally_descriptor_get_num_variants', c_int, [c_void_p, c_uint32_p]), ('wally_descriptor_parse', c_int, [c_char_p, POINTER(wally_map), c_uint32, c_uint32, POINTER(c_void_p)]), diff --git a/src/transaction.c b/src/transaction.c index 0bb1e3ccf..fd1a18cb7 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -2794,7 +2794,7 @@ static int analyze_tx(const unsigned char *bytes, size_t bytes_len, if (is_elements) { if (flags & WALLY_TX_FLAG_PRE_BIP144) - return WALLY_EINVAL; /* No pre-BIP 144 serialisation for elements */ + return WALLY_EINVAL; /* No pre-BIP 144 serialization for elements */ *expect_witnesses = *p++ != 0; } else { if (!(flags & WALLY_TX_FLAG_PRE_BIP144) && *p == 0) { diff --git a/src/wasm_package/src/const.js b/src/wasm_package/src/const.js index f8eb3545b..2c0e012e8 100755 --- a/src/wasm_package/src/const.js +++ b/src/wasm_package/src/const.js @@ -121,15 +121,19 @@ export const WALLY_MAX_OP_RETURN_LEN = 80; /* Maximum length of OP_RETURN data p export const WALLY_MINISCRIPT_DEPTH_MASK = 0xffff0000; /** Mask for limiting maximum depth */ export const WALLY_MINISCRIPT_DEPTH_SHIFT = 16; /** Shift to convert maximum depth to flags */ export const WALLY_MINISCRIPT_ONLY = 0x02; /** Only allow miniscript (not descriptor) expressions */ +export const WALLY_MINISCRIPT_POLICY_TEMPLATE = 0x08; /** Only allow policy templates with @n BIP32 keys */ export const WALLY_MINISCRIPT_REQUIRE_CHECKSUM = 0x04; /** Require a checksum to be present */ 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_MS_CANONICAL_NO_CHECKSUM = 0x01; /** Do not include a checksum */ export const WALLY_MS_IS_DESCRIPTOR = 0x20; /** Contains only descriptor expressions (no miniscript) */ export const WALLY_MS_IS_MULTIPATH = 0x02; /** Allows multiple paths via ```` */ +export const WALLY_MS_IS_PARENTED = 0x80; /** Contains at least one key key with a parent key origin */ export const WALLY_MS_IS_PRIVATE = 0x04; /** Contains at least one private key */ export const WALLY_MS_IS_RANGED = 0x01; /** Allows key ranges via ``*`` */ export const WALLY_MS_IS_RAW = 0x10; /** Contains at least one raw key */ export const WALLY_MS_IS_UNCOMPRESSED = 0x08; /** Contains at least one uncompressed key */ +export const WALLY_MS_IS_X_ONLY = 0x40; /** Contains at least one x-only key */ export const WALLY_NETWORK_BITCOIN_MAINNET = 0x01; /** Bitcoin mainnet */ export const WALLY_NETWORK_BITCOIN_REGTEST = 0xff ; /** Bitcoin regtest: Behaves as testnet except for segwit */ export const WALLY_NETWORK_BITCOIN_TESTNET = 0x02; /** Bitcoin testnet */ @@ -224,7 +228,7 @@ export const WALLY_TX_DUMMY_SIG = 0x2; /* A dummy signature */ export const WALLY_TX_DUMMY_SIG_LOW_R = 0x4; /* A dummy signature created with EC_FLAG_GRIND_R */ export const WALLY_TX_FLAG_ALLOW_PARTIAL = 0x4; /* Allow partially complete transactions */ export const WALLY_TX_FLAG_BLINDED_INITIAL_ISSUANCE = 0x1; -export const WALLY_TX_FLAG_PRE_BIP144 = 0x8; /* Encode/Decode using pre-BIP 144 serialisation */ +export const WALLY_TX_FLAG_PRE_BIP144 = 0x8; /* Encode/Decode using pre-BIP 144 serialization */ export const WALLY_TX_FLAG_USE_ELEMENTS = 0x2; /* Encode/Decode as an elements transaction */ export const WALLY_TX_FLAG_USE_WITNESS = 0x1; /* Encode witness data if present */ export const WALLY_TX_INDEX_MASK = 0x3fffffff; diff --git a/src/wasm_package/src/functions.js b/src/wasm_package/src/functions.js index 2b7557126..e07023149 100644 --- a/src/wasm_package/src/functions.js +++ b/src/wasm_package/src/functions.js @@ -9,14 +9,6 @@ const hex_n_to_bytes_len = (_hex, hex_len) => Math.floor(hex_len / 2) const base58_to_bytes_len = (base58, flags) => base58_get_length(base58) const base58_n_to_bytes_len = (base58, n, flags) => base58_n_get_length(base58, n) -const aes_len = (_key, bytes, _flags) => - // ECB mode with no padding - output size is always exactly the same as the input - bytes.length - -const aes_cbc_len = (_key, iv, bytes, _flags) => - // CBC mode with PKCS#7 padding - output must be padded to the next block size multiply (for the pad length byte) - (Math.floor(bytes.length/C.AES_BLOCK_LEN) + 1) * C.AES_BLOCK_LEN - const wif_to_public_key_len = (wif, _prefix) => wif_is_uncompressed(wif) ? C.EC_PUBLIC_KEY_UNCOMPRESSED_LEN : C.EC_PUBLIC_KEY_LEN @@ -78,8 +70,9 @@ export const ae_host_commit_from_bytes = wrap('wally_ae_host_commit_from_bytes', export const ae_sig_from_bytes = wrap('wally_ae_sig_from_bytes', [T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, C.EC_SIGNATURE_LEN)]); export const ae_signer_commit_from_bytes = wrap('wally_ae_signer_commit_from_bytes', [T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, C.WALLY_S2C_OPENING_LEN)]); export const ae_verify = wrap('wally_ae_verify', [T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Int32, T.Bytes]); -export const aes = wrap('wally_aes', [T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, aes_len, false)]); -export const aes_cbc = wrap('wally_aes_cbc', [T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrVarLen(T.Bytes, aes_cbc_len, true)]); +export const aes_cbc_get_maximum_length = wrap('wally_aes_cbc_get_maximum_length', [T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtr(T.Int32)]); +export const aes_cbc_with_ecdh_key_get_maximum_length = wrap('wally_aes_cbc_with_ecdh_key_get_maximum_length', [T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtr(T.Int32)]); +export const aes_len = wrap('wally_aes_len', [T.Bytes, T.Bytes, T.Int32, T.DestPtr(T.Int32)]); export const asset_blinding_key_from_seed = wrap('wally_asset_blinding_key_from_seed', [T.Bytes, T.DestPtrSized(T.Bytes, C.HMAC_SHA512_LEN)]); 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)]); @@ -171,7 +164,13 @@ export const descriptor_free = wrap('wally_descriptor_free', [T.OpaqueRef]); export const descriptor_get_checksum = wrap('wally_descriptor_get_checksum', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); export const descriptor_get_depth = wrap('wally_descriptor_get_depth', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const descriptor_get_features = wrap('wally_descriptor_get_features', [T.OpaqueRef, T.DestPtr(T.Int32)]); +export const descriptor_get_key = wrap('wally_descriptor_get_key', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); +export const descriptor_get_key_child_path_str_len = wrap('wally_descriptor_get_key_child_path_str_len', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); +export const descriptor_get_key_features = wrap('wally_descriptor_get_key_features', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); +export const descriptor_get_key_origin_fingerprint = wrap('wally_descriptor_get_key_origin_fingerprint', [T.OpaqueRef, T.Int32, T.DestPtrSized(T.Bytes, C.BIP32_KEY_FINGERPRINT_LEN)]); +export const descriptor_get_key_origin_path_str_len = wrap('wally_descriptor_get_key_origin_path_str_len', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]); export const descriptor_get_network = wrap('wally_descriptor_get_network', [T.OpaqueRef, T.DestPtr(T.Int32)]); +export const descriptor_get_num_keys = wrap('wally_descriptor_get_num_keys', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const descriptor_get_num_paths = wrap('wally_descriptor_get_num_paths', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const descriptor_get_num_variants = wrap('wally_descriptor_get_num_variants', [T.OpaqueRef, T.DestPtr(T.Int32)]); export const descriptor_parse = wrap('wally_descriptor_parse', [T.String, T.OpaqueRef, T.Int32, T.Int32, T.DestPtrPtr(T.OpaqueRef)]); @@ -766,11 +765,16 @@ export const witness_p2wpkh_from_der = wrap('wally_witness_p2wpkh_from_der', [T. export const witness_p2wpkh_from_sig = wrap('wally_witness_p2wpkh_from_sig', [T.Bytes, T.Bytes, T.Int32, T.DestPtrPtr(T.OpaqueRef)]); export const witness_program_from_bytes = wrap('wally_witness_program_from_bytes', [T.Bytes, T.Int32, T.DestPtrVarLen(T.Bytes, C.WALLY_WITNESSSCRIPT_MAX_LEN, true)]); export const witness_program_from_bytes_and_version = wrap('wally_witness_program_from_bytes_and_version', [T.Bytes, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, C.WALLY_WITNESSSCRIPT_MAX_LEN, true)]); +export const aes = wrap('wally_aes', [T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, aes_len, false)]); +export const aes_cbc = wrap('wally_aes_cbc', [T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrVarLen(T.Bytes, aes_cbc_get_maximum_length, true)]); +export const aes_cbc_with_ecdh_key = wrap('wally_aes_cbc_with_ecdh_key', [T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrVarLen(T.Bytes, aes_cbc_with_ecdh_key_get_maximum_length, true)]); export const asset_pak_whitelistproof = wrap('wally_asset_pak_whitelistproof', [T.Bytes, T.Bytes, T.Int32, T.Bytes, T.Bytes, T.Bytes, T.DestPtrVarLen(T.Bytes, asset_pak_whitelistproof_len, false)]); export const asset_surjectionproof = wrap('wally_asset_surjectionproof', [T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.Bytes, T.DestPtrVarLen(T.Bytes, asset_surjectionproof_len, false)]); export const base58_n_to_bytes = wrap('wally_base58_n_to_bytes', [T.String, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, base58_n_to_bytes_len, true)]); export const base58_to_bytes = wrap('wally_base58_to_bytes', [T.String, T.Int32, T.DestPtrVarLen(T.Bytes, base58_to_bytes_len, true)]); export const base64_to_bytes = wrap('wally_base64_to_bytes', [T.String, T.Int32, T.DestPtrVarLen(T.Bytes, base64_get_maximum_length, true)]); +export const descriptor_get_key_child_path_str = wrap('wally_descriptor_get_key_child_path_str', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); +export const descriptor_get_key_origin_path_str = wrap('wally_descriptor_get_key_origin_path_str', [T.OpaqueRef, T.Int32, T.DestPtrPtr(T.String)]); export const descriptor_to_script = wrap('wally_descriptor_to_script', [T.OpaqueRef, T.Int32, T.Int32, T.Int32, T.Int32, T.Int32, T.Int32, T.DestPtrVarLen(T.Bytes, descriptor_to_script_get_maximum_length, true)]); export const ec_sig_from_bytes = wrap('wally_ec_sig_from_bytes', [T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, ec_sig_from_bytes_len, false)]); export const ec_sig_from_bytes_aux = wrap('wally_ec_sig_from_bytes_aux', [T.Bytes, T.Bytes, T.Bytes, T.Int32, T.DestPtrSized(T.Bytes, ec_sig_from_bytes_aux_len, false)]); diff --git a/src/wasm_package/src/index.d.ts b/src/wasm_package/src/index.d.ts index 121b9566b..70efc9c1f 100644 --- a/src/wasm_package/src/index.d.ts +++ b/src/wasm_package/src/index.d.ts @@ -30,8 +30,9 @@ export function ae_host_commit_from_bytes(entropy: Buffer|Uint8Array, flags: num export function ae_sig_from_bytes(priv_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, entropy: Buffer|Uint8Array, flags: number): Buffer; export function ae_signer_commit_from_bytes(priv_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, commitment: Buffer|Uint8Array, flags: number): Buffer; export function ae_verify(pub_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, entropy: Buffer|Uint8Array, s2c_opening: Buffer|Uint8Array, flags: number, sig: Buffer|Uint8Array): void; -export function aes(key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): Buffer; -export function aes_cbc(key: Buffer|Uint8Array, iv: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): Buffer; +export function aes_cbc_get_maximum_length(key: Buffer|Uint8Array, iv: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): number; +export function aes_cbc_with_ecdh_key_get_maximum_length(priv_key: Buffer|Uint8Array, iv: Buffer|Uint8Array, bytes: Buffer|Uint8Array, pub_key: Buffer|Uint8Array, label: Buffer|Uint8Array, flags: number): number; +export function aes_len(key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): number; 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; @@ -123,7 +124,13 @@ export function descriptor_free(descriptor: Ref_wally_descriptor): void; export function descriptor_get_checksum(descriptor: Ref_wally_descriptor, flags: number): string; export function descriptor_get_depth(descriptor: Ref_wally_descriptor): number; export function descriptor_get_features(descriptor: Ref_wally_descriptor): number; +export function descriptor_get_key(descriptor: Ref_wally_descriptor, index: number): string; +export function descriptor_get_key_child_path_str_len(descriptor: Ref_wally_descriptor, index: number): number; +export function descriptor_get_key_features(descriptor: Ref_wally_descriptor, index: number): number; +export function descriptor_get_key_origin_fingerprint(descriptor: Ref_wally_descriptor, index: number): Buffer; +export function descriptor_get_key_origin_path_str_len(descriptor: Ref_wally_descriptor, index: number): number; export function descriptor_get_network(descriptor: Ref_wally_descriptor): number; +export function descriptor_get_num_keys(descriptor: Ref_wally_descriptor): number; export function descriptor_get_num_paths(descriptor: Ref_wally_descriptor): number; export function descriptor_get_num_variants(descriptor: Ref_wally_descriptor): number; export function descriptor_parse(descriptor: string, vars_in: Ref_wally_map, network: number, flags: number): Ref_wally_descriptor; @@ -718,11 +725,16 @@ export function witness_p2wpkh_from_der(pub_key: Buffer|Uint8Array, sig: Buffer| export function witness_p2wpkh_from_sig(pub_key: Buffer|Uint8Array, sig: Buffer|Uint8Array, sighash: number): Ref_wally_tx_witness_stack; export function witness_program_from_bytes(bytes: Buffer|Uint8Array, flags: number): Buffer; export function witness_program_from_bytes_and_version(bytes: Buffer|Uint8Array, version: number, flags: number): Buffer; +export function aes(key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): Buffer; +export function aes_cbc(key: Buffer|Uint8Array, iv: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): Buffer; +export function aes_cbc_with_ecdh_key(priv_key: Buffer|Uint8Array, iv: Buffer|Uint8Array, bytes: Buffer|Uint8Array, pub_key: Buffer|Uint8Array, label: Buffer|Uint8Array, flags: number): Buffer; export function asset_pak_whitelistproof(online_keys: Buffer|Uint8Array, offline_keys: Buffer|Uint8Array, key_index: number, sub_pubkey: Buffer|Uint8Array, online_priv_key: Buffer|Uint8Array, summed_key: Buffer|Uint8Array): Buffer; export function asset_surjectionproof(output_asset: Buffer|Uint8Array, output_abf: Buffer|Uint8Array, output_generator: Buffer|Uint8Array, bytes: Buffer|Uint8Array, asset: Buffer|Uint8Array, abf: Buffer|Uint8Array, generator: Buffer|Uint8Array): Buffer; export function base58_n_to_bytes(str_in: string, str_len: number, flags: number): Buffer; export function base58_to_bytes(str_in: string, flags: number): Buffer; export function base64_to_bytes(str_in: string, flags: number): Buffer; +export function descriptor_get_key_child_path_str(descriptor: Ref_wally_descriptor, index: number): string; +export function descriptor_get_key_origin_path_str(descriptor: Ref_wally_descriptor, index: number): string; export function descriptor_to_script(descriptor: Ref_wally_descriptor, depth: number, index: number, variant: number, multi_index: number, child_num: number, flags: number): Buffer; export function ec_sig_from_bytes(priv_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, flags: number): Buffer; export function ec_sig_from_bytes_aux(priv_key: Buffer|Uint8Array, bytes: Buffer|Uint8Array, aux_rand: Buffer|Uint8Array, flags: number): Buffer; diff --git a/src/wasm_package/test/bip32.js b/src/wasm_package/test/bip32.js index c31735765..da05e563e 100644 --- a/src/wasm_package/test/bip32.js +++ b/src/wasm_package/test/bip32.js @@ -109,7 +109,7 @@ test('BIP32 derivation', () => { const derivedSerialized = wally.bip32_key_serialize(derivedKey, wally.BIP32_FLAG_KEY_PRIVATE) const initSerialized = wally.bip32_key_serialize(initKey, wally.BIP32_FLAG_KEY_PRIVATE) - assert.deepEqual(initSerialized, derivedSerialized, 'BIP32 initialisation by member failed') + assert.deepEqual(initSerialized, derivedSerialized, 'BIP32 initialization by member failed') wally.bip32_key_free(seedKey) wally.bip32_key_free(derivedKey) diff --git a/tools/build_wrappers.py b/tools/build_wrappers.py index 7aea69e2f..4c57e20fd 100755 --- a/tools/build_wrappers.py +++ b/tools/build_wrappers.py @@ -26,8 +26,6 @@ # The boolean is whether the length function is a maximum length, # True = Yes, False = Exact length MISSING_LEN_FUNCS = { - 'wally_aes': False, - 'wally_aes_cbc': True, # is_upper_bound=true only needed for the case of decryption 'wally_base58_to_bytes': True, 'wally_base58_n_to_bytes': True, 'wally_elements_pegin_contract_script_from_bytes': True, diff --git a/tools/wasm_exports.sh b/tools/wasm_exports.sh index bf18a3edb..3b84e8593 100644 --- a/tools/wasm_exports.sh +++ b/tools/wasm_exports.sh @@ -63,6 +63,10 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_ae_verify' \ ,'_wally_aes' \ ,'_wally_aes_cbc' \ +,'_wally_aes_cbc_get_maximum_length' \ +,'_wally_aes_cbc_with_ecdh_key' \ +,'_wally_aes_cbc_with_ecdh_key_get_maximum_length' \ +,'_wally_aes_len' \ ,'_wally_base58_from_bytes' \ ,'_wally_base58_get_length' \ ,'_wally_base58_n_get_length' \ @@ -81,7 +85,15 @@ EXPORTED_FUNCTIONS="['_malloc','_free','_bip32_key_free' \ ,'_wally_descriptor_get_checksum' \ ,'_wally_descriptor_get_depth' \ ,'_wally_descriptor_get_features' \ +,'_wally_descriptor_get_key' \ +,'_wally_descriptor_get_key_child_path_str' \ +,'_wally_descriptor_get_key_child_path_str_len' \ +,'_wally_descriptor_get_key_features' \ +,'_wally_descriptor_get_key_origin_fingerprint' \ +,'_wally_descriptor_get_key_origin_path_str' \ +,'_wally_descriptor_get_key_origin_path_str_len' \ ,'_wally_descriptor_get_network' \ +,'_wally_descriptor_get_num_keys' \ ,'_wally_descriptor_get_num_paths' \ ,'_wally_descriptor_get_num_variants' \ ,'_wally_descriptor_parse' \