txscript: add unit tests and test vectors for miniscript

This commit adds test vectors taken from
a0648b3a4d/src/miniscript/ms_tests.rs
to the miniscript implementation.

Co-authored-by: Oliver Gugger <gugger@gmail.com>
This commit is contained in:
Marko Bencun 2023-11-12 16:35:00 -06:00 committed by Oliver Gugger
parent a2030044bd
commit a3e7dc3ef4
No known key found for this signature in database
GPG key ID: 8E4256593F177720
10 changed files with 48339 additions and 0 deletions

View file

@ -0,0 +1,514 @@
package miniscript
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/stretchr/testify/require"
)
// TestSplitString tests the splitString function.
func TestSplitString(t *testing.T) {
separators := func(c rune) bool {
return c == '(' || c == ')' || c == ','
}
testCases := []struct {
str string
expected []string
}{
{
str: "",
expected: []string{},
},
{
str: "0",
expected: []string{"0"},
},
{
str: "0)(1(",
expected: []string{"0", ")", "(", "1", "("},
},
{
str: "or_b(pk(key_1),s:pk(key_2))",
expected: []string{
"or_b", "(", "pk", "(", "key_1", ")", ",",
"s:pk", "(", "key_2", ")", ")",
},
},
}
for _, tc := range testCases {
require.Equal(t, tc.expected, splitString(tc.str, separators))
}
}
// checkMiniscript makes sure the passed miniscript is top level, has the
// expected type and script length.
func checkMiniscript(miniscript, expectedType string, opCodes int) error {
node, err := Parse(miniscript)
if err := node.IsValidTopLevel(); err != nil {
return err
}
sortString := func(s string) string {
r := []rune(s)
sort.Slice(r, func(i, j int) bool {
return r[i] < r[j]
})
return string(r)
}
if sortString(expectedType) != sortString(node.formattedType()) {
return fmt.Errorf("expected type %s, got %s",
sortString(expectedType),
sortString(node.formattedType()))
}
err = node.ApplyVars(func(identifier string) ([]byte, error) {
if len(identifier) == 64 {
return nil, nil
}
// Return an arbitrary unique 33 bytes.
return append(
chainhash.HashB([]byte(identifier)), 0,
), nil
})
if err != nil {
return err
}
script, err := node.Script()
if err != nil {
return err
}
if len(script) != node.scriptLen {
return fmt.Errorf("expected script length %d but got %d for "+
"script %s", node.scriptLen, len(script),
node.DrawTree())
}
if opCodes != 0 && opCodes != node.maxOpCount() {
return fmt.Errorf("expected %d opcodes but got %d for "+
"miniscript %s", opCodes, node.maxOpCount(),
miniscript)
}
return nil
}
// TestVectors asserts all test vectors in the test data text files pass.
func TestVectors(t *testing.T) {
t.Parallel()
testCases := []struct {
fileName string
valid bool
withOpCodes bool
}{
{
// Invalid expressions (failed type check).
fileName: "testdata/invalid_from_alloy.txt",
valid: false,
},
{
// Valid miniscript expressions including the expected
// type.
fileName: "testdata/valid_8f1e8_from_alloy.txt",
valid: true,
},
{
// Valid miniscript expressions including the expected
// type.
fileName: "testdata/valid_from_alloy.txt",
valid: true,
},
{
// Valid expressions but do not contain the `m` type
// property, i.e. the script is guaranteed to have a
// non-malleable satisfaction.
fileName: "testdata/malleable_from_alloy.txt",
valid: true,
},
{
// miniscripts with time lock mixing in `after` (same
// expression contains both time-based and block-based
// time locks). This unit test is not testing this
// currently, see
// https://github.com/rust-bitcoin/rust-miniscript/issues/514.
fileName: "testdata/conflict_from_alloy.txt",
valid: true,
},
{
// miniscripts with number of opcodes.
fileName: "testdata/opcodes.txt",
valid: true,
withOpCodes: true,
},
}
for _, tc := range testCases {
content, err := os.ReadFile(tc.fileName)
require.NoError(t, err)
lines := strings.Split(string(content), "\n")
for i, line := range lines {
if line == "" {
continue
}
if !tc.valid {
_, err := Parse(line)
require.Errorf(
t, err, "failure on line %d: %s", i,
line,
)
continue
}
parts := strings.Split(line, " ")
var opCodes int
if tc.withOpCodes {
require.Lenf(
t, parts, 3, "malformed test on line "+
"%d: %s", i, line,
)
opCodes, err = strconv.Atoi(parts[2])
require.NoError(t, err)
} else {
require.Lenf(
t, parts, 2, "malformed test on line "+
"%d: %s", i, line,
)
}
miniscript, expectedType := parts[0], parts[1]
require.NoError(
t, checkMiniscript(
miniscript, expectedType, opCodes,
), "failure on line %d: %s", i, line,
)
}
}
}
type testSignFn func(pubKey []byte, hash []byte) (signature []byte,
available bool)
func testRedeem(t *testing.T, miniscript string,
lookupVar func(identifier string) ([]byte, error), sequence uint32,
sign testSignFn, preimage PreimageFunc) error {
// We construct a p2wsh(<miniscript>) UTXO, which we will spend with a
// satisfaction generated from the miniscript.
node, err := Parse(miniscript)
if err != nil {
return err
}
err = node.IsSane()
if err != nil {
return err
}
err = node.ApplyVars(lookupVar)
if err != nil {
return err
}
t.Logf("Tree for miniscript %v: %v", miniscript, node.DrawTree())
t.Logf("Max op count: %v (%d + %d)", node.maxOpCount(),
node.opCount.count, node.opCount.sat.value)
t.Logf("Script: %v", scriptStr(node, false))
// Create the script.
witnessScript, err := node.Script()
if err != nil {
return err
}
// Create the p2wsh(<script>) UTXO.
addr, err := btcutil.NewAddressWitnessScriptHash(
chainhash.HashB(witnessScript), &chaincfg.TestNet3Params,
)
if err != nil {
return err
}
utxoAmount := int64(999799)
if err != nil {
return err
}
utxoPkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return err
}
// Our test spend is a 1-input 1-output transaction. The input spends
// the miniscript UTXO. The output is an arbitrary output - we use a
// OP_RETURN burn output.
burnPkScript, err := txscript.NullDataScript(nil)
if err != nil {
return err
}
// Dummy prevout hash.
hash, err := chainhash.NewHashFromStr(
"000000000000000000000000000000000000000000000000000000000000" +
"0000",
)
if err != nil {
return err
}
txInput := wire.NewTxIn(&wire.OutPoint{Hash: *hash}, nil, nil)
txInput.Sequence = sequence
transaction := wire.MsgTx{
Version: 2,
TxIn: []*wire.TxIn{txInput},
TxOut: []*wire.TxOut{{
Value: utxoAmount - 200,
PkScript: burnPkScript,
}},
LockTime: 0,
}
// We only have one input, for which we will execute the script.
inputIndex := 0
// We only have one input, so the previous outputs fetcher for the
// transaction simply returns our UTXO. The previous output is needed as
// it is signed as part of the transaction sighash for the input.
previousOutputs := txscript.NewCannedPrevOutputFetcher(
utxoPkScript, utxoAmount,
)
// Compute the signature hash to be signed for the first input:
sigHashes := txscript.NewTxSigHashes(&transaction, previousOutputs)
signatureHash, err := txscript.CalcWitnessSigHash(
witnessScript, sigHashes, txscript.SigHashAll, &transaction,
inputIndex, utxoAmount,
)
if err != nil {
return err
}
// Construct a satisfaction (witness) from the miniscript.
witness, err := node.Satisfy(&Satisfier{
CheckOlder: func(lockTime uint32) (bool, error) {
return CheckOlder(
lockTime, uint32(transaction.Version),
transaction.TxIn[inputIndex].Sequence,
), nil
},
CheckAfter: func(lockTime uint32) (bool, error) {
return CheckAfter(
lockTime, transaction.LockTime,
transaction.TxIn[inputIndex].Sequence,
), nil
},
Sign: func(pubKey []byte) ([]byte, bool) {
signature, available := sign(pubKey, signatureHash)
if !available {
return nil, false
}
signature = append(signature, byte(txscript.SigHashAll))
return signature, true
},
Preimage: preimage,
})
if err != nil {
return err
}
// Put the created witness into the transaction input, then execute the
// script to test that the UTXO can be spent successfully.
transaction.TxIn[inputIndex].Witness = append(witness, witnessScript)
engine, err := txscript.NewEngine(
utxoPkScript, &transaction, inputIndex,
txscript.StandardVerifyFlags, nil, sigHashes, utxoAmount,
previousOutputs,
)
if err != nil {
return err
}
err = engine.Execute()
if err != nil {
return err
}
var rawTx bytes.Buffer
err = transaction.Serialize(&rawTx)
require.NoError(t, err)
t.Logf("Raw witness: %v", witness.ToHexStrings())
t.Logf("Raw transaction: %x", rawTx.Bytes())
return nil
}
type RedeemTestVectors struct {
Identifiers map[string]string `json:"identifiers"`
TestCases []RedeemTestCase `json:"test_cases"`
}
type RedeemTestCase struct {
Miniscript string `json:"miniscript"`
ScriptDescription string `json:"script_description,omitempty"`
Comment string `json:"comment"`
Valid bool `json:"valid"`
Sequence uint32 `json:"sequence,omitempty"`
CanSign1 bool `json:"can_sign_1,omitempty"`
CanSign2 bool `json:"can_sign_2,omitempty"`
CanSign3 bool `json:"can_sign_3,omitempty"`
HasPreimage bool `json:"has_preimage,omitempty"`
}
// TestRedeem tests that the script generated from a miniscript can be spent
// successfully.
func TestRedeem(t *testing.T) {
t.Parallel()
fileBytes, err := os.ReadFile(filepath.Join("testdata", "redeem.json"))
require.NoError(t, err)
vec := &RedeemTestVectors{}
err = json.Unmarshal(fileBytes, vec)
require.NoError(t, err)
unHex := func(s string) []byte {
b, err := hex.DecodeString(s)
require.NoError(t, err)
return b
}
lookupVar := func(identifier string) ([]byte, error) {
return unHex(vec.Identifiers[identifier]), nil
}
sign := func(canSign1, canSign2, canSign3 bool) testSignFn {
privKey1Bytes := unHex(vec.Identifiers["pk_1"])
privKey2Bytes := unHex(vec.Identifiers["pk_2"])
privKey3Bytes := unHex(vec.Identifiers["pk_3"])
privKey1, pubKey1 := btcec.PrivKeyFromBytes(privKey1Bytes)
privKey2, pubKey2 := btcec.PrivKeyFromBytes(privKey2Bytes)
privKey3, pubKey3 := btcec.PrivKeyFromBytes(privKey3Bytes)
return func(pk []byte, hash []byte) ([]byte, bool) {
isPk1 := bytes.Equal(pk, pubKey1.SerializeCompressed())
isPk2 := bytes.Equal(pk, pubKey2.SerializeCompressed())
isPk3 := bytes.Equal(pk, pubKey3.SerializeCompressed())
if canSign1 && isPk1 {
return ecdsa.Sign(privKey1, hash).Serialize(),
true
}
if canSign2 && isPk2 {
return ecdsa.Sign(privKey2, hash).Serialize(),
true
}
if canSign3 && isPk3 {
return ecdsa.Sign(privKey3, hash).Serialize(),
true
}
return nil, false
}
}
preimage := func(hasPreimage bool) PreimageFunc {
preimage := unHex(vec.Identifiers["preimage_1"])
return func(hashFunc string, hash []byte) ([]byte, bool) {
if !hasPreimage {
return nil, false
}
switch hashFunc {
case "ripemd160":
h := btcutil.Hash160(preimage)
return preimage, bytes.Equal(hash, h)
case "sha256":
h := chainhash.HashB(preimage)
return preimage, bytes.Equal(hash, h)
}
return nil, false
}
}
for _, tc := range vec.TestCases {
t.Logf("-----------------------------------")
t.Logf("Test case: %s", tc.Comment)
t.Logf("-----------------------------------")
vec.TestCases = append(vec.TestCases, tc)
err := testRedeem(
t, tc.Miniscript, lookupVar, tc.Sequence,
sign(tc.CanSign1, tc.CanSign2, tc.CanSign3),
preimage(tc.HasPreimage),
)
t.Logf("\n\n")
if !tc.Valid {
require.Errorf(
t, err, "comment: %s, miniscript: %s",
tc.Comment, tc.Miniscript,
)
continue
}
require.NoErrorf(
t, err, "comment: %s, miniscript: %s", tc.Comment,
tc.Miniscript,
)
}
}
// TestComputeOpCount tests that the maxOpCount function returns the correct
// number of operations.
func TestComputeOpCount(t *testing.T) {
testCases := []struct {
script string
maxOpCount int
}{
{
script: "or_i(multi(2,key1,key2,key3)," +
"multi(3,key4,key5,key6,key7))",
maxOpCount: 9,
},
{
script: "thresh(2,or_i(multi(2,key1,key2,key3)," +
"multi(3,key4,key5,key6,key7))," +
"s:pk(key8),s:pk(key9))",
maxOpCount: 16,
},
{
script: "thresh(2,or_d(multi(2,key1,key2,key3)," +
"multi(3,key4,key5,key6,key7))," +
"s:pk(key8),s:pk(key9))",
maxOpCount: 19,
},
}
for _, tc := range testCases {
node, err := Parse(tc.script)
require.NoError(t, err)
require.Equal(t, tc.maxOpCount, node.maxOpCount())
}
}

View file

@ -0,0 +1,3 @@
Test vectors taken from:
https://github.com/rust-bitcoin/rust-miniscript/blob/a0648b3a4d63abbe53f621308614f97f04a04096/src/miniscript/ms_tests.rs

File diff suppressed because it is too large Load diff

5574
txscript/miniscript/testdata/invalid.txt vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

18185
txscript/miniscript/testdata/opcodes.txt vendored Normal file

File diff suppressed because it is too large Load diff

304
txscript/miniscript/testdata/redeem.json vendored Normal file
View file

@ -0,0 +1,304 @@
{
"identifiers": {
"pk_1": "22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c",
"key_1": "02a673638cb9587cb68ea08dbef685c6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5",
"pk_2": "9106e8d2191b58e6c12f10b70d86ba54396db99ecedffd3c150e72960bd11305",
"key_2": "034a3f8cebf79df4dbaf67db66b4b20d115a1003c1d8863f977d027600b4db175b",
"pk_3": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"key_3": "026a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3",
"preimage_1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"h256_1": "e0e77a507412b120f6ede61f62295b1a7b2ff19d3dcc8f7253e51663470c888e",
"h160_1": "b3256e789b42b4e73b0954beb516ec7dfc032dd3",
"h256_2": "926a54995ca48600920a19bf7bc502ca5f2f7d07e6f804c4f00ebf0325084dbc"
},
"test_cases": [
{
"miniscript": "pk(key_1)",
"script_description": "A single key.",
"comment": "key_1 signs.",
"valid": true,
"can_sign_1": true
},
{
"miniscript": "pk(key_1)",
"comment": "No satisfaction (key_1 missing).",
"valid": false
},
{
"miniscript": "or_b(pk(key_1),s:pk(key_2))",
"script_description": "One of two keys (equally likely).",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_b(pk(key_1),s:pk(key_2))",
"comment": "key_1 signs.",
"valid": true,
"can_sign_1": true
},
{
"miniscript": "or_b(pk(key_1),s:pk(key_2))",
"comment": "key_2 signs.",
"valid": true,
"can_sign_2": true
},
{
"miniscript": "or_b(pk(key_1),s:pk(key_2))",
"comment": "both keys sign.",
"valid": true,
"can_sign_1": true,
"can_sign_2": true
},
{
"miniscript": "and_v(v:pk(key_1),or_d(pk(key_2),older(12960)))",
"script_description": "A user and a 2FA service need to sign off, but after 90 days the user alone is enough.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "and_v(v:pk(key_1),or_d(pk(key_2),older(12960)))",
"comment": "key_1 signs alone before 12960.",
"valid": false,
"sequence": 12959,
"can_sign_1": true
},
{
"miniscript": "and_v(v:pk(key_1),or_d(pk(key_2),older(12960)))",
"comment": "key_1 signs alone, utxo older than 12960.",
"valid": true,
"sequence": 12960,
"can_sign_1": true
},
{
"miniscript": "and_v(v:pk(key_1),or_d(pk(key_2),older(12960)))",
"comment": "key_2 signs alone before 12960.",
"valid": false,
"sequence": 12959,
"can_sign_2": true
},
{
"miniscript": "and_v(v:pk(key_1),or_d(pk(key_2),older(12960)))",
"comment": "key_2 signs alone after 12960.",
"valid": false,
"sequence": 12960,
"can_sign_2": true
},
{
"miniscript": "and_v(v:pk(key_1),or_d(pk(key_2),older(12960)))",
"comment": "Both sign before 12960.",
"valid": true,
"can_sign_1": true,
"can_sign_2": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"script_description": "A 3-of-3 that turns into a 2-of-3 after 90 days.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "All three sign before 12960.",
"valid": true,
"can_sign_1": true,
"can_sign_2": true,
"can_sign_3": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "Only one signs before 12960.",
"valid": false,
"can_sign_1": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "Only two sign before 12960.",
"valid": false,
"can_sign_1": true,
"can_sign_2": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "Only two sign before 12960.",
"valid": false,
"can_sign_1": true,
"can_sign_3": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "Only two sign before 12960.",
"valid": false,
"can_sign_2": true,
"can_sign_3": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "Two sign after 12960.",
"valid": true,
"sequence": 12960,
"can_sign_1": true,
"can_sign_2": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "Two sign after 12960.",
"valid": true,
"sequence": 12960,
"can_sign_1": true,
"can_sign_3": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "One signs after 12960.",
"valid": false,
"sequence": 12960,
"can_sign_1": true
},
{
"miniscript": "thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))",
"comment": "No one signs after 12960.",
"valid": false,
"sequence": 12960
},
{
"miniscript": "andor(pk(key_1),older(1008),pk(key_2))",
"script_description": "The BOLT #3 to_local policy.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "andor(pk(key_1),older(1008),pk(key_2))",
"comment": "key_1 signs before 1008.",
"valid": false,
"can_sign_1": true
},
{
"miniscript": "andor(pk(key_1),older(1008),pk(key_2))",
"comment": "key_1 signs after 1008.",
"valid": true,
"sequence": 1008,
"can_sign_1": true
},
{
"miniscript": "andor(pk(key_1),older(1008),pk(key_2))",
"comment": "key_2 signs before 1008.",
"valid": true,
"can_sign_2": true
},
{
"miniscript": "t:or_c(pk(key_1),and_v(v:pk(key_2),or_c(pk(key_3),v:hash160(h160_1))))",
"script_description": "The BOLT #3 offered HTLC policy",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "and_v(v:pk(key_1),sha256(h256_1))",
"script_description": "A key and a preimage.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "and_v(v:pk(key_1),sha256(h256_1))",
"comment": "key_1 but no preimage.",
"valid": false,
"can_sign_1": true
},
{
"miniscript": "and_v(v:pk(key_1),sha256(h256_1))",
"comment": "Preimage but no key_1.",
"valid": false,
"has_preimage": true
},
{
"miniscript": "and_v(v:pk(key_1),sha256(h256_1))",
"comment": "Both key_1 and preimage.",
"valid": true,
"can_sign_1": true,
"has_preimage": true
},
{
"miniscript": "and_v(v:pk(key_1),pk(key_2))",
"script_description": "A 2-of-2 multisig.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "and_b(c:pk_k(key_1),s:pk(key_2))",
"script_description": "a 2-of-2 multisig with boolean logic.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_b(pk(key_1),s:pk(key_2))",
"script_description": "A 1-of-2 multisig with boolean logic.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "multi(2,key_1,key_2,key_3)",
"script_description": "A 2-of-3 multisig.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "thresh(1,thresh(2,sha256(h256_2),sc:pk_k(key_2),sc:pk_k(key_3)),sc:pk_k(key_4),sc:pk_k(key_5))",
"script_description": "Nested threshold with preimage and 4 keys.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "and_v(or_c(multi(2,key_1,key_2,key_3),v:sha256(h256_2)),1)",
"script_description": "A 2-of-3 multisig or hash lock with superfluous boolean AND.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "and_v(v:sha256(h256_1),c:pk_k(key_1))",
"script_description": "A hash lock and single key.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_d(multi(2,key_1,key_2,key_3),multi(2,key_4,key_5))",
"script_description": "A 2-of-3 multisig or a 2-of-2 multisig.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_i(multi(2,key_1,key_2,key_3),multi(2,key_4,key_5))",
"script_description": "A 2-of-3 multisig or a 2-of-2 multisig with IF.",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "and_v(v:pk(key_1),or_d(pk(key_2),after(12960)))",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_d(pk(key_1),and_v(v:pk(key_2),older(0)))",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_b(pk(key_1),s:pk(key_2))",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "andor(pk(key_1),pk(key_2),and_v(v:multi(2,key_3,key_4,key_5),older(2)))",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_b(and_b(pk(key_1),s:pk(key_2)),s:pk(key_3))",
"comment": "No satisfaction (keys missing).",
"valid": false
},
{
"miniscript": "or_b(and_b(sha256(h256_1),s:pk(key_2)),s:pk(key_3))",
"comment": "No satisfaction (keys missing).",
"valid": false
}
]
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff