Skip to content

Commit 0f896e5

Browse files
committed
Make unstructured annex standard
Re-worked version of e71f939 by Joost Jager.
1 parent b48333e commit 0f896e5

2 files changed

Lines changed: 80 additions & 16 deletions

File tree

src/policy/policy.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,40 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
220220
return true;
221221
}
222222

223+
bool IsAnnexStandard(const std::vector<unsigned char>& annex)
224+
{
225+
// If we are incorrectly called on a zero-sized vector, which is not
226+
// actually an annex, just return false to avoid an out-of-bounds index
227+
// later.
228+
if (annex.size() == 0) {
229+
return false;
230+
}
231+
232+
// Get the size of the annex excluding the tag byte.
233+
size_t annex_size = annex.size() - 1;
234+
235+
// Allow an empty annex. This allows inputs to opt-in to annex
236+
// usage with the minimal number of bytes.
237+
if (annex_size == 0) {
238+
return true;
239+
}
240+
241+
// Deny a non-empty annex, unless it starts with the byte 0x00
242+
if (annex[1] != 0) {
243+
return false;
244+
}
245+
246+
return true;
247+
}
248+
223249
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
224250
{
225251
if (tx.IsCoinBase())
226252
return true; // Coinbases are skipped
227253

254+
// Track the number of inputs that commit to an annex.
255+
unsigned int annex_input_count = 0;
256+
228257
for (unsigned int i = 0; i < tx.vin.size(); i++)
229258
{
230259
// We don't care if witness for this input is empty, since it must not be bloated.
@@ -283,8 +312,17 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
283312
// Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341)
284313
Span stack{tx.vin[i].scriptWitness.stack};
285314
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
286-
// Annexes are nonstandard as long as no semantics are defined for them.
287-
return false;
315+
// An annex is present. Remove it from the stack and save it for
316+
// standardness checks.
317+
const auto& annex = SpanPopBack(stack);
318+
319+
// Check that the annex is standard.
320+
if (!IsAnnexStandard(annex)) {
321+
return false;
322+
}
323+
324+
// Increment the number of inputs that commit to an annex.
325+
annex_input_count++;
288326
}
289327
if (stack.size() >= 2) {
290328
// Script path spend (2 or more stack elements after removing optional annex)
@@ -306,6 +344,12 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
306344
}
307345
}
308346
}
347+
348+
// If any inputs commit to an annex, all inputs must commit to an annex.
349+
if (annex_input_count > 0 && annex_input_count != tx.vin.size()) {
350+
return false;
351+
}
352+
309353
return true;
310354
}
311355

test/functional/feature_taproot.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ def to_script(elem):
459459
scriptsig_list = flatten(get(ctx, "scriptsig"))
460460
scriptsig = CScript(b"".join(bytes(to_script(elem)) for elem in scriptsig_list))
461461
witness_stack = flatten(get(ctx, "witness"))
462-
return (scriptsig, witness_stack)
462+
return (scriptsig, witness_stack, get(ctx, "annex"))
463463

464464

465465
# === Spender objects ===
@@ -708,14 +708,23 @@ def spenders_taproot_active():
708708

709709
# == Tests for signature hashing ==
710710

711-
# Run all tests once with no annex, and once with a valid random annex.
712-
for annex in [None, lambda _: bytes([ANNEX_TAG]) + random.randbytes(random.randrange(0, 250))]:
713-
# Non-empty annex is non-standard
714-
no_annex = annex is None
711+
# No annex is standard.
712+
no_annex = (None, True)
715713

714+
# An empty annex is standard.
715+
empty_annex = (lambda _: bytes([ANNEX_TAG]), True)
716+
717+
# An annex is standard if it starts with 0x00; any length is allowed.
718+
std_unstructured_annex = (lambda _: bytes([ANNEX_TAG, 0]) + random.randbytes(random.randint(0, 50_000)), True)
719+
720+
# Define non-standard annex (starts with non-0x00).
721+
non_std_annex = (lambda _: bytes([ANNEX_TAG, random.randint(1, 255)]) + random.randbytes(random.randint(0, 500)), False)
722+
723+
# Run all tests once with the various types of annexes.
724+
for (annex, standard) in [no_annex, empty_annex, std_unstructured_annex, non_std_annex]:
716725
# Sighash mutation tests (test all sighash combinations)
717726
for hashtype in VALID_SIGHASHES_TAPROOT:
718-
common = {"annex": annex, "hashtype": hashtype, "standard": no_annex}
727+
common = {"annex": annex, "hashtype": hashtype, "standard": standard}
719728

720729
# Pure pubkey
721730
tap = taproot_construct(pubs[0])
@@ -729,12 +738,12 @@ def spenders_taproot_active():
729738

730739
# Test SIGHASH_SINGLE behavior in combination with mismatching outputs
731740
if hashtype in VALID_SIGHASHES_TAPROOT_SINGLE:
732-
add_spender(spenders, "sighash/keypath_hashtype_mis_%x" % hashtype, tap=tap, key=secs[0], annex=annex, standard=no_annex, hashtype_actual=random.choice(VALID_SIGHASHES_TAPROOT_NO_SINGLE), failure={"hashtype_actual": hashtype}, **ERR_SIG_HASHTYPE, need_vin_vout_mismatch=True)
733-
add_spender(spenders, "sighash/scriptpath_hashtype_mis_%x" % hashtype, tap=tap, leaf="s0", key=secs[1], annex=annex, standard=no_annex, hashtype_actual=random.choice(VALID_SIGHASHES_TAPROOT_NO_SINGLE), **SINGLE_SIG, failure={"hashtype_actual": hashtype}, **ERR_SIG_HASHTYPE, need_vin_vout_mismatch=True)
741+
add_spender(spenders, "sighash/keypath_hashtype_mis_%x" % hashtype, tap=tap, key=secs[0], annex=annex, standard=standard, hashtype_actual=random.choice(VALID_SIGHASHES_TAPROOT_NO_SINGLE), failure={"hashtype_actual": hashtype}, **ERR_SIG_HASHTYPE, need_vin_vout_mismatch=True)
742+
add_spender(spenders, "sighash/scriptpath_hashtype_mis_%x" % hashtype, tap=tap, leaf="s0", key=secs[1], annex=annex, standard=standard, hashtype_actual=random.choice(VALID_SIGHASHES_TAPROOT_NO_SINGLE), **SINGLE_SIG, failure={"hashtype_actual": hashtype}, **ERR_SIG_HASHTYPE, need_vin_vout_mismatch=True)
734743

735744
# Test OP_CODESEPARATOR impact on sighashing.
736745
hashtype = lambda _: random.choice(VALID_SIGHASHES_TAPROOT)
737-
common = {"annex": annex, "hashtype": hashtype, "standard": no_annex}
746+
common = {"annex": annex, "hashtype": hashtype, "standard": standard}
738747
scripts = [
739748
("pk_codesep", CScript(random_checksig_style(pubs[1]) + bytes([OP_CODESEPARATOR]))), # codesep after checksig
740749
("codesep_pk", CScript(bytes([OP_CODESEPARATOR]) + random_checksig_style(pubs[1]))), # codesep before checksig
@@ -752,7 +761,7 @@ def spenders_taproot_active():
752761
add_spender(spenders, "sighash/branched_codesep/right", tap=tap, leaf="branched_codesep", key=secs[1], codeseppos=6, **common, inputs=[getter("sign"), b''], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
753762

754763
# Reusing the scripts above, test that various features affect the sighash.
755-
add_spender(spenders, "sighash/annex", tap=tap, leaf="pk_codesep", key=secs[1], hashtype=hashtype, standard=False, **SINGLE_SIG, annex=bytes([ANNEX_TAG]), failure={"sighash": override(default_sighash, annex=None)}, **ERR_SIG_SCHNORR)
764+
add_spender(spenders, "sighash/annex", tap=tap, leaf="pk_codesep", key=secs[1], hashtype=hashtype, standard=True, **SINGLE_SIG, annex=bytes([ANNEX_TAG]), failure={"sighash": override(default_sighash, annex=None)}, **ERR_SIG_SCHNORR)
756765
add_spender(spenders, "sighash/script", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, script_taproot=tap.leaves["codesep_pk"].script)}, **ERR_SIG_SCHNORR)
757766
add_spender(spenders, "sighash/leafver", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leafversion=random.choice([x & 0xFE for x in range(0x100) if x & 0xFE != LEAF_VERSION_TAPSCRIPT]))}, **ERR_SIG_SCHNORR)
758767
add_spender(spenders, "sighash/scriptpath", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, failure={"sighash": override(default_sighash, leaf=None)}, **ERR_SIG_SCHNORR)
@@ -1069,7 +1078,7 @@ def big_spend_inputs(ctx):
10691078
# n OP_CHECKSIGADDs and 1 OP_CHECKSIG, but also an OP_CHECKSIGADD with an empty signature.
10701079
lambda n, pk: (CScript([OP_DROP, OP_0, OP_10, pk, OP_CHECKSIGADD, OP_10, OP_EQUALVERIFY, pk] + [OP_2DUP, OP_16, OP_SWAP, OP_CHECKSIGADD, b'\x11', OP_EQUALVERIFY] * n + [OP_CHECKSIG]), n + 1),
10711080
]
1072-
for annex in [None, bytes([ANNEX_TAG]) + random.randbytes(random.randrange(1000))]:
1081+
for annex in [None, bytes([ANNEX_TAG, random.randint(1, 255)]) + random.randbytes(random.randrange(1000))]:
10731082
for hashtype in [SIGHASH_DEFAULT, SIGHASH_ALL]:
10741083
for pubkey in [pubs[1], random.randbytes(random.choice([x for x in range(2, 81) if x != 32]))]:
10751084
for fn_num, fn in enumerate(SIGOPS_RATIO_SCRIPTS):
@@ -1494,15 +1503,26 @@ def test_spenders(self, node, spenders, input_counts):
14941503
# Expected message with each input failure, may be None(which is ignored)
14951504
expected_fail_msg = None if fail_input is None else input_utxos[fail_input].spender.err_msg
14961505
# Fill inputs/witnesses
1506+
annex_count = 0
14971507
for i in range(len(input_utxos)):
1498-
tx.vin[i].scriptSig = input_data[i][i != fail_input][0]
1499-
tx.wit.vtxinwit[i].scriptWitness.stack = input_data[i][i != fail_input][1]
1508+
sat_fn_output = input_data[i][i != fail_input]
1509+
1510+
tx.vin[i].scriptSig = sat_fn_output[0]
1511+
tx.wit.vtxinwit[i].scriptWitness.stack = sat_fn_output[1]
1512+
1513+
# Count annex inputs.
1514+
annex = sat_fn_output[2]
1515+
if annex is not None:
1516+
annex_count += 1
1517+
15001518
# Submit to mempool to check standardness
15011519
is_standard_tx = (
15021520
fail_input is None # Must be valid to be standard
15031521
and (all(utxo.spender.is_standard for utxo in input_utxos)) # All inputs must be standard
15041522
and tx.version >= 1 # The tx version must be standard
1505-
and tx.version <= 2)
1523+
and tx.version <= 2
1524+
and (annex_count == 0 or annex_count == len(input_utxos)) # Opt-in annexes
1525+
)
15061526
tx.rehash()
15071527
msg = ','.join(utxo.spender.comment + ("*" if n == fail_input else "") for n, utxo in enumerate(input_utxos))
15081528
if is_standard_tx:

0 commit comments

Comments
 (0)