mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 06:48:16 +00:00
feat(legacy): Support native SegWit external inputs with non-ownership proof.
This commit is contained in:
parent
fa2d618f7d
commit
ec9756cabd
1
legacy/firmware/.changelog.d/2718.added
Normal file
1
legacy/firmware/.changelog.d/2718.added
Normal file
@ -0,0 +1 @@
|
||||
Support native SegWit external inputs with non-ownership proof.
|
@ -150,6 +150,9 @@ bool fsm_checkCoinPath(const CoinInfo *coin, InputScriptType script_type,
|
||||
uint32_t address_n_count, const uint32_t *address_n,
|
||||
bool has_multisig, bool show_warning);
|
||||
|
||||
bool fsm_getOwnershipId(uint8_t *script_pubkey, size_t script_pubkey_size,
|
||||
uint8_t ownership_id[32]);
|
||||
|
||||
void fsm_abortWorkflows(void);
|
||||
|
||||
#endif
|
||||
|
@ -528,27 +528,37 @@ static bool formatFeeRate(uint64_t fee, uint64_t tx_weight, char *output,
|
||||
}
|
||||
|
||||
void layoutConfirmTx(const CoinInfo *coin, AmountUnit amount_unit,
|
||||
uint64_t total_in, uint64_t total_out, uint64_t change_out,
|
||||
uint64_t total_in, uint64_t external_in,
|
||||
uint64_t total_out, uint64_t change_out,
|
||||
uint64_t tx_weight) {
|
||||
char str_out[32] = {0};
|
||||
formatAmountDifference(coin, amount_unit, total_in, change_out, str_out,
|
||||
sizeof(str_out));
|
||||
|
||||
char str_fee[32] = {0};
|
||||
formatAmountDifference(coin, amount_unit, total_in, total_out, str_fee,
|
||||
sizeof(str_fee));
|
||||
if (external_in == 0) {
|
||||
char str_fee[32] = {0};
|
||||
formatAmountDifference(coin, amount_unit, total_in, total_out, str_fee,
|
||||
sizeof(str_fee));
|
||||
|
||||
char str_fee_rate[32] = {0};
|
||||
bool show_fee_rate = total_in >= total_out;
|
||||
char str_fee_rate[32] = {0};
|
||||
bool show_fee_rate = total_in >= total_out;
|
||||
|
||||
if (show_fee_rate) {
|
||||
formatFeeRate(total_in - total_out, tx_weight, str_fee_rate,
|
||||
sizeof(str_fee_rate), coin->has_segwit);
|
||||
if (show_fee_rate) {
|
||||
formatFeeRate(total_in - total_out, tx_weight, str_fee_rate,
|
||||
sizeof(str_fee_rate), coin->has_segwit);
|
||||
}
|
||||
|
||||
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
||||
_("Confirm sending:"), str_out, _("including fee:"),
|
||||
str_fee, show_fee_rate ? str_fee_rate : NULL, NULL);
|
||||
} else {
|
||||
char str_spend[32] = {0};
|
||||
formatAmountDifference(coin, amount_unit, total_in - external_in,
|
||||
change_out, str_spend, sizeof(str_spend));
|
||||
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
||||
_("You are contributing:"), str_spend,
|
||||
_("to the total amount:"), str_out, NULL, NULL);
|
||||
}
|
||||
|
||||
layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), NULL,
|
||||
_("Confirm sending:"), str_out, _("including fee:"),
|
||||
str_fee, show_fee_rate ? str_fee_rate : NULL, NULL);
|
||||
}
|
||||
|
||||
void layoutConfirmReplacement(const char *description, uint8_t txid[32]) {
|
||||
|
@ -57,7 +57,8 @@ void layoutConfirmOutput(const CoinInfo *coin, AmountUnit amount_unit,
|
||||
void layoutConfirmOmni(const uint8_t *data, uint32_t size);
|
||||
void layoutConfirmOpReturn(const uint8_t *data, uint32_t size);
|
||||
void layoutConfirmTx(const CoinInfo *coin, AmountUnit amount_unit,
|
||||
uint64_t total_in, uint64_t total_out, uint64_t change_out,
|
||||
uint64_t total_in, uint64_t external_in,
|
||||
uint64_t total_out, uint64_t change_out,
|
||||
uint64_t tx_weight);
|
||||
void layoutConfirmReplacement(const char *description, uint8_t txid[32]);
|
||||
void layoutConfirmModifyOutput(const CoinInfo *coin, AmountUnit amount_unit,
|
||||
|
@ -72,8 +72,9 @@ enum {
|
||||
} signing_stage;
|
||||
static bool foreign_address_confirmed; // indicates that user approved warning
|
||||
static bool taproot_only; // indicates whether all internal inputs are Taproot
|
||||
static uint32_t idx1; // The index of the input or output in the current tx
|
||||
// which is being processed, signed or serialized.
|
||||
static bool has_unverified_external_input;
|
||||
static uint32_t idx1; // The index of the input or output in the current tx
|
||||
// which is being processed, signed or serialized.
|
||||
static uint32_t idx2; // The index of the input or output in the original tx
|
||||
// (Phase 1), in the previous tx (Phase 2) or in the
|
||||
// current tx when computing the legacy digest (Phase 2).
|
||||
@ -98,8 +99,9 @@ static uint8_t sig[64]; // Used in Phase 1 to store signature of original tx
|
||||
#if !BITCOIN_ONLY
|
||||
static uint8_t decred_hash_prefix[32];
|
||||
#endif
|
||||
static uint64_t total_in, total_out, change_out;
|
||||
static uint64_t orig_total_in, orig_total_out, orig_change_out;
|
||||
static uint64_t total_in, external_in, total_out, change_out;
|
||||
static uint64_t orig_total_in, orig_external_in, orig_total_out,
|
||||
orig_change_out;
|
||||
static uint32_t progress, progress_step, progress_meta_step;
|
||||
static uint32_t tx_weight;
|
||||
|
||||
@ -362,14 +364,6 @@ static bool is_external_input(uint32_t i) {
|
||||
return external_inputs[i / 32] & (1 << (i % 32));
|
||||
}
|
||||
|
||||
static bool has_external_input(void) {
|
||||
uint32_t sum = 0;
|
||||
for (size_t i = 0; i < sizeof(external_inputs) / sizeof(uint32_t); ++i) {
|
||||
sum |= external_inputs[i];
|
||||
}
|
||||
return sum != 0;
|
||||
}
|
||||
|
||||
void send_req_1_input(void) {
|
||||
signing_stage = STAGE_REQUEST_1_INPUT;
|
||||
resp.has_request_type = true;
|
||||
@ -1100,13 +1094,16 @@ void signing_init(const SignTx *msg, const CoinInfo *_coin,
|
||||
|
||||
foreign_address_confirmed = false;
|
||||
taproot_only = true;
|
||||
has_unverified_external_input = false;
|
||||
signatures = 0;
|
||||
idx1 = 0;
|
||||
total_in = 0;
|
||||
external_in = 0;
|
||||
total_out = 0;
|
||||
change_out = 0;
|
||||
change_count = 0;
|
||||
orig_total_in = 0;
|
||||
orig_external_in = 0;
|
||||
orig_total_out = 0;
|
||||
orig_change_out = 0;
|
||||
memzero(external_inputs, sizeof(external_inputs));
|
||||
@ -1175,6 +1172,13 @@ static bool signing_validate_input(const TxInputType *txinput) {
|
||||
signing_abort();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (txinput->has_ownership_proof) {
|
||||
fsm_sendFailure(FailureType_Failure_DataError,
|
||||
_("Ownership proof provided but not expected."));
|
||||
signing_abort();
|
||||
return false;
|
||||
}
|
||||
} else if (txinput->script_type == InputScriptType_EXTERNAL) {
|
||||
if (txinput->address_n_count != 0) {
|
||||
fsm_sendFailure(FailureType_Failure_DataError,
|
||||
@ -1214,6 +1218,13 @@ static bool signing_validate_input(const TxInputType *txinput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (txinput->has_commitment_data && !txinput->has_ownership_proof) {
|
||||
fsm_sendFailure(FailureType_Failure_DataError,
|
||||
_("commitment_data field provided but not expected."));
|
||||
signing_abort();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (txinput->has_orig_hash) {
|
||||
if (!txinput->has_orig_index) {
|
||||
fsm_sendFailure(FailureType_Failure_DataError,
|
||||
@ -1892,7 +1903,7 @@ static bool signing_add_orig_output(TxOutputType *orig_output) {
|
||||
}
|
||||
|
||||
static bool signing_confirm_tx(void) {
|
||||
if (has_external_input()) {
|
||||
if (has_unverified_external_input) {
|
||||
layoutConfirmUnverifiedExternalInputs();
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
@ -1952,12 +1963,23 @@ static bool signing_confirm_tx(void) {
|
||||
}
|
||||
uint64_t orig_fee = orig_total_in - orig_total_out;
|
||||
|
||||
// Reject adding external inputs to the original transaction, so that we
|
||||
// don't have to deal with the UI implications. This could be used for
|
||||
// BIP-78 Payjoins when we support presigned external inputs.
|
||||
if (external_in != orig_external_in) {
|
||||
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||
_("Adding external inputs is not supported."));
|
||||
signing_abort();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanity check. Replacement transactions are only allowed to make
|
||||
// amendments which do not increase the amount that we are spending on
|
||||
// external outputs. Additional funds can only go towards the fee, which is
|
||||
// confirmed by the user. The check may fail if the replacement transaction
|
||||
// starts mixing accounts and breaks change-output identification.
|
||||
if (total_out - change_out > orig_total_out - orig_change_out) {
|
||||
if (total_out - change_out - external_in >
|
||||
orig_total_out - orig_change_out - orig_external_in) {
|
||||
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||
_("Invalid replacement transaction."));
|
||||
signing_abort();
|
||||
@ -2000,8 +2022,8 @@ static bool signing_confirm_tx(void) {
|
||||
}
|
||||
|
||||
// last confirmation
|
||||
layoutConfirmTx(coin, amount_unit, total_in, total_out, change_out,
|
||||
tx_weight);
|
||||
layoutConfirmTx(coin, amount_unit, total_in, external_in, total_out,
|
||||
change_out, tx_weight);
|
||||
if (!protectButton(ButtonRequestType_ButtonRequest_SignTx, false)) {
|
||||
fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
|
||||
signing_abort();
|
||||
@ -2824,11 +2846,39 @@ void signing_txack(TransactionType *tx) {
|
||||
to.is_segwit = true;
|
||||
#endif
|
||||
} else if (tx->inputs[0].script_type == InputScriptType_EXTERNAL) {
|
||||
if (config_getSafetyCheckLevel() == SafetyCheckLevel_Strict) {
|
||||
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||
_("External inputs not allowed."));
|
||||
signing_abort();
|
||||
return;
|
||||
if (tx->inputs[0].has_ownership_proof) {
|
||||
uint8_t ownership_id[OWNERSHIP_ID_SIZE] = {0};
|
||||
if (!fsm_getOwnershipId(tx->inputs[0].script_pubkey.bytes,
|
||||
tx->inputs[0].script_pubkey.size,
|
||||
ownership_id)) {
|
||||
signing_abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tx_input_verify_nonownership(coin, tx->inputs, ownership_id)) {
|
||||
fsm_sendFailure(FailureType_Failure_DataError,
|
||||
_("Invalid external input."));
|
||||
signing_abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!add_amount(&external_in, tx->inputs[0].amount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tx->inputs[0].has_orig_hash) {
|
||||
if (!add_amount(&orig_external_in, tx->inputs[0].amount)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
has_unverified_external_input = true;
|
||||
if (config_getSafetyCheckLevel() == SafetyCheckLevel_Strict) {
|
||||
fsm_sendFailure(FailureType_Failure_ProcessError,
|
||||
_("Unverifiable external input."));
|
||||
signing_abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
set_external_input(idx1);
|
||||
} else {
|
||||
|
@ -1310,3 +1310,147 @@ bool get_ownership_proof(const CoinInfo *coin, InputScriptType script_type,
|
||||
out->ownership_proof.size = r;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tx_input_verify_nonownership(
|
||||
const CoinInfo *coin, const TxInputType *txinput,
|
||||
const uint8_t ownership_id[OWNERSHIP_ID_SIZE]) {
|
||||
size_t r = 0;
|
||||
// Check versionMagic.
|
||||
if (txinput->ownership_proof.size < r + sizeof(SLIP19_VERSION_MAGIC) ||
|
||||
memcmp(txinput->ownership_proof.bytes + r, SLIP19_VERSION_MAGIC,
|
||||
sizeof(SLIP19_VERSION_MAGIC)) != 0) {
|
||||
return false;
|
||||
}
|
||||
r += sizeof(SLIP19_VERSION_MAGIC);
|
||||
|
||||
// Skip flags.
|
||||
r += 1;
|
||||
|
||||
// Ensure that there is only one ownership ID.
|
||||
if (txinput->ownership_proof.size < r + 1 ||
|
||||
txinput->ownership_proof.bytes[r] != 1) {
|
||||
return false;
|
||||
}
|
||||
r += 1;
|
||||
|
||||
// Ensure that the ownership ID is not ours.
|
||||
if (txinput->ownership_proof.size < r + OWNERSHIP_ID_SIZE ||
|
||||
memcmp(txinput->ownership_proof.bytes + r, ownership_id,
|
||||
OWNERSHIP_ID_SIZE) == 0) {
|
||||
return false;
|
||||
}
|
||||
r += OWNERSHIP_ID_SIZE;
|
||||
|
||||
// Compute the ownership proof digest.
|
||||
Hasher hasher = {0};
|
||||
hasher_InitParam(&hasher, HASHER_SHA2, NULL, 0);
|
||||
hasher_Update(&hasher, txinput->ownership_proof.bytes, r);
|
||||
tx_script_hash(&hasher, txinput->script_pubkey.size,
|
||||
txinput->script_pubkey.bytes);
|
||||
tx_script_hash(&hasher, txinput->commitment_data.size,
|
||||
txinput->commitment_data.bytes);
|
||||
uint8_t digest[SHA256_DIGEST_LENGTH] = {0};
|
||||
hasher_Final(&hasher, digest);
|
||||
|
||||
// Ensure that there is no scriptSig, since we only support native SegWit
|
||||
// ownership proofs.
|
||||
if (txinput->ownership_proof.size < r + 1 ||
|
||||
txinput->ownership_proof.bytes[r] != 0) {
|
||||
return false;
|
||||
}
|
||||
r += 1;
|
||||
|
||||
if (txinput->script_pubkey.size == 22 &&
|
||||
memcmp(txinput->script_pubkey.bytes, "\x00\x14", 2) == 0) {
|
||||
// SegWit v0 (probably P2WPKH)
|
||||
const uint8_t *pubkey_hash = txinput->script_pubkey.bytes + 2;
|
||||
|
||||
// Ensure that there are two stack items.
|
||||
if (txinput->ownership_proof.size < r + 1 ||
|
||||
txinput->ownership_proof.bytes[r] != 2) {
|
||||
return false;
|
||||
}
|
||||
r += 1;
|
||||
|
||||
// Read the signature.
|
||||
if (txinput->ownership_proof.size < r + 1) {
|
||||
return false;
|
||||
}
|
||||
size_t signature_size = txinput->ownership_proof.bytes[r];
|
||||
r += 1;
|
||||
|
||||
uint8_t signature[64] = {0};
|
||||
if (txinput->ownership_proof.size < r + signature_size ||
|
||||
ecdsa_sig_from_der(txinput->ownership_proof.bytes + r,
|
||||
signature_size - 1, signature) != 0) {
|
||||
return false;
|
||||
}
|
||||
r += signature_size;
|
||||
|
||||
// Read the public key.
|
||||
if (txinput->ownership_proof.size < r + 34 ||
|
||||
txinput->ownership_proof.bytes[r] != 33) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t *public_key = txinput->ownership_proof.bytes + r + 1;
|
||||
r += 34;
|
||||
|
||||
// Check the public key matches the scriptPubKey.
|
||||
uint8_t expected_pubkey_hash[20] = {0};
|
||||
ecdsa_get_pubkeyhash(public_key, coin->curve->hasher_pubkey,
|
||||
expected_pubkey_hash);
|
||||
if (memcmp(pubkey_hash, expected_pubkey_hash,
|
||||
sizeof(expected_pubkey_hash)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that we have read the entire ownership proof.
|
||||
if (r != txinput->ownership_proof.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_SECP256K1_ZKP_ECDSA
|
||||
if (coin->curve->params == &secp256k1) {
|
||||
if (zkp_ecdsa_verify_digest(coin->curve->params, public_key, signature,
|
||||
digest) != 0) {
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
if (ecdsa_verify_digest(coin->curve->params, public_key, signature,
|
||||
digest) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (txinput->script_pubkey.size == 34 &&
|
||||
memcmp(txinput->script_pubkey.bytes, "\x51\x20", 2) == 0) {
|
||||
// SegWit v1 (P2TR)
|
||||
const uint8_t *output_public_key = txinput->script_pubkey.bytes + 2;
|
||||
|
||||
// Ensure that there is one stack item consisting of 64 bytes.
|
||||
if (txinput->ownership_proof.size < r + 2 ||
|
||||
memcmp(txinput->ownership_proof.bytes + r, "\x01\x40", 2) != 0) {
|
||||
return false;
|
||||
}
|
||||
r += 2;
|
||||
|
||||
// Read the signature.
|
||||
const uint8_t *signature = txinput->ownership_proof.bytes + r;
|
||||
r += 64;
|
||||
|
||||
// Ensure that we have read the entire ownership proof.
|
||||
if (r != txinput->ownership_proof.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (zkp_bip340_verify_digest(output_public_key, signature, digest) != 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Unsupported script type.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -152,5 +152,8 @@ bool get_ownership_proof(const CoinInfo *coin, InputScriptType script_type,
|
||||
size_t script_pubkey_size,
|
||||
const uint8_t *commitment_data,
|
||||
size_t commitment_data_size, OwnershipProof *out);
|
||||
bool tx_input_verify_nonownership(
|
||||
const CoinInfo *coin, const TxInputType *txinput,
|
||||
const uint8_t ownership_id[OWNERSHIP_ID_SIZE]);
|
||||
|
||||
#endif
|
||||
|
@ -577,7 +577,6 @@ def test_p2wpkh_in_p2sh_with_proof(client: Client):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip_t1
|
||||
def test_p2wpkh_with_proof(client: Client):
|
||||
inp1 = messages.TxInputType(
|
||||
# seed "alcohol woman abuse must during monitor noble actual mixed trade anger aisle"
|
||||
@ -611,16 +610,18 @@ def test_p2wpkh_with_proof(client: Client):
|
||||
)
|
||||
|
||||
with client:
|
||||
t1 = client.features.model == "1"
|
||||
tt = client.features.model == "T"
|
||||
client.set_expected_responses(
|
||||
[
|
||||
request_input(0),
|
||||
request_input(1),
|
||||
request_output(0),
|
||||
messages.ButtonRequest(code=B.ConfirmOutput),
|
||||
messages.ButtonRequest(code=B.ConfirmOutput),
|
||||
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
|
||||
request_output(1),
|
||||
messages.ButtonRequest(code=B.ConfirmOutput),
|
||||
messages.ButtonRequest(code=B.ConfirmOutput),
|
||||
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
|
||||
messages.ButtonRequest(code=B.SignTx),
|
||||
request_input(0),
|
||||
request_meta(TXHASH_e5b7e2),
|
||||
@ -636,6 +637,7 @@ def test_p2wpkh_with_proof(client: Client):
|
||||
request_input(1),
|
||||
request_output(0),
|
||||
request_output(1),
|
||||
(t1, request_input(0)),
|
||||
request_input(1),
|
||||
request_finished(),
|
||||
]
|
||||
@ -656,7 +658,7 @@ def test_p2wpkh_with_proof(client: Client):
|
||||
|
||||
# Test corrupted ownership proof.
|
||||
inp1.ownership_proof[10] ^= 1
|
||||
with pytest.raises(TrezorFailure, match="Invalid signature"):
|
||||
with pytest.raises(TrezorFailure, match="Invalid signature|Invalid external input"):
|
||||
btc.sign_tx(
|
||||
client,
|
||||
"Testnet",
|
||||
@ -666,7 +668,6 @@ def test_p2wpkh_with_proof(client: Client):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip_t1
|
||||
@pytest.mark.setup_client(
|
||||
mnemonic="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
)
|
||||
@ -703,17 +704,20 @@ def test_p2tr_with_proof(client: Client):
|
||||
)
|
||||
|
||||
with client:
|
||||
t1 = client.features.model == "1"
|
||||
tt = client.features.model == "T"
|
||||
client.set_expected_responses(
|
||||
[
|
||||
request_input(0),
|
||||
request_input(1),
|
||||
request_output(0),
|
||||
messages.ButtonRequest(code=B.ConfirmOutput),
|
||||
messages.ButtonRequest(code=B.ConfirmOutput),
|
||||
(tt, messages.ButtonRequest(code=B.ConfirmOutput)),
|
||||
messages.ButtonRequest(code=B.SignTx),
|
||||
request_input(0),
|
||||
request_input(1),
|
||||
request_output(0),
|
||||
(t1, request_input(0)),
|
||||
request_input(1),
|
||||
request_finished(),
|
||||
]
|
||||
@ -732,11 +736,10 @@ def test_p2tr_with_proof(client: Client):
|
||||
|
||||
# Test corrupted ownership proof.
|
||||
inp1.ownership_proof[10] ^= 1
|
||||
with pytest.raises(TrezorFailure, match="Invalid signature"):
|
||||
with pytest.raises(TrezorFailure, match="Invalid signature|Invalid external input"):
|
||||
btc.sign_tx(client, "Testnet", [inp1, inp2], [out1], prev_txes=TX_CACHE_TESTNET)
|
||||
|
||||
|
||||
@pytest.mark.skip_t1
|
||||
def test_p2wpkh_with_false_proof(client: Client):
|
||||
inp1 = messages.TxInputType(
|
||||
# tb1qkvwu9g3k2pdxewfqr7syz89r3gj557l3uuf9r9
|
||||
|
@ -269,7 +269,10 @@
|
||||
"T1_bitcoin-test_signtx_amount_unit.py::test_signtx_testnet[AmountUnit.SATOSHI]": "177fd9048c7661977db228678f9be74b1e769deb4cec87aa146ef51d20c604a1",
|
||||
"T1_bitcoin-test_signtx_amount_unit.py::test_signtx_testnet[None]": "f2be7c23251127b50596f1a772a9eb933e0b1cef4c30afbc912930d1413f8694",
|
||||
"T1_bitcoin-test_signtx_external.py::test_p2tr_external_unverified": "19e56e826e17f0b5cb5ab26d684dd4d1ef73da2711edc8bef2a086962c1382b0",
|
||||
"T1_bitcoin-test_signtx_external.py::test_p2tr_with_proof": "5c2f96acb6e23f1e5698276e697c7afa299454fa12f1b2f6eae8b35490d69f6c",
|
||||
"T1_bitcoin-test_signtx_external.py::test_p2wpkh_external_unverified": "6695ba71c171c82c4335db3f70dbb5f861c44141ea0eeea98e295ab6b51da3bb",
|
||||
"T1_bitcoin-test_signtx_external.py::test_p2wpkh_with_false_proof": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"T1_bitcoin-test_signtx_external.py::test_p2wpkh_with_proof": "8f55fbd32b618e6373ed0c68dee2b3e275e0fce6ff7502bbb34ab4702238dae7",
|
||||
"T1_bitcoin-test_signtx_invalid_path.py::test_attack_path_segwit": "ee152e7534c4dd60f939b7403a8169eb7d1703e3bdab819a575bc80f261212a9",
|
||||
"T1_bitcoin-test_signtx_invalid_path.py::test_invalid_path_fail": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"T1_bitcoin-test_signtx_invalid_path.py::test_invalid_path_fail_asap": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
|
Loading…
Reference in New Issue
Block a user