From 6840f0b9e15be3ef42c8407c37649cdd36d937e0 Mon Sep 17 00:00:00 2001 From: "Jamie C. Driver" Date: Thu, 23 Jun 2022 17:45:37 +0100 Subject: [PATCH] sign tx: allow passing sighash value for each input to be signed Use the sighash when signing, which defaults to SIGHASH_ALL if not passed. However, add a check which only allows SIGHASH_ALL, so this change is preparatory only and should not actually allow other sighashes to be used at this time. --- docs/index.rst | 21 ++++--- jadepy/jade.py | 6 ++ main/process/process_utils.c | 13 +++++ main/process/process_utils.h | 7 ++- main/process/sign_liquid_tx.c | 37 ++++++------ main/process/sign_tx.c | 25 +++++--- main/wallet.c | 33 ++++++----- main/wallet.h | 8 +-- test_data/liquid_txn_sighashes.json | 91 +++++++++++++++++++++++++++++ test_data/txn_segwit_sighashes.json | 45 ++++++++++++++ test_jade.py | 53 ++++++++++++++--- 11 files changed, 272 insertions(+), 67 deletions(-) create mode 100644 test_data/liquid_txn_sighashes.json create mode 100644 test_data/txn_segwit_sighashes.json diff --git a/docs/index.rst b/docs/index.rst index 452b32d7..331486ff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1189,13 +1189,15 @@ A batch of 'tx_input' messages should be sent to Jade - one for each tx input. "method": "tx_input", "params": { "is_witness": false, - "input_tx": - "script": - "path": [2147483697, 2147483648, 2147483648, 0, 34] + "input_tx": , + "script": , + "path": [2147483697, 2147483648, 2147483648, 0, 34], + "sighash": 2 } } -* 'is_witness', 'script' and 'path' should be omitted if a signature for this input is not required. +* 'is_witness', 'script', 'path' and 'sighash' should be omitted if a signature for this input is not required. +* 'sighash' is optional, and defaults to 1 (SIGHASH_ALL) * If provided, 'script' should be the script-sig/redeem-script required to satisfy the input utxo. * 'input_tx' should be the streamed bytes of the txn which output the utxo being spent. * NOTE: if this is the only input, and 'is_witness' is 'true', the 'input_tx' can (optionally) be replaced with a 'satoshi' element, eg: '"satoshi": 2200000'. @@ -1255,9 +1257,10 @@ However, in this case the message must include the 'ae_host_commitment'. "method": "tx_input", "params": { "is_witness": false, - "input_tx": - "script": + "input_tx": , + "script": , "path": [2147483697, 2147483648, 2147483648, 0, 34], + "sighash": 3, "ae_host_commitment": <32 bytes> } } @@ -1633,11 +1636,12 @@ A batch of 'tx_input' messages should be sent to Jade - one for each tx input, a "is_witness": true, "script": , "value_commitment": <33 bytes>, - "path": [2147483697, 2147483648, 2147483648, 0, 34] + "path": [2147483697, 2147483648, 2147483648, 0, 34], + "sighash": 3 } } -* 'is_witness', 'script' and 'path' are as in sign_tx_legacy_input_request_. +* 'is_witness', 'script', 'path' and 'sighash' are as in sign_tx_legacy_input_request_. * In addition, if a signature is required for this input and 'is_witness' is 'true', then the input utxo 'value_commitment' must be passed. * NOTE: no 'input_tx' is needed. @@ -1701,6 +1705,7 @@ However, in this case the message must include the 'ae_host_commitment'. "script": , "value_commitment": <33 bytes>, "path": [2147483697, 2147483648, 2147483648, 0, 34], + "sighash": 2, "ae_host_commitment": <32 bytes> } } diff --git a/jadepy/jade.py b/jadepy/jade.py index ecfce5fb..7ae0dda6 100644 --- a/jadepy/jade.py +++ b/jadepy/jade.py @@ -1337,6 +1337,9 @@ def sign_liquid_tx(self, network, txn, inputs, commitments, change, use_ae_signa path, [int] - the bip32 path to sign with value_commitment, 33-bytes - The value commitment of ths input + This is optional if signing this input: + sighash, int - The sighash to use, defaults to 0x01 (SIGHASH_ALL) + These are only required for Anti-Exfil signatures: ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures ae_host_entropy, 32-bytes - The host-entropy for Anti-Exfil signatures @@ -1426,6 +1429,9 @@ def sign_tx(self, network, txn, inputs, change, use_ae_signatures=False): script, bytes- the redeem script path, [int] - the bip32 path to sign with + This is optional if signing this input: + sighash, int - The sighash to use, defaults to 0x01 (SIGHASH_ALL) + These are only required for Anti-Exfil signatures: ae_host_commitment, 32-bytes - The host-commitment for Anti-Exfil signatures ae_host_entropy, 32-bytes - The host-entropy for Anti-Exfil signatures diff --git a/main/process/process_utils.c b/main/process/process_utils.c index 80a19e73..e96b0410 100644 --- a/main/process/process_utils.c +++ b/main/process/process_utils.c @@ -284,6 +284,19 @@ bool params_tx_input_signing_data(const bool use_ae_signatures, CborValue* param return false; } + // Get any explicit sighash byte (defaults to SIGHASH_ALL) + if (rpc_has_field_data("sighash", params)) { + size_t sighash = 0; + if (!rpc_get_sizet("sighash", params, &sighash) || sighash > UINT8_MAX) { + *errmsg = "Failed to fetch valid sighash from parameters"; + return false; + } + sig_data->sighash = (uint8_t)sighash; + } else { + // Default to SIGHASH_ALL if not passed + sig_data->sighash = WALLY_SIGHASH_ALL; + } + // If required, read anti-exfil host commitment data if (use_ae_signatures) { rpc_get_bytes_ptr("ae_host_commitment", params, ae_host_commitment, ae_host_commitment_len); diff --git a/main/process/process_utils.h b/main/process/process_utils.h index 4a2ed4ba..a17d4c3f 100644 --- a/main/process/process_utils.h +++ b/main/process/process_utils.h @@ -26,12 +26,13 @@ typedef struct { } commitment_t; typedef struct { + uint8_t sig[EC_SIGNATURE_DER_MAX_LEN + 1]; /* +1 for sighash byte */ uint32_t path[MAX_PATH_LEN]; - size_t path_len; uint8_t signature_hash[SHA256_LEN]; - uint8_t sig[EC_SIGNATURE_DER_MAX_LEN + 1]; /* +1 for sighash byte */ - size_t sig_len; char id[MAXLEN_ID + 1]; + size_t path_len; + size_t sig_len; + uint8_t sighash; } signing_data_t; #define HAS_NO_CURRENT_MESSAGE(process) \ diff --git a/main/process/sign_liquid_tx.c b/main/process/sign_liquid_tx.c index 7953d3f3..1fe13d17 100644 --- a/main/process/sign_liquid_tx.c +++ b/main/process/sign_liquid_tx.c @@ -527,7 +527,9 @@ void sign_liquid_tx_process(void* process_ptr) // green/multisig/other) so we can show a warning to the user if so. script_flavour_t aggregate_inputs_scripts_flavour = SCRIPT_FLAVOUR_NONE; - // Run through each input message and generate a signature for each one + // Run through each input message and generate a signature-hash for each one + // NOTE: atm we only accept 'SIGHASH_ALL' for inputs we are signing + const uint8_t expected_sighash = WALLY_SIGHASH_ALL; for (size_t index = 0; index < num_inputs; ++index) { jade_process_load_in_message(process, true); if (!IS_CURRENT_MESSAGE(process, "tx_input")) { @@ -558,6 +560,7 @@ void sign_liquid_tx_process(void* process_ptr) // Path node can be omitted if we don't want to sign this input // (But if passed must be valid - empty/root path is not allowed for signing) + // Make signature-hash (should have a prevout script in hand) const bool has_path = rpc_has_field_data("path", ¶ms); if (has_path) { // Get all common tx-signing input fields which must be present if a path is given @@ -566,30 +569,28 @@ void sign_liquid_tx_process(void* process_ptr) jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, errmsg, NULL); goto cleanup; } - } - - size_t value_len = 0; - const uint8_t* value_commitment = NULL; - if (has_path && is_witness) { - JADE_LOGD("For segwit input using explicitly passed value_commitment"); - rpc_get_bytes_ptr("value_commitment", ¶ms, &value_commitment, &value_len); - if (value_len != ASSET_COMMITMENT_LEN && value_len != WALLY_TX_ASSET_CT_VALUE_UNBLIND_LEN) { - jade_process_reject_message( - process, CBOR_RPC_BAD_PARAMETERS, "Failed to extract value commitment from parameters", NULL); + // NOTE: atm we only accept 'SIGHASH_ALL' + if (sig_data->sighash != expected_sighash) { + jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, "Unsupported sighash value", NULL); goto cleanup; } - } - // Make signature if given a path (should have a script in hand) - if (has_path) { - JADE_ASSERT(script); - JADE_ASSERT(script_len > 0); - JADE_ASSERT(sig_data->path_len > 0); + size_t value_len = 0; + const uint8_t* value_commitment = NULL; + if (is_witness) { + JADE_LOGD("For segwit input using explicitly passed value_commitment"); + rpc_get_bytes_ptr("value_commitment", ¶ms, &value_commitment, &value_len); + if (value_len != ASSET_COMMITMENT_LEN && value_len != WALLY_TX_ASSET_CT_VALUE_UNBLIND_LEN) { + jade_process_reject_message( + process, CBOR_RPC_BAD_PARAMETERS, "Failed to extract value commitment from parameters", NULL); + goto cleanup; + } + } // Generate hash of this input which we will sign later if (!wallet_get_elements_tx_input_hash(tx, index, is_witness, script, script_len, - value_len == 0 ? NULL : value_commitment, value_len, sig_data->signature_hash, + value_len == 0 ? NULL : value_commitment, value_len, sig_data->sighash, sig_data->signature_hash, sizeof(sig_data->signature_hash))) { jade_process_reject_message(process, CBOR_RPC_INTERNAL_ERROR, "Failed to make tx input hash", NULL); goto cleanup; diff --git a/main/process/sign_tx.c b/main/process/sign_tx.c index bc0fd651..d6c5feec 100644 --- a/main/process/sign_tx.c +++ b/main/process/sign_tx.c @@ -234,8 +234,8 @@ void send_ae_signature_replies(jade_process_t* process, signing_data_t* all_sign // Generate Anti-Exfil signature if (!wallet_sign_tx_input_hash(sig_data->signature_hash, sizeof(sig_data->signature_hash), sig_data->path, - sig_data->path_len, ae_host_entropy, ae_host_entropy_len, sig_data->sig, sizeof(sig_data->sig), - &sig_data->sig_len)) { + sig_data->path_len, sig_data->sighash, ae_host_entropy, ae_host_entropy_len, sig_data->sig, + sizeof(sig_data->sig), &sig_data->sig_len)) { jade_process_reject_message(process, CBOR_RPC_INTERNAL_ERROR, "Failed to sign tx input", NULL); goto cleanup; } @@ -266,7 +266,8 @@ void send_ec_signature_replies( if (sig_data->path_len > 0) { // Generate EC signature if (!wallet_sign_tx_input_hash(sig_data->signature_hash, sizeof(sig_data->signature_hash), sig_data->path, - sig_data->path_len, NULL, 0, sig_data->sig, sizeof(sig_data->sig), &sig_data->sig_len)) { + sig_data->path_len, sig_data->sighash, NULL, 0, sig_data->sig, sizeof(sig_data->sig), + &sig_data->sig_len)) { jade_process_reject_message_with_id(sig_data->id, CBOR_RPC_INTERNAL_ERROR, "Failed to sign tx input", NULL, 0, msgbuf, sizeof(msgbuf), source); goto cleanup; @@ -416,8 +417,11 @@ void sign_tx_process(void* process_ptr) // green/multisig/other) so we can show a warning to the user if so. script_flavour_t aggregate_inputs_scripts_flavour = SCRIPT_FLAVOUR_NONE; - // Run through each input message and generate a signature for each one + // Run through each input message and generate a signature-hash for each one uint64_t input_amount = 0; + + // NOTE: atm we only accept 'SIGHASH_ALL' for inputs we are signing + const uint8_t expected_sighash = WALLY_SIGHASH_ALL; for (size_t index = 0; index < num_inputs; ++index) { jade_process_load_in_message(process, true); if (!IS_CURRENT_MESSAGE(process, "tx_input")) { @@ -459,6 +463,12 @@ void sign_tx_process(void* process_ptr) jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, errmsg, NULL); goto cleanup; } + + // NOTE: atm we only accept 'SIGHASH_ALL' + if (sig_data->sighash != expected_sighash) { + jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, "Unsupported sighash value", NULL); + goto cleanup; + } } // Full input tx can be omitted for transactions with only one single witness @@ -527,12 +537,9 @@ void sign_tx_process(void* process_ptr) // Make signature if given a path (should have a prevout script in hand) if (has_path) { - JADE_ASSERT(script); - JADE_ASSERT(script_len > 0); - JADE_ASSERT(sig_data->path_len > 0); - // Generate hash of this input which we will sign later - if (!wallet_get_tx_input_hash(tx, index, is_witness, script, script_len, input_satoshi, + JADE_ASSERT(sig_data->sighash == WALLY_SIGHASH_ALL); + if (!wallet_get_tx_input_hash(tx, index, is_witness, script, script_len, input_satoshi, sig_data->sighash, sig_data->signature_hash, sizeof(sig_data->signature_hash))) { jade_process_reject_message(process, CBOR_RPC_INTERNAL_ERROR, "Failed to make tx input hash", NULL); goto cleanup; diff --git a/main/wallet.c b/main/wallet.c index d57d9bdc..fdc8880f 100644 --- a/main/wallet.c +++ b/main/wallet.c @@ -880,15 +880,16 @@ bool wallet_get_signer_commitment(const uint8_t* signature_hash, const size_t si } // Function to sign an input hash with a derived key - cannot be the root key, and value must be a sha256 hash. -// If 'ae_host_entropy' is passed it is used to generate an 'anti-exfil' signature, otherwise a standard EC -// signature (ie. using rfc6979) is created. The output signature is returned in DER format, with a SIGHASH_ALL -// postfix. Output buffer size must be EC_SIGNATURE_DER_MAX_LEN. NOTE: the standard EC signature will 'grind-r' to -// produce a 'low-r' signature, the anti-exfil case cannot (as the entropy is provided explicitly). +// If 'ae_host_entropy' is passed it is used to generate an 'anti-exfil' signature, otherwise a standard EC signature +// (ie. using rfc6979) is created. The output signature is returned in DER format, with a SIGHASH_ postfix. +// Output buffer size must be EC_SIGNATURE_DER_MAX_LEN. +// NOTE: the standard EC signature will 'grind-r' to produce a 'low-r' signature, the anti-exfil case +// cannot (as the entropy is provided explicitly). bool wallet_sign_tx_input_hash(const uint8_t* signature_hash, const size_t signature_hash_len, const uint32_t* path, - const size_t path_len, const uint8_t* ae_host_entropy, const size_t ae_host_entropy_len, uint8_t* output, - const size_t output_len, size_t* written) + const size_t path_len, const uint8_t sighash, const uint8_t* ae_host_entropy, const size_t ae_host_entropy_len, + uint8_t* output, const size_t output_len, size_t* written) { - if (!signature_hash || signature_hash_len != SHA256_LEN || !path || path_len == 0 || !output + if (!signature_hash || signature_hash_len != SHA256_LEN || !path || path_len == 0 || sighash == 0 || !output || output_len < EC_SIGNATURE_DER_MAX_LEN + 1 || !written) { return false; } @@ -926,8 +927,8 @@ bool wallet_sign_tx_input_hash(const uint8_t* signature_hash, const size_t signa JADE_WALLY_VERIFY(wally_ec_sig_to_der(signature, sizeof(signature), output, output_len - 1, written)); JADE_ASSERT(*written <= output_len - 1); - // Append the sighash - TODO: make configurable - output[*written] = WALLY_SIGHASH_ALL & 0xff; + // Append the sighash used + output[*written] = sighash; *written += 1; return true; @@ -935,16 +936,16 @@ bool wallet_sign_tx_input_hash(const uint8_t* signature_hash, const size_t signa // Function to fetch a hash for a transaction input - output buffer should be of size SHA256_LEN bool wallet_get_tx_input_hash(struct wally_tx* tx, const size_t index, const bool is_witness, const uint8_t* script, - const size_t script_len, const uint64_t satoshi, uint8_t* output, const size_t output_len) + const size_t script_len, const uint64_t satoshi, const uint8_t sighash, uint8_t* output, const size_t output_len) { - if (!tx || !script || script_len == 0 || !output || output_len != SHA256_LEN) { + if (!tx || !script || script_len == 0 || sighash == 0 || !output || output_len != SHA256_LEN) { return false; } // Generate the btc signature hash to sign const size_t hash_flags = is_witness ? WALLY_TX_FLAG_USE_WITNESS : 0; const int wret = wally_tx_get_btc_signature_hash( - tx, index, script, script_len, satoshi, WALLY_SIGHASH_ALL, hash_flags, output, output_len); + tx, index, script, script_len, satoshi, sighash, hash_flags, output, output_len); if (wret != WALLY_OK) { JADE_LOGE("Failed to get btc signature hash, error %d", wret); return false; @@ -954,17 +955,17 @@ bool wallet_get_tx_input_hash(struct wally_tx* tx, const size_t index, const boo // Function to fetch a hash for an elements input - output buffer should be of size SHA256_LEN bool wallet_get_elements_tx_input_hash(struct wally_tx* tx, const size_t index, const bool is_witness, - const uint8_t* script, const size_t script_len, const uint8_t* satoshi, const size_t satoshi_len, uint8_t* output, - const size_t output_len) + const uint8_t* script, const size_t script_len, const uint8_t* satoshi, const size_t satoshi_len, + const uint8_t sighash, uint8_t* output, const size_t output_len) { - if (!tx || !script || script_len == 0 || !output || output_len != SHA256_LEN) { + if (!tx || !script || script_len == 0 || sighash == 0 || !output || output_len != SHA256_LEN) { return false; } // Generate the elements signature hash to sign const size_t hash_flags = is_witness ? WALLY_TX_FLAG_USE_WITNESS : 0; const int wret = wally_tx_get_elements_signature_hash( - tx, index, script, script_len, satoshi, satoshi_len, WALLY_SIGHASH_ALL, hash_flags, output, output_len); + tx, index, script, script_len, satoshi, satoshi_len, sighash, hash_flags, output, output_len); if (wret != WALLY_OK) { JADE_LOGE("Failed to get elements signature hash, error %d", wret); return false; diff --git a/main/wallet.h b/main/wallet.h index 645db2f2..17f2aa29 100644 --- a/main/wallet.h +++ b/main/wallet.h @@ -71,12 +71,12 @@ bool wallet_sign_message_hash(const uint8_t* signature_hash, size_t signature_ha size_t* written); bool wallet_get_tx_input_hash(struct wally_tx* tx, size_t index, bool is_witness, const uint8_t* script, - size_t script_len, uint64_t satoshi, uint8_t* output, size_t output_len); + size_t script_len, uint64_t satoshi, uint8_t sighash, uint8_t* output, size_t output_len); bool wallet_get_signer_commitment(const uint8_t* signature_hash, size_t signature_hash_len, const uint32_t* path, size_t path_len, const uint8_t* commitment, size_t commitment_len, uint8_t* output, size_t output_len); bool wallet_sign_tx_input_hash(const uint8_t* signature_hash, size_t signature_hash_len, const uint32_t* path, - size_t path_len, const uint8_t* ae_host_entropy, size_t ae_host_entropy_len, uint8_t* output, size_t output_len, - size_t* written); + size_t path_len, uint8_t sighash, const uint8_t* ae_host_entropy, size_t ae_host_entropy_len, uint8_t* output, + size_t output_len, size_t* written); bool wallet_hmac_with_master_key(const uint8_t* data, size_t data_len, uint8_t* output, size_t output_len); bool wallet_get_public_blinding_key(const uint8_t* master_blinding_key, size_t master_blinding_key_len, @@ -88,6 +88,6 @@ bool wallet_get_blinding_factor(const uint8_t* master_blinding_key, size_t maste const uint8_t* hash_prevouts, size_t hash_len, size_t output_index, BlindingFactorType_t type, uint8_t* output, size_t output_len); bool wallet_get_elements_tx_input_hash(struct wally_tx* tx, size_t index, bool is_witness, const uint8_t* script, - size_t script_len, const uint8_t* satoshi, size_t satoshi_len, uint8_t* output, size_t output_len); + size_t script_len, const uint8_t* satoshi, size_t satoshi_len, uint8_t sighash, uint8_t* output, size_t output_len); #endif /* WALLET_H_ */ diff --git a/test_data/liquid_txn_sighashes.json b/test_data/liquid_txn_sighashes.json new file mode 100644 index 00000000..ab46d5e3 --- /dev/null +++ b/test_data/liquid_txn_sighashes.json @@ -0,0 +1,91 @@ +{ + "input": { + "network": "testnet-liquid", + "use_ae_signatures": true, + "txn": "020000000103c8767dcad247e81530f9945024bd9e8e2795371e5ff74678ad2767fde526936300000000230000000000000000000000000000000000000000000000000000000000000000000000feffffff6a1ac734265516e15b8fa3e57f7131d01b7186394d3d8d73fe09fce1b02c331502000000230000000000000000000000000000000000000000000000000000000000000000000000fefffffff73ab12bb0b003d65f71976607541ba4cf621eb29615ec5c442e6882879170b402000000230000000000000000000000000000000000000000000000000000000000000000000000feffffff030ac8980afb5d2248250b3d6356ad051a5e64754aa1f98276578049a60d075622c0091faaa20b8c26ed7ff5ab75b4292828e9af90fcddd59c16a6f5850d34aa13a9df03a79484d4bbc169b343fd61c77a1ec9a5d01c3d79385cc4bcf06f782eaec6544f17a9148cd7a15b827c33558d8767c4a9be80712ed805d5870b6b56af3674bcaf17a82664abb43d0a6d2af51330064a4f3536a0dd5463982e350872c92a71bb5f449eb00c8d96d6ab3d1e4407a2b0bcf177713d963f8f2c97e8d7025a0cdd85ddf906f0ec2c1a3903a22dd4bde6567346951523e3032b1fec3a558c17a9143e2d8dd880fbee277b6b619b513bb2cd8704b9688701499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c1401000000000000011e00002475040000000400480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051748c632102b21244f7c1977902cac6ccefc329d775c9551aad1ae2f094fdf8c09d92ea232bad6703ffff00b275682103a7c1c7a7d7bea899f2dbab5798334b4bc5830b51001b5f3129363f7296bc740cac0000000400480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051748c632103bc0e4e6495a0558ff7583f5b728e064799561723698f482514f6fe4fa9db8946ad6703ffff00b275682103366c9ea6d38b7881f12270cf43aaeef46d177474d100e96e34d1ea8c706d78d1ac0000000400480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051748c6321029a01f0db4d7dea13cb32ef58dfc0830bb86e861f682bbe25dc2e2deada3977f6ad6703ffff00b275682102dbcce235e7eaee6c9fc4b6079a63407c6bcf77632ab20a2fb1fa92c8cfe4bb24ac0083030007d9e6c4c2ad57902fe08c8d0361b9a7a6786f6ffed3ee872ce15f45db30985b9f81a1629fafc85bdf4345df2fdfc048e0b1e725eb2652d855881f60b26d5ca4b635ce3861ab1e791b80e921ad88d94482c2a1ac93c1747f81ff781f7137f998e599a7bc005a4afda0b9ae1d31baf84aa9b0a8037b58ffc3ad793c8b2c61d1b13efd4e10603300000000000000010f28d800b13c59b59d8272b5770d5c9c02b0e861f9ee1244692a7eff21856e26d13dd208aca73b21667c04cae6f12576d125fb357b99d604967bf334a79bd89ed38e119f9872ac27fe3e65534ecff8fb5eb3e108e7ba229285bbbbb18d4f4a6ea6b504549ed2fe3fccffe02da8a1c3cf0e9f9573afccb661062d57dd687a395316106ae9e2f331d8beb9126a0c3ae5d8d2ca176b6c25ff2608885606d18542e5ba6edc4441d5b750e044f2bbb7f1b0149aed59a33d4000ad1d1b2432fa1a635802ee13b7da09f596eac87bc532d6132fe31fb1c3719579a4b9407da6d22dfa55ffd790f3424508d4e452b1f532d7bae1a3237793059680de079f5e4b7010c8976105d23a03eb5361c87e5b6aaa14623d9345a2c8d9dbc495f5272128060425cda295aa0435ddf9c0355ddaca03e8bb08c338cad006b075ddf7215be1cac9df20ae98807f9375c3c84259d80c0a54c03d5efcf802ea9360770d12d5d0c02defa9600fe619f919fce1365e288e46743f45a3d5e75bb3c93d866ef3db54bf1c56e77b66361f0b10375aa13e82d8f59f84d1ccd8817cba5488c62595608f90e9abe263142fc8497f75cb9e7533f041399ec08798525fe71a28b58611ebe1e331c30789ef9749c8bfff6c0e563a742cc481c4ad8b9b01eba47e017a980b8f91771c78f280bdec2c221af525c976462e731b21a54e4e6e2b3ba96d0338331d62d397a75e66613cafb6b7aa5b35d306f9b686c2bb27719cecb4421e6a7d6a5acac3e889b09a8aec11ab21b0dec8e788c9946c1facbedf4335e3de0da408ef15d2c1a073295ab5de88c5f65a2f68cb18758a6d31344c484daf6a5b6ef7da5786a0b5a176ad88da67a6c435c7c28b9c4107fbfaf3469d8fb10917658a0772d38372aa924bf7b39cf25d1d103cecf0fb70aab4d8883f8dd0b1a2fdcc45682536d6f414d64e0655d50ef4234669ed447380059b3bb4ea9432babd08c1c77c574a05722cc40f867cb57fe3b6657e666f4965582aa067880f1efb7d70a200aaf80c6a5a722ec2c56a5919ae3722ace78904a1b763ca6d81d473a4d887bbec38c8e335a05aada54da30b9b615e0392ad75ae80a92a61725f7c1b8d7544473f7ee161ddfb940c720d2d31e2ec820cd4d0335e894f64d89ab510d4a1e4fe79d106cc539c4faf889ec48f4cc9e6b888f70612de33842a088be7106d0a5f08efd24efda340ec1e8454e5d8ae8bc32ad2f9e0be60f747cf7004591495df131f31fc6d7a27f8f746d118995046bdb88d8a51bc4776a1642a2ec38821181a486b32edf19f6ac11b839747408c14a767a12f33c7b99b97c27990280e20931a4eac60e34cd0a643cf80cf065e3174a5d39e7d95cb5597e0ae112b470575a35c799b653d3ea7b381a78778453c6b99319cb2e4924ec06dfcbe856cb19b43b04e4ea66bb7c3d31a763980f9250cad10e5d83c7c38124e38e45d431b6e47003ef8c8520c206cde43c125d18c49e9d850f29ed0718129d1b095e1e6593f3013beb67d57f7c1e86ebb8ef144e7e366d163de6faf4e6978cd7233693e9ea8aac528c15361c43106d6175b94fba3f08176b22821b0ca56cf96313aed960e4fe70d5c6a5e863c17afe3648ecbc488db57e5d6e7e509e264e2b9c794d1476747c170ac5e7b664fbd98ca26ad1868329801a737daf71ae289432a7bd283caebc0ef18d1d3c76f9de0b50832af847176f0cbc695798d5efa1ab0c93adc0714dcfad7cfa8e6116ce8ca3ad938ff0a6a7987cae9e08bf378ce48fd1719ba37471d421a6707ce6fb4646b3e3d81e8cd0e72b1147b69454af1b9f9fb575ab99753c7bcd3e5860fd3658b769d1ed32b13560fcff46eac9c69b2eb963f43a888dbfd81580e41a34ad43b95be9b5146f7ed53f66bb4c0fe7232776dfbd59ae9ccd031ba8d4f1d86ff49ba5ac49acd809abc5855bbe39814f9dae8c5fa7071c02eaba071d1a7e4938ee91023e1b3ac1b698cfbcf74e5486018cd4521664af49f58ef30bcac48e0f19f19672c04f7dbe10299b4ea0fa52ccb3caf0eab33cd706a9c20b96abb74428d9cec6729e2148b9c6de2ad246e0346e33886d3cf721fbb969cb6cc971706b077d4f851c57202848c6b423b70a719fab4f55f0705f0b926e65c67118d0fe20b76bb8351a9b39af15b64b9f1fe9a4d4e27cc189dec806f8eff7474d703a5f33c705461e04c43f9a58e2f99f226a0e5dccee65536cfed9f2368b896cd02a88048df388c97645bd88cbb5e6a94f6a83d4bc7c53439be21ffb606aa0ff27e26274c13d8f03f0714b6b953293f29471905925304f5cdccd04fce6ccf4db2c7db2231fbe2cf72a47ba02ff511b1da40d2ef8de65baa7f455df09846234418cd6fb9fdd30d8550bb334e655ca4e6be6401a0f36fda98107f247f5b9f4c94da5a2b7d1788cdcc30498874a12a1ee7d16725318f8fdbb344b15189a735216b766c341bc0ecf0b3bbb1da369ebf8a4bb679e17ce25882c7291ee93ea90ab15079ab7c869792af0d7d49f0d76cef0a41eecafc256d464e8bc9a1edcd5245ae544fdf15243a43ba9f8fa68ef485213e4b27c5aab5e05744bdf444f7ca47aaef77bbb119bd37406d0dca4dfad43912ae152e668deff8828181c01fe29ee6b7ddf66560b27c97dfb77dde30c658aa4cc70de85f2022f0ba0afb36e05a9a76cb484dfcc49d79428eca286a857667629d6bc379844b5738a84d02340865720f408dba372be5c40476ac39c4279f84e4494c78e854ef23d097153165bb7489e35b376f4c0ddeb5d3decfc4a0713178a199b04f8f3aecb2b5bf4edd18422a3ee3d0cd5ffdf9097c5d4cd6a27222f9b04b25e65aecfb9f1ec5f70e1af06823e965622750cc5dbdbc39c754a5a01f59d10e2856849df58cd852a936aa15a4309c59dbb7a7bdf1c3ad83c44ed74feec3844993638cce4c0228cb5246f65ac7ff12f1e1838d6724b73bfe5d3ed6ee10de17dcb4dd8f3ebd5c6204c894d9bb580a18258a1c8d6a0bf7aca0a1f08547e828a7bf1ec6a3dff5388ba730ec3930b94384b10eddaeadbf57692e99608734e4f7ebabe5cc72bd543299174dd673c8ae70037f71bdd134e43761832b6181009ae097bced2fe873fcdf0a21e6b77a40885fcdfdce68a199808d6a5f00d2ab1a075cc7c558d7d9b79e4ac728dda300ff759f4317dbad322b815dd18e7ceed788b27c8812affa43f9ac3299627d88cecf82c95776fbdf8faa67e2a88f6b935af99e1304d28bc82d6d390a3776e07a2c8d37d3c1359712ff71d661e8068e42d430441dbc4177bea3b082970cb17678507dcb7d3039997c28445705a5a1d056d7910c720a632f3b4300da95022e7923543c28b827fc4c458dc3ba8c3787d55468df46a0e941d765f3715c19985ff5c90ae1b4fe4aeabda29701c04e40c270bded1043a9f9ed05860d881fabf913b672bfe258ebb5fff66c20d4c9c9f165bb2ec31538a826e90adfd19d9cf889ea717417cb3339dea4177ef7f9a185569b42eb862f8ea00246f99bd6f356cc629060e33772d8c001f009cd4dd868f07d6fab9a5809b5d9e90ceff792afdd3c196db4afafcaa2abdfec051e349a0815a230259ad63ca32bbc4e3d7a0926ee4a8c5089b8fedd68c2674a1288195f50a9399dffe0ed70ad1e36ed25ee59ffefa96778424c5f2325745e95f712838d7d9e47df1f7f6e89b9e0b22c916b036a9ad87bb60b573eb6494df41321b0058785e530c64291ee666906daf74f1bf4f13e7977acfc370ffe00da5570116e2635891d446d56d6a6a035f6da9d9bcfd4c515abf8e8eee0b71d51a98a9c1db06e446d56bf61b00cc9705e0c4a37a411794dca6eec2cabff426f562a9637338d3428914a4ca793e3623abddc7371a06619e9c38fc3308e93e671e336801a2d672abcf80be2a3914fbbabccb26e850465e1f23ada25780eb86fc8ec6ea9cca2369f97adbad4441315ec1608632addd71f8c4008837095074dcad6ca6c3e75a5c7c283b5f48e8d17303ec6229f3781d1e99cfd07538721b2e6eaba2f0ace11373d798e87fc8c8d6f0856eec726cb3bb5a04715f2d5707b5756be5527f6f8bf74eacd9e3296386ba2bb3937fcfda46cb26ed40f80c27f06cdc6500ca8b5b4bc3acd447abb85230c0c7db2df6832b2742e324034d02c99bdab4126d97efc8eb43377737349c183549b6877637666ff77d0f1961a4eccfafffdeb1b1f7fb525c675f0dc9b47b1b5c4346ddf794b95650f55cd4d9149c48cf89d5840a3c2605eeff364c5afe2a07e4a4d71d23223e200e828db11ec93797efcab888a49acb0cf817958b268bed86e0acd2570551e39fd09df0c583cdb32fdca6a74da81340794e5a6d85c32c980633a7a7c3e827fc332e6fbcfb97777c20eb4288263b2423216accfeb1cb72b9408399c254e7d62d46c22d902551b882d9b17386620046b73d05fee8215c58946c90408221997b2be9cb7d3e1533b5807bf896586cc3e576a6a4ed8feda2e6570feff9715bb4f6f18cc7e45a4186ba7731dfcde6ceab9126eb0315d6b3a0ac6a2b7a761f30f7d9a0cdd78657e8ae1f18a3a1c46238f49b2cc20c72242ec58ce7dd62999919dd44e958b9fb6c5ee3885b02b1b805337c8ffe8306cb885bf03b179e9a6be29414994bf6d8bd3657a169f9ff51036c2b5e4def28b59df786a1269308ceae676d3a2da86c5e94665b7070fb9301b65a872f1b044617b5c32e5928cc4fb781cf2388137ee4db7ad63b625d54d88b041455e4e58c043f54daef62eb0cb87f4b26156ac7fd39ee1160380a8631fee747105392f79c15062aeda7795d791981e1f907a4a524b9057e06c4b363a4233623834cb5bf47a2518f099fc93ae5698ceba8c2b22b8baaa7b019f6c098c6c2c11c1395b1135b2b5953bf2fa8d0f4dbba9cc69db5b8debde4cf1b2bda1191981f7f30817dbf5bcef2a342275f8cc429b30ea2aba7f4732cdae9501db21d5fa414ddf922f27de6912d30455111faf46f8014ab9267b1db85da2857783064fb89369c14dde22c51729285f58a682fabcb21cc7417dfe391f282173d2ff0240add510e5426bb0fff2988d6dc07b19d93221f16d6786bcbf1d9fb93e33b66a0a2eb10b5911c8f9694c9a1dd8473d1a636211a5758cd4874592ab060ffb374bf908afc5c273b2643f766b248471b299d6cc17d5038dcf23b40339b4e4b3fcfd7da3223db64f019a780e9997cda0f99983a209d8048529bf1d30615e70c8267ab348a3d37ea76fea26405235fe35953f71819162cbd7ca8a59c1fefd1af40f1ada499712224b015c8d21bbf148e615ede6f80a6723da0fd269a09e15763bded0ef9a6b0d06d2f9ff7e5861f558be362eee41066989e719484203e77694ed76dbd574af990e341982b3183d98c1b069b74d4b2d9d90883c40d1a3dc2301dab83851fdfa185984aaa9e711ed864d0e2f912c75b416851dbbd56946a9fcd8b78679314968082d8beb27aa640b4d40dc653d57563a8a2f67d4282665f87aa994da4034f453ad829181ec5f5ea4ca7f3a289830471ddb94012a431ca7e46ff780e6d879a213b4e7db394b1cd2ee5358af4f5c678e58d22b493d10aad868e7903113877d7cb57789c11db48daa3438e012bb4c8f6260a2fbef927ce28ff4f81404312e6cc584e43027bd69f7b6c4cb88468c583fff83065232b31a4281062a06594363f2fb62fc11cb4b751cb55543fba8209f2aa525b22372125c6879f801644c59bf1ad4e7b48db90bf86aaba0fab2a0ca62e0c34eb394cbc68b718b7d2051b3d8dfd5ac86e18ade9765c162b15f90d0477d7346a1e229ba57f35778b4f9c9fa10475da5cf336d5e7f5538450b1b3f1d994f332af71dcdd550549fc12d9d928967f67305224740598303000787da84e821a1e09335ad70fc80c2808108789d0501cebd8c781d738d34953c6da3e00461ef9f519df450be3c0e5b6d5856a8010e068811be4f85eaebfd474fe787e53b8069a4d1d972ec28f92091140f98c8f34ca13b939f0c2a939db02f83d0cec6bd28f52cf118291e45d087ec4b9a239160a760ec1a37800967fd24f4c129fd4e1060330000000000000001b036ca01c55cf827cdfdd9cee877de72ea3306645bd612669f6762aca64f09f1db8dd00daa8078eea749f66ff06303511990dd4b19fef429e2be1cd5692b79fc7408947d8a9bd424b3c6616dc0de1d3e6268b7d4eb40ff9d4d0462a991c9a7cca484d9b68d1acacc13d3e790e64fb342c266d765bca71c4274eabe1903f3660036a57a73f4b9db647f65f013aa8e79abe14a57f1c443477b68034a636c5b0a592218a27584d885787c4e07a4cefaae17c9180032a74366d6a6c12505e11ea9ee826a84bd48afd6fcd26ecf3baf3f4f4f129b0d685f5bc1d9eaced68fe71cdc80598df73e2f4843807fd39bc0abdcb7f2d5f34ceb47d18620b541c77bdbe15ef20617e8809aa4758e060b5e8aed3d8ea2e4e8c68ae87d0509a54fe1f43ad96e5f30081dc2aa9f22a2f45f26888fed0dddf6945509454f7e16f72f528ac895d1214f9dfcb0eb1847534a6ef104178d3360a2ee845b7fa015d14487286732e8384562ba5c159fe3284fab81b0e803623a5c52cc9a56c0c19f2d29d5148af0b999a6410ab8ea9745b4d91206d3da87f8cdb296ab131ceab6c39cc1e76767e647a2dda910f4616873e384cddbaebd3c85209b03c102ba10c7a48b174e1459866457d604f05233a2d46563ecb1e6265d6f89937c75858d2810fc37b7e264a55fb15786d5f0e0972b88c665565c55330fb4d9c01aa00264309353ce865a3b54cf0e5737eb7cbd3e032bc38626ca87591d926996b73220dd36b41f4dbd950852e499a21e1ee7c6648d130ed24abde945d1b39bced5c16e94dc8e7f9b05305298f3a7dbc99721c99696916382096a2488350fd9f3c05dc83c3cb23dd8bc430dc2a2df3921b30474d3fa5a38f517fe68b9707d8efe60407a7d2c05f2ad887b2225cbb1b8c7a27a219ea0444da78055c9227742a33880b2a66db1ab4083f615e929322013f2e968537eac510826fa243a82159016c0308bdbf9b62dcc895f8a92bda4c8232196a0dccc8bbbf20218cf176f69befe3feaac70d9b0d3b66ca5d22b731ec79c1cffec2dfde9a4a1f563506fa1c948ad0b89aa626bca7caac0e09ff93785c46efd0dfda1a54be75de2472948c5c071a47d092cceb0354ef8eb7d6ed1857bbf05d356a024280e0752ff01b76b95e17eca6ca8f2f144c1ffcccd07268b9c3ac1aa133100ebb086c1f67e18abcd0849b28fb82e4fed51821f49fee4d98fba44658d077f5a71083e2b18368fd94098a95a4ce5fd158595e57e1a4388382a2575be6dd46da1d5bf06e1972d953cc12d72d95cde2881354fad42ce5b8f54f7c8d8bef0dbcf4a734fae96c8c6e010cf6c110a2402cd2e3fe91740ef4b4a27aa19ccb038dded5f6f5c52fbae62956febf126d6c88f1fa153802f0369f4fa82ed99be17713a6ff61c08cfa163d7908e77ad2e0c394a88c48c64a353ddd0cde8f457316fa3881b223a2ae319e44733a502f6aea9db144f514df0296847ec95be646bab9df6fcb7f4c476627dfc53fadaf1b7ed13d4dc8d2941cbd984fdb93b3e733ca52c25411ba23bb7c937763f9ed95d2e844942342a9df698e0eaa3d43fc945d31f283978b3a288d75ac1e030101aa47f4824879c6355a0524958df5f4b8b051b9b93c362ee634d372858b868269fbc096707c34b9fdef4c9a505f6f761d9ab9623500d8fbbdf1b6b56feda5e94a778c8c39095a1bbd7a187f0ea15c7f9e9d51c97f52b873617b2566e26d14af3854e24f816a81c4e4fb83e99f4ff070bda9d1da7690ddcb157a7dfb0c92a8f1ae145e5983f4e7e39fd62431cd43bc243b7dd7d7ae8f9607886f21643d88add120c6e17a401e429345e8d665330b3c4900514bc590282532a56c65e98a3d20af8c2ef3290550f4fbc5fa61330b85be5001a238b84c07d6085756c62eca2fc35e72be75eb132d4f86c64e7745858f4bcfcb066d21efa45c23e46589bdcea4e9c7457e70a058cb2a12d4615cd35125c9cbe92fc802fc9719566ab8e916c1d453a35196ffce99bd40f9dce5cd19b022dbaae4e582b99150aeb5e07f25b8aa9db74d7ad5cffdd44de59c17ccc36c8f8913941386bcd0b0736404cfb66b7dab6e56f0231d1dc23460e3f7ab6fb740199b7fd7650898cd9d0f7ed02c7b9d820063dafd4d0664bc7746eed1b87f0a4299afe61074133969af7878d05ae2a62642dca7c08d89b14020edd8cd85e6f6eaf732202ac0dab05ae79e96b846625dc58756481c684704da47aa129efc4ddb51c114a484245b8980dce8c54c90fe6a92866342597919a09641a6b52f22cb55730296a9a14e1ae0db777a7813069185923ae923f665231ff7d5b1e81f2ff5b097641f42e494127c364f4443fbea7b788b70416f69a832829b3afcc8f776adc63844b30eb01c0f4d1211b3c2b3951240ca31c30899185afbfea28b7e1b474cbc893cf9c4e44543bf621bdf64b5a5a5083e225910db1876b672f2e3d0371cc7ae8f3d2fcfcf20cb16cc6971304a3f6f939025baeb6577265afd11e0c49a3e72c32c3d9570edcf6d9438d95da5727f89d393b1baf1d62b42ac827fb62cf379bd7429de1a15a5db42bf3b44126b5198f04b32c61adcec56b19f0c7b911d027f5368dcf0d205a813b25692aced49a7c794b96e2d8634d53875c8d0cd1874a0bc6a62f0e45f6a41eef3b9901564940c9fa0f7ebfb9a0f60ce75ef86f6529557acb4eb7cec8429f9cf2c0fd9ee9df9dc61ff8c3080dd85da96c53605af9bb8ddc933147163d997c63dab3a5367d1a30bf1484402ced70fe1549379b06a6e6538e4c6211e2130554bf9454fc15c30162519516d42067542fb21f526afd28989dd8ea2d27f99fbdda1bdabf8c3456adf115e0d7500589aca404eda064d56025e87abb9b32616b68ab2b33a30cbab88c4b2fdc7e44276cf0ba73c219b01520418affb756c7b2e05f43a2ca1079e8b55106f1a6d8092758459a0b31ddae8b26517c15aab0174d06b4b99f627cb4823e8e9d88b845c040447aba5653590a6b026cb0542c1bc5c97377f414ebb4edc559ac94a72fa97c81b6089b767bd52a77d27fac7f5f75f22be348a9a9e130ff454d9711b5f249ba65ea71dfd3f7e0f133d361053af923b06a382d05da3325e33dc9a9912e6343a1e81a7f34b6f8ad5b4deb93e656d04841bb7952cd352e26bc31dac40247a0deb0e059a1498afc8b020409d5ef90c0cb19e8317683a1318b58b4eb0fc855b27751d33ebd33b711445e8d113c6fa0f0b7826356703645dda62bca3a426f08fa56f5604027d6ad8356f85d1ef2457c2cab4dd69b36fe4ad95a29991efb05bec055a0b95dbcdc1ac44c676e681a7950f64c0713ff5c378389dc01ca0b03fcc4757f16c1663d37c046231aed562ede50fd8e3614d0c1c5e8232c05dbaa064b5850cccb8b9437ff1913338f0928a39bd8df928364b120327d89d6693ae82752ebb34b7637c372b2da460361c48634eb183dafefe82ec6d9550cd3108e4a39d49954b17a171566ef7ac1b5530a616854294357683dc55b917cc6a5c30e30dcd35d19fce7c108224d23d7c4f9a004e6a3971b3d3bd01ac384b687d6493407b1b7854ecaf21e17e9dcb64a0bf908b7bc315586b9e2c3ea69ed60aa31c45e64bf0b7d8c182eb42c719c34b9239ec8e7cf43e351ea96ed78ab19a94583bd49bb58ea4319a3046f8fbe883dd1241dca17e3ceda747be9664f343f88f908ab7d422dcd267004f20785a6840759da759576f5b0c57e407ee448cf3f1953b809036aa2e26b590d97016e1f6a516b3a6b1b06051bee15c22d1fa479b68557df61aaf5b2d669432ca7cbdb66176d3d3ab5f49eb5c813bd65636144869f7d9daadd37022c7ccb7115107b42b3d67147f0196167bec39303dd14b667e5edbfa4bab91cd95a924346fb3b2272773b64c006e3e80676c77b6897e11d7b740ac10ef44b10c0dba376b477c077ab2922f40f93432b01d0e61d4e5f976f2d84865b4d57ffc54cf34156b81fd7478ec2f9fb857c7fd026630dadfb893807cd8c6ba6f0de009d3f1f26af9ca177fc8a7ec4e0de875893443d22468f4942a251e2a652f50bf2887d1c867f0295e08ff76f6881213242239e965805689535f27aa61c98ed5c8eca3e587c13a3b5881e89077ea4cf6bfa445d4ec294f8bb5f2ce8e8d9111c1d68e41a493bb335bade400116b05f3abde06a85e424337dbea3a1a9f7be84a4078d2d107418df202614b27499d51c438a9ccb1a1f6da86fc7ba5aee7f05a235cbda39657a2a7950797112ffe0e8bb4a59be52f6057dec947e15a0149f35becb3ca41967950fef100a9a15e276d4659007973674f9d17e578ffa5b362545629eaa4979edc7f53198263ab2ac862b31c6e8e3df4ec8df0f845c65848cfaf25b99aa57eeae81acd77ebc1a863558ea8930290773ad8043fd97d28c0cbb6a9dca760d0c8533cebfdab21d67dd3758f02f1abb38160e4a2d303ff45b0d4d49e934b3f07f0db9723506b5af214ffce549f5cb9017ce49dd865a38a643e751cf1730de32722c511a37acd82b8c860b3effa8cf1ad930c2da244f200dd7f439cd440d966fbe8df122b53b9e150d143e953b7df5d9f5a6451017733e6d7676e0c2a8cdb095891a1015b0ae40afbf2c652b3c777217fb2688b5b276c38c627847070d382d52ac67b560e4778915238f6fcfb2ff6f233fef6ab344a4c2b9e4b4774e4e406490768e41147ae8adfc2ca8545a6d3e3eb1b52e3705bca3fed0739c8b2a489f8f6b68a82fdad75050914445ae5bcb1ad7fbd849808ce532ec37256daa91224aa5aa2f297f6479f8918458ab026479b64cd68c8a04066809e1adc30389988e386a65bd9023e076166e156e3e22febde0c54925459c3425e8e098d5771f76408a2c49f10aca6170090bca2748c4b46d6f8548031d941116c3c3c58cc579bd97290d57408b028941089b746366e19053fbe01e3c548c69239338d3c33660843625c90ab66c8790b60a7882e0d751fd3fdf29477d30dd1b4403b7a72e47271fe8e06d72c79bde1171108ce198f720931562d3edafcaa5f7fc329357f23ef5bfddc72af0e5bed387c5206bf16349bdbac5c21b21ab56b000c53bbe85263197da695cc7bf660b6304141342310ac9052dc8042a0cc0a3d824ff527ee6a18054106c2c370cc969ad0b63635b88edab245d4e8f964c3734f28ea4259f514f1295cb8d47adb6d6dc839e3df26902f363a19e906889a081f916a82adbb06faa295616eada227ba20bb5e583bc204570079c629d34be03925cadd9f4e065230dc649ad03e46bfe284e88e24b80877a88d0e4c0696e96e35473abbeb32329d406feb2c9ce77ed6199a168a724d66e5e4811c20535234ae1d2051d6d120c05d2887a65b8ee39899bd663f80074888e42f28b029a4e9a28b1902e251442cb6e81ac641cd801c322b479906c0af626672f48b396e976c7bc8e9ed929e19458fd57f9aa28cca078696f8372e23b6fb83f854275574b00ca54054086ffe14355c5dc60a0096d6162fd6806b98e30e8980f0e85960e462882f5a0c8ad381e6fa386d892e0a212a1c5e080a7fd23d04fae98fca335693490a1dbc8cb8668d44da17c05216cef320192bf4010fe13f9d0cb9ca7f011d8e4ddcfd2bfd2803fbd44445f2e3b2c8a2f94c0c8d8cf4a331f151fd31b05226da0abfeed1290cb2b6210a93cdd7e71f3227a667acfe636f73b3631a55f9cb84aa8208198cda80bbabed696592de31af051754935635c414f869190be6568994be323f01031b1046d95db5478bed8c42d0c53c25efd23f0350218d73a3d3320e64002cbf519c642d32625c53d3d66a8ef1ebbc46565b2edae7f4310e68f5fc1b4b12d427d3c6c61bf688f3db56ca715310451ef578be9d9f195e101dc4e3c0000", + "trusted_commitments": [ + { + "abf": "2c1e9e3dfdff0a3227b5cdb23742b36f156413917c783502ed47d24817958d5c", + "vbf": "05f05c9bbb102f2a99ef44992681781191994a7210ead0f6f614f30455d4ba1a", + "asset_generator": "0bbe55a1efea784192fceb18df102d6f41a57f498d2251fac18160463e4f5a7bea", + "value_commitment": "08a29dcfc292b5fcfb45db3c2f4f1dbeceb6ab42605b7c1f45bf376c99ee0134e0", + "asset_id": "144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49", + "value": 295000, + "blinding_key": "037a84a985081fe25368c61d28aa8b49122272434f311762f6f8a7e783b6712285" + }, + { + "abf": "d3a87ebe7fca0904e335575b60a12ea5adee6e95ce760a3e14c6ecfbfa8f6312", + "vbf": "168ecf22bbf05dcad2e05d0b6c17aeec9162f5ac9f51987b0d177e107b90af25", + "asset_generator": "0a63a82eb46accdbc9e9f8da60e3e95a36ddad0a0a98b20249601138f62dd98453", + "value_commitment": "084c4989aaf5ed52b013028c9d16a997b074fbe1eef1bbdcfa7e6c8f007de10676", + "asset_id": "144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49", + "value": 2853, + "blinding_key": "0342d623f1ebeab5cf5f438fc28b47bfcf67c68210cfd0d008700ff3f875613606" + }, + null + ], + "change": [ + null, + { + "path": [ + 1, + 64 + ], + "csv_blocks": 65535 + }, + null + ], + "inputs": [ + { + "is_witness": true, + "path": [ + 1, + 46 + ], + "sighash": 1, + "value_commitment": "089dee47c534d93b0009ccb62362722f5c007b26a9f85e993d53fd2c5ff9221f0a", + "script": "748c632102b21244f7c1977902cac6ccefc329d775c9551aad1ae2f094fdf8c09d92ea232bad6703ffff00b275682103a7c1c7a7d7bea899f2dbab5798334b4bc5830b51001b5f3129363f7296bc740cac", + "ae_host_commitment": "32bb83b662e67a56e0d379be1dc5b62f641ac43d995658040a50743d17249704", + "ae_host_entropy": "5ecebacebc3c1108c0a88771d811fe28702fa6974eaf31dac43b28d24c8ddd3c" + }, + { + "is_witness": true, + "path": [ + 1, + 55 + ], + "sighash": 1, + "value_commitment": "08c892737f40587d6183fcf552aea15e9c42ab35bf7fb23d4e4f889eb2a9c559a8", + "script": "748c632103bc0e4e6495a0558ff7583f5b728e064799561723698f482514f6fe4fa9db8946ad6703ffff00b275682103366c9ea6d38b7881f12270cf43aaeef46d177474d100e96e34d1ea8c706d78d1ac", + "ae_host_commitment": "da9a1b339273cd56478c6564b053d8462f3771d3839ea338ad1d6af9035ed7ae", + "ae_host_entropy": "c6c447e6815fdaa4da7d27b0caa95f324b4e9773d130d0671629f0f16ba9bcca" + }, + { + "is_witness": true, + "path": [ + 1, + 60 + ], + "sighash": 1, + "value_commitment": "0896d40015241987ea988d9a24059b6c2b73a157a49235ead58590aa49beca9971", + "script": "748c6321029a01f0db4d7dea13cb32ef58dfc0830bb86e861f682bbe25dc2e2deada3977f6ad6703ffff00b275682102dbcce235e7eaee6c9fc4b6079a63407c6bcf77632ab20a2fb1fa92c8cfe4bb24ac", + "ae_host_commitment": "68ef120e8262397a94abd25559869c0b8099cf7febe9a8567662537a6d489596", + "ae_host_entropy": "a7733dfb563f32d713b14d9b2739d7175d92a1298a3a3318f8f3853544739dfe" + } + ] + }, + "expected_output": [ + [ + "022775e851e09ea799747212b013c430f75da486e630b51a1476dbff9d00d737f9", + "304402204a6f793b21cdb695f0900ddae5b5cb3871bc73c46f248714912381c73175224802202004f075397726de1ee4a5d27728768ed16505bf7c77266eadd502290a86816601" + ], + [ + "034c0d42b377220560e310e696649f0bc708781210d0926c85e7ed99490ff57b58", + "3045022100ca0eec59badd57eba800294b2bd437f9f6b73b3503243562adf067ffb09df4be02201c8ae500ded4243e45f6642fea0d8bb78c3cbbff4ed74d121e4ba1f029cd1e9b01" + ], + [ + "032a535a20626521c8270a4b462b0cd9b583cba1c333f559e85e6f3a4bec9b5c11", + "304402207e62298e051b2c7e9c941f17c96c6c0007ee99f345773fcf2522584d7fbd3df6022024130096ee741c410152dc3bc242b0e83f63d1bfa2726f033170e84658a5411401" + ] + ] +} diff --git a/test_data/txn_segwit_sighashes.json b/test_data/txn_segwit_sighashes.json new file mode 100644 index 00000000..fec6ae14 --- /dev/null +++ b/test_data/txn_segwit_sighashes.json @@ -0,0 +1,45 @@ +{ + "input": { + "network": "testnet", + "txn": "02000000000102540f2df1493cf20666cb3964f5581cae9b95160adc250b89db2cd839eadba85e00000000230000000000000000000000000000000000000000000000000000000000000000000000fdffffffa3c5dbe4ccb0a749a9d54d049173f0b9f22c9d918b22f88c20d3556cce3813b900000000230000000000000000000000000000000000000000000000000000000000000000000000fdffffff0280d1f0080000000017a9144d68baaa27406a98626fea4339b64ffdd15136b4878672d21a0000000017a9143f873fac1310396e0f271d8f08da5d0b531b3b91870400480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047522102f22c0fea3e893e63c27e32e8021298b336a275945ccc1d67af4c389042807fb921028de5082354ecc9e329d4326bba41199a1e32f2476e84c7c60d45bad28e8b79bd52ae0400480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047522102f22c0fea3e893e63c27e32e8021298b336a275945ccc1d67af4c389042807fb921028de5082354ecc9e329d4326bba41199a1e32f2476e84c7c60d45bad28e8b79bd52ae64090000", + "change": [ + null, + { + "path": [ + 1, + 31 + ], + "recovery_xpub": null, + "csv_blocks": "0" + } + ], + "inputs": [ + { + "is_witness": true, + "satoshi": null, + "input_tx": "020000000001038917245731167f35fe4d3b5e8de684b578451ce6ea57ef25a3ac9d751f3ba9dc0000000017160014baf4130cab76f5887d8113f3e82e803b4fba5f1ffeffffff5ab37616dcbfba7c2a611d3f868eb756fb30cd2431a5149121051ba3c690b35601000000171600148afc9190718e82294acc2eb4aa8832d0f80eefbbfeffffff7208f6bd23de84008ad5a02fbe575df26c1c3fec1610166c559aaf9aad95c6620000000017160014bffdb4f425808ce8c8a44bdc30e3c1d2f0d48e52feffffff0200e1f5050000000017a914a0952e622e510e0480c449c4e8aa97bc7256c24187927a2f000000000017a9144eafd7c34953967e56528c1a3fe4f7ce2ed2308c870247304402203b914cef4c1006de6328d7fd9bc84af8f344efe826b9ac3c4bd617b8fa01487202200b1d9472584b2ee25ee4bc41af3206d7f504fce0fc1dc91800239e061143e1e90121022885162a256c45c19adcaa482ad5e4b0494d9cf5a3b40fd7ef76ac95bc3679bf0247304402200312889d4755757484b51a34d89f5cf70bc4ca2be5f28f3118fd4cdfe85dbc620220363b8206c16a281aee0845f37525df4e6ee3bbb1d15bda377d859172b86644cb012102d320db1bc04b5734308e11eafb9487bb8593f61d5c8f2918431ffb2c4af39c890247304402201127826b159fc7ba357486a264d4d99a453319301ffbcf5a5a0e426078a95cf802205198c7f71c8ede9adad481ed8dd1840468fc41f2a7cea95d9bc135ed7af6ae04012103685040421f42854bd6d59c7bf2909d038f96733ad3ea6018e237c6694f635cd2ee040000", + "script": "522102f22c0fea3e893e63c27e32e8021298b336a275945ccc1d67af4c389042807fb921028de5082354ecc9e329d4326bba41199a1e32f2476e84c7c60d45bad28e8b79bd52ae", + "path": [ + 1, + 1 + ], + "sighash": 1 + }, + { + "is_witness": true, + "satoshi": {}, + "input_tx": "0200000000010529ae8189746c2de4cc4e12446811596376d3f1dc8f7942aa8fea7cae64a9cb670100000017160014ddd266dda17d51ed3ed8aab8a7f75396810a0448feffffffff873298e0ab4a1861338087590fa55025ec5153c966f49d9a836e3abb16ab130000000017160014c4c574e7fa2bda439bc29c233566e1da0e6ce42efefffffffeaa8f865a899eb619f6ebcbc9e09f046678aceb74b51f1b03481255236febfb000000001716001472a2ec7fcc0d70f9e5b83aa368c617dbe062381efeffffffeee21337a13faf08e0671def1dee25af4d98691b7930ad4f3679a7eb40201ae70000000017160014ba68aea365bf729e15e43cff7d9ee43e4b716bcafeffffff2716e3c4c454a8970150b6b953877ec74ae141958277e816183ca9b3195b7a1a0000000017160014d27236517fadad475a7d541c837755050a87eaebfeffffff020065cd1d0000000017a9144d68baaa27406a98626fea4339b64ffdd15136b48795bb0f000000000017a9143820e7036c87ac90262ffeabf7607530bf2b70798702473044022045c82d111be51060a7c2fcb4fcb57e1263443e3fd7ad89c078fc2fea927c4633022078bcb077c01e7b356fcfd6093dc070d0e4ec4d6170e673e627061b999c777b5c01210308fc66a20497f42719f91f6f8bf5aa3ba1523283ac8920fe0ed7d794270d1d5202473044022015dec8fd6789499c3623fb2737934f2b0896f3465a7df8af1c9802784a2c85f102202f0e835616768f46c2ce6325b73ee8044cc39792a7a80fbe5f10147a802e257c01210208595579436bc619fe6575ceaf0e50fc8ca10d9cebf676b29da84953b037d0ff024730440220645d9f1382bff8def0603db90f3ec6bf065a88bc2f245886b7e285b3759507e002202c781ba2480493efcc2139329a0f35c8bab0078888694584ab740f6e4d23df630121023d9b3be9249d1db225b762f07226c7c05ae80f878597e272fef8f98df8e9fc9e0247304402205c9c11906d264b7ac4bfadc3683a524f739d953c909d6a5dc4242073fcf103ad02205d9f0e400343d60a7cf9862502a35271da383faaf2aa08d68c04d48fee82ebaf012103a4d4903a6995967bfad100520cfb5d7385fa46cc66e884bf7b07f72e0c78b3e60247304402200687cf4940a1b695e27fa4feeb3fc3c478dd12d82fa7a2fc41523b1e7ec78f3a02204ceaefde0197700eb6400d1e6ce5e378adece6f45ee51d10b0abddcf2eec6ce3012102e17daebdcc62ab971c8843c299ed87f9471752217e950143489759eeaefdeabd64090000", + "script": "522102f22c0fea3e893e63c27e32e8021298b336a275945ccc1d67af4c389042807fb921028de5082354ecc9e329d4326bba41199a1e32f2476e84c7c60d45bad28e8b79bd52ae", + "path": [ + 1, + 1 + ], + "sighash": 1 + } + ] + }, + "expected_output": [ + "304402205440973ce839f380b37cd33975d080d1aeb1094ffe2a80d424f49eac263a299702202a7db8611b46aece8621fa94dd8f69a7fe054d141960557896373ddf514b54c301", + "304402201e3a08a0129c16a5e35423009cf0244ae9b7feaa1ec82f4c304d73d60592646702201e1b3879aeb86a6dc7e88e558cd20e32a2cd8360ca97bfbf6c1dd000344f859b01" + ] +} diff --git a/test_jade.py b/test_jade.py index 772d3671..384b392c 100644 --- a/test_jade.py +++ b/test_jade.py @@ -1295,11 +1295,24 @@ def test_bad_params(jade): {'is_witness': False, 'path': [0], 'satoshi': 9, 'script': h2b('AB')}), 'extract input_tx'), (('badinput9', 'tx_input', - {'is_witness': False, 'input_tx': ''}), 'extract input_tx'), + {'is_witness': False, 'input_tx': None}), 'extract input_tx'), (('badinput10', 'tx_input', {'is_witness': False, 'input_tx': 'notbin'}), 'extract input_tx'), - (('badinput11', 'tx_input', # odd number of hex chars - {'is_witness': False, 'input_tx': 'abc'}), 'extract input_tx')] + (('badinput11', 'tx_input', + {'is_witness': True, 'path': [0], 'satoshi': 12345, + 'script': TEST_SCRIPT, 'sighash': 'SIGHASH_ALL'}), 'fetch valid sighash'), + (('badinput12', 'tx_input', + {'is_witness': True, 'path': [0], 'satoshi': 12345, + 'script': TEST_SCRIPT, 'sighash': h2b('02')}), 'fetch valid sighash'), + (('badinput13', 'tx_input', + {'is_witness': True, 'path': [0], 'satoshi': 12345, + 'script': TEST_SCRIPT, 'sighash': 300}), 'fetch valid sighash'), + (('badinput14', 'tx_input', + {'is_witness': True, 'path': [0], 'satoshi': 12345, + 'script': TEST_SCRIPT, 'sighash': 0}), 'Unsupported sighash value'), + (('badinput15', 'tx_input', + {'is_witness': True, 'path': [0], 'satoshi': 12345, + 'script': TEST_SCRIPT, 'sighash': 2}), 'Unsupported sighash value')] # Test all the simple cases for badmsg, errormsg in bad_params: @@ -1612,12 +1625,32 @@ def _commitsValueBlindProof(val): (('badliqin5', 'tx_input', {'is_witness': True, 'path': [0], 'script': h2b('abcd12')}), 'extract value commitment'), - (('badliqin5', 'tx_input', + (('badliqin6', 'tx_input', {'is_witness': True, 'path': [0], 'script': h2b('abcd12'), 'value commitment': 15200}), 'extract value commitment'), - (('badliqin5', 'tx_input', + (('badliqin7', 'tx_input', + {'is_witness': True, 'path': [0], 'script': h2b('abcd12'), + 'value commitment': 'notbin'}), 'extract value commitment'), + (('badliqin8', 'tx_input', + {'is_witness': True, 'path': [0], 'script': h2b('abcd12'), + 'value commitment': GOOD_COMMITMENT, 'sighash': 'SIGHASH_ALL'}), + 'fetch valid sighash'), + (('badliqin9', 'tx_input', + {'is_witness': True, 'path': [0], 'script': h2b('abcd12'), + 'value commitment': GOOD_COMMITMENT, 'sighash': h2b('03')}), + 'fetch valid sighash'), + (('badliqin10', 'tx_input', + {'is_witness': True, 'path': [0], 'script': h2b('abcd12'), + 'value commitment': GOOD_COMMITMENT, 'sighash': 300}), + 'fetch valid sighash'), + (('badliqin11', 'tx_input', + {'is_witness': True, 'path': [0], 'script': h2b('abcd12'), + 'value commitment': GOOD_COMMITMENT, 'sighash': 0}), + 'Unsupported sighash value'), + (('badliqin12', 'tx_input', {'is_witness': True, 'path': [0], 'script': h2b('abcd12'), - 'value commitment': 'notbin'}), 'extract value commitment')] + 'value commitment': GOOD_COMMITMENT, 'sighash': 2}), + 'Unsupported sighash value')] # Some bad commitment data is detected immediately... esp if it is # missing or not syntactically valid, unparseable etc. @@ -2116,13 +2149,14 @@ def _check_tx_signatures(jadeapi, testcase, rslt): # Verify signature (if we signed this input) if len(signature): inputdata = test_input['inputs'][i] + sighash = inputdata.get('sighash', wally.WALLY_SIGHASH_ALL) # Get the signature message hash (ie. the hash value that was signed) tx_flags = wally.WALLY_TX_FLAG_USE_WITNESS if inputdata['is_witness'] else 0 if is_liquid: msghash = wally.tx_get_elements_signature_hash( txn, i, inputdata['script'], inputdata.get('value_commitment'), - wally.WALLY_SIGHASH_ALL, tx_flags) + sighash, tx_flags) else: if inputdata.get('input_tx'): # Get satoshi amount from input tx if we have one @@ -2136,9 +2170,10 @@ def _check_tx_signatures(jadeapi, testcase, rslt): satoshi = inputdata['satoshi'] msghash = wally.tx_get_btc_signature_hash( - txn, i, inputdata['script'], satoshi, wally.WALLY_SIGHASH_ALL, tx_flags) + txn, i, inputdata['script'], satoshi, sighash, tx_flags) - # Verify signature! + # Check trailing sighash byte and verify signature! + assert int.from_bytes(signature[-1:], 'little') == sighash rawsig = wally.ec_sig_from_der(signature[:-1]) # truncate sighash byte host_entropy = inputdata.get('ae_host_entropy') if use_ae_signatures else None _verify_signature(jadeapi, network, msghash, inputdata['path'],