mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-17 13:07:54 +02:00
Implement taphash for elements
This commit is contained in:
parent
6c985308d7
commit
4618097ab4
3 changed files with 120 additions and 28 deletions
|
|
@ -1757,6 +1757,17 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/** Compute the (single) SHA256 of the concatenation of all outpoint flags of a tx. */
|
||||
template <class T>
|
||||
uint256 GetOutpointFlagsSHA256(const T& txTo)
|
||||
{
|
||||
CHashWriter ss(SER_GETHASH, 0);
|
||||
for (const auto& txin : txTo.vin) {
|
||||
ss << (unsigned char) ((!txin.assetIssuance.IsNull() << 7) + (txin.m_is_pegin << 6));
|
||||
}
|
||||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
/** Compute the (single) SHA256 of the concatenation of all prevouts of a tx. */
|
||||
template <class T>
|
||||
uint256 GetPrevoutsSHA256(const T& txTo)
|
||||
|
|
@ -1779,7 +1790,8 @@ uint256 GetSequencesSHA256(const T& txTo)
|
|||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
/** Compute the (single) SHA256 of the concatenation of all txouts of a tx. */
|
||||
/** Compute the (single) SHA256 of the concatenation of all issuances of a tx. */
|
||||
// Used for segwitv0/taproot sighash calculation
|
||||
template <class T>
|
||||
uint256 GetIssuanceSHA256(const T& txTo)
|
||||
{
|
||||
|
|
@ -1793,6 +1805,34 @@ uint256 GetIssuanceSHA256(const T& txTo)
|
|||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
/** Compute the (single) SHA256 of the concatenation of all output witnesses
|
||||
* (rangeproof and surjection proof) in `CTxWitness`*/
|
||||
// Used in taphash calculation
|
||||
template <class T>
|
||||
uint256 GetOutputWitnessesSHA256(const T& txTo)
|
||||
{
|
||||
CHashWriter ss(SER_GETHASH, 0);
|
||||
for (const auto& outwit : txTo.witness.vtxoutwit) {
|
||||
ss << outwit;
|
||||
}
|
||||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
/** Compute the (single) SHA256 of the concatenation of all input issuance witnesses
|
||||
* (vchIssuanceAmountRangeproof and vchInflationKeysRangeproof proof) in `CTxInWitness`*/
|
||||
// Used in taphash calculation
|
||||
template <class T>
|
||||
uint256 GetIssuanceRangeproofsSHA256(const T& txTo)
|
||||
{
|
||||
CHashWriter ss(SER_GETHASH, 0);
|
||||
for (const auto& inwit : txTo.witness.vtxinwit) {
|
||||
ss << inwit.vchIssuanceAmountRangeproof;
|
||||
ss << inwit.vchInflationKeysRangeproof;
|
||||
}
|
||||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
// Compute a (single) SHA256 of the concatenation of all outputs
|
||||
template <class T>
|
||||
uint256 GetOutputsSHA256(const T& txTo)
|
||||
{
|
||||
|
|
@ -1803,11 +1843,13 @@ uint256 GetOutputsSHA256(const T& txTo)
|
|||
return ss.GetSHA256();
|
||||
}
|
||||
|
||||
/** Compute the (single) SHA256 of the concatenation of all amounts spent by a tx. */
|
||||
uint256 GetSpentAmountsSHA256(const std::vector<CTxOut>& outputs_spent)
|
||||
/** Compute the (single) SHA256 of the concatenation of all asset and amounts commitments spent by a tx. */
|
||||
// Elements TapHash only
|
||||
uint256 GetSpentAssetsAmountsSHA256(const std::vector<CTxOut>& outputs_spent)
|
||||
{
|
||||
CHashWriter ss(SER_GETHASH, 0);
|
||||
for (const auto& txout : outputs_spent) {
|
||||
ss << txout.nAsset;
|
||||
ss << txout.nValue;
|
||||
}
|
||||
return ss.GetSHA256();
|
||||
|
|
@ -1878,17 +1920,21 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent
|
|||
m_prevouts_single_hash = GetPrevoutsSHA256(txTo);
|
||||
m_sequences_single_hash = GetSequencesSHA256(txTo);
|
||||
m_outputs_single_hash = GetOutputsSHA256(txTo);
|
||||
m_issuances_single_hash = GetIssuanceSHA256(txTo);
|
||||
}
|
||||
if (uses_bip143_segwit) {
|
||||
hashPrevouts = SHA256Uint256(m_prevouts_single_hash);
|
||||
hashSequence = SHA256Uint256(m_sequences_single_hash);
|
||||
hashIssuance = SHA256Uint256(GetIssuanceSHA256(txTo));
|
||||
hashIssuance = SHA256Uint256(m_issuances_single_hash);
|
||||
hashOutputs = SHA256Uint256(m_outputs_single_hash);
|
||||
hashRangeproofs = GetRangeproofsHash(txTo);
|
||||
m_bip143_segwit_ready = true;
|
||||
}
|
||||
if (uses_bip341_taproot) {
|
||||
m_spent_amounts_single_hash = GetSpentAmountsSHA256(m_spent_outputs);
|
||||
m_outpoints_flag_single_hash = GetOutpointFlagsSHA256(txTo);
|
||||
m_spent_asset_amounts_single_hash = GetSpentAssetsAmountsSHA256(m_spent_outputs);
|
||||
m_issuance_rangeproofs_single_hash = GetIssuanceRangeproofsSHA256(txTo);
|
||||
m_output_witnesses_single_hash = GetOutputWitnessesSHA256(txTo);
|
||||
m_spent_scripts_single_hash = GetSpentScriptsSHA256(m_spent_outputs);
|
||||
m_bip341_taproot_ready = true;
|
||||
}
|
||||
|
|
@ -1907,13 +1953,14 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo,
|
|||
template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo);
|
||||
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
|
||||
|
||||
static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
|
||||
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
|
||||
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
|
||||
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
|
||||
|
||||
static const CHashWriter HASHER_TAPLEAF_ELEMENTS = TaggedHash("TapLeaf/elements");
|
||||
static const CHashWriter HASHER_TAPBRANCH_ELEMENTS = TaggedHash("TapBranch/elements");
|
||||
static const CHashWriter HASHER_TAPTWEAK_ELEMENTS = TaggedHash("TapTweak/elements");
|
||||
static const CHashWriter HASHER_TAPSIGHASH_ELEMENTS = TaggedHash("TapSighash/elements");
|
||||
|
||||
PrecomputedTransactionData::PrecomputedTransactionData(const uint256& hash_genesis_block)
|
||||
: m_tapsighash_hasher(CHashWriter(HASHER_TAPSIGHASH) << hash_genesis_block << hash_genesis_block) {}
|
||||
: m_tapsighash_hasher(CHashWriter(HASHER_TAPSIGHASH_ELEMENTS) << hash_genesis_block << hash_genesis_block) {}
|
||||
|
||||
template<typename T>
|
||||
bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache)
|
||||
|
|
@ -1940,9 +1987,9 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata
|
|||
|
||||
CHashWriter ss = cache.m_tapsighash_hasher;
|
||||
|
||||
// Epoch
|
||||
static constexpr uint8_t EPOCH = 0;
|
||||
ss << EPOCH;
|
||||
// no epoch in elements taphash
|
||||
// static constexpr uint8_t EPOCH = 0;
|
||||
// ss << EPOCH;
|
||||
|
||||
// Hash type
|
||||
const uint8_t output_type = (hash_type == SIGHASH_DEFAULT) ? SIGHASH_ALL : (hash_type & SIGHASH_OUTPUT_MASK); // Default (no sighash byte) is equivalent to SIGHASH_ALL
|
||||
|
|
@ -1954,37 +2001,56 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata
|
|||
ss << tx_to.nVersion;
|
||||
ss << tx_to.nLockTime;
|
||||
if (input_type != SIGHASH_ANYONECANPAY) {
|
||||
ss << cache.m_outpoints_flag_single_hash;
|
||||
ss << cache.m_prevouts_single_hash;
|
||||
ss << cache.m_spent_amounts_single_hash;
|
||||
ss << cache.m_spent_asset_amounts_single_hash;
|
||||
ss << cache.m_spent_scripts_single_hash;
|
||||
ss << cache.m_sequences_single_hash;
|
||||
ss << cache.m_issuances_single_hash;
|
||||
ss << cache.m_issuance_rangeproofs_single_hash;
|
||||
}
|
||||
if (output_type == SIGHASH_ALL) {
|
||||
ss << cache.m_outputs_single_hash;
|
||||
ss << cache.m_output_witnesses_single_hash;
|
||||
}
|
||||
|
||||
// Data about the input/prevout being spent
|
||||
assert(execdata.m_annex_init);
|
||||
const bool have_annex = execdata.m_annex_present;
|
||||
const uint8_t spend_type = (ext_flag << 1) + (have_annex ? 1 : 0); // The low bit indicates whether an annex is present.
|
||||
ss << spend_type;
|
||||
if (input_type == SIGHASH_ANYONECANPAY) {
|
||||
ss << (unsigned char) ((!tx_to.vin[in_pos].assetIssuance.IsNull() << 7) + (tx_to.vin[in_pos].m_is_pegin << 6));
|
||||
ss << tx_to.vin[in_pos].prevout;
|
||||
ss << cache.m_spent_outputs[in_pos];
|
||||
ss << cache.m_spent_outputs[in_pos].nAsset;
|
||||
ss << cache.m_spent_outputs[in_pos].nValue;
|
||||
ss << cache.m_spent_outputs[in_pos].scriptPubKey;
|
||||
ss << tx_to.vin[in_pos].nSequence;
|
||||
if (tx_to.vin[in_pos].assetIssuance.IsNull()) {
|
||||
ss << (unsigned char)0;
|
||||
} else {
|
||||
ss << tx_to.vin[in_pos].assetIssuance;
|
||||
|
||||
CHashWriter sha_single_input_issuance_witness(SER_GETHASH, 0);
|
||||
sha_single_input_issuance_witness << tx_to.witness.vtxinwit[in_pos].vchIssuanceAmountRangeproof;
|
||||
sha_single_input_issuance_witness << tx_to.witness.vtxinwit[in_pos].vchInflationKeysRangeproof;
|
||||
ss << sha_single_input_issuance_witness.GetSHA256();
|
||||
}
|
||||
} else {
|
||||
ss << in_pos;
|
||||
}
|
||||
if (have_annex) {
|
||||
ss << execdata.m_annex_hash;
|
||||
}
|
||||
|
||||
// Data about the output (if only one).
|
||||
if (output_type == SIGHASH_SINGLE) {
|
||||
if (in_pos >= tx_to.vout.size()) return false;
|
||||
CHashWriter sha_single_output(SER_GETHASH, 0);
|
||||
sha_single_output << tx_to.vout[in_pos];
|
||||
ss << sha_single_output.GetSHA256();
|
||||
|
||||
CHashWriter sha_single_output_witness(SER_GETHASH, 0);
|
||||
sha_single_output_witness << tx_to.witness.vtxoutwit[in_pos];
|
||||
ss << sha_single_output_witness.GetSHA256();
|
||||
}
|
||||
|
||||
// Additional data for BIP 342 signatures
|
||||
|
|
@ -2301,10 +2367,10 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
|
|||
const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
|
||||
const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
|
||||
const XOnlyPubKey q{uint256(program)};
|
||||
tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
|
||||
tapleaf_hash = (CHashWriter(HASHER_TAPLEAF_ELEMENTS) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
|
||||
uint256 k = tapleaf_hash;
|
||||
for (int i = 0; i < path_len; ++i) {
|
||||
CHashWriter ss_branch{HASHER_TAPBRANCH};
|
||||
CHashWriter ss_branch = CHashWriter{HASHER_TAPBRANCH_ELEMENTS};
|
||||
Span<const unsigned char> node(control.data() + TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE);
|
||||
if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) {
|
||||
ss_branch << k << node;
|
||||
|
|
@ -2313,7 +2379,7 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
|
|||
}
|
||||
k = ss_branch.GetSHA256();
|
||||
}
|
||||
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
|
||||
k = (CHashWriter(HASHER_TAPTWEAK_ELEMENTS) << MakeSpan(p) << k).GetSHA256();
|
||||
return q.CheckPayToContract(p, k, control[0] & 1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,12 +167,18 @@ struct PrecomputedTransactionData
|
|||
uint256 m_outputs_single_hash;
|
||||
uint256 m_spent_amounts_single_hash;
|
||||
uint256 m_spent_scripts_single_hash;
|
||||
//! Whether the 5 fields above are initialized.
|
||||
// Elements
|
||||
uint256 m_outpoints_flag_single_hash;
|
||||
uint256 m_spent_asset_amounts_single_hash;
|
||||
uint256 m_issuances_single_hash;
|
||||
uint256 m_output_witnesses_single_hash;
|
||||
uint256 m_issuance_rangeproofs_single_hash;
|
||||
//! Whether the 10 fields above are initialized.
|
||||
bool m_bip341_taproot_ready = false;
|
||||
|
||||
// BIP143 precomputed data (double-SHA256).
|
||||
uint256 hashPrevouts, hashSequence, hashOutputs, hashIssuance, hashRangeproofs;
|
||||
//! Whether the 3 fields above are initialized.
|
||||
//! Whether the 5 fields above are initialized.
|
||||
bool m_bip143_segwit_ready = false;
|
||||
|
||||
std::vector<CTxOut> m_spent_outputs;
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ from test_framework.messages import (
|
|||
CTxIn,
|
||||
CTxInWitness,
|
||||
CTxOut,
|
||||
CTxOutValue,
|
||||
ToHex,
|
||||
CTxOutValue, CTxOutWitness,
|
||||
ToHex, uint256_from_str,
|
||||
)
|
||||
from test_framework.script import (
|
||||
ANNEX_TAG,
|
||||
|
|
@ -123,6 +123,11 @@ import random
|
|||
#
|
||||
# in that ctx3 will globally use hashtype=SIGHASH_DEFAULT (including in the hashtype byte appended to the signature)
|
||||
# while ctx2 only uses the modified hashtype inside the sighash calculation.
|
||||
#
|
||||
# ELEMENTS:
|
||||
# Elements taphash calculation also depends on genesis_block_hash which is stored as
|
||||
# `genesis_hash` in the test config
|
||||
g_genesis_hash = None
|
||||
|
||||
def deep_eval(ctx, expr):
|
||||
"""Recursively replace any callables c in expr (including inside lists) with c(ctx)."""
|
||||
|
|
@ -191,11 +196,13 @@ def default_controlblock(ctx):
|
|||
"""Default expression for "controlblock": combine leafversion, negflag, pubkey_inner, merklebranch."""
|
||||
return bytes([get(ctx, "leafversion") + get(ctx, "negflag")]) + get(ctx, "pubkey_inner") + get(ctx, "merklebranch")
|
||||
|
||||
#ELEMENTS: taphash depends on genesis hash
|
||||
def default_sighash(ctx):
|
||||
"""Default expression for "sighash": depending on mode, compute BIP341, BIP143, or legacy sighash."""
|
||||
tx = get(ctx, "tx")
|
||||
idx = get(ctx, "idx")
|
||||
hashtype = get(ctx, "hashtype_actual")
|
||||
genesis_hash = get(ctx, "genesis_hash")
|
||||
mode = get(ctx, "mode")
|
||||
if mode == "taproot":
|
||||
# BIP341 signature hash
|
||||
|
|
@ -205,9 +212,9 @@ def default_sighash(ctx):
|
|||
codeseppos = get(ctx, "codeseppos")
|
||||
leaf_ver = get(ctx, "leafversion")
|
||||
script = get(ctx, "script_taproot")
|
||||
return TaprootSignatureHash(tx, utxos, hashtype, idx, scriptpath=True, script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex)
|
||||
return TaprootSignatureHash(tx, utxos, hashtype, genesis_hash, idx, scriptpath=True, script=script, leaf_ver=leaf_ver, codeseparator_pos=codeseppos, annex=annex)
|
||||
else:
|
||||
return TaprootSignatureHash(tx, utxos, hashtype, idx, scriptpath=False, annex=annex)
|
||||
return TaprootSignatureHash(tx, utxos, hashtype, genesis_hash, idx, scriptpath=False, annex=annex)
|
||||
elif mode == "witv0":
|
||||
# BIP143 signature hash
|
||||
scriptcode = get(ctx, "scriptcode")
|
||||
|
|
@ -373,6 +380,8 @@ DEFAULT_CONTEXT = {
|
|||
"leaf": None,
|
||||
# The input arguments to provide to the executed script
|
||||
"inputs": [],
|
||||
# Genesis hash(required for taproot outputs)
|
||||
"genesis_hash": None,
|
||||
|
||||
# == Parameters to be set before evaluation: ==
|
||||
# - mode: what spending style to use ("taproot", "witv0", or "legacy").
|
||||
|
|
@ -382,6 +391,7 @@ DEFAULT_CONTEXT = {
|
|||
# - utxos: the UTXOs being spent (needed in mode=="witv0" and mode=="taproot").
|
||||
# - idx: the input position being signed.
|
||||
# - scriptcode: the scriptcode to include in legacy and witv0 sighashes.
|
||||
# - genesisHash: The genesis hash of the block
|
||||
}
|
||||
|
||||
def flatten(lst):
|
||||
|
|
@ -433,7 +443,7 @@ def spend(tx, idx, utxos, **kwargs):
|
|||
|
||||
Spender = namedtuple("Spender", "script,comment,is_standard,sat_function,err_msg,sigops_weight,no_fail,need_vin_vout_mismatch")
|
||||
|
||||
def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs):
|
||||
def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, genesis_hash=None, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs):
|
||||
"""Helper for constructing Spender objects using the context signing framework.
|
||||
|
||||
* tap: a TaprootInfo object (see taproot_construct), for Taproot spends (cannot be combined with pkh, witv0, or script)
|
||||
|
|
@ -449,6 +459,10 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=
|
|||
"""
|
||||
|
||||
conf = dict()
|
||||
global g_genesis_hash
|
||||
if genesis_hash is None:
|
||||
genesis_hash = g_genesis_hash
|
||||
conf["genesis_hash"] = genesis_hash
|
||||
|
||||
# Compute scriptPubKey and set useful defaults based on the inputs.
|
||||
if witv0:
|
||||
|
|
@ -1216,7 +1230,6 @@ class TaprootTest(BitcoinTestFramework):
|
|||
extra_output_script = CScript([OP_CHECKSIG]*((MAX_BLOCK_SIGOPS_WEIGHT - sigops_weight) // WITNESS_SCALE_FACTOR))
|
||||
if extra_output_script == CScript():
|
||||
extra_output_script = None ## ELEMENTS: an explicitly empty coinbase scriptpubkey would be rejected with bad-cb-fee
|
||||
|
||||
block = create_block(self.tip, create_coinbase(self.lastblockheight + 1, pubkey=cb_pubkey, extra_output_script=extra_output_script, fees=fees), self.lastblocktime + 1)
|
||||
block.nVersion = 4
|
||||
for tx in txs:
|
||||
|
|
@ -1309,9 +1322,12 @@ class TaprootTest(BitcoinTestFramework):
|
|||
amount = int(random.randrange(int(avg*0.85 + 0.5), int(avg*1.15 + 0.5)) + 0.5)
|
||||
balance -= amount
|
||||
fund_tx.vout.append(CTxOut(amount, spenders[done + i].script))
|
||||
fund_tx.wit.vtxoutwit.append(CTxOutWitness())
|
||||
# Add change
|
||||
fund_tx.vout.append(CTxOut(balance - 10000, random.choice(host_spks)))
|
||||
fund_tx.wit.vtxoutwit.append(CTxOutWitness())
|
||||
fund_tx.vout.append(CTxOut(10000)) # ELEMENTS: and fee
|
||||
fund_tx.wit.vtxoutwit.append(CTxOutWitness())
|
||||
# Ask the wallet to sign
|
||||
ss = BytesIO(bytes.fromhex(node.signrawtransactionwithwallet(ToHex(fund_tx))["hex"]))
|
||||
fund_tx.deserialize(ss)
|
||||
|
|
@ -1387,6 +1403,7 @@ class TaprootTest(BitcoinTestFramework):
|
|||
assert in_value >= 0 and fee - num_outputs * DUST_LIMIT >= MIN_FEE
|
||||
for i in range(num_outputs):
|
||||
tx.vout.append(CTxOut())
|
||||
tx.wit.vtxoutwit.append(CTxOutWitness())
|
||||
if in_value <= DUST_LIMIT:
|
||||
tx.vout[-1].nValue = CTxOutValue(DUST_LIMIT)
|
||||
elif i < num_outputs - 1:
|
||||
|
|
@ -1399,6 +1416,7 @@ class TaprootTest(BitcoinTestFramework):
|
|||
fee += in_value
|
||||
assert fee >= 0
|
||||
tx.vout.append(CTxOut(fee))
|
||||
tx.wit.vtxoutwit.append(CTxOutWitness())
|
||||
|
||||
# Select coinbase pubkey
|
||||
cb_pubkey = random.choice(host_pubkeys)
|
||||
|
|
@ -1453,6 +1471,8 @@ class TaprootTest(BitcoinTestFramework):
|
|||
# Post-taproot activation tests go first (pre-taproot tests' blocks are invalid post-taproot).
|
||||
self.log.info("Post-activation tests...")
|
||||
self.nodes[1].generate(101)
|
||||
global g_genesis_hash
|
||||
g_genesis_hash = uint256_from_str(bytes.fromhex(self.nodes[1].getblockhash(0))[::-1])
|
||||
self.test_spenders(self.nodes[1], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3])
|
||||
|
||||
# Transfer funds to pre-taproot node.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue