Cross-Node Test Results: Core v30.2, Knots v29.3, Libre v30.2
Full btcaaron core_test suite run against 3 Bitcoin node implementations on the same dedicated VPS (16GB RAM, 4-core Xeon, Debian). This is the second run — first was v29-era nodes. Upgrading to v30 surfaced one new divergence and resolved another.
| Node |
Version |
Pass Rate |
| Core |
v30.2.0 |
22/22 |
| Knots |
v29.3.knots20260210 |
19/22 (3 musig() failures) |
| Libre |
libre-relay-v30.2-2 |
22/22 |
0 consensus divergences. 0 string-level divergences.
Regtest Results — 16/16 PASS (all 3 nodes)
| Test |
Cases |
Core |
Knots |
Libre |
| tutorial_tx_policy_consensus |
3 |
ALL PASS |
ALL PASS |
ALL PASS |
| task2_controlblock_mutation_matrix |
5 |
ALL PASS |
ALL PASS |
ALL PASS |
| task3_sig_output_correctness_matrix |
4 |
ALL PASS |
ALL PASS |
ALL PASS |
| task4_policy_consensus_split_matrix |
4 |
ALL PASS |
ALL PASS |
ALL PASS |
All three nodes fully synced (initialblockdownload: false), each ran its own isolated regtest instance. Zero differences in transaction acceptance/rejection, Taproot validation, control block verification, Schnorr signatures, fee policy enforcement, or consensus mining.
Mainnet Descriptor Results — musig() Divergence
Tests 5 & 6 (per node)
| Case |
Core |
Knots |
Libre |
| valid_plain_pk_timelock |
PASS |
PASS |
PASS |
| invalid_nested_pk_musig |
PASS |
FAIL* |
PASS |
| nonstandard_candidate_policy_deferred |
PASS |
PASS |
PASS |
| control_musig_keypath |
PASS |
FAIL* |
PASS |
| control_miniscript_plain_pk |
PASS |
PASS |
PASS |
| target_pk_musig_nested |
PASS |
FAIL* |
PASS |
Test 7: Cross-Instance Consistency (all 3 simultaneously)
| Case |
Core |
Knots |
Libre |
Consistent? |
| control/musig_keypath |
OK |
ERR_OTHER |
OK |
DIVERGENT |
| control/plain_pk_timelock |
OK |
OK |
OK |
YES |
| target/nested_pk_musig |
ERR_INVALID_MUSIG |
ERR_OTHER |
ERR_INVALID_MUSIG |
DIVERGENT |
Divergence Analysis
BIP-390 musig() Descriptors — Genuine Gap
Knots' 3 failures are all musig() descriptor cases. This is not a parsing quirk — Core PR #31244 ("descriptors: MuSig2") merged into master on 2025-07-31, after v29.0 shipped (2025-04-15), and landed in v30.0. No v30-based Knots release exists yet. The musig() descriptor code simply isn't in Knots' source tree.
Verified empirically via getdescriptorinfo:
# tr(musig(key1,key2,key3)) — valid BIP-390 keypath descriptor
Core v30.2: SUCCESS (descriptor with checksum, issolvable=true)
Knots v29.3: ERROR ("key 'musig(...)' is not valid")
Libre v30.2: SUCCESS (descriptor with checksum, issolvable=true)
# pk(musig(key1,key2)) nested in script leaf — invalid musig usage
Core v30.2: REJECTED ("Invalid musig() expression") ← musig-aware
Knots v29.3: REJECTED ("key '...' is not valid") ← generic
Libre v30.2: REJECTED ("Invalid musig() expression") ← musig-aware
Impact: Tooling targeting Core v30.2+ or Libre v30.2+ can use musig() descriptors for watchonly wallets, address generation, and PSBT workflows. Same workflows will fail on Knots until a v30-based release ships.
What Changed: v29 vs v30
| Node |
Previous |
Current |
| Core |
v29.0.0 |
v30.2.0 |
| Knots |
v29.2.knots20251110 |
v29.3.knots20260210 |
| Libre |
libre-relay-v29.1-1 |
libre-relay-v30.2-2 |
-
NEW: BIP-390 musig() descriptors — Core and Libre now parse and validate musig() key expressions. Not present in v29. Knots still on v29 codebase.
-
NEW: musig-aware error classification — Core/Libre v30.2 return specific Invalid musig() expression errors for invalid musig usage, vs generic key-parsing failures. btcaaron's Task 7 can now distinguish "musig aware but rejecting" from "doesn't know what musig is."
-
RESOLVED: reject-reason string convergence — In v29, Core/Libre used mandatory-script-verify-flag-failed while Knots used mempool-script-verify-flag-failed. Core v30.2 adopted the mempool- prefix. All three now match.
-
UNCHANGED: regtest consensus — 16/16 PASS on all three, both runs. No regressions.
Untested Policy Boundaries
The suite probes consensus-level Taproot behavior. Known policy divergences in untested areas that would likely surface additional findings:
- OP_RETURN / datacarrier limits — Knots defaults to 42, Core uses 83
- Dust thresholds — Libre may accept lower dust amounts
- Bare multisig relay — Knots may reject what Core/Libre relay
- Full-RBF policy — Libre adopted full-RBF early
- Non-standard script relay — varying policies across implementations
Operator Notes
- btcrun + btcaaron installed cleanly with
pip install -e .
- Minor btcrun config gap: no
port= directive in generated config, so a second regtest silently fails if port 18444 is already bound
- All regtest tests ran in under 30 seconds per node
- Test date: 2026-03-08, Tester: pirq
Cross-Node Test Results: Core v30.2, Knots v29.3, Libre v30.2
Full btcaaron
core_testsuite run against 3 Bitcoin node implementations on the same dedicated VPS (16GB RAM, 4-core Xeon, Debian). This is the second run — first was v29-era nodes. Upgrading to v30 surfaced one new divergence and resolved another.0 consensus divergences. 0 string-level divergences.
Regtest Results — 16/16 PASS (all 3 nodes)
All three nodes fully synced (
initialblockdownload: false), each ran its own isolated regtest instance. Zero differences in transaction acceptance/rejection, Taproot validation, control block verification, Schnorr signatures, fee policy enforcement, or consensus mining.Mainnet Descriptor Results — musig() Divergence
Tests 5 & 6 (per node)
Test 7: Cross-Instance Consistency (all 3 simultaneously)
Divergence Analysis
BIP-390 musig() Descriptors — Genuine Gap
Knots' 3 failures are all musig() descriptor cases. This is not a parsing quirk — Core PR #31244 ("descriptors: MuSig2") merged into master on 2025-07-31, after v29.0 shipped (2025-04-15), and landed in v30.0. No v30-based Knots release exists yet. The musig() descriptor code simply isn't in Knots' source tree.
Verified empirically via
getdescriptorinfo:Impact: Tooling targeting Core v30.2+ or Libre v30.2+ can use
musig()descriptors for watchonly wallets, address generation, and PSBT workflows. Same workflows will fail on Knots until a v30-based release ships.What Changed: v29 vs v30
NEW: BIP-390 musig() descriptors — Core and Libre now parse and validate
musig()key expressions. Not present in v29. Knots still on v29 codebase.NEW: musig-aware error classification — Core/Libre v30.2 return specific
Invalid musig() expressionerrors for invalid musig usage, vs generic key-parsing failures. btcaaron's Task 7 can now distinguish "musig aware but rejecting" from "doesn't know what musig is."RESOLVED: reject-reason string convergence — In v29, Core/Libre used
mandatory-script-verify-flag-failedwhile Knots usedmempool-script-verify-flag-failed. Core v30.2 adopted themempool-prefix. All three now match.UNCHANGED: regtest consensus — 16/16 PASS on all three, both runs. No regressions.
Untested Policy Boundaries
The suite probes consensus-level Taproot behavior. Known policy divergences in untested areas that would likely surface additional findings:
Operator Notes
pip install -e .port=directive in generated config, so a second regtest silently fails if port 18444 is already bound