Skip to content

Commit 9334aa2

Browse files
committed
bip379: add test vectors from rust-miniscript
1 parent 01d2c84 commit 9334aa2

16 files changed

Lines changed: 76076 additions & 1 deletion

bip-0379.md

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,75 @@ paths hit any of the limits.
393393

394394
## Test Vectors
395395

396-
TBD
396+
### Conventions
397+
398+
One vector per line, no comments. `*.txt` is space separated, `*.tsv` tab
399+
separated. Expressions contain no whitespace. A key argument is a letter
400+
`A`..`Z` standing for the public key of the secret key `0x00…01``0x00…1a`,
401+
serialized compressed in P2WSH and x-only in Tapscript. Hash arguments are
402+
literal hex. A type string is the basic type (`B`/`V`/`K`/`W`) plus its
403+
properties (`zondumsfe`) in arbitrary order, so compare sorted. Files with a
404+
`_tap` suffix are Tapscript (`multi_a`, x-only, Schnorr), the others P2WSH.
405+
406+
### Files
407+
408+
| File | Lines | Line format | Asserts |
409+
|------|-------|-------------|---------|
410+
| `valid_from_alloy.txt` | 5,896 | `<expr> <type>` | parses, is a valid top level, has that type |
411+
| `valid_8f1e8_from_alloy.txt` | 3,492 | same | same, corpus of an earlier upstream revision |
412+
| `malleable_from_alloy.txt` | 7,024 | same | parses, but has no `m` property, i.e. is not sane |
413+
| `conflict_from_alloy.txt` | 1,773 | same | parses, but mixes timelock kinds on one path |
414+
| `edge_cases.txt` | 42 | `<expr>` | the only hand-written file: `thresh` at `k=1`/`k=n`, `multi` with 1 and 20 keys, all four hash fragments, the timelock boundaries (`499999999`/`500000000`, `4194304`/`4194305`), and the sugar forms |
415+
| `invalid.txt` | 5,574 | `<expr>` | must be rejected (mostly type-check failures) |
416+
| `opcodes.txt` | 18,184 | `<expr> <type> <ops>` | as above, plus the max ops of a satisfaction, i.e. the quantity the 201-op limit bounds |
417+
| `props_from_rust[_tap].tsv` | 8,198 + header | `expr script_size op_count sat_witness_elements exec_stack mixed_timelocks non_malleable requires_sig sat_size` | the whole static analysis: type system, timelock mixing, all four resource limits. A two-field row ending in `PARSE_ERR` must be rejected (1,470 of them in the Tapscript file, from the `multi``multi_a` substitution) |
418+
| `scripts_from_rust[_tap].tsv` | 8,198 | `<expr>\t<script hex>` | the translation table byte for byte, incl. `v:` collapsing into the `VERIFY` opcode and minimal number encoding. `ERR` = no encoding in that context |
419+
| `redeem.json` | 46 cases | JSON | optional, and the only satisfaction vectors: `{miniscript, valid, can_sign_N, …}` plus an `identifiers` map of keys, preimages and hashes |
420+
421+
Signature sizes assumed by `sat_size`: 73 bytes ECDSA, 66 bytes Schnorr, each
422+
element including its length prefix.
423+
424+
### Execution
425+
426+
There is a Golang based minimal harness script available that executes all test vectors against
427+
the [Go implementation](https://github.com/btcsuite/btcd/pull/2568).
428+
429+
With at least `Go 1.25.0` installed, they can be run with:
430+
431+
```shell
432+
$ cd bip-0379
433+
$ go run main.go
434+
```
435+
436+
### Provenance
437+
438+
The `*.txt` corpora come from rust-miniscript's `src/miniscript/ms_tests.rs` at
439+
`59ad2ed`; the `*.tsv` tables were extracted from a patched rust-miniscript and
440+
cross-checked against an
441+
[independent Go implementation](https://github.com/btcsuite/btcd/pull/2568).
442+
`redeem.json` comes from this project's end-to-end spend tests.
443+
444+
`props_from_rust[_tap].tsv` are generated after applying a
445+
[bugfix in `rust-miniscript`](https://github.com/rust-bitcoin/rust-miniscript/pull/1015).
446+
447+
## Gaps
448+
449+
The generated corpora are dominated by `multi`, `sha256`, `after`, `andor` and
450+
`or_d`; `older()`, `ripemd160()`, `hash256()`, `hash160()` and `and_n()` occur
451+
only in the 42 hand-written lines, never in composition.
452+
453+
The following vector cases are currently missing:
454+
1. `older()` alongside `after()` in the positions `after()` appears in, since
455+
the two differ in the relative/absolute timelock analysis, and the generated
456+
corpora only ever use `after()`.
457+
2. One vector per resource limit at its boundary, in both contexts: 201 ops,
458+
3,600 script bytes, 100 witness elements, 1,000 stack elements.
459+
3. The four timelock-mixing combinations of the BIP's own wording (absolute
460+
height with absolute time, relative height with relative time, and the two
461+
legal mixes), which `conflict_from_alloy.txt` covers only for `after()`.
462+
4. A `sortedmulti_a()` vector, which BIP387 defines and no corpus here reaches
463+
through miniscript.
464+
397465

398466
## Backwards Compatibility
399467

bip-0379/conflict_from_alloy.txt

Lines changed: 1773 additions & 0 deletions
Large diffs are not rendered by default.

bip-0379/edge_cases.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
thresh(1,pk(A))
2+
and_b(pk(A),s:thresh(1,pk(B)))
3+
or_b(pk(A),s:thresh(1,pk(B)))
4+
and_b(pk(A),s:thresh(1,c:pk_k(B)))
5+
and_v(v:thresh(1,pk(A)),pk(B))
6+
and_b(or_i(pk(A),pk(B)),s:thresh(1,pk(C)))
7+
thresh(1,pk(A),s:pk(B))
8+
thresh(2,pk(A),s:pk(B),s:pk(C),s:pk(D),s:pk(E))
9+
thresh(4,pk(A),s:pk(B),s:pk(C),s:pk(D),s:pk(E))
10+
thresh(5,pk(A),s:pk(B),s:pk(C),s:pk(D),s:pk(E))
11+
multi(1,A)
12+
multi(1,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T)
13+
multi(20,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T)
14+
multi(15,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T)
15+
c:pk_k(A)
16+
c:pk_h(A)
17+
pk(A)
18+
pkh(A)
19+
after(1)
20+
after(2147483647)
21+
after(500000000)
22+
after(499999999)
23+
older(1)
24+
older(4194304)
25+
older(4194305)
26+
older(2147483647)
27+
sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)
28+
hash256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)
29+
ripemd160(4355a46b19d348dc2f57c046f8ef63d4538ebb93)
30+
hash160(4355a46b19d348dc2f57c046f8ef63d4538ebb93)
31+
and_v(v:pk(A),after(500000000))
32+
or_d(pk(A),and_v(v:pk(B),older(52560)))
33+
andor(pk(A),older(4194305),pk(B))
34+
l:pk(A)
35+
u:pk(A)
36+
and_n(pk(A),pk(B))
37+
tv:pk(A)
38+
and_v(vc:pk_k(A),pk(B))
39+
or_i(and_v(v:pk(A),pk(B)),thresh(1,pk(C)))
40+
thresh(1,or_i(multi(2,A,B,C),pk(D)),s:pk(E),s:pk(F))
41+
thresh(2,or_i(multi(2,A,B,C),pk(D)),s:pk(E),s:pk(F),s:pk(G))
42+
thresh(1,or_d(multi(2,A,B,C),pk(D)),s:pk(E),s:pk(F))

bip-0379/go.mod

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module bip-379
2+
3+
go 1.25.0
4+
5+
// That commit only exists in the pull request it belongs to
6+
// (btcsuite/btcd#2568), and the module proxy cannot resolve a commit that is
7+
// not on a branch or tag of the repository, so the very same commit is taken
8+
// from the fork it was pushed from. Once it is merged, this line can go.
9+
replace github.com/btcsuite/btcd/descriptors => github.com/guggero/btcd/descriptors v0.0.0-20260806115333-3f5e7b69115e
10+
11+
require (
12+
github.com/btcsuite/btcd/address/v2 v2.0.0
13+
github.com/btcsuite/btcd/btcec/v2 v2.5.0
14+
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 // indirect
15+
github.com/btcsuite/btcd/chainhash/v2 v2.0.0 // indirect
16+
// The descriptors module isn't in master yet, see replace directive above.
17+
github.com/btcsuite/btcd/descriptors v0.0.0-00000000000000-000000000000
18+
github.com/btcsuite/btcd/txscript/v2 v2.0.0
19+
github.com/btcsuite/btcd/wire/v2 v2.0.0 // indirect
20+
)
21+
22+
require (
23+
github.com/btcsuite/btclog v1.0.0 // indirect
24+
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
25+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
26+
golang.org/x/crypto v0.45.0 // indirect
27+
golang.org/x/sys v0.38.0 // indirect
28+
)

bip-0379/go.sum

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
github.com/btcsuite/btcd/address/v2 v2.0.0 h1:UVu8Hal6Siu4XastFe+JX5JkeBYONbDUIY5E+SVTs6I=
2+
github.com/btcsuite/btcd/address/v2 v2.0.0/go.mod h1:htJK1AtaeK3bKNfZY63ep2oN8LbrI6qvmPGe1vekb3I=
3+
github.com/btcsuite/btcd/btcec/v2 v2.5.0 h1:KioMXOWa76b86sTZZOmbzv/ldaQCmB8KFAyn5PbB8E8=
4+
github.com/btcsuite/btcd/btcec/v2 v2.5.0/go.mod h1:+K/MYXcLBtHEQjRbjHuJChuybk4LCgjdjgRwil+e+Kk=
5+
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 h1:M/RTtXfXA9odC1RUEOyZFXj/NXKVHPYZXVjb60xTOok=
6+
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0/go.mod h1:rHgHIXYYfn70m25a+BJ9f9z7VZAsTiDQGB2XYaippGQ=
7+
github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA=
8+
github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8=
9+
github.com/btcsuite/btcd/txscript/v2 v2.0.0 h1:pEmmHaC8eRx6KSB63zSVJD7qrit9/c9cLSrw++XrYP8=
10+
github.com/btcsuite/btcd/txscript/v2 v2.0.0/go.mod h1:pZXabc11Xr9nz/18kXY3yErdAajYc3gi28Zqb3KqlFo=
11+
github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8=
12+
github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM=
13+
github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns=
14+
github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ=
15+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
16+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
17+
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
18+
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
19+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
20+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
21+
github.com/guggero/btcd/descriptors v0.0.0-20260806115333-3f5e7b69115e h1:RY6hk8KfS0GX2Sav5Z4RJjeO1tQyp09I9grNhnhDneo=
22+
github.com/guggero/btcd/descriptors v0.0.0-20260806115333-3f5e7b69115e/go.mod h1:FxK5G+x3lh/EYizkhnKq0yMpCPPyBQ+WBb29wXkZaYg=
23+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
24+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
25+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
26+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
27+
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
28+
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
29+
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
30+
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
31+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)