Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion bip-0379.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,75 @@ paths hit any of the limits.

## Test Vectors

TBD
### Conventions

One vector per line, no comments. `*.txt` is space separated, `*.tsv` tab
separated. Expressions contain no whitespace. A key argument is a letter
`A`..`Z` standing for the public key of the secret key `0x00…01` … `0x00…1a`,
serialized compressed in P2WSH and x-only in Tapscript. Hash arguments are
literal hex. A type string is the basic type (`B`/`V`/`K`/`W`) plus its
properties (`zondumsfe`) in arbitrary order, so compare sorted. Files with a
`_tap` suffix are Tapscript (`multi_a`, x-only, Schnorr), the others P2WSH.

### Files

| File | Lines | Line format | Asserts |
|------|-------|-------------|---------|
| `valid_from_alloy.txt` | 5,896 | `<expr> <type>` | parses, is a valid top level, has that type |
| `valid_8f1e8_from_alloy.txt` | 3,492 | same | same, corpus of an earlier upstream revision |
| `malleable_from_alloy.txt` | 7,024 | same | parses, but has no `m` property, i.e. is not sane |
| `conflict_from_alloy.txt` | 1,773 | same | parses, but mixes timelock kinds on one path |
| `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 |
| `invalid.txt` | 5,574 | `<expr>` | must be rejected (mostly type-check failures) |
| `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 |
| `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) |
| `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 |
| `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 |

Signature sizes assumed by `sat_size`: 73 bytes ECDSA, 66 bytes Schnorr, each
element including its length prefix.

### Execution

There is a Golang based minimal harness script available that executes all test vectors against
the [Go implementation](https://github.com/btcsuite/btcd/pull/2568).

With at least `Go 1.25.0` installed, they can be run with:

```shell
$ cd bip-0379
$ go run main.go
```

### Provenance

The `*.txt` corpora come from rust-miniscript's `src/miniscript/ms_tests.rs` at
`59ad2ed`; the `*.tsv` tables were extracted from a patched rust-miniscript and
cross-checked against an
[independent Go implementation](https://github.com/btcsuite/btcd/pull/2568).
`redeem.json` comes from this project's end-to-end spend tests.

`props_from_rust[_tap].tsv` are generated after applying a
[bugfix in `rust-miniscript`](https://github.com/rust-bitcoin/rust-miniscript/pull/1015).

## Gaps

The generated corpora are dominated by `multi`, `sha256`, `after`, `andor` and
`or_d`; `older()`, `ripemd160()`, `hash256()`, `hash160()` and `and_n()` occur
only in the 42 hand-written lines, never in composition.

The following vector cases are currently missing:
1. `older()` alongside `after()` in the positions `after()` appears in, since
the two differ in the relative/absolute timelock analysis, and the generated
corpora only ever use `after()`.
2. One vector per resource limit at its boundary, in both contexts: 201 ops,
3,600 script bytes, 100 witness elements, 1,000 stack elements.
3. The four timelock-mixing combinations of the BIP's own wording (absolute
height with absolute time, relative height with relative time, and the two
legal mixes), which `conflict_from_alloy.txt` covers only for `after()`.
4. A `sortedmulti_a()` vector, which BIP387 defines and no corpus here reaches
through miniscript.


## Backwards Compatibility

Expand Down
1,773 changes: 1,773 additions & 0 deletions bip-0379/conflict_from_alloy.txt

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions bip-0379/edge_cases.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
thresh(1,pk(A))
and_b(pk(A),s:thresh(1,pk(B)))
or_b(pk(A),s:thresh(1,pk(B)))
and_b(pk(A),s:thresh(1,c:pk_k(B)))
and_v(v:thresh(1,pk(A)),pk(B))
and_b(or_i(pk(A),pk(B)),s:thresh(1,pk(C)))
thresh(1,pk(A),s:pk(B))
thresh(2,pk(A),s:pk(B),s:pk(C),s:pk(D),s:pk(E))
thresh(4,pk(A),s:pk(B),s:pk(C),s:pk(D),s:pk(E))
thresh(5,pk(A),s:pk(B),s:pk(C),s:pk(D),s:pk(E))
multi(1,A)
multi(1,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T)
multi(20,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T)
multi(15,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T)
c:pk_k(A)
c:pk_h(A)
pk(A)
pkh(A)
after(1)
after(2147483647)
after(500000000)
after(499999999)
older(1)
older(4194304)
older(4194305)
older(2147483647)
sha256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)
hash256(926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc)
ripemd160(4355a46b19d348dc2f57c046f8ef63d4538ebb93)
hash160(4355a46b19d348dc2f57c046f8ef63d4538ebb93)
and_v(v:pk(A),after(500000000))
or_d(pk(A),and_v(v:pk(B),older(52560)))
andor(pk(A),older(4194305),pk(B))
l:pk(A)
u:pk(A)
and_n(pk(A),pk(B))
tv:pk(A)
and_v(vc:pk_k(A),pk(B))
or_i(and_v(v:pk(A),pk(B)),thresh(1,pk(C)))
thresh(1,or_i(multi(2,A,B,C),pk(D)),s:pk(E),s:pk(F))
thresh(2,or_i(multi(2,A,B,C),pk(D)),s:pk(E),s:pk(F),s:pk(G))
thresh(1,or_d(multi(2,A,B,C),pk(D)),s:pk(E),s:pk(F))
28 changes: 28 additions & 0 deletions bip-0379/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module bip-379

go 1.25.0

// That commit only exists in the pull request it belongs to
// (btcsuite/btcd#2568), and the module proxy cannot resolve a commit that is
// not on a branch or tag of the repository, so the very same commit is taken
// from the fork it was pushed from. Once it is merged, this line can go.
replace github.com/btcsuite/btcd/descriptors => github.com/guggero/btcd/descriptors v0.0.0-20260806115333-3f5e7b69115e

require (
github.com/btcsuite/btcd/address/v2 v2.0.0
github.com/btcsuite/btcd/btcec/v2 v2.5.0
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 // indirect
github.com/btcsuite/btcd/chainhash/v2 v2.0.0 // indirect
// The descriptors module isn't in master yet, see replace directive above.
github.com/btcsuite/btcd/descriptors v0.0.0-00000000000000-000000000000
github.com/btcsuite/btcd/txscript/v2 v2.0.0
github.com/btcsuite/btcd/wire/v2 v2.0.0 // indirect
)

require (
github.com/btcsuite/btclog v1.0.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/sys v0.38.0 // indirect
)
32 changes: 32 additions & 0 deletions bip-0379/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
github.com/btcsuite/btcd/address/v2 v2.0.0 h1:UVu8Hal6Siu4XastFe+JX5JkeBYONbDUIY5E+SVTs6I=
github.com/btcsuite/btcd/address/v2 v2.0.0/go.mod h1:htJK1AtaeK3bKNfZY63ep2oN8LbrI6qvmPGe1vekb3I=
github.com/btcsuite/btcd/btcec/v2 v2.5.0 h1:KioMXOWa76b86sTZZOmbzv/ldaQCmB8KFAyn5PbB8E8=
github.com/btcsuite/btcd/btcec/v2 v2.5.0/go.mod h1:+K/MYXcLBtHEQjRbjHuJChuybk4LCgjdjgRwil+e+Kk=
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0 h1:M/RTtXfXA9odC1RUEOyZFXj/NXKVHPYZXVjb60xTOok=
github.com/btcsuite/btcd/chaincfg/v2 v2.0.0/go.mod h1:rHgHIXYYfn70m25a+BJ9f9z7VZAsTiDQGB2XYaippGQ=
github.com/btcsuite/btcd/chainhash/v2 v2.0.0 h1:PMLlSloHJuEeB80XG9EjpXWNEKAZAMLl6YHZ6YsEuoA=
github.com/btcsuite/btcd/chainhash/v2 v2.0.0/go.mod h1:mKxcZ7oGTXE7IRV+sS9hP4EVBwc/SzfNR+52IsOP9j8=
github.com/btcsuite/btcd/txscript/v2 v2.0.0 h1:pEmmHaC8eRx6KSB63zSVJD7qrit9/c9cLSrw++XrYP8=
github.com/btcsuite/btcd/txscript/v2 v2.0.0/go.mod h1:pZXabc11Xr9nz/18kXY3yErdAajYc3gi28Zqb3KqlFo=
github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04TFRS2z8=
github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM=
github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns=
github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/guggero/btcd/descriptors v0.0.0-20260806115333-3f5e7b69115e h1:RY6hk8KfS0GX2Sav5Z4RJjeO1tQyp09I9grNhnhDneo=
github.com/guggero/btcd/descriptors v0.0.0-20260806115333-3f5e7b69115e/go.mod h1:FxK5G+x3lh/EYizkhnKq0yMpCPPyBQ+WBb29wXkZaYg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading