Skip to content

Commit

Permalink
sign tx: allow passing sighash value for each input to be signed
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JamieDriver committed May 16, 2023
1 parent ee45290 commit 6840f0b
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 67 deletions.
21 changes: 13 additions & 8 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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": <bytes>
"script": <bytes>
"path": [2147483697, 2147483648, 2147483648, 0, 34]
"input_tx": <bytes>,
"script": <bytes>,
"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'.
Expand Down Expand Up @@ -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": <bytes>
"script": <bytes>
"input_tx": <bytes>,
"script": <bytes>,
"path": [2147483697, 2147483648, 2147483648, 0, 34],
"sighash": 3,
"ae_host_commitment": <32 bytes>
}
}
Expand Down Expand Up @@ -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": <bytes>,
"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.

Expand Down Expand Up @@ -1701,6 +1705,7 @@ However, in this case the message must include the 'ae_host_commitment'.
"script": <bytes>,
"value_commitment": <33 bytes>,
"path": [2147483697, 2147483648, 2147483648, 0, 34],
"sighash": 2,
"ae_host_commitment": <32 bytes>
}
}
Expand Down
6 changes: 6 additions & 0 deletions jadepy/jade.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions main/process/process_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions main/process/process_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
37 changes: 19 additions & 18 deletions main/process/sign_liquid_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Expand Down Expand Up @@ -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", &params);
if (has_path) {
// Get all common tx-signing input fields which must be present if a path is given
Expand All @@ -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", &params, &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", &params, &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;
Expand Down
25 changes: 16 additions & 9 deletions main/process/sign_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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")) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
33 changes: 17 additions & 16 deletions main/wallet.c
Original file line number Diff line number Diff line change
Expand Up @@ -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_<xxx> 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;
}
Expand Down Expand Up @@ -926,25 +927,25 @@ 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;
}

// 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;
Expand All @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions main/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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_ */
91 changes: 91 additions & 0 deletions test_data/liquid_txn_sighashes.json

Large diffs are not rendered by default.

Loading

0 comments on commit 6840f0b

Please sign in to comment.