diff --git a/api/firmware/backup_test.go b/api/firmware/backup_test.go index c457338..93c175c 100644 --- a/api/firmware/backup_test.go +++ b/api/firmware/backup_test.go @@ -3,7 +3,6 @@ package firmware import ( - "bytes" "testing" "github.com/stretchr/testify/require" @@ -12,7 +11,7 @@ import ( func TestSimulatorBackups(t *testing.T) { const seedLen = 32 const testName = "test wallet name" - testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() require.NoError(t, device.SetDeviceName(testName)) diff --git a/api/firmware/bip85_test.go b/api/firmware/bip85_test.go index d372262..1319d7a 100644 --- a/api/firmware/bip85_test.go +++ b/api/firmware/bip85_test.go @@ -3,7 +3,6 @@ package firmware import ( - "bytes" "encoding/hex" "testing" @@ -16,7 +15,7 @@ func TestSimulatorBIP85AppBip39(t *testing.T) { } func TestSimulatorBIP85AppLN(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() entropy, err := device.BIP85AppLN() require.NoError(t, err) diff --git a/api/firmware/bitboxsync_test.go b/api/firmware/bitboxsync_test.go index 8a8d803..727c253 100644 --- a/api/firmware/bitboxsync_test.go +++ b/api/firmware/bitboxsync_test.go @@ -195,7 +195,7 @@ func TestBitBoxSyncHostSideValidation(t *testing.T) { } func TestSimulatorBitBoxSyncIdentity(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() if !device.Version().AtLeast(bitBoxSyncMinVersion) { identity, err := device.BitBoxSyncIdentity() @@ -214,7 +214,7 @@ func TestSimulatorBitBoxSyncIdentity(t *testing.T) { } func TestSimulatorBitBoxSyncSignaturesAndUnwrap(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() challenge := bytes.Repeat([]byte{0x42}, bitBoxSyncChallengeLength) namespaceID := bytes.Repeat([]byte{0x11}, bitBoxSyncNamespaceIDLen) diff --git a/api/firmware/btc.go b/api/firmware/btc.go index 729492a..b6e73f1 100644 --- a/api/firmware/btc.go +++ b/api/firmware/btc.go @@ -258,8 +258,14 @@ func (device *Device) nonAtomicNestedQueryBtcSign(request *messages.BTCRequest) } func isTaproot(sc *messages.BTCScriptConfigWithKeypath) bool { - simpleTypeConfig, ok := sc.ScriptConfig.Config.(*messages.BTCScriptConfig_SimpleType_) - return ok && simpleTypeConfig.SimpleType == messages.BTCScriptConfig_P2TR + switch config := sc.ScriptConfig.Config.(type) { + case *messages.BTCScriptConfig_SimpleType_: + return config.SimpleType == messages.BTCScriptConfig_P2TR + case *messages.BTCScriptConfig_Policy_: + return strings.HasPrefix(config.Policy.GetPolicy(), "tr(") + default: + return false + } } // BTCSignNeedsPrevTxs returns true if the PrevTx field in BTCTxInput needs to be populated before diff --git a/api/firmware/btc_test.go b/api/firmware/btc_test.go index 9babce3..2528645 100644 --- a/api/firmware/btc_test.go +++ b/api/firmware/btc_test.go @@ -3,120 +3,31 @@ package firmware import ( - "bytes" "errors" "fmt" - "slices" "testing" "github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages" "github.com/BitBoxSwiss/bitbox02-api-go/util/semver" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/hdkeychain" - "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" "google.golang.org/protobuf/proto" ) const hardenedKeyStart = 0x80000000 -func p2wpkhPkScript(pubkey *btcec.PublicKey) []byte { - pubkeyHash := btcutil.Hash160(pubkey.SerializeCompressed()) - addr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash, &chaincfg.MainNetParams) - if err != nil { - panic(err) - } - pkScript, err := txscript.PayToAddrScript(addr) - if err != nil { - panic(err) - } - return pkScript -} - -func p2trPkScript(xonlyPubkey []byte) []byte { - addr, err := btcutil.NewAddressTaproot(xonlyPubkey, &chaincfg.MainNetParams) - if err != nil { - panic(err) - } - pkScript, err := txscript.PayToAddrScript(addr) - if err != nil { - panic(err) - } - return pkScript -} - -func p2shPkScript(redeemScript []byte) []byte { - addr, err := btcutil.NewAddressScriptHash(redeemScript, &chaincfg.MainNetParams) - if err != nil { - panic(err) - } - pkScript, err := txscript.PayToAddrScript(addr) - if err != nil { - panic(err) - } - return pkScript -} - -// P2WSH multisig witnessScript and pubkeyScript from these xpubs, derived at /<0;1>/*. -// The pubkeys will be sorted lexicographically. -func multisigP2WSH(threshold int, xpubs []string, change bool, index uint32) ([]byte, []byte) { - pubkeys := make([]*btcutil.AddressPubKey, len(xpubs)) - for i, xpubStr := range xpubs { - changeIndex := uint32(0) - if change { - changeIndex = 1 - } - xpub := mustXpub(xpubStr, changeIndex, index) - pubKey, err := xpub.ECPubKey() - if err != nil { - panic(err) - } - addrPubKey, err := btcutil.NewAddressPubKey(pubKey.SerializeCompressed(), &chaincfg.MainNetParams) - if err != nil { - panic(err) - } - pubkeys[i] = addrPubKey - } - slices.SortFunc(pubkeys, func(a, b *btcutil.AddressPubKey) int { - return bytes.Compare(a.ScriptAddress(), b.ScriptAddress()) - }) - witnessScript, err := txscript.MultiSigScript(pubkeys, threshold) - if err != nil { - panic(err) - } - addr, err := btcutil.NewAddressWitnessScriptHash(chainhash.HashB(witnessScript), &chaincfg.MainNetParams) - if err != nil { - panic(err) - } - pkScript, err := txscript.PayToAddrScript(addr) - if err != nil { - panic(err) - } - return witnessScript, pkScript -} - -//nolint:unparam -func mustOutpoint(s string) *wire.OutPoint { - outPoint, err := wire.NewOutPointFromString(s) - if err != nil { - panic(err) - } - return outPoint -} - func parseECDSASignature(t *testing.T, sig []byte) *ecdsa.Signature { t.Helper() require.Len(t, sig, 64) r := new(btcec.ModNScalar) - r.SetByteSlice(sig[:32]) + require.False(t, r.SetByteSlice(sig[:32]), "ECDSA r scalar overflows the group order") + require.False(t, r.IsZero(), "ECDSA r scalar is zero") s := new(btcec.ModNScalar) - s.SetByteSlice(sig[32:]) + require.False(t, s.SetByteSlice(sig[32:]), "ECDSA s scalar overflows the group order") + require.False(t, s.IsZero(), "ECDSA s scalar is zero") return ecdsa.NewSignature(r, s) } @@ -134,7 +45,7 @@ func TestNewXPub(t *testing.T) { } func TestSimulatorBTCXpub(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() xpub, err := device.BTCXPub(messages.BTCCoin_TBTC, []uint32{ 49 + hardenedKeyStart, @@ -147,7 +58,7 @@ func TestSimulatorBTCXpub(t *testing.T) { } func TestSimulatorBTCXPubs(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() xpubs, err := device.BTCXPubs(messages.BTCCoin_TBTC, [][]uint32{ @@ -180,7 +91,7 @@ func TestSimulatorBTCXPubs(t *testing.T) { } func TestSimulatorBTCAddress(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() // TBTC, P2WPKH address, err := device.BTCAddress( @@ -264,7 +175,7 @@ func simulatorPub(t *testing.T, device *Device, keypath ...uint32) *btcec.Public } func TestSimulatorBTCSignMessage(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() coin := messages.BTCCoin_BTC keypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 10} @@ -485,516 +396,47 @@ func TestBTCSignMessage(t *testing.T) { }) } -func makeTaprootOutput(t *testing.T, pubkey *btcec.PublicKey) (*btcec.PublicKey, []byte) { - t.Helper() - outputKey := txscript.ComputeTaprootKeyNoScript(pubkey) - outputPkScript, err := txscript.PayToTaprootScript(outputKey) - require.NoError(t, err) - return outputKey, outputPkScript -} - -// Test signing; all inputs are BIP86 Taproot keyspends. -func TestSimulatorBTCSignTaprootKeySpend(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - coin := messages.BTCCoin_BTC - accountKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart} - inputKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0} - input2Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 1} - changeKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 1, 0} - - _, input1PkScript := makeTaprootOutput(t, simulatorPub(t, device, inputKeypath...)) - _, input2PkScript := makeTaprootOutput(t, simulatorPub(t, device, input2Keypath...)) - - prevTx := &wire.MsgTx{ - Version: 2, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 60_000_000, - PkScript: input1PkScript, - }, - { - Value: 40_000_000, - PkScript: input2PkScript, - }, - }, - LockTime: 0, - } - - scriptConfigs := []*messages.BTCScriptConfigWithKeypath{ - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR), - Keypath: accountKeypath, - }, - } - require.False(t, BTCSignNeedsPrevTxs(scriptConfigs)) - - prevTxHash := prevTx.TxHash() - _, err := device.BTCSign( - coin, - scriptConfigs, - nil, - &BTCTx{ - Version: 2, - Inputs: []*BTCTxInput{ - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 0, - PrevOutValue: uint64(prevTx.TxOut[0].Value), - Sequence: 0xFFFFFFFF, - Keypath: inputKeypath, - ScriptConfigIndex: 0, - }, - }, - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 1, - PrevOutValue: uint64(prevTx.TxOut[1].Value), - Sequence: 0xFFFFFFFF, - Keypath: input2Keypath, - ScriptConfigIndex: 0, - }, - }, - }, - Outputs: []*messages.BTCSignOutputRequest{ - { - Ours: true, - Value: 70_000_000, - Keypath: changeKeypath, - }, - { - Value: 20_000_000, - Payload: []byte("11111111111111111111111111111111"), - Type: messages.BTCOutputType_P2WSH, - }, - }, - Locktime: 0, - }, - messages.BTCSignInitRequest_DEFAULT, - ) - require.NoError(t, err) - }) -} - -// Test signing; mixed input types (p2wpkh, p2wpkh-p2sh, p2tr) -func TestSimulatorBTCSignMixed(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - coin := messages.BTCCoin_BTC - changeKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 1, 0} - input0Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0} - input1Keypath := []uint32{84 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0} - input2Keypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0} - - prevTx := &wire.MsgTx{ - Version: 2, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: func() []byte { - _, script := makeTaprootOutput(t, simulatorPub(t, device, input0Keypath...)) - return script - }(), - }, - { - Value: 100_000_000, - PkScript: p2wpkhPkScript(simulatorPub(t, device, input1Keypath...)), - }, - { - Value: 100_000_000, - PkScript: p2shPkScript(p2wpkhPkScript(simulatorPub(t, device, input2Keypath...))), - }, - }, - LockTime: 0, - } - convertedPrevTx := NewBTCPrevTxFromBtcd(prevTx) - - scriptConfigs := []*messages.BTCScriptConfigWithKeypath{ - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR), - Keypath: input0Keypath[:3], - }, - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH), - Keypath: input1Keypath[:3], - }, - - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH_P2SH), - Keypath: input2Keypath[:3], - }, - } - require.True(t, BTCSignNeedsPrevTxs(scriptConfigs)) - - prevTxHash := prevTx.TxHash() - _, err := device.BTCSign( - coin, - scriptConfigs, - nil, - &BTCTx{ - Version: 2, - Inputs: []*BTCTxInput{ - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 0, - PrevOutValue: uint64(prevTx.TxOut[0].Value), - Sequence: 0xFFFFFFFF, - Keypath: input0Keypath, - ScriptConfigIndex: 0, - }, - PrevTx: convertedPrevTx, - }, - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 1, - PrevOutValue: uint64(prevTx.TxOut[1].Value), - Sequence: 0xFFFFFFFF, - Keypath: input1Keypath, - ScriptConfigIndex: 1, - }, - PrevTx: convertedPrevTx, - }, - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 2, - PrevOutValue: uint64(prevTx.TxOut[2].Value), - Sequence: 0xFFFFFFFF, - Keypath: input2Keypath, - ScriptConfigIndex: 2, - }, - PrevTx: convertedPrevTx, - }, - }, - Outputs: []*messages.BTCSignOutputRequest{ - { - Ours: true, - Value: 270_000_000, - Keypath: changeKeypath, - }, - { - Value: 20_000_000, - Payload: []byte("11111111111111111111111111111111"), - Type: messages.BTCOutputType_P2WSH, - }, - }, - Locktime: 0, - }, - messages.BTCSignInitRequest_DEFAULT, - ) - require.NoError(t, err) - }) -} - -// Test that we can send to a silent payment output (generated by the BitBox) and verify the -// corresponding DLEQ proof on the host that the output was generated correctly. -func TestSimulatorBTCSignSilentPayment(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - coin := messages.BTCCoin_BTC - accountKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart} - input1Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0} - input2Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 1} - changeKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 1, 0} - input1Pubkey := simulatorPub(t, device, input1Keypath...) - input2Pubkey := simulatorPub(t, device, input2Keypath...) - input1OutputKey, input1PkScript := makeTaprootOutput(t, input1Pubkey) - input2OutputKey, input2PkScript := makeTaprootOutput(t, input2Pubkey) - - prevTx := &wire.MsgTx{ - Version: 2, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 60_000_000, - PkScript: input1PkScript, - }, - { - Value: 40_000_000, - PkScript: input2PkScript, - }, - }, - LockTime: 0, - } - prevTxHash := prevTx.TxHash() - result, err := device.BTCSign( - coin, - []*messages.BTCScriptConfigWithKeypath{ - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR), - Keypath: accountKeypath, - }, - }, - nil, - &BTCTx{ - Version: 2, - Inputs: []*BTCTxInput{ - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 0, - PrevOutValue: uint64(prevTx.TxOut[0].Value), - Sequence: 0xFFFFFFFF, - Keypath: input1Keypath, - ScriptConfigIndex: 0, - }, - BIP352Pubkey: schnorr.SerializePubKey(input1OutputKey), - }, - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 1, - PrevOutValue: uint64(prevTx.TxOut[1].Value), - Sequence: 0xFFFFFFFF, - Keypath: input2Keypath, - ScriptConfigIndex: 0, - }, - BIP352Pubkey: schnorr.SerializePubKey(input2OutputKey), - }, - }, - Outputs: []*messages.BTCSignOutputRequest{ - { - Ours: true, - Value: 70_000_000, - Keypath: changeKeypath, - }, - { - Value: 20_000_000, - SilentPayment: &messages.BTCSignOutputRequest_SilentPayment{ - Address: "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", - }, - }, - }, - Locktime: 0, - }, - messages.BTCSignInitRequest_DEFAULT, - ) - - if device.version.AtLeast(semver.NewSemVer(9, 21, 0)) { - require.NoError(t, err) - require.Equal(t, - map[int][]byte{ - 1: unhex("5120f99b8e8d97aa7b068dd7b4e7ae31f51784f5c2a0cae280748cfd23832b7dcba7"), - }, - result.GeneratedOutputs, - ) - } else { - require.EqualError(t, err, UnsupportedError("9.21.0").Error()) - } - }) -} - -// Tests that the BitBox displays the output as being of the same account in a self-send. -func TestSimulatorSignBTCTransactionSendSelfSameAccount(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - coin := messages.BTCCoin_BTC - - input0Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0} - input1Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 1} - - prevTx := &wire.MsgTx{ - Version: 2, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: func() []byte { - _, script := makeTaprootOutput(t, simulatorPub(t, device, input0Keypath...)) - return script - }(), - }, - }, - LockTime: 0, - } - convertedPrevTx := NewBTCPrevTxFromBtcd(prevTx) - - scriptConfigs := []*messages.BTCScriptConfigWithKeypath{ - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR), - Keypath: input0Keypath[:3], - }, - } - - prevTxHash := prevTx.TxHash() - _, err := device.BTCSign( - coin, - scriptConfigs, - nil, - &BTCTx{ - Version: 2, - Inputs: []*BTCTxInput{ - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 0, - PrevOutValue: uint64(prevTx.TxOut[0].Value), - Sequence: 0xFFFFFFFF, - Keypath: input0Keypath, - ScriptConfigIndex: 0, - }, - PrevTx: convertedPrevTx, - }, - }, - Outputs: []*messages.BTCSignOutputRequest{ - { - Ours: true, - Value: 70_000_000, - Keypath: input1Keypath, - }, - }, - Locktime: 0, - }, - messages.BTCSignInitRequest_DEFAULT, - ) - require.NoError(t, err) - - switch { - // Display changed in v9.26.0. - case device.Version().AtLeast(semver.NewSemVer(9, 26, 0)): - require.Contains(t, - stdOut.String(), - "This BitBox (same account): bc1p sz0t sdr9 sgnu kfcx 4gtw pp5e xyeq dycf qjvm 2jw6 tvsj 3k3e avts 20yu ag", - ) - // Display changed in v9.22.0. - case device.Version().AtLeast(semver.NewSemVer(9, 22, 0)): - require.Contains(t, - stdOut.String(), - "This BitBox (same account): bc1psz0tsdr9sgnukfcx4gtwpp5exyeqdycfqjvm2jw6tvsj3k3eavts20yuag", - ) - case device.Version().AtLeast(semver.NewSemVer(9, 20, 0)): - require.Contains(t, - stdOut.String(), - "This BitBox02: bc1psz0tsdr9sgnukfcx4gtwpp5exyeqdycfqjvm2jw6tvsj3k3eavts20yuag", - ) - } - // Before simulator v9.20, address confirmation data was not written to stdout. - }) -} - -// Tests that the BitBox displays the output as being of the same keystore, but different account. -func TestSimulatorSignBTCTransactionSendSelfDifferentAccount(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - coin := messages.BTCCoin_BTC - - input0Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0} - input1Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 1 + hardenedKeyStart, 0, 0} - - prevTx := &wire.MsgTx{ - Version: 2, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: func() []byte { - _, script := makeTaprootOutput(t, simulatorPub(t, device, input0Keypath...)) - return script - }(), - }, - }, - LockTime: 0, - } - convertedPrevTx := NewBTCPrevTxFromBtcd(prevTx) - - scriptConfigs := []*messages.BTCScriptConfigWithKeypath{ - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR), - Keypath: input0Keypath[:3], - }, - } - outputScriptConfigs := []*messages.BTCScriptConfigWithKeypath{ - { - ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR), - Keypath: input1Keypath[:3], - }, - } - outputScriptConfigIndex := uint32(0) - - prevTxHash := prevTx.TxHash() - _, err := device.BTCSign( - coin, - scriptConfigs, - outputScriptConfigs, - &BTCTx{ - Version: 2, - Inputs: []*BTCTxInput{ - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 0, - PrevOutValue: uint64(prevTx.TxOut[0].Value), - Sequence: 0xFFFFFFFF, - Keypath: input0Keypath, - ScriptConfigIndex: 0, - }, - PrevTx: convertedPrevTx, - }, - }, - Outputs: []*messages.BTCSignOutputRequest{ - { - Ours: true, - Value: 70_000_000, - Keypath: input1Keypath, - OutputScriptConfigIndex: &outputScriptConfigIndex, - }, - }, - Locktime: 0, - }, - messages.BTCSignInitRequest_DEFAULT, - ) - - switch { - // Display changed in v9.26.0. - case device.Version().AtLeast(semver.NewSemVer(9, 26, 0)): - require.NoError(t, err) - require.Contains(t, - stdOut.String(), - "This BitBox (account #2): bc1p zeyh tmk2 d5jr juna m30d us0p 3409 5m62 2dq7 trm7 r0g8 pwac 2gvq xh8d 47", - ) - case device.Version().AtLeast(semver.NewSemVer(9, 22, 0)): - require.NoError(t, err) - require.Contains(t, - stdOut.String(), - "This BitBox (account #2): bc1pzeyhtmk2d5jrjunam30dus0p34095m622dq7trm7r0g8pwac2gvqxh8d47", - ) - default: - // Introduced in v9.22.0. - require.EqualError(t, err, UnsupportedError("9.22.0").Error()) - return - } - }) +func TestIsTaprootScriptConfig(t *testing.T) { + tests := []struct { + name string + config *messages.BTCScriptConfig + isTaproot bool + }{ + { + name: "simple Taproot", + config: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR), + isTaproot: true, + }, + { + name: "Taproot key-spend policy", + config: NewBTCScriptConfigPolicy("tr(@0/<0;1>/*)", nil), + isTaproot: true, + }, + { + name: "Taproot script-spend policy", + config: NewBTCScriptConfigPolicy("tr(@0/<0;1>/*,pk(@1/<0;1>/*))", nil), + isTaproot: true, + }, + { + name: "SegWit policy", + config: NewBTCScriptConfigPolicy("wsh(pk(@0/<0;1>/*))", nil), + isTaproot: false, + }, + { + name: "native SegWit", + config: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH), + isTaproot: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config := &messages.BTCScriptConfigWithKeypath{ScriptConfig: test.config} + require.Equal(t, test.isTaproot, isTaproot(config)) + require.Equal(t, !test.isTaproot, BTCSignNeedsPrevTxs( + []*messages.BTCScriptConfigWithKeypath{config}, + )) + }) + } } // setupMultisigAccount is a helper function that sets up a multisig account for testing. @@ -1054,7 +496,7 @@ func setupMultisigAccount(t *testing.T, device *Device, coin messages.BTCCoin) * // 1-of-3 multisig registration and address display/verification. func TestSimulatorBTCAddressMultisig(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() coin := messages.BTCCoin_BTC @@ -1124,79 +566,3 @@ CONFIRM SCREEN END } }) } - -// 1-of-3 P2WSH multisig spend -func TestSimulatorBTCSignMultisig(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - coin := messages.BTCCoin_BTC - - setup := setupMultisigAccount(t, device, coin) - - _, inputPkScript := multisigP2WSH(1, setup.Xpubs, false, 0) - - prevTx := &wire.MsgTx{ - Version: 2, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: inputPkScript, - }, - }, - LockTime: 0, - } - convertedPrevTx := NewBTCPrevTxFromBtcd(prevTx) - - scriptConfigs := []*messages.BTCScriptConfigWithKeypath{ - { - ScriptConfig: setup.ScriptConfig, - Keypath: setup.KeypathAccount, - }, - } - require.True(t, BTCSignNeedsPrevTxs(scriptConfigs)) - - prevTxHash := prevTx.TxHash() - _, err := device.BTCSign( - coin, - scriptConfigs, - nil, - &BTCTx{ - Version: 2, - Inputs: []*BTCTxInput{ - { - Input: &messages.BTCSignInputRequest{ - PrevOutHash: prevTxHash[:], - PrevOutIndex: 0, - PrevOutValue: uint64(prevTx.TxOut[0].Value), - Sequence: 0xFFFFFFFF, - Keypath: setup.ReceiveKeypath, - ScriptConfigIndex: 0, - }, - PrevTx: convertedPrevTx, - }, - }, - Outputs: []*messages.BTCSignOutputRequest{ - { - Ours: true, - Value: 70_000_000, - Keypath: setup.ChangeKeypath, - }, - { - Value: 20_000_000, - Payload: []byte("11111111111111111111111111111111"), - Type: messages.BTCOutputType_P2WSH, - }, - }, - Locktime: 0, - }, - messages.BTCSignInitRequest_DEFAULT, - ) - require.NoError(t, err) - }) -} diff --git a/api/firmware/btc_transaction_vectors_test.go b/api/firmware/btc_transaction_vectors_test.go new file mode 100644 index 0000000..497bcb1 --- /dev/null +++ b/api/firmware/btc_transaction_vectors_test.go @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firmware + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages" + "github.com/BitBoxSwiss/bitbox02-api-go/util/semver" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +const ( + btcTransactionTestVectorsFilename = "testdata/btc-transaction-test-vectors.json" + btcVectorStdoutStableFor = 50 * time.Millisecond + btcVectorStdoutTimeout = 5 * time.Second + btcVectorDeviceErrorDisabled = 106 +) + +type btcTestVectorOutcome string + +const ( + btcTestVectorOutcomeSuccess btcTestVectorOutcome = "success" + btcTestVectorOutcomeUnsupported btcTestVectorOutcome = "unsupported" + btcTestVectorOutcomeInvalidInput btcTestVectorOutcome = "invalid_input" +) + +type btcTestVectorSignatureKind string + +const ( + btcTestVectorSignatureECDSA btcTestVectorSignatureKind = "ecdsa" + btcTestVectorSignatureTaprootKey btcTestVectorSignatureKind = "taproot_key" + btcTestVectorSignatureTaprootScript btcTestVectorSignatureKind = "taproot_script" +) + +type btcTestVectorSighash string + +const ( + btcTestVectorSighashAll btcTestVectorSighash = "all" + btcTestVectorSighashDefault btcTestVectorSighash = "default" +) + +type btcTestVector struct { + ID string `json:"id"` + Description string `json:"description"` + Coin string `json:"coin"` + PSBT btcTestVectorPSBT `json:"psbt"` + ExpectedNeedsPrevTxs bool `json:"expected_needs_prevtxs"` + Expectations []btcTestVectorVersionExpectation `json:"expectations"` + Registrations []btcTestVectorRegistration `json:"registrations,omitempty"` + ExpectedSignatures []btcTestVectorSignature `json:"expected_signatures,omitempty"` + ExpectedGeneratedOutputs map[int]string `json:"expected_generated_outputs,omitempty"` +} + +type btcTestVectorPSBT struct { + Transaction string `json:"transaction"` + Options btcTestVectorPSBTSignOptions `json:"options,omitempty"` +} + +type btcTestVectorVersionExpectation struct { + MinVersion *semver.SemVer `json:"min_version,omitempty"` + MaxVersionExclusive *semver.SemVer `json:"max_version_exclusive,omitempty"` + Outcome btcTestVectorOutcome `json:"outcome"` + UnsupportedVersion *string `json:"unsupported_version,omitempty"` + Screens []simulatorScreen `json:"screens"` +} + +type btcTestVectorPSBTSignOptions struct { + ForceScriptConfig *btcTestVectorScriptConfigWithKeypath `json:"force_script_config,omitempty"` + Outputs map[int]btcTestVectorPSBTOutputOptions `json:"outputs,omitempty"` + PaymentRequests []btcTestVectorPaymentRequest `json:"payment_requests,omitempty"` + FormatUnit string `json:"format_unit,omitempty"` +} + +type btcTestVectorPSBTOutputOptions struct { + SilentPaymentAddress string `json:"silent_payment_address,omitempty"` + PaymentRequestIndex *uint32 `json:"payment_request_index,omitempty"` +} + +type btcTestVectorPaymentRequest struct { + RecipientName string `json:"recipient_name"` + TotalAmount uint64 `json:"total_amount"` + Nonce string `json:"nonce,omitempty"` + Memos []btcTestVectorPaymentRequestMemo `json:"memos"` + Signature string `json:"signature"` +} + +type btcTestVectorPaymentRequestMemo struct { + Type string `json:"type"` + Note string `json:"note,omitempty"` + CoinType uint32 `json:"coin_type,omitempty"` + Amount string `json:"amount,omitempty"` + Address string `json:"address,omitempty"` + AddressKeypath string `json:"address_keypath,omitempty"` +} + +type btcTestVectorScriptConfigWithKeypath struct { + ScriptConfig btcTestVectorScriptConfig `json:"script_config"` + Keypath string `json:"keypath"` +} + +type btcTestVectorScriptConfig struct { + Type string `json:"type"` + ScriptType string `json:"script_type,omitempty"` + Threshold uint32 `json:"threshold,omitempty"` + Xpubs []string `json:"xpubs,omitempty"` + OurXpubIndex uint32 `json:"our_xpub_index,omitempty"` + Policy string `json:"policy,omitempty"` + Keys []btcTestVectorKeyOriginInfo `json:"keys,omitempty"` +} + +type btcTestVectorKeyOriginInfo struct { + RootFingerprint *string `json:"root_fingerprint,omitempty"` + Keypath *string `json:"keypath,omitempty"` + Xpub string `json:"xpub"` +} + +type btcTestVectorRegistration struct { + ScriptConfig btcTestVectorScriptConfig `json:"script_config"` + Keypath *string `json:"keypath,omitempty"` + Name string `json:"name"` +} + +type btcTestVectorSignature struct { + InputIndex int `json:"input_index"` + Kind btcTestVectorSignatureKind `json:"kind"` + Pubkey string `json:"pubkey,omitempty"` + LeafHash string `json:"leaf_hash,omitempty"` + Sighash btcTestVectorSighash `json:"sighash"` +} + +func loadBTCTransactionTestVectors(t *testing.T) []btcTestVector { + t.Helper() + data, err := os.ReadFile(btcTransactionTestVectorsFilename) + require.NoError(t, err) + var result struct { + Vectors []btcTestVector `json:"vectors"` + } + require.NoError(t, json.Unmarshal(data, &result)) + return result.Vectors +} + +func btcTestVectorExpectation( + t *testing.T, + expectations []btcTestVectorVersionExpectation, + version *semver.SemVer, +) *btcTestVectorVersionExpectation { + t.Helper() + for index := range expectations { + expectation := &expectations[index] + if expectation.MinVersion != nil && !version.AtLeast(expectation.MinVersion) { + continue + } + if expectation.MaxVersionExclusive != nil && version.AtLeast(expectation.MaxVersionExclusive) { + continue + } + return expectation + } + require.FailNowf(t, "missing test vector expectation", "firmware version: %s", version) + return nil +} + +func parseBTCVectorKeypath(value string) ([]uint32, error) { + if value == "m" { + return []uint32{}, nil + } + components := strings.Split(strings.TrimPrefix(value, "m/"), "/") + result := make([]uint32, len(components)) + for index, component := range components { + hardened := strings.HasSuffix(component, "'") + if hardened { + component = strings.TrimSuffix(component, "'") + } + child, err := strconv.ParseUint(component, 10, 31) + if err != nil { + return nil, err + } + result[index] = uint32(child) + if hardened { + result[index] |= HARDENED + } + } + return result, nil +} + +func btcTestVectorCoin(coin string) (messages.BTCCoin, error) { + switch coin { + case "btc": + return messages.BTCCoin_BTC, nil + case "tbtc": + return messages.BTCCoin_TBTC, nil + case "ltc": + return messages.BTCCoin_LTC, nil + default: + return 0, fmt.Errorf("unknown Bitcoin test vector coin %q", coin) + } +} + +func btcTestVectorFormatUnit(formatUnit string) (messages.BTCSignInitRequest_FormatUnit, error) { + switch formatUnit { + case "", "default": + return messages.BTCSignInitRequest_DEFAULT, nil + case "sat": + return messages.BTCSignInitRequest_SAT, nil + default: + return 0, fmt.Errorf("unknown Bitcoin test vector format unit %q", formatUnit) + } +} + +func convertBTCVectorScriptConfig(config btcTestVectorScriptConfig) (*messages.BTCScriptConfig, error) { + switch config.Type { + case "simple": + var simpleType messages.BTCScriptConfig_SimpleType + switch config.ScriptType { + case "p2wpkh": + simpleType = messages.BTCScriptConfig_P2WPKH + case "p2wpkh_p2sh": + simpleType = messages.BTCScriptConfig_P2WPKH_P2SH + case "p2tr": + simpleType = messages.BTCScriptConfig_P2TR + default: + return nil, fmt.Errorf("unknown simple script type %q", config.ScriptType) + } + return NewBTCScriptConfigSimple(simpleType), nil + + case "multisig": + converted, err := NewBTCScriptConfigMultisig( + config.Threshold, + config.Xpubs, + config.OurXpubIndex, + ) + if err != nil { + return nil, err + } + switch config.ScriptType { + case "p2wsh": + converted.GetMultisig().ScriptType = messages.BTCScriptConfig_Multisig_P2WSH + case "p2wsh_p2sh": + converted.GetMultisig().ScriptType = messages.BTCScriptConfig_Multisig_P2WSH_P2SH + default: + return nil, fmt.Errorf("unknown multisig script type %q", config.ScriptType) + } + return converted, nil + + case "policy": + keys := make([]*messages.KeyOriginInfo, len(config.Keys)) + for index, key := range config.Keys { + xpub, err := NewXPub(key.Xpub) + if err != nil { + return nil, err + } + var rootFingerprint []byte + if key.RootFingerprint != nil { + rootFingerprint, err = hex.DecodeString(*key.RootFingerprint) + if err != nil { + return nil, err + } + } + var keypath []uint32 + if key.Keypath != nil { + keypath, err = parseBTCVectorKeypath(*key.Keypath) + if err != nil { + return nil, err + } + } + keys[index] = &messages.KeyOriginInfo{ + RootFingerprint: rootFingerprint, + Keypath: keypath, + Xpub: xpub, + } + } + return NewBTCScriptConfigPolicy(config.Policy, keys), nil + + default: + return nil, fmt.Errorf("unknown script config type %q", config.Type) + } +} + +func convertBTCVectorScriptConfigWithKeypath( + config btcTestVectorScriptConfigWithKeypath, +) (*messages.BTCScriptConfigWithKeypath, error) { + converted, err := convertBTCVectorScriptConfig(config.ScriptConfig) + if err != nil { + return nil, err + } + keypath, err := parseBTCVectorKeypath(config.Keypath) + if err != nil { + return nil, err + } + return &messages.BTCScriptConfigWithKeypath{ + ScriptConfig: converted, + Keypath: keypath, + }, nil +} + +func convertBTCVectorPaymentRequest( + paymentRequest btcTestVectorPaymentRequest, +) (*messages.BTCPaymentRequestRequest, error) { + nonce, err := hex.DecodeString(paymentRequest.Nonce) + if err != nil { + return nil, err + } + signature, err := hex.DecodeString(paymentRequest.Signature) + if err != nil { + return nil, err + } + + memos := make([]*messages.BTCPaymentRequestRequest_Memo, len(paymentRequest.Memos)) + for index, memo := range paymentRequest.Memos { + switch memo.Type { + case "text": + memos[index] = &messages.BTCPaymentRequestRequest_Memo{ + Memo: &messages.BTCPaymentRequestRequest_Memo_TextMemo_{ + TextMemo: &messages.BTCPaymentRequestRequest_Memo_TextMemo{Note: memo.Note}, + }, + } + case "coin_purchase": + keypath, err := parseBTCVectorKeypath(memo.AddressKeypath) + if err != nil { + return nil, err + } + memos[index] = &messages.BTCPaymentRequestRequest_Memo{ + Memo: &messages.BTCPaymentRequestRequest_Memo_CoinPurchaseMemo_{ + CoinPurchaseMemo: &messages.BTCPaymentRequestRequest_Memo_CoinPurchaseMemo{ + CoinType: memo.CoinType, + Amount: memo.Amount, + Address: memo.Address, + AddressDerivation: &messages.BTCPaymentRequestRequest_Memo_CoinPurchaseMemo_Eth{ + Eth: &messages.BTCPaymentRequestRequest_Memo_CoinPurchaseMemo_EthAddressDerivation{ + Keypath: keypath, + }, + }, + }, + }, + } + default: + return nil, fmt.Errorf("unknown payment request memo type %q", memo.Type) + } + } + + return &messages.BTCPaymentRequestRequest{ + RecipientName: paymentRequest.RecipientName, + Nonce: nonce, + TotalAmount: paymentRequest.TotalAmount, + Memos: memos, + Signature: signature, + }, nil +} + +func convertBTCVectorPSBTOptions(options btcTestVectorPSBTSignOptions) (*PSBTSignOptions, error) { + formatUnit, err := btcTestVectorFormatUnit(options.FormatUnit) + if err != nil { + return nil, err + } + if options.ForceScriptConfig == nil && len(options.Outputs) == 0 && + len(options.PaymentRequests) == 0 && formatUnit == messages.BTCSignInitRequest_DEFAULT { + return nil, nil + } + result := &PSBTSignOptions{ + FormatUnit: formatUnit, + Outputs: make(map[int]*PSBTSignOutputOptions, len(options.Outputs)), + } + if options.ForceScriptConfig != nil { + result.ForceScriptConfig, err = convertBTCVectorScriptConfigWithKeypath(*options.ForceScriptConfig) + if err != nil { + return nil, err + } + } + for index, output := range options.Outputs { + result.Outputs[index] = &PSBTSignOutputOptions{ + SilentPaymentAddress: output.SilentPaymentAddress, + PaymentRequestIndex: output.PaymentRequestIndex, + } + } + result.PaymentRequests = make([]*messages.BTCPaymentRequestRequest, len(options.PaymentRequests)) + for index, paymentRequest := range options.PaymentRequests { + result.PaymentRequests[index], err = convertBTCVectorPaymentRequest(paymentRequest) + if err != nil { + return nil, err + } + } + return result, nil +} + +func registerBTCVectorConfigs( + device *Device, + coin messages.BTCCoin, + registrations []btcTestVectorRegistration, +) error { + for index := range registrations { + registration := ®istrations[index] + scriptConfig, err := convertBTCVectorScriptConfig(registration.ScriptConfig) + if err != nil { + return err + } + var keypath []uint32 + if registration.Keypath != nil { + keypath, err = parseBTCVectorKeypath(*registration.Keypath) + if err != nil { + return err + } + } + registered, err := device.BTCIsScriptConfigRegistered(coin, scriptConfig, keypath) + if err != nil { + return err + } + if registered { + return fmt.Errorf("script config %q is already registered", registration.Name) + } + if err := device.BTCRegisterScriptConfig(coin, scriptConfig, keypath, registration.Name); err != nil { + return err + } + } + return nil +} + +func decodeBTCVectorPSBT(encoded string) (*psbt.Packet, error) { + decoded, err := hex.DecodeString(encoded) + if err != nil { + return nil, err + } + return psbt.NewFromRawBytes(bytes.NewReader(decoded), false) +} + +func btcTestVectorSighashName(sighash txscript.SigHashType) (btcTestVectorSighash, error) { + switch sighash { + case txscript.SigHashAll: + return btcTestVectorSighashAll, nil + case txscript.SigHashDefault: + return btcTestVectorSighashDefault, nil + default: + return "", fmt.Errorf("unexpected signature sighash 0x%x", uint32(sighash)) + } +} + +func btcTestVectorTaprootSignatureSighash(signature []byte) (btcTestVectorSighash, error) { + if len(signature) != schnorr.SignatureSize && len(signature) != schnorr.SignatureSize+1 { + return "", fmt.Errorf("unexpected Taproot signature length %d", len(signature)) + } + if _, err := schnorr.ParseSignature(signature[:schnorr.SignatureSize]); err != nil { + return "", err + } + if len(signature) == schnorr.SignatureSize { + return btcTestVectorSighashDefault, nil + } + return btcTestVectorSighashName(txscript.SigHashType(signature[schnorr.SignatureSize])) +} + +func btcTestVectorObservedSignatureSlots( + packet *psbt.Packet, +) ([]btcTestVectorSignature, error) { + result := []btcTestVectorSignature{} + for inputIndex := range packet.Inputs { + input := &packet.Inputs[inputIndex] + for _, partialSignature := range input.PartialSigs { + if len(partialSignature.Signature) < 2 { + return nil, fmt.Errorf("input %d contains an empty ECDSA signature", inputIndex) + } + if _, err := btcec.ParsePubKey(partialSignature.PubKey); err != nil { + return nil, err + } + if _, err := ecdsa.ParseDERSignature(partialSignature.Signature[:len(partialSignature.Signature)-1]); err != nil { + return nil, err + } + sighash, err := btcTestVectorSighashName( + txscript.SigHashType(partialSignature.Signature[len(partialSignature.Signature)-1]), + ) + if err != nil { + return nil, err + } + result = append(result, btcTestVectorSignature{ + InputIndex: inputIndex, + Kind: btcTestVectorSignatureECDSA, + Pubkey: hex.EncodeToString(partialSignature.PubKey), + Sighash: sighash, + }) + } + if input.TaprootKeySpendSig != nil { + sighash, err := btcTestVectorTaprootSignatureSighash(input.TaprootKeySpendSig) + if err != nil { + return nil, err + } + result = append(result, btcTestVectorSignature{ + InputIndex: inputIndex, + Kind: btcTestVectorSignatureTaprootKey, + Pubkey: hex.EncodeToString(input.TaprootInternalKey), + Sighash: sighash, + }) + } + for _, taprootSignature := range input.TaprootScriptSpendSig { + if _, err := schnorr.ParseSignature(taprootSignature.Signature); err != nil { + return nil, err + } + sighash, err := btcTestVectorSighashName(taprootSignature.SigHash) + if err != nil { + return nil, err + } + result = append(result, btcTestVectorSignature{ + InputIndex: inputIndex, + Kind: btcTestVectorSignatureTaprootScript, + Pubkey: hex.EncodeToString(taprootSignature.XOnlyPubKey), + LeafHash: hex.EncodeToString(taprootSignature.LeafHash), + Sighash: sighash, + }) + } + } + return result, nil +} + +func btcTestVectorInsertedSignatureSlots( + before []btcTestVectorSignature, + after []btcTestVectorSignature, +) ([]btcTestVectorSignature, error) { + remaining := make(map[btcTestVectorSignature]int, len(after)) + for _, signature := range after { + remaining[signature]++ + } + for _, signature := range before { + if remaining[signature] == 0 { + return nil, fmt.Errorf("signing removed or changed signature slot %+v", signature) + } + remaining[signature]-- + } + + inserted := make([]btcTestVectorSignature, 0, len(after)-len(before)) + for signature, count := range remaining { + for range count { + inserted = append(inserted, signature) + } + } + return inserted, nil +} + +func changedBTCVectorPSBTOutputs(before [][]byte, packet *psbt.Packet) map[int]string { + var result map[int]string + for index, output := range packet.UnsignedTx.TxOut { + if !bytes.Equal(before[index], output.PkScript) { + if result == nil { + result = map[int]string{} + } + result[index] = hex.EncodeToString(output.PkScript) + } + } + return result +} + +func btcVectorFinalizationSkipReason(vector *btcTestVector) string { + switch vector.ID { + case "policy-wsh": + return "btcd's PSBT finalizer does not support this BIP388 WSH policy" + case "policy-tr-unspendable-internal-key", "policy-tr-unspendable-internal-key-complex": + return "btcd's PSBT finalizer does not order Taproot multi_a signatures correctly" + default: + return "" + } +} + +// The btcd finalizer requires witness UTXOs even when a valid SegWit PSBT supplied the full +// previous transaction. Add them only after signing so the vector still exercises conversion of +// its original PSBT representation. +func addBTCVectorWitnessUTXOsForFinalization(packet *psbt.Packet) { + for index := range packet.Inputs { + input := &packet.Inputs[index] + if input.WitnessUtxo != nil || input.NonWitnessUtxo == nil { + continue + } + outputIndex := packet.UnsignedTx.TxIn[index].PreviousOutPoint.Index + previousOutput := input.NonWitnessUtxo.TxOut[outputIndex] + input.WitnessUtxo = wire.NewTxOut(previousOutput.Value, append([]byte(nil), previousOutput.PkScript...)) + } +} + +func assertBTCVectorSignOutcome( + t *testing.T, + err error, + expectation *btcTestVectorVersionExpectation, +) bool { + t.Helper() + switch expectation.Outcome { + case btcTestVectorOutcomeSuccess: + require.NoError(t, err) + return true + case btcTestVectorOutcomeUnsupported: + require.NotNil(t, expectation.UnsupportedVersion) + require.EqualError(t, err, UnsupportedError(*expectation.UnsupportedVersion).Error()) + return false + case btcTestVectorOutcomeInvalidInput: + require.Error(t, err) + require.Truef( + t, + isErrorCode(err, ErrInvalidInput), + "expected invalid input, got %T: %v", + err, + err, + ) + return false + default: + require.Failf(t, "unknown Bitcoin test vector outcome", "%q", expectation.Outcome) + return false + } +} + +func assertBTCVectorSetupOutcome( + t *testing.T, + err error, + expectation *btcTestVectorVersionExpectation, +) { + t.Helper() + switch expectation.Outcome { + case btcTestVectorOutcomeSuccess: + require.NoError(t, err) + case btcTestVectorOutcomeUnsupported: + require.NotNil(t, expectation.UnsupportedVersion) + require.Error(t, err) + clientUnsupported := err.Error() == UnsupportedError(*expectation.UnsupportedVersion).Error() + deviceUnsupported := isErrorCode(err, ErrInvalidInput) || + isErrorCode(err, btcVectorDeviceErrorDisabled) + require.Truef( + t, + clientUnsupported || deviceUnsupported, + "expected unsupported setup error for firmware before %s, got %T: %v", + *expectation.UnsupportedVersion, + err, + err, + ) + case btcTestVectorOutcomeInvalidInput: + require.Error(t, err) + require.Truef( + t, + isErrorCode(err, ErrInvalidInput), + "expected invalid input, got %T: %v", + err, + err, + ) + default: + require.Failf(t, "unknown Bitcoin test vector outcome", "%q", expectation.Outcome) + } +} + +func runBTCPSBTTestVector( + t *testing.T, + device *Device, + coin messages.BTCCoin, + vector *btcTestVector, + expectation *btcTestVectorVersionExpectation, +) { + t.Helper() + packet, err := decodeBTCVectorPSBT(vector.PSBT.Transaction) + require.NoError(t, err) + options, err := convertBTCVectorPSBTOptions(vector.PSBT.Options) + require.NoError(t, err) + beforeSignatures, err := btcTestVectorObservedSignatureSlots(packet) + require.NoError(t, err) + + needsNonWitnessUTXOs, err := device.BTCSignNeedsNonWitnessUTXOs(packet, options) + require.NoError(t, err) + require.Equal(t, vector.ExpectedNeedsPrevTxs, needsNonWitnessUTXOs) + + outputScriptsBefore := make([][]byte, len(packet.UnsignedTx.TxOut)) + for index, output := range packet.UnsignedTx.TxOut { + outputScriptsBefore[index] = append([]byte(nil), output.PkScript...) + } + err = device.BTCSignPSBT(coin, packet, options) + afterSignatures, signaturesErr := btcTestVectorObservedSignatureSlots(packet) + require.NoError(t, signaturesErr) + if !assertBTCVectorSignOutcome(t, err, expectation) { + require.ElementsMatch( + t, + beforeSignatures, + afterSignatures, + "failed signing changed PSBT signatures", + ) + return + } + + insertedSignatures, err := btcTestVectorInsertedSignatureSlots(beforeSignatures, afterSignatures) + require.NoError(t, err) + require.ElementsMatch(t, vector.ExpectedSignatures, insertedSignatures) + + require.Equal(t, vector.ExpectedGeneratedOutputs, changedBTCVectorPSBTOutputs(outputScriptsBefore, packet)) + + if reason := btcVectorFinalizationSkipReason(vector); reason != "" { + t.Run("finalization", func(t *testing.T) { + t.Skip(reason) + }) + return + } + addBTCVectorWitnessUTXOsForFinalization(packet) + require.NoError(t, psbt.MaybeFinalizeAll(packet)) + require.NoError(t, txValidityCheck(packet)) +} + +func runBTCTransactionTestVector( + t *testing.T, + device *Device, + stdout *simulatorStdout, + vector *btcTestVector, +) { + t.Helper() + expectation := btcTestVectorExpectation(t, vector.Expectations, device.Version()) + + coin, err := btcTestVectorCoin(vector.Coin) + require.NoError(t, err) + setupErr := registerBTCVectorConfigs(device, coin, vector.Registrations) + require.NoError(t, stdout.waitUntilStable(btcVectorStdoutStableFor, btcVectorStdoutTimeout)) + + screenCapture := newSimulatorScreenCapture(stdout) + if setupErr != nil { + assertBTCVectorSetupOutcome(t, setupErr, expectation) + } else { + runBTCPSBTTestVector(t, device, coin, vector, expectation) + } + var screens []simulatorScreen + if setupErr == nil && expectation.Outcome == btcTestVectorOutcomeSuccess { + screens, err = screenCapture.waitForTerminalStatus(btcVectorStdoutStableFor, btcVectorStdoutTimeout) + } else { + require.NoError(t, stdout.waitUntilStable(btcVectorStdoutStableFor, btcVectorStdoutTimeout)) + screens, err = screenCapture.screensSinceCheckpoint() + } + require.NoError(t, err) + if os.Getenv("UPDATE_BTC_VECTOR_SCREENS") == "1" { + encodedScreens, err := json.Marshal(screens) + require.NoError(t, err) + t.Logf("screens for %s on firmware %s: %s", vector.ID, device.Version(), encodedScreens) + return + } + require.Equal(t, expectation.Screens, screens) +} + +func TestSimulatorBTCTransactionVectors(t *testing.T) { + vectors := loadBTCTransactionTestVectors(t) + for vectorIndex := range vectors { + vector := &vectors[vectorIndex] + t.Run(vector.ID, func(t *testing.T) { + t.Log(vector.Description) + testInitializedSimulators(t, func(t *testing.T, device *Device, stdout *simulatorStdout) { + t.Helper() + runBTCTransactionTestVector(t, device, stdout, vector) + }) + }) + } +} + +func TestBTCVectorPaymentRequestSighash(t *testing.T) { + vectors := loadBTCTransactionTestVectors(t) + var vector *btcTestVector + for index := range vectors { + if vectors[index].ID == "payment-request" { + vector = &vectors[index] + break + } + } + require.NotNil(t, vector) + + packet, err := decodeBTCVectorPSBT(vector.PSBT.Transaction) + require.NoError(t, err) + paymentRequest, err := convertBTCVectorPaymentRequest(vector.PSBT.Options.PaymentRequests[0]) + require.NoError(t, err) + + outputIndex := -1 + for index, output := range vector.PSBT.Options.Outputs { + if output.PaymentRequestIndex != nil && *output.PaymentRequestIndex == 0 { + outputIndex = index + break + } + } + require.NotEqual(t, -1, outputIndex) + output := packet.UnsignedTx.TxOut[outputIndex] + _, addresses, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, &chaincfg.TestNet3Params) + require.NoError(t, err) + require.Len(t, addresses, 1) + + sighash, err := ComputePaymentRequestSighash( + paymentRequest, + 1, + uint64(output.Value), + addresses[0].EncodeAddress(), + ) + require.NoError(t, err) + privateKey, _ := btcec.PrivKeyFromBytes([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + signature, err := ecdsa.SignCompact(privateKey, sighash, true) + require.NoError(t, err) + require.Equal(t, paymentRequest.Signature, signature[1:]) +} diff --git a/api/firmware/cardano_test.go b/api/firmware/cardano_test.go index 450e73f..cd9b79d 100644 --- a/api/firmware/cardano_test.go +++ b/api/firmware/cardano_test.go @@ -3,7 +3,6 @@ package firmware import ( - "bytes" "encoding/hex" "testing" @@ -12,7 +11,7 @@ import ( ) func TestSimulatorCardanoXPubs(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() xpubs, err := device.CardanoXPubs( [][]uint32{ @@ -34,7 +33,7 @@ func TestSimulatorCardanoXPubs(t *testing.T) { } func TestSimulatorCardanoAddress(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() const account = uint32(1) const rolePayment = uint32(0) // receive diff --git a/api/firmware/device_test.go b/api/firmware/device_test.go index b6a3ce3..945a225 100644 --- a/api/firmware/device_test.go +++ b/api/firmware/device_test.go @@ -35,7 +35,136 @@ import ( "google.golang.org/protobuf/proto" ) -func runSimulator(filename string) (func() error, *Device, *bytes.Buffer, error) { +type simulatorStdout struct { + mu sync.Mutex + cond *sync.Cond + buffer bytes.Buffer + lastWrite time.Time + closed bool + scanErr error +} + +func newSimulatorStdout() *simulatorStdout { + stdout := &simulatorStdout{} + stdout.cond = sync.NewCond(&stdout.mu) + return stdout +} + +func (stdout *simulatorStdout) writeLine(line []byte) { + stdout.mu.Lock() + defer stdout.mu.Unlock() + _, _ = stdout.buffer.Write(line) + _ = stdout.buffer.WriteByte('\n') + stdout.lastWrite = time.Now() + stdout.cond.Broadcast() +} + +func (stdout *simulatorStdout) WriteString(value string) (int, error) { + stdout.mu.Lock() + defer stdout.mu.Unlock() + written, err := stdout.buffer.WriteString(value) + stdout.lastWrite = time.Now() + stdout.cond.Broadcast() + return written, err +} + +func (stdout *simulatorStdout) String() string { + stdout.mu.Lock() + defer stdout.mu.Unlock() + return stdout.buffer.String() +} + +func (stdout *simulatorStdout) checkpoint() int { + stdout.mu.Lock() + defer stdout.mu.Unlock() + return stdout.buffer.Len() +} + +func (stdout *simulatorStdout) snapshot(checkpoint int) (string, error) { + stdout.mu.Lock() + defer stdout.mu.Unlock() + return stdout.snapshotLocked(checkpoint) +} + +func (stdout *simulatorStdout) snapshotLocked(checkpoint int) (string, error) { + if checkpoint < 0 || checkpoint > stdout.buffer.Len() { + return "", fmt.Errorf( + "simulator stdout checkpoint %d exceeds stdout length %d", + checkpoint, + stdout.buffer.Len(), + ) + } + return string(stdout.buffer.Bytes()[checkpoint:]), nil +} + +// wait returns once ready matches and stdout has remained unchanged for stableFor. +func (stdout *simulatorStdout) wait( + checkpoint int, + ready func(string) bool, + stableFor time.Duration, + timeout time.Duration, +) (string, error) { + started := time.Now() + deadline := started.Add(timeout) + stdout.mu.Lock() + defer stdout.mu.Unlock() + + for { + snapshot, err := stdout.snapshotLocked(checkpoint) + if err != nil { + return "", err + } + now := time.Now() + isReady := ready(snapshot) + stableSince := started + if stdout.lastWrite.After(stableSince) { + stableSince = stdout.lastWrite + } + if isReady && (stdout.closed || now.Sub(stableSince) >= stableFor) { + return snapshot, nil + } + if stdout.closed { + if stdout.scanErr != nil { + return "", fmt.Errorf("simulator stdout closed: %w", stdout.scanErr) + } + return "", errors.New("simulator stdout closed before expected output") + } + if !now.Before(deadline) { + return "", fmt.Errorf("timed out waiting for simulator stdout after %s", timeout) + } + + wakeAt := deadline + if isReady { + stableAt := stableSince.Add(stableFor) + if stableAt.Before(wakeAt) { + wakeAt = stableAt + } + } + timer := time.AfterFunc(time.Until(wakeAt), func() { + stdout.mu.Lock() + stdout.cond.Broadcast() + stdout.mu.Unlock() + }) + stdout.cond.Wait() + timer.Stop() + } +} + +func (stdout *simulatorStdout) waitUntilStable(stableFor time.Duration, timeout time.Duration) error { + checkpoint := stdout.checkpoint() + _, err := stdout.wait(checkpoint, func(string) bool { return true }, stableFor, timeout) + return err +} + +func (stdout *simulatorStdout) close(scanErr error) { + stdout.mu.Lock() + defer stdout.mu.Unlock() + stdout.closed = true + stdout.scanErr = scanErr + stdout.cond.Broadcast() +} + +func runSimulator(filename string) (func() error, *Device, *simulatorStdout, error) { cmd := exec.Command("stdbuf", "-oL", filename) // Create pipe before starting process @@ -47,13 +176,15 @@ func runSimulator(filename string) (func() error, *Device, *bytes.Buffer, error) return nil, nil, nil, err } - var stdoutBuf bytes.Buffer + stdoutBuf := newSimulatorStdout() + scannerDone := make(chan struct{}) scanner := bufio.NewScanner(stdout) go func() { + defer close(scannerDone) for scanner.Scan() { - stdoutBuf.Write(scanner.Bytes()) - stdoutBuf.WriteByte('\n') + stdoutBuf.writeLine(scanner.Bytes()) } + stdoutBuf.close(scanner.Err()) }() var conn net.Conn @@ -65,6 +196,9 @@ func runSimulator(filename string) (func() error, *Device, *bytes.Buffer, error) time.Sleep(10 * time.Millisecond) } if err != nil { + _ = cmd.Process.Kill() + <-scannerDone + _ = cmd.Wait() return nil, nil, nil, err } const bitboxCMD = 0x80 + 0x40 + 0x01 @@ -74,11 +208,15 @@ func runSimulator(filename string) (func() error, *Device, *bytes.Buffer, error) &mocks.Config{}, communication, &mocks.Logger{}, ) return func() error { - if err := conn.Close(); err != nil { - return err + connErr := conn.Close() + killErr := cmd.Process.Kill() + <-scannerDone + _ = cmd.Wait() + if errors.Is(killErr, os.ErrProcessDone) { + killErr = nil } - return cmd.Process.Kill() - }, device, &stdoutBuf, nil + return errors.Join(connErr, killErr) + }, device, stdoutBuf, nil } // Download BitBox simulators based on testdata/simulators.json to testdata/simulators/*. @@ -190,7 +328,7 @@ func downloadSimulators() ([]string, error) { var downloadSimulatorsOnce = sync.OnceValues(downloadSimulators) // Runs tests against a simulator which is not initialized (not paired, not seeded). -func testSimulators(t *testing.T, run func(*testing.T, *Device, *bytes.Buffer)) { +func testSimulators(t *testing.T, run func(*testing.T, *Device, *simulatorStdout)) { t.Helper() if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { t.Skip("Skipping simulator tests: not running on linux-amd64") @@ -217,9 +355,9 @@ func testSimulators(t *testing.T, run func(*testing.T, *Device, *bytes.Buffer)) } // Runs tests against a simulator which is not initialized, but paired (not seeded). -func testSimulatorsAfterPairing(t *testing.T, run func(*testing.T, *Device, *bytes.Buffer)) { +func testSimulatorsAfterPairing(t *testing.T, run func(*testing.T, *Device, *simulatorStdout)) { t.Helper() - testSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() require.NoError(t, device.Init()) device.ChannelHashVerify(true) @@ -230,9 +368,9 @@ func testSimulatorsAfterPairing(t *testing.T, run func(*testing.T, *Device, *byt // Runs tests againt a simulator that is seeded with this mnemonic: boring mistake dish oyster truth // pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid // off enable tide -func testInitializedSimulators(t *testing.T, run func(*testing.T, *Device, *bytes.Buffer)) { +func testInitializedSimulators(t *testing.T, run func(*testing.T, *Device, *simulatorStdout)) { t.Helper() - testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() require.NoError(t, device.RestoreFromMnemonic()) run(t, device, stdOut) @@ -240,7 +378,7 @@ func testInitializedSimulators(t *testing.T, run func(*testing.T, *Device, *byte } func TestSimulatorRootFingerprint(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() fp, err := device.RootFingerprint() require.NoError(t, err) @@ -520,7 +658,7 @@ func TestVersion(t *testing.T) { } func TestSimulatorProduct(t *testing.T) { - testSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() require.NoError(t, device.Init()) // Since v9.24.0, the simulator simulates a Nova device. diff --git a/api/firmware/eth_test.go b/api/firmware/eth_test.go index 923c70b..a11c82e 100644 --- a/api/firmware/eth_test.go +++ b/api/firmware/eth_test.go @@ -388,7 +388,7 @@ func TestETHSignTypedMessageRejectsLargeString(t *testing.T) { } func TestSimulatorETHPub(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() chainID := uint64(1) xpub, err := device.ETHPub( @@ -431,7 +431,7 @@ func TestSimulatorETHPub(t *testing.T) { } func TestSimulatorETHSignMessage(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() chainID := uint64(1) keypath := []uint32{ @@ -456,7 +456,7 @@ func TestSimulatorETHSignMessage(t *testing.T) { } func TestSimulatorETHSignTypedMessage(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() sig, err := device.ETHSignTypedMessage( 1, @@ -476,7 +476,7 @@ func TestSimulatorETHSignTypedMessage(t *testing.T) { } func TestSimulatorETHSignTypedMessageAntikleptoEnabled(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() keypath := []uint32{ 44 + hardenedKeyStart, @@ -498,7 +498,7 @@ func TestSimulatorETHSignTypedMessageAntikleptoEnabled(t *testing.T) { } func TestSimulatorETHSignTypedMessageAntikleptoDisabled(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() keypath := []uint32{ 44 + hardenedKeyStart, @@ -526,7 +526,7 @@ func TestSimulatorETHSignTypedMessageAntikleptoDisabled(t *testing.T) { } func TestSimulatorETHSign(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() chainID := uint64(1) keypath := []uint32{ @@ -561,7 +561,7 @@ func TestSimulatorETHSign(t *testing.T) { } func TestSimulatorETHSignStreaming(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() if !device.Version().AtLeast(semver.NewSemVer(9, 26, 0)) { t.Skip("requires firmware >= 9.26.0") @@ -591,7 +591,7 @@ func TestSimulatorETHSignStreaming(t *testing.T) { } func TestSimulatorETHSignEIP1559(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() chainID := uint64(1) keypath := []uint32{ @@ -629,7 +629,7 @@ func TestSimulatorETHSignEIP1559(t *testing.T) { } func TestSimulatorETHSignEIP1559Streaming(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() if !device.Version().AtLeast(semver.NewSemVer(9, 26, 0)) { t.Skip("requires firmware >= 9.26.0") @@ -661,7 +661,7 @@ func TestSimulatorETHSignEIP1559Streaming(t *testing.T) { } func TestSimulatorETHSignTypedMessageStreamingBytes(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() sig, err := device.ETHSignTypedMessage( diff --git a/api/firmware/mnemonic_test.go b/api/firmware/mnemonic_test.go index c14f78c..63cef87 100644 --- a/api/firmware/mnemonic_test.go +++ b/api/firmware/mnemonic_test.go @@ -3,21 +3,20 @@ package firmware import ( - "bytes" "testing" "github.com/stretchr/testify/require" ) func TestSimulatorShowMnemonic(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() require.NoError(t, device.ShowMnemonic()) }) } func TestSimulatorSetMnemonicPassphraseEnabled(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() info, err := device.DeviceInfo() require.NoError(t, err) diff --git a/api/firmware/psbt.go b/api/firmware/psbt.go index 7f7004e..2c5aa78 100644 --- a/api/firmware/psbt.go +++ b/api/firmware/psbt.go @@ -544,6 +544,12 @@ func newBTCTxFromPSBT( outputScriptConfigIndex = &outputIdx } } + var silentPayment *messages.BTCSignOutputRequest_SilentPayment + if outputOptions.SilentPaymentAddress != "" { + silentPayment = &messages.BTCSignOutputRequest_SilentPayment{ + Address: outputOptions.SilentPaymentAddress, + } + } if scriptConfig != nil { outputs[outputIndex] = &messages.BTCSignOutputRequest{ @@ -552,16 +558,16 @@ func newBTCTxFromPSBT( Keypath: ourKey.keypath(), ScriptConfigIndex: scriptConfigIndex, OutputScriptConfigIndex: outputScriptConfigIndex, + SilentPayment: silentPayment, + PaymentRequestIndex: outputOptions.PaymentRequestIndex, } } else { - var silentPayment *messages.BTCSignOutputRequest_SilentPayment var outputType messages.BTCOutputType var payload []byte - if outputOptions.SilentPaymentAddress != "" { - silentPayment = &messages.BTCSignOutputRequest_SilentPayment{ - Address: outputOptions.SilentPaymentAddress, - } - } else { + // A generated silent-payment output has no script yet. If a script is present, + // preserve it so invalid combinations such as OP_RETURN plus silent-payment + // metadata reach the firmware unchanged. + if silentPayment == nil || len(txOutput.PkScript) != 0 { outputType, payload, err = payloadFromPkScript(txOutput.PkScript) if err != nil { return nil, err @@ -643,14 +649,13 @@ func (device *Device) BTCSignPSBT( case ourKey.taprootInternal != nil: psbtInput.TaprootKeySpendSig = signatureCompact case ourKey.taprootScript != nil: - psbtInput.TaprootScriptSpendSig = []*psbt.TaprootScriptSpendSig{ - { - XOnlyPubKey: ourKey.taprootScript.XOnlyPubKey, - LeafHash: ourKey.taprootScript.LeafHashes[0], - Signature: signatureCompact, - SigHash: txscript.SigHashDefault, - }, - } + setTaprootScriptSpendSig( + signatureCompact, + psbtInput, + ourKey.taprootScript.XOnlyPubKey, + ourKey.taprootScript.LeafHashes[0], + txscript.SigHashDefault, + ) default: panic("unreachable") } @@ -663,6 +668,39 @@ func (device *Device) BTCSignPSBT( return nil } +// setTaprootScriptSpendSig replaces or adds a script-path signature without dropping signatures +// contributed by other signers. +func setTaprootScriptSpendSig( + sig []byte, + psbtInput *psbt.PInput, + xOnlyPubKey []byte, + leafHash []byte, + sigHash txscript.SigHashType, +) { + for index, partialSig := range psbtInput.TaprootScriptSpendSig { + samePubkey := bytes.Equal(partialSig.XOnlyPubKey, xOnlyPubKey) + sameLeaf := bytes.Equal(partialSig.LeafHash, leafHash) + if samePubkey && sameLeaf { + psbtInput.TaprootScriptSpendSig[index] = &psbt.TaprootScriptSpendSig{ + XOnlyPubKey: xOnlyPubKey, + LeafHash: leafHash, + Signature: sig, + SigHash: sigHash, + } + return + } + } + psbtInput.TaprootScriptSpendSig = append( + psbtInput.TaprootScriptSpendSig, + &psbt.TaprootScriptSpendSig{ + XOnlyPubKey: xOnlyPubKey, + LeafHash: leafHash, + Signature: sig, + SigHash: sigHash, + }, + ) +} + // setPartialSig replaces or adds a partial signature for the given public key, // depending on whether a partial signature already exists for the key or not. func setPartialSig( diff --git a/api/firmware/psbt_test.go b/api/firmware/psbt_test.go index a3db3fa..0362754 100644 --- a/api/firmware/psbt_test.go +++ b/api/firmware/psbt_test.go @@ -4,16 +4,12 @@ package firmware import ( "bytes" - "encoding/binary" "encoding/hex" "testing" "github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages" "github.com/BitBoxSwiss/bitbox02-api-go/util/errp" "github.com/BitBoxSwiss/bitbox02-api-go/util/semver" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" @@ -240,991 +236,3 @@ func TestNewBTCTxFromPSBT_P2WPKH(t *testing.T) { assert.Len(t, result.scriptConfigs, 1) require.True(t, proto.Equal(result.scriptConfigs[0], expectedScriptConfig)) } - -func TestSimulatorBTCPSBTTaprootKeySpend(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - changePath := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 1, 0} - changePubKey := simulatorPub(t, device, changePath...) - _, changePkScript := makeTaprootOutput(t, changePubKey) - - input0Path := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0} - input0Pubkey := simulatorPub(t, device, input0Path...) - _, input0PkScript := makeTaprootOutput(t, input0Pubkey) - - input1Path := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 1} - input1PubKey := simulatorPub(t, device, input1Path...) - _, input1PkScript := makeTaprootOutput(t, input1PubKey) - - // A previous tx which creates some UTXOs we can reference later. - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - SignatureScript: nil, - Sequence: 0xFFFFFFFF, - Witness: nil, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: input0PkScript, - }, - { - Value: 100_000_000, - PkScript: input1PkScript, - }, - }, - } - - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{ - Hash: prevTx.TxHash(), - Index: 0, - }, - SignatureScript: nil, - Sequence: 0xFFFFFFFF, - Witness: nil, - }, - { - PreviousOutPoint: wire.OutPoint{ - Hash: prevTx.TxHash(), - Index: 1, - }, - SignatureScript: nil, - Sequence: 0xFFFFFFFF, - Witness: nil, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: changePkScript, - }, - { - Value: 20_000_000, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - PkScript: p2trPkScript(unhex("e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576")), - }, - }, - } - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - // Add input and change infos. - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(input0Pubkey) - psbt_.Inputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input0Path, - }} - - psbt_.Inputs[1].WitnessUtxo = prevTx.TxOut[1] - psbt_.Inputs[1].TaprootInternalKey = schnorr.SerializePubKey(input1PubKey) - psbt_.Inputs[1].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[1].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input1Path, - }} - - psbt_.Outputs[0].TaprootInternalKey = schnorr.SerializePubKey(changePubKey) - psbt_.Outputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Outputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: changePath, - }} - - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, nil) - require.NoError(t, err) - require.False(t, needsPrevTxs) - - // Sign & validate. - require.NoError(t, device.BTCSignPSBT(messages.BTCCoin_TBTC, psbt_, nil)) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - }) -} - -// All inputs are Taproot, but the change output is not Taproot. Some BitBox firmwares erroneously -// also request the previous transactions in this case. -func TestSimulatorBTCPSBTTaprootSpendsToNonTaproot(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - changePath := []uint32{84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 1, 0} - changePubKey := simulatorPub(t, device, changePath...) - changePkScript := p2wpkhPkScript(changePubKey) - - input0Path := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0} - input0Pubkey := simulatorPub(t, device, input0Path...) - _, input0PkScript := makeTaprootOutput(t, input0Pubkey) - - input1Path := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 1} - input1PubKey := simulatorPub(t, device, input1Path...) - _, input1PkScript := makeTaprootOutput(t, input1PubKey) - - // A previous tx which creates some UTXOs we can reference later. - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - SignatureScript: nil, - Sequence: 0xFFFFFFFF, - Witness: nil, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: input0PkScript, - }, - { - Value: 100_000_000, - PkScript: input1PkScript, - }, - }, - } - - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{ - Hash: prevTx.TxHash(), - Index: 0, - }, - SignatureScript: nil, - Sequence: 0xFFFFFFFF, - Witness: nil, - }, - { - PreviousOutPoint: wire.OutPoint{ - Hash: prevTx.TxHash(), - Index: 1, - }, - SignatureScript: nil, - Sequence: 0xFFFFFFFF, - Witness: nil, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: changePkScript, - }, - { - Value: 20_000_000, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - PkScript: p2trPkScript(unhex("e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576")), - }, - }, - } - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - // Add input and change infos. - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(input0Pubkey) - psbt_.Inputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input0Path, - }} - - psbt_.Inputs[1].WitnessUtxo = prevTx.TxOut[1] - psbt_.Inputs[1].TaprootInternalKey = schnorr.SerializePubKey(input1PubKey) - psbt_.Inputs[1].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[1].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input1Path, - }} - - psbt_.Outputs[0].TaprootInternalKey = schnorr.SerializePubKey(changePubKey) - psbt_.Outputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Outputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: changePath, - }} - - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, nil) - require.NoError(t, err) - require.True(t, needsPrevTxs) - psbt_.Inputs[0].NonWitnessUtxo = prevTx - psbt_.Inputs[1].NonWitnessUtxo = prevTx - - // Sign & validate. - require.NoError(t, device.BTCSignPSBT(messages.BTCCoin_TBTC, psbt_, nil)) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - }) -} - -func TestSimulatorBTCPSBTMixedSpend(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - // Derivation paths - changePath := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 1, 0} - input0Path := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0} // P2TR - input1Path := []uint32{84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0} // P2WPKH - input2Path := []uint32{49 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0} // P2SH-P2WPKH - - // Generate public keys - input0Pub := simulatorPub(t, device, input0Path...) - input1Pub := simulatorPub(t, device, input1Path...) - input2Pub := simulatorPub(t, device, input2Path...) - - // Convert to required formats - changePubKey := simulatorPub(t, device, changePath...) - _, changePkScript := makeTaprootOutput(t, changePubKey) - // P2WPKH redeem script - input2RedeemScript := p2wpkhPkScript(input2Pub) - - // Previous transaction with mixed outputs - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }}, - TxOut: []*wire.TxOut{ - { // P2TR - Value: 100_000_000, - PkScript: func() []byte { - _, script := makeTaprootOutput(t, input0Pub) - return script - }(), - }, - { // P2WPKH - Value: 100_000_000, - PkScript: p2wpkhPkScript(input1Pub), - }, - { // P2SH-P2WPKH - Value: 100_000_000, - PkScript: p2shPkScript(input2RedeemScript), - }, - }, - } - - // Spending transaction - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 0}}, // P2TR input - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 1}}, // P2WPKH input - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 2}}, // P2SH-P2WPKH input - }, - TxOut: []*wire.TxOut{ - { // Change output (P2TR) - Value: 100_000_000, - PkScript: changePkScript, - }, - { // External output - Value: 20_000_000, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - PkScript: p2trPkScript(unhex("e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576")), - }, - }, - } - - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - // Setup PSBT inputs - // Input 0 (P2TR) - psbt_.Inputs[0].NonWitnessUtxo = prevTx - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(changePubKey) - psbt_.Inputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input0Path, - }} - - // Input 1 (P2WPKH) - psbt_.Inputs[1].NonWitnessUtxo = prevTx - psbt_.Inputs[1].WitnessUtxo = prevTx.TxOut[1] - psbt_.Inputs[1].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: input1Pub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input1Path, - }} - - // Input 2 (P2SH-P2WPKH) - psbt_.Inputs[2].NonWitnessUtxo = prevTx - psbt_.Inputs[2].WitnessUtxo = prevTx.TxOut[2] - psbt_.Inputs[2].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: input2Pub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input2Path, - }} - psbt_.Inputs[2].RedeemScript = input2RedeemScript - - // Setup change output - psbt_.Outputs[0].TaprootInternalKey = schnorr.SerializePubKey(changePubKey) - psbt_.Outputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Outputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: changePath, - }} - - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, nil) - require.NoError(t, err) - require.True(t, needsPrevTxs) - - // Sign & validate - require.NoError(t, device.BTCSignPSBT(messages.BTCCoin_TBTC, psbt_, nil)) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - }) -} - -func TestSimulatorBTCSignPSBTOpReturn(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - - if !device.Version().AtLeast(semver.NewSemVer(9, 24, 0)) { - t.Skip("OP_RETURN support was added in v9.24.0") - } - - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - inputPath := []uint32{84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 5} - changePath := []uint32{84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 1, 0} - - inputPub := simulatorPub(t, device, inputPath...) - changePub := simulatorPub(t, device, changePath...) - - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }}, - TxOut: []*wire.TxOut{{ - Value: 50_000_000, - PkScript: p2wpkhPkScript(inputPub), - }}, - } - - opReturnData := []byte("hello world") - opReturnScript, err := txscript.NewScriptBuilder(). - AddOp(txscript.OP_RETURN). - AddData(opReturnData). - Script() - require.NoError(t, err) - - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: wire.OutPoint{ - Hash: prevTx.TxHash(), - Index: 0, - }, - Sequence: 0xFFFFFFFF, - }}, - TxOut: []*wire.TxOut{ - { - Value: 49_000_000, - PkScript: p2wpkhPkScript(changePub), - }, - { - Value: 0, - PkScript: opReturnScript, - }, - }, - } - - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - psbt_.Inputs[0].NonWitnessUtxo = prevTx - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: inputPub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: inputPath, - }} - - psbt_.Outputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: changePub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: changePath, - }} - - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, nil) - require.NoError(t, err) - require.True(t, needsPrevTxs) - - require.NoError(t, device.BTCSignPSBT(messages.BTCCoin_TBTC, psbt_, nil)) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - - require.Contains(t, stdOut.String(), "TITLE: OP_RETURN") - require.Contains(t, stdOut.String(), "BODY: hello world") - }) -} - -func TestSimulatorBTCPSBTSilentPayment(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - // Derivation paths - changePath := []uint32{86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 0} - input0Path := []uint32{86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0} // P2TR - input1Path := []uint32{84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0} // P2WPKH - input2Path := []uint32{49 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0} // P2SH-P2WPKH - - // Generate public keys - input0Pub := simulatorPub(t, device, input0Path...) - input1Pub := simulatorPub(t, device, input1Path...) - input2Pub := simulatorPub(t, device, input2Path...) - - // Convert to required formats - changePubKey := simulatorPub(t, device, changePath...) - _, changePkScript := makeTaprootOutput(t, changePubKey) - // P2WPKH redeem script - input2RedeemScript := p2wpkhPkScript(input2Pub) - - // Previous transaction with mixed outputs - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }}, - TxOut: []*wire.TxOut{ - { // P2TR - Value: 100_000_000, - PkScript: func() []byte { - _, script := makeTaprootOutput(t, input0Pub) - return script - }(), - }, - { // P2WPKH - Value: 100_000_000, - PkScript: p2wpkhPkScript(input1Pub), - }, - { // P2SH-P2WPKH - Value: 100_000_000, - PkScript: p2shPkScript(input2RedeemScript), - }, - }, - } - - // Spending transaction - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 0}}, // P2TR input - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 1}}, // P2WPKH input - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 2}}, // P2SH-P2WPKH input - }, - TxOut: []*wire.TxOut{ - { // Change output (P2TR) - Value: 100_000_000, - PkScript: changePkScript, - }, - { // External output - Value: 20_000_000, - // Will be generated, silent payment output - PkScript: nil, - }, - }, - } - - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - // Setup PSBT inputs - // Input 0 (P2TR) - psbt_.Inputs[0].NonWitnessUtxo = prevTx - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(input0Pub) - psbt_.Inputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input0Path, - }} - - // Input 1 (P2WPKH) - psbt_.Inputs[1].NonWitnessUtxo = prevTx - psbt_.Inputs[1].WitnessUtxo = prevTx.TxOut[1] - psbt_.Inputs[1].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: input1Pub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input1Path, - }} - - // Input 2 (P2SH-P2WPKH) - psbt_.Inputs[2].NonWitnessUtxo = prevTx - psbt_.Inputs[2].WitnessUtxo = prevTx.TxOut[2] - psbt_.Inputs[2].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: input2Pub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input2Path, - }} - psbt_.Inputs[2].RedeemScript = input2RedeemScript - - // Setup change output - psbt_.Outputs[0].TaprootInternalKey = schnorr.SerializePubKey(changePubKey) - psbt_.Outputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Outputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: changePath, - }} - - signOptions := &PSBTSignOptions{ - Outputs: map[int]*PSBTSignOutputOptions{ - 1: { - SilentPaymentAddress: "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", - }, - }, - } - - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, signOptions) - require.NoError(t, err) - require.True(t, needsPrevTxs) - - // Sign & validate - err = device.BTCSignPSBT(messages.BTCCoin_BTC, psbt_, signOptions) - if !device.version.AtLeast(semver.NewSemVer(9, 21, 0)) { - require.EqualError(t, err, UnsupportedError("9.21.0").Error()) - return - } - require.NoError(t, err) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - }) -} - -func TestSimulatorBTCPSBTSendSelfSameAccount(t *testing.T) { - type Test struct { - sendSelfPath []uint32 - // expected from v9.26 - expected926 string - // expected from v9.22 - expected922 string - // expected until v9.22 - expectedOld string - } - tests := []Test{ - { - // Same account - sendSelfPath: []uint32{84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0}, - expected926: "This BitBox (same account): tb1q l34n y8mc pgjq r0ng snjm lpzj pgnc yz2y gh2g ye", - expected922: "This BitBox (same account): tb1ql34ny8mcpgjqr0ngsnjmlpzjpgncyz2ygh2gye", - expectedOld: "This BitBox02: tb1ql34ny8mcpgjqr0ngsnjmlpzjpgncyz2ygh2gye", - }, - { - // Different account - sendSelfPath: []uint32{84 + HARDENED, 1 + HARDENED, 1 + HARDENED, 0, 0}, - expected926: "This BitBox (account #2): tb1q vrcm 2akp 30d7 ecnq djk8 qdu0 9962 ak00 5rcp 6j", - expected922: "This BitBox (account #2): tb1qvrcm2akp30d7ecnqdjk8qdu09962ak005rcp6j", - expectedOld: "tb1qvrcm2akp30d7ecnqdjk8qdu09962ak005rcp6j", - }, - } - - for _, test := range tests { - t.Run("", func(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - // Derivation paths - input0Path := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0} // P2TR - changePath := []uint32{49 + HARDENED, 1 + HARDENED, 0 + HARDENED, 1, 0} // P2WPKH-P2SH - sendSelfPath := test.sendSelfPath - - input0Pub := simulatorPub(t, device, input0Path...) - changePub := simulatorPub(t, device, changePath...) - sendSelfPub := simulatorPub(t, device, sendSelfPath...) - changeRedeemScript := p2wpkhPkScript(changePub) - changePkScript := p2shPkScript(changeRedeemScript) - changeAddress, err := btcutil.NewAddressScriptHash(changeRedeemScript, &chaincfg.TestNet3Params) - require.NoError(t, err) - - // Previous transaction with one output. - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }}, - TxOut: []*wire.TxOut{ - { // P2TR - Value: 100_000_000, - PkScript: func() []byte { - _, script := makeTaprootOutput(t, input0Pub) - return script - }(), - }, - }, - } - - // Spending transaction - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 0}}, // P2TR input - }, - TxOut: []*wire.TxOut{ - { // Change output (P2TR) - Value: 50_000_000, - PkScript: changePkScript, - }, - { // External output - Value: 20_000_000, - PkScript: p2wpkhPkScript(sendSelfPub), - }, - }, - } - - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - // Setup PSBT inputs - // Input 0 (P2TR) - psbt_.Inputs[0].NonWitnessUtxo = prevTx - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(changePub) - psbt_.Inputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input0Path, - }} - - // Setup change output (P2SH-P2WPKH) - psbt_.Outputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: changePub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: changePath, - }} - psbt_.Outputs[0].RedeemScript = changeRedeemScript - - // Setup send-to-self output - psbt_.Outputs[1].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: sendSelfPub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: sendSelfPath, - }} - - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, nil) - require.NoError(t, err) - require.True(t, needsPrevTxs) - - // Sign & validate - require.NoError(t, device.BTCSignPSBT(messages.BTCCoin_TBTC, psbt_, nil)) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - - // v9.22/v9.26 changed the format of the output string. - switch { - case device.Version().AtLeast(semver.NewSemVer(9, 26, 0)): - require.Contains(t, stdOut.String(), "ADDRESS: "+test.expected926) - case device.Version().AtLeast(semver.NewSemVer(9, 22, 0)): - require.Contains(t, stdOut.String(), "ADDRESS: "+test.expected922) - case device.Version().AtLeast(semver.NewSemVer(9, 20, 0)): - require.Contains(t, stdOut.String(), "ADDRESS: "+test.expectedOld) - default: - // Before simulator v9.20, address confirmation data was not written to stdout. - } - - // Change address is not confirmed - require.NotContains(t, stdOut.String(), changeAddress.String()) - }) - }) - } -} - -func TestSimulatorBTCPSBTPaymentRequest(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - - if !device.Version().AtLeast(semver.NewSemVer(9, 24, 0)) { - // While payment requests were added in v9.19.0, the simulator only enabled the - // test merchant in v9.24.0. - t.Skip() - } - - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - // Derivation paths - input0Path := []uint32{86 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0} // P2TR - changePath := []uint32{49 + HARDENED, 1 + HARDENED, 0 + HARDENED, 1, 0} // P2WPKH-P2SH - - input0Pub := simulatorPub(t, device, input0Path...) - changePub := simulatorPub(t, device, changePath...) - changeRedeemScript := p2wpkhPkScript(changePub) - changePkScript := p2shPkScript(changeRedeemScript) - require.NoError(t, err) - - address, err := btcutil.DecodeAddress( - "tb1q9kvhpyd32aqhpsc8yrdm48gx5dnadq63lservm", - &chaincfg.TestNet3Params) - require.NoError(t, err) - pkScript, err := txscript.PayToAddrScript(address) - require.NoError(t, err) - - value := uint64(20_000_000) - - paymentRequest := &messages.BTCPaymentRequestRequest{ - RecipientName: "Test Merchant", // Hard-coded test merchant in simulator - Nonce: nil, - TotalAmount: value, - Memos: []*messages.BTCPaymentRequestRequest_Memo{ - { - Memo: &messages.BTCPaymentRequestRequest_Memo_TextMemo_{ - TextMemo: &messages.BTCPaymentRequestRequest_Memo_TextMemo{ - Note: "TextMemo line1\nTextMemo line2", - }, - }, - }, - }, - } - // Sign the payment request. - sighash, err := ComputePaymentRequestSighash(paymentRequest, 1, value, address.String()) - require.NoError(t, err) - privKey, _ := btcec.PrivKeyFromBytes([]byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) - require.NoError(t, err) - signature, err := ecdsa.SignCompact(privKey, sighash, true) - require.NoError(t, err) - paymentRequest.Signature = signature[1:] - - // Previous transaction with one output. - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }}, - TxOut: []*wire.TxOut{ - { // P2TR - Value: 100_000_000, - PkScript: func() []byte { - _, script := makeTaprootOutput(t, input0Pub) - return script - }(), - }, - }, - } - - // Spending transaction - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 0}}, // P2TR input - }, - TxOut: []*wire.TxOut{ - { // Change output (P2TR) - Value: 50_000_000, - PkScript: changePkScript, - }, - { // External output - Value: int64(value), - PkScript: pkScript, - }, - }, - } - - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - // Setup PSBT inputs - // Input 0 (P2TR) - psbt_.Inputs[0].NonWitnessUtxo = prevTx - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(changePub) - psbt_.Inputs[0].TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{ - XOnlyPubKey: psbt_.Inputs[0].TaprootInternalKey, - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: input0Path, - }} - - // Setup change output (P2SH-P2WPKH) - psbt_.Outputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: changePub.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: changePath, - }} - psbt_.Outputs[0].RedeemScript = changeRedeemScript - - paymentRequestIndex := uint32(0) - signOptions := &PSBTSignOptions{ - PaymentRequests: []*messages.BTCPaymentRequestRequest{paymentRequest}, - Outputs: map[int]*PSBTSignOutputOptions{ - 1: { - PaymentRequestIndex: &paymentRequestIndex, - }, - }, - } - - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, signOptions) - require.NoError(t, err) - require.True(t, needsPrevTxs) - - // Sign & validate - require.NoError(t, device.BTCSignPSBT(messages.BTCCoin_TBTC, psbt_, signOptions)) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - - const expected1 = `CONFIRM TRANSACTION ADDRESS SCREEN START -AMOUNT: 0.20000000 TBTC -ADDRESS: Test Merchant -CONFIRM TRANSACTION ADDRESS SCREEN END` - - const expected2 = ` -BODY: Memo from - -Test Merchant -CONFIRM SCREEN END -CONFIRM SCREEN START -TITLE: Memo 1/2 -BODY: TextMemo line1 -CONFIRM SCREEN END -CONFIRM SCREEN START -TITLE: Memo 2/2 -BODY: TextMemo line2 -CONFIRM SCREEN END` - - require.Contains(t, stdOut.String(), expected1) - - require.Contains(t, stdOut.String(), expected2) - - // The actual address is not shown in a payment request. - require.NotContains(t, stdOut.String(), address.String()) - }) -} - -// 1-of-3 P2WSH multisig spend -func TestSimulatorBTCPSBTMultisig(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { - t.Helper() - - fingerprint, err := device.RootFingerprint() - require.NoError(t, err) - - coin := messages.BTCCoin_BTC - setup := setupMultisigAccount(t, device, coin) - - inputPubKey := simulatorPub(t, device, setup.ReceiveKeypath...) - changePubKey := simulatorPub(t, device, setup.ChangeKeypath...) - - inputWitnessScript, inputPkScript := multisigP2WSH(1, setup.Xpubs, false, 0) - changeWitnessScript, changePkScript := multisigP2WSH(1, setup.Xpubs, true, 0) - - // Previous transaction with mixed outputs - prevTx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"), - Sequence: 0xFFFFFFFF, - }}, - TxOut: []*wire.TxOut{ - { - Value: 100_000_000, - PkScript: inputPkScript, - }, - }, - } - - // Spending transaction - tx := &wire.MsgTx{ - Version: 2, - LockTime: 0, - TxIn: []*wire.TxIn{ - {PreviousOutPoint: wire.OutPoint{Hash: prevTx.TxHash(), Index: 0}}, - }, - TxOut: []*wire.TxOut{ - { // Change output (P2WSH multisig) - Value: 70_000_000, - PkScript: changePkScript, - }, - { // External output - Value: 20_000_000, - // random private key: - // 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8 - PkScript: p2trPkScript(unhex("e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576")), - }, - }, - } - - psbt_, err := psbt.NewFromUnsignedTx(tx) - require.NoError(t, err) - - // Setup PSBT inputs - // Input 0 (P2WSH multisig) - psbt_.Inputs[0].NonWitnessUtxo = prevTx - psbt_.Inputs[0].WitnessUtxo = prevTx.TxOut[0] - psbt_.Inputs[0].WitnessScript = inputWitnessScript - psbt_.Inputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: inputPubKey.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: setup.ReceiveKeypath, - }} - - // Setup change output (P2WSH multisig) - psbt_.Outputs[0].WitnessScript = changeWitnessScript - psbt_.Outputs[0].Bip32Derivation = []*psbt.Bip32Derivation{{ - PubKey: changePubKey.SerializeCompressed(), - MasterKeyFingerprint: binary.LittleEndian.Uint32(fingerprint), - Bip32Path: setup.ChangeKeypath, - }} - - signOptions := &PSBTSignOptions{ - ForceScriptConfig: &messages.BTCScriptConfigWithKeypath{ - ScriptConfig: setup.ScriptConfig, - Keypath: setup.KeypathAccount, - }, - } - needsPrevTxs, err := device.BTCSignNeedsNonWitnessUTXOs(psbt_, signOptions) - require.NoError(t, err) - require.True(t, needsPrevTxs) - - // Sign & validate - require.NoError(t, device.BTCSignPSBT(coin, psbt_, signOptions)) - require.NoError(t, psbt.MaybeFinalizeAll(psbt_)) - require.NoError(t, txValidityCheck(psbt_)) - }) -} diff --git a/api/firmware/sdcard_test.go b/api/firmware/sdcard_test.go index 1850b7f..d387500 100644 --- a/api/firmware/sdcard_test.go +++ b/api/firmware/sdcard_test.go @@ -3,14 +3,13 @@ package firmware import ( - "bytes" "testing" "github.com/stretchr/testify/require" ) func TestSimulatorCheckSDCard(t *testing.T) { - testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() inserted, err := device.CheckSDCard() require.NoError(t, err) @@ -20,7 +19,7 @@ func TestSimulatorCheckSDCard(t *testing.T) { } func TestSimutorInsertSDCard(t *testing.T) { - testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() require.NoError(t, device.InsertSDCard()) }) diff --git a/api/firmware/simulator_screens_test.go b/api/firmware/simulator_screens_test.go new file mode 100644 index 0000000..a8961cd --- /dev/null +++ b/api/firmware/simulator_screens_test.go @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firmware + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type simulatorScreenType string + +const ( + simulatorScreenConfirm simulatorScreenType = "confirm" + simulatorScreenTransactionAddress simulatorScreenType = "transaction_address" + simulatorScreenTransactionFee simulatorScreenType = "transaction_fee" + simulatorScreenStatus simulatorScreenType = "status" + simulatorScreenSwap simulatorScreenType = "swap" + + simulatorScreenFieldTitle = "TITLE" + simulatorScreenFieldBody = "BODY" + simulatorScreenFieldAmount = "AMOUNT" + simulatorScreenFieldAddress = "ADDRESS" + simulatorScreenFieldFee = "FEE" + simulatorScreenFieldFrom = "FROM" + simulatorScreenFieldTo = "TO" + + simulatorTestTransactionAmount = "0.20000000 TBTC" + simulatorTestTransactionTitle = "Transaction" +) + +// simulatorScreen is the screen representation observable in simulator stdout. +type simulatorScreen struct { + Type simulatorScreenType `json:"type"` + Title string `json:"title,omitempty"` + Body string `json:"body,omitempty"` + Amount string `json:"amount,omitempty"` + Address string `json:"address,omitempty"` + Fee string `json:"fee,omitempty"` + From string `json:"from,omitempty"` + To string `json:"to,omitempty"` +} + +type simulatorScreenCapture struct { + stdout *simulatorStdout + checkpoint int +} + +func newSimulatorScreenCapture(stdout *simulatorStdout) *simulatorScreenCapture { + return &simulatorScreenCapture{ + stdout: stdout, + checkpoint: stdout.checkpoint(), + } +} + +func (capture *simulatorScreenCapture) screensSinceCheckpoint() ([]simulatorScreen, error) { + stdout, err := capture.stdout.snapshot(capture.checkpoint) + if err != nil { + return nil, err + } + return parseSimulatorScreens(stdout) +} + +func (capture *simulatorScreenCapture) waitForTerminalStatus( + stableFor time.Duration, + timeout time.Duration, +) ([]simulatorScreen, error) { + stdout, err := capture.stdout.wait( + capture.checkpoint, + func(snapshot string) bool { + screens, err := parseSimulatorScreens(snapshot) + return err == nil && len(screens) > 0 && screens[len(screens)-1].Type == simulatorScreenStatus + }, + stableFor, + timeout, + ) + if err != nil { + return nil, err + } + return parseSimulatorScreens(stdout) +} + +type simulatorScreenFormat struct { + end string + fields []string + screenType simulatorScreenType +} + +var simulatorScreenFormats = map[string]simulatorScreenFormat{ + "CONFIRM SCREEN START": { + end: "CONFIRM SCREEN END", + fields: []string{simulatorScreenFieldTitle, simulatorScreenFieldBody}, + screenType: simulatorScreenConfirm, + }, + "CONFIRM TRANSACTION ADDRESS SCREEN START": { + end: "CONFIRM TRANSACTION ADDRESS SCREEN END", + fields: []string{simulatorScreenFieldAmount, simulatorScreenFieldAddress}, + screenType: simulatorScreenTransactionAddress, + }, + "CONFIRM TRANSACTION FEE SCREEN START": { + end: "CONFIRM TRANSACTION FEE SCREEN END", + fields: []string{simulatorScreenFieldAmount, simulatorScreenFieldFee}, + screenType: simulatorScreenTransactionFee, + }, + "STATUS SCREEN START": { + end: "STATUS SCREEN END", + fields: []string{simulatorScreenFieldTitle}, + screenType: simulatorScreenStatus, + }, + "CONFIRM SWAP SCREEN START": { + end: "CONFIRM SWAP SCREEN END", + fields: []string{simulatorScreenFieldTitle, simulatorScreenFieldFrom, simulatorScreenFieldTo}, + screenType: simulatorScreenSwap, + }, +} + +func parseSimulatorScreenFields(lines []string, fields []string) (map[string]string, error) { + result := make(map[string]string, len(fields)) + lineIndex := 0 + + for fieldIndex, field := range fields { + if lineIndex >= len(lines) { + return nil, fmt.Errorf("missing %s field", field) + } + + prefix := field + ": " + if !strings.HasPrefix(lines[lineIndex], prefix) { + return nil, fmt.Errorf("expected %q, got %q", prefix, lines[lineIndex]) + } + + valueLines := []string{strings.TrimPrefix(lines[lineIndex], prefix)} + lineIndex++ + if fieldIndex+1 < len(fields) { + nextPrefix := fields[fieldIndex+1] + ": " + for lineIndex < len(lines) && !strings.HasPrefix(lines[lineIndex], nextPrefix) { + valueLines = append(valueLines, lines[lineIndex]) + lineIndex++ + } + } else { + valueLines = append(valueLines, lines[lineIndex:]...) + lineIndex = len(lines) + } + result[field] = strings.Join(valueLines, "\n") + } + + return result, nil +} + +func parseSimulatorStatusScreenFields(lines []string) (map[string]string, error) { + if len(lines) == 0 { + return nil, fmt.Errorf("missing %s field", simulatorScreenFieldTitle) + } + prefix := simulatorScreenFieldTitle + ": " + if !strings.HasPrefix(lines[0], prefix) { + return nil, fmt.Errorf("expected %q, got %q", prefix, lines[0]) + } + return map[string]string{ + simulatorScreenFieldTitle: strings.TrimPrefix(lines[0], prefix), + simulatorScreenFieldBody: strings.Join(lines[1:], "\n"), + }, nil +} + +func newSimulatorScreen(screenType simulatorScreenType, fields map[string]string) simulatorScreen { + return simulatorScreen{ + Type: screenType, + Title: fields[simulatorScreenFieldTitle], + Body: fields[simulatorScreenFieldBody], + Amount: fields[simulatorScreenFieldAmount], + Address: fields[simulatorScreenFieldAddress], + Fee: fields[simulatorScreenFieldFee], + From: fields[simulatorScreenFieldFrom], + To: fields[simulatorScreenFieldTo], + } +} + +func isSimulatorScreenEnd(line string) bool { + for _, format := range simulatorScreenFormats { + if line == format.end { + return true + } + } + return false +} + +func isSimulatorScreenMarker(line string) bool { + return strings.HasSuffix(line, " SCREEN START") || strings.HasSuffix(line, " SCREEN END") +} + +// parseSimulatorScreens extracts the structured UI screens from simulator stdout. Other simulator +// log lines are ignored, but recognized screen blocks must match the stdout UI stub format exactly. +func parseSimulatorScreens(stdout string) ([]simulatorScreen, error) { + lines := strings.Split(stdout, "\n") + result := []simulatorScreen{} + + for lineIndex := 0; lineIndex < len(lines); lineIndex++ { + format, ok := simulatorScreenFormats[lines[lineIndex]] + if !ok { + if isSimulatorScreenMarker(lines[lineIndex]) { + return nil, fmt.Errorf("unknown screen marker %q on line %d", lines[lineIndex], lineIndex+1) + } + continue + } + + startLine := lineIndex + 1 + endLine := startLine + for ; endLine < len(lines) && lines[endLine] != format.end; endLine++ { + if _, nested := simulatorScreenFormats[lines[endLine]]; nested { + return nil, fmt.Errorf("screen starting on line %d contains a nested screen on line %d", lineIndex+1, endLine+1) + } + if isSimulatorScreenEnd(lines[endLine]) { + return nil, fmt.Errorf("screen starting on line %d has unexpected end marker %q on line %d", lineIndex+1, lines[endLine], endLine+1) + } + if isSimulatorScreenMarker(lines[endLine]) { + return nil, fmt.Errorf("screen starting on line %d has unexpected marker %q on line %d", lineIndex+1, lines[endLine], endLine+1) + } + } + if endLine == len(lines) { + return nil, fmt.Errorf("screen starting on line %d is missing %q", lineIndex+1, format.end) + } + + var fields map[string]string + var err error + if format.screenType == simulatorScreenStatus { + fields, err = parseSimulatorStatusScreenFields(lines[startLine:endLine]) + } else { + fields, err = parseSimulatorScreenFields(lines[startLine:endLine], format.fields) + } + if err != nil { + return nil, fmt.Errorf("screen starting on line %d: %w", lineIndex+1, err) + } + result = append(result, newSimulatorScreen(format.screenType, fields)) + lineIndex = endLine + } + + return result, nil +} + +func TestParseSimulatorScreens(t *testing.T) { + stdout := `simulator startup log +CONFIRM SCREEN START +TITLE: Memo +BODY: first line + +third line +CONFIRM SCREEN END +CONFIRM TRANSACTION ADDRESS SCREEN START +AMOUNT: 0.20000000 TBTC +ADDRESS: This BitBox (same account): tb1q example +CONFIRM TRANSACTION ADDRESS SCREEN END +CONFIRM TRANSACTION FEE SCREEN START +AMOUNT: 0.20000000 TBTC +FEE: 0.00001000 TBTC +CONFIRM TRANSACTION FEE SCREEN END +STATUS SCREEN START +TITLE: Transaction +signed +STATUS SCREEN END +CONFIRM SWAP SCREEN START +TITLE: Confirm swap +FROM: 1.0 BTC +on Bitcoin +TO: 20.0 ETH +on Ethereum +CONFIRM SWAP SCREEN END +unrelated trailing log +` + + screens, err := parseSimulatorScreens(stdout) + require.NoError(t, err) + require.Equal(t, []simulatorScreen{ + { + Type: simulatorScreenConfirm, + Title: "Memo", + Body: "first line\n\nthird line", + }, + { + Type: simulatorScreenTransactionAddress, + Amount: simulatorTestTransactionAmount, + Address: "This BitBox (same account): tb1q example", + }, + { + Type: simulatorScreenTransactionFee, + Amount: simulatorTestTransactionAmount, + Fee: "0.00001000 TBTC", + }, + { + Type: simulatorScreenStatus, + Title: simulatorTestTransactionTitle, + Body: "signed", + }, + { + Type: simulatorScreenSwap, + Title: "Confirm swap", + From: "1.0 BTC\non Bitcoin", + To: "20.0 ETH\non Ethereum", + }, + }, screens) +} + +func TestSimulatorScreenJSON(t *testing.T) { + encoded, err := json.Marshal([]simulatorScreen{ + {Type: simulatorScreenConfirm, Title: "OP_RETURN", Body: "hello world"}, + { + Type: simulatorScreenTransactionAddress, + Amount: simulatorTestTransactionAmount, + Address: "Test Merchant", + }, + {Type: simulatorScreenStatus, Title: simulatorTestTransactionTitle, Body: "confirmed"}, + }) + require.NoError(t, err) + require.JSONEq(t, `[ + {"type":"confirm","title":"OP_RETURN","body":"hello world"}, + {"type":"transaction_address","amount":"0.20000000 TBTC","address":"Test Merchant"}, + {"type":"status","title":"Transaction","body":"confirmed"} + ]`, string(encoded)) +} + +func TestSimulatorScreenCaptureCheckpoint(t *testing.T) { + stdout := newSimulatorStdout() + _, err := stdout.WriteString(`CONFIRM SCREEN START +TITLE: Setup +BODY: Before checkpoint +CONFIRM SCREEN END +`) + require.NoError(t, err) + capture := newSimulatorScreenCapture(stdout) + _, err = stdout.WriteString(`STATUS SCREEN START +TITLE: Signed +STATUS SCREEN END +`) + require.NoError(t, err) + + screens, err := capture.screensSinceCheckpoint() + require.NoError(t, err) + require.Equal(t, []simulatorScreen{{Type: simulatorScreenStatus, Title: "Signed"}}, screens) +} + +func TestSimulatorScreenCaptureWaitForTerminalStatus(t *testing.T) { + stdout := newSimulatorStdout() + capture := newSimulatorScreenCapture(stdout) + + go func() { + _, _ = stdout.WriteString(`CONFIRM SCREEN START +TITLE: Confirm +BODY: transaction +CONFIRM SCREEN END +`) + time.Sleep(10 * time.Millisecond) + _, _ = stdout.WriteString(`STATUS SCREEN START +TITLE: Transaction +confirmed +STATUS SCREEN END +`) + }() + + screens, err := capture.waitForTerminalStatus(5*time.Millisecond, time.Second) + require.NoError(t, err) + require.Equal(t, []simulatorScreen{ + {Type: simulatorScreenConfirm, Title: "Confirm", Body: "transaction"}, + {Type: simulatorScreenStatus, Title: simulatorTestTransactionTitle, Body: "confirmed"}, + }, screens) +} + +func TestParseSimulatorScreensRejectsMalformedBlocks(t *testing.T) { + tests := map[string]string{ + "missing end marker": ` +CONFIRM SCREEN START +TITLE: Title +BODY: Body`, + "missing field": ` +CONFIRM TRANSACTION FEE SCREEN START +AMOUNT: 1 BTC +CONFIRM TRANSACTION FEE SCREEN END`, + "wrong field order": ` +CONFIRM SWAP SCREEN START +FROM: 1 BTC +TITLE: Swap +TO: 20 ETH +CONFIRM SWAP SCREEN END`, + "extra data before fields": ` +CONFIRM SCREEN START +unexpected +TITLE: Title +BODY: Body +CONFIRM SCREEN END`, + "nested screen": ` +CONFIRM SCREEN START +TITLE: Outer +BODY: Body +STATUS SCREEN START +TITLE: Inner +STATUS SCREEN END +CONFIRM SCREEN END`, + "mismatched end marker": ` +CONFIRM SCREEN START +TITLE: Title +BODY: Body +STATUS SCREEN END +CONFIRM SCREEN END`, + "unknown start marker": ` +FUTURE SCREEN START +TITLE: Future +FUTURE SCREEN END`, + "unknown end marker": ` +FUTURE SCREEN END`, + } + + for name, stdout := range tests { + t.Run(name, func(t *testing.T) { + _, err := parseSimulatorScreens(stdout) + require.Error(t, err) + }) + } +} diff --git a/api/firmware/system_test.go b/api/firmware/system_test.go index 55c3d9e..b5d1cfc 100644 --- a/api/firmware/system_test.go +++ b/api/firmware/system_test.go @@ -3,7 +3,6 @@ package firmware import ( - "bytes" "fmt" "testing" @@ -13,7 +12,7 @@ import ( ) func TestSimulatorDeviceName(t *testing.T) { - testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() info, err := device.DeviceInfo() require.NoError(t, err) @@ -38,7 +37,7 @@ func TestSimulatorDeviceName(t *testing.T) { func TestSimulatorSetPassword(t *testing.T) { for _, seedLen := range []int{16, 32} { t.Run(fmt.Sprintf("seedLen=%d", seedLen), func(t *testing.T) { - testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() require.NoError(t, device.SetPassword(seedLen)) require.Equal(t, StatusSeeded, device.Status()) @@ -48,7 +47,7 @@ func TestSimulatorSetPassword(t *testing.T) { } func TestSimulatorChangePassword(t *testing.T) { - testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) { + testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *simulatorStdout) { t.Helper() err := device.ChangePassword() if device.Version().AtLeast(semver.NewSemVer(9, 25, 0)) { diff --git a/api/firmware/testdata/btc-transaction-test-vectors.json b/api/firmware/testdata/btc-transaction-test-vectors.json new file mode 100644 index 0000000..43d1d50 --- /dev/null +++ b/api/firmware/testdata/btc-transaction-test-vectors.json @@ -0,0 +1,4858 @@ +{ + "simulator_seed": "boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide", + "vectors": [ + { + "id": "taproot-key-spend", + "description": "Signs two BIP86 Taproot key-spend inputs and recognizes a BIP86 change output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100b20200000002f8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270000000000fffffffff8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270100000000ffffffff0200e1f50500000000225120cc9235442b0f4fdf2f3c39516207c389e08fed3ae6cfa90cdb820d791a9d6ba4002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001012b00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db42221169e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434319004c00739d56000080010000800000008000000000010000000117209e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434300010520c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f7852107c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f78519004c00739d56000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "taproot_key", + "pubkey": "9e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a40574343", + "sighash": "default" + } + ] + }, + { + "id": "mixed-spend", + "description": "Signs P2TR, native P2WPKH and nested P2SH-P2WPKH inputs from one previous transaction and recognizes BIP86 change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100db0200000003e3ba22f94fa7f97d19e4b6c31853ce54c220cf225952b1ba9082ba0c1ba316260000000000ffffffffe3ba22f94fa7f97d19e4b6c31853ce54c220cf225952b1ba9082ba0c1ba316260100000000ffffffffe3ba22f94fa7f97d19e4b6c31853ce54c220cf225952b1ba9082ba0c1ba316260200000000ffffffff0200e1f50500000000225120cc9235442b0f4fdf2f3c39516207c389e08fed3ae6cfa90cdb820d791a9d6ba4002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a2782094400e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc66870000000021166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a2782094400e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc668700000000220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a2782094400e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc668700000000010416001468a00bbd6de555d756e92815e523b789def6c0b022060329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0184c00739d310000800100008000000080000000000000000000010520c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f7852107c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f78519004c00739d56000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 TBTC", + "fee": "1.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 TBTC", + "fee": "1.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + }, + { + "input_index": 2, + "kind": "ecdsa", + "pubkey": "0329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0", + "sighash": "all" + } + ] + }, + { + "id": "op-return", + "description": "Signs a P2WPKH spend with a zero-value OP_RETURN output containing one printable data push and a P2WPKH change output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01006802000000012b769ad9a4c846c82a6005040bbbc22a05ce02e42a6e1787a8ba8e57977bb7080000000000ffffffff0240aeeb02000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e9500000000000000000d6a0b68656c6c6f20776f726c640000000000010052020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0180f0fa0200000000160014bfbb8f964b61fb712f04587acaebea9e103161bc0000000001011f80f0fa0200000000160014bfbb8f964b61fb712f04587acaebea9e103161bc220602e62431dfc2aa92e24d23118f8f078894ee118271ddc13715c5bd33d7d2ffe229184c00739d540000800100008000000080000000000500000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "OP_RETURN", + "body": "hello world", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.01000000 TBTC", + "fee": "0.01000000 TBTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02e62431dfc2aa92e24d23118f8f078894ee118271ddc13715c5bd33d7d2ffe229", + "sighash": "all" + } + ] + }, + { + "id": "multisig-p2wsh", + "description": "Signs the device branch of a registered 1-of-2 sortedmulti P2WSH input, recognizes descriptor-derived change, and finalizes the witness.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001189002448bf29a8299625a5e0bbba99c561bd2ea07c71bb55b72be77e00becd60000000000ffffffff02801d2c04000000002200208dd9dc6a4969db2b4ed0a01a078e16c92328ea56ba3854f7416138132d2e6e48002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002200208b7d9944e24b23ca0e5d02a1233611fbaf2faf95bfc17cd35d68a800309352ec000000000105475121024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed20921039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde52ae2206024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed2090c4707983700000000000000002206039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde1c4c00739d300000800100008000000080020000800000000000000000000101475121031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb2103d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c52ae2202031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb1c4c00739d300000800100008000000080020000800100000000000000220203d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c0c4707983701000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test wsh multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'", + "name": "test wsh multisig" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde", + "sighash": "all" + } + ] + }, + { + "id": "policy-wsh", + "description": "Signs the device branch of a registered BIP388 WSH or-policy and recognizes descriptor-derived change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff010089020000000117bb7376f1aed39ca76fac738a9eecfb490d3d8e0d9cb47a08b227dd04bb7abc0000000000ffffffff02801d2c0400000000220020593d9bd9f01f27f05e9a8ad4096961eb5f1bcd43a6fed6f2df9020fc82673560002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002200204bff256253c47cfb36eb7d58f4e6b97d484701191075ecde1d3332ecdf8111010000000001054821033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2ac7c21024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed209ac9b2206024f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed2090c4707983700000000000000002206033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21c4c00739d3000008001000080000000800300008000000000000000000001014821028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aac7c2103d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074cac9b2202028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1c4c00739d300000800100008000000080030000800100000000000000220203d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c0c4707983701000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test wsh policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test wsh policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test wsh policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + } + ] + }, + "name": "test wsh policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "sighash": "all" + } + ] + }, + { + "id": "policy-tr-keyspend", + "description": "Signs the key-spend path of a registered BIP388 Taproot policy using only a witness UTXO and recognizes descriptor-derived change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001c0470cb5928e1e538404293a10b5c47e8d2482e27834ec4fb855d2c9e708a1480000000000ffffffff02801d2c04000000002251202cf99a9e98b0ee07ccb287502f76b2f856af59b726a746cd1ab303adc6749129002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120eb86719b281a383608399cab552dbd40368c66cd958f38bcb42347830e729ef021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21d004c00739d3000008001000080000000800300008000000000000000000117203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2000105208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a21078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1d004c00739d3000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*)", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n1 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr keyspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*)", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/1", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n1 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr keyspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*)", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/1", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*)", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test tr keyspend policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "sighash": "default" + } + ] + }, + { + "id": "policy-tr-scriptspend", + "description": "Signs the script path owned by the device in a registered BIP388 Taproot policy whose internal key belongs to another signer.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000014625d1da866927cc28dae6171aabba10fdb6abaee03829bc717fa57c05fc6b1c0000000000ffffffff02801d2c04000000002251206ea53e5447e4771e25f4e3ef3e3c5973e00ed2a4f7680507134a797b7df3c3cc002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120d0692388c73a40274236cad74af812db4d49ecf07b0838e644189693c4a1c87f2215c14f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed20923203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2acc021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b23d01c3aa2951885f3ddabebfba4eb4873dbdb2c41e287562d542cf8782e2a857071b4c00739d30000080010000800000008003000080000000000000000021164f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed2090d004707983700000000000000000117204f9153a64648289b50ba2fdf6a20feaf912d05fb00dbc65d53b0ad1ef84ed209011820c3aa2951885f3ddabebfba4eb4873dbdb2c41e287562d542cf8782e2a857071b00010520d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c01062500c022208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aac21078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a3d01bdda447b6076e3cc5aef2434e05b03e88a74dba86599553f3371fac3e86112b74c00739d3000008001000080000000800300008001000000000000002107d84603fefb986e343dc535bb6c24c47fdb6c4052c2c36b29598f1aecf5a1074c0d004707983701000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "keys": [ + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr scriptspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr scriptspend policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,pk(@1/<0;1>/*))", + "keys": [ + { + "xpub": "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test tr scriptspend policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_script", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "leaf_hash": "c3aa2951885f3ddabebfba4eb4873dbdb2c41e287562d542cf8782e2a857071b", + "sighash": "default" + } + ] + }, + { + "id": "taproot-to-non-taproot-change", + "description": "Signs all-Taproot inputs with P2WPKH change, requiring previous transactions for the added non-Taproot output config.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100a60200000002f8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270000000000fffffffff8e60a3e44fa93f5d7a7b57dc5983aac5d50f84071772938b4ddff45ce9bf3270100000000ffffffff0200e1f505000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef140000000000010089020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0200e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db4220000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e00010089020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0200e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db4220000000001012b00e1f5050000000022512036c6a3ef943986e3fdfb7d5a750ae013f746966f6fe0971d3bdc7360f29db42221169e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434319004c00739d56000080010000800000008000000000010000000117209e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a4057434300220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "1.00000000 TBTC", + "fee": "0.80000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 400.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "taproot_key", + "pubkey": "9e2a4b40d2c021b903ca48aa305545f56716f5c85e998bdc677d968a40574343", + "sighash": "default" + } + ] + }, + { + "id": "silent-payment", + "description": "Signs mixed inputs with BIP352 metadata and a device-generated silent-payment output.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100b9020000000314f5c987750e136e84be08a812ad8273b6597dbd76ded0806e714c183644cdc40000000000ffffffff14f5c987750e136e84be08a812ad8273b6597dbd76ded0806e714c183644cdc40100000000ffffffff14f5c987750e136e84be08a812ad8273b6597dbd76ded0806e714c183644cdc40200000000ffffffff0200e1f50500000000225120aaf8631d8fffabc65d283faa12aedc768bdc669c1ba6ff1166f356f13eefe089002d31010000000000000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a294300e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b00e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870000000001012b00e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a29432116cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff0952719004c00739d5600008000000080000000800000000000000000011720cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff095270001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a294300e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b00e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870000000001011f00e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b220602c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d54000080000000800000008000000000000000000001009d020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0300e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a294300e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b00e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870000000001012000e1f5050000000017a914ee9a3e5e0bedc5ec5c7e2461c62b5f0b5109c4f9870104160014d14ce318a324bc9eb4ff285d94ee02a297c0496f220603d47c62767f5463ffc008b9250fa6bb7eac11f2aff84804cf19d80ea46922bd73184c00739d31000080000000800000008000000000000000000001052067426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee5210767426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee519004c00739d56000080000000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "silent_payment_address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + } + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 BTC", + "fee": "1.80000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "sp1q qgst e7k9 hx0q ftg6 qmwl kqtw uy6c ycya vzmz j85c 6qdf hjdp djtd gqju exzk 6mur w56s uy3e 0rd2 cgqv ycxt tddw svgx e2us fpxu mr70 xc9p kqwv" + }, + { + "type": "transaction_fee", + "amount": "2.00000000 BTC", + "fee": "1.80000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 900.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff09527", + "sighash": "default" + }, + { + "input_index": 1, + "kind": "ecdsa", + "pubkey": "02c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048", + "sighash": "all" + }, + { + "input_index": 2, + "kind": "ecdsa", + "pubkey": "03d47c62767f5463ffc008b9250fa6bb7eac11f2aff84804cf19d80ea46922bd73", + "sighash": "all" + } + ], + "expected_generated_outputs": { + "1": "5120d826829cb603fc008e5ef99d0818f2126d3569c3ab8a6cd069f07a20e892bd59" + } + }, + { + "id": "send-self-same-account", + "description": "Covers ownership detection for the input account, suppression of change confirmation and version-specific account labels.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100720200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff0280f0fa020000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d310100000000160014fc6b321f780a2401be6884e5bf84520a27820944000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d310000800100008000000080010000000000000000220203a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.22.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox02: tb1ql34ny8mcpgjqr0ngsnjmlpzjpgncyz2ygh2gye" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.22.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (same account): tb1ql34ny8mcpgjqr0ngsnjmlpzjpgncyz2ygh2gye" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (same account): tb1q l34n y8mc pgjq r0ng snjm lpzj pgnc yz2y gh2g ye" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "send-self-different-account", + "description": "Covers ownership detection for another account and version-specific account labels.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100720200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff0280f0fa020000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d31010000000016001460f1b576c18bdbece2606cac70378f2974aed9ef000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d310000800100008000000080010000000000000000220203cee733ae6350507a6c63656d45de6dfc98a252a9f242116cfbe6c6ade2626b0c184c00739d540000800100008001000080000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.22.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1qvrcm2akp30d7ecnqdjk8qdu09962ak005rcp6j" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.22.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (account #2): tb1qvrcm2akp30d7ecnqdjk8qdu09962ak005rcp6j" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "This BitBox (account #2): tb1q vrcm 2akp 30d7 ecnq djk8 qdu0 9962 ak00 5rcp 6j" + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "payment-request", + "description": "Covers signed SLIP-24 payment-request metadata, merchant and multiline memo screens, address suppression and the pre-v9.24 simulator merchant limitation.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100720200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff0280f0fa020000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d3101000000001600142d997091b1574170c30720dbba9d06a367d68351000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e0001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d31000080010000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "text", + "note": "TextMemo line1\nTextMemo line2" + } + ], + "signature": "3e67d38390b3d32938bd3e3c8f239b201efc278fedadf371bea9561c94da89ea450ad806eeae079983babd8158ac9c7c6a3c12e589435e7535a571d562f9f337" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "Test Merchant" + }, + { + "type": "confirm", + "title": "", + "body": "Memo from\n\nTest Merchant", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 1/2", + "body": "TextMemo line1", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 2/2", + "body": "TextMemo line2", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.50000000 TBTC", + "fee": "0.30000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 150.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "payment-request-owned-output", + "description": "Covers version-specific handling of payment-request metadata attached to an output owned by this device, rejected since v9.26.3.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001b68d0167988df1602974d874a0f28f65525c86a88d66a09c92587a19408a30db0000000000ffffffff02801d2c0400000000225120cc9235442b0f4fdf2f3c39516207c389e08fed3ae6cfa90cdb820d791a9d6ba4002d310100000000160014fc6b321f780a2401be6884e5bf84520a27820944000000000001005e020000000131313131313131313131313131313131313131313131313131313131313131310000000000ffffffff0100e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a0000000001012b00e1f505000000002251205745c9989285350a6aa88c88f6fb3c43e6bcce823446a0920eb3bc6a93995f1a21166c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e19004c00739d56000080010000800000008000000000000000000117206c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e00010520c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f7852107c5de774ff4c41546478d7e273827d26d2b0908bcfa86cb5861c29f0b05d1f78519004c00739d560000800100008000000080010000000000000000220203a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "text", + "note": "TextMemo line1\nTextMemo line2" + } + ], + "signature": "4e7837fadb6a322439108c295906d50229f5fd79a1624957e12eeb171cf4ceed36b854ba948ed2630ec89b38aec462b06ed236ea32fa74ad9040dadf430712cd" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.3", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "Test Merchant" + }, + { + "type": "confirm", + "title": "", + "body": "Memo from\n\nTest Merchant", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 1/2", + "body": "TextMemo line1", + "longtouch": false + }, + { + "type": "confirm", + "title": "Memo 2/2", + "body": "TextMemo line2", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.3", + "outcome": "invalid_input", + "screens": [] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "6c9e72468f297c110e46bfedd7ffe83ab20470a1ae6a7fc13ccf94986541292e", + "sighash": "default" + } + ] + }, + { + "id": "multiple-output-types-bitcoin-coin", + "description": "Displays P2PKH, P2SH, P2WPKH and P2WSH recipients, warns about two change outputs, and signs the transaction.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001c38bf8f182f907c18224bd5f9cbd843e118019c68c288f5045bc4941672d30380000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd20296490000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600140b664ee927e3e41c6f5acd135d5b31fca4ed8e4a640000000000000016001491d160f1749d830eec2b9402b0b1fcc5ccd52ab1000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f0000000001011f8057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f22060218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d184c00739d54000080000000800a000080000000000500000000000000002202024ea6cfaca7a9f7689293a9b67c77e0a3e5aa8b160b5cfa12474b52767b4b94cf184c00739d54000080000000800a000080010000000300000000220202b8564af1c586c4477df4c233987002147a77987163bdc0535d364569306a74b6184c00739d54000080000000800a000080010000001e00000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH" + }, + { + "type": "transaction_address", + "amount": "12.34567890 BTC", + "address": "34oVnh4gNviJGMnNvgquMeLAxvXJuaRVMZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "0.05419010 BTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH" + }, + { + "type": "transaction_address", + "amount": "12.34567890 BTC", + "address": "34oV nh4g NviJ GMnN vgqu MeLA xvXJ uaRV MZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1q xven xven xven xven xven xven xven xven 2ymj t8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "0.05419010 BTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d", + "sighash": "all" + } + ] + }, + { + "id": "multiple-output-types-bitcoin-satoshi", + "description": "Displays P2PKH, P2SH, P2WPKH and P2WSH recipients, warns about two change outputs, and signs the transaction.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001c38bf8f182f907c18224bd5f9cbd843e118019c68c288f5045bc4941672d30380000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd20296490000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600140b664ee927e3e41c6f5acd135d5b31fca4ed8e4a640000000000000016001491d160f1749d830eec2b9402b0b1fcc5ccd52ab1000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f0000000001011f8057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f22060218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d184c00739d54000080000000800a000080000000000500000000000000002202024ea6cfaca7a9f7689293a9b67c77e0a3e5aa8b160b5cfa12474b52767b4b94cf184c00739d54000080000000800a000080010000000300000000220202b8564af1c586c4477df4c233987002147a77987163bdc0535d364569306a74b6184c00739d54000080000000800a000080010000001e00000000", + "options": { + "format_unit": "sat" + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "100000000 sat", + "address": "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH" + }, + { + "type": "transaction_address", + "amount": "1234567890 sat", + "address": "34oVnh4gNviJGMnNvgquMeLAxvXJuaRVMZ" + }, + { + "type": "transaction_address", + "amount": "6000 sat", + "address": "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8" + }, + { + "type": "transaction_address", + "amount": "7000 sat", + "address": "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "1339999900 sat", + "fee": "5419010 sat", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "100000000 sat", + "address": "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH" + }, + { + "type": "transaction_address", + "amount": "1234567890 sat", + "address": "34oV nh4g NviJ GMnN vgqu MeLA xvXJ uaRV MZ" + }, + { + "type": "transaction_address", + "amount": "6000 sat", + "address": "bc1q xven xven xven xven xven xven xven xven 2ymj t8" + }, + { + "type": "transaction_address", + "amount": "7000 sat", + "address": "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "1339999900 sat", + "fee": "5419010 sat", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d", + "sighash": "all" + } + ] + }, + { + "id": "multiple-output-types-litecoin-coin", + "description": "Displays P2PKH, P2SH, P2WPKH and P2WSH recipients, warns about two change outputs, and signs the transaction.", + "coin": "ltc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001df6504f0b9af7c7d6795aa190cb900553f2b56690c26ed0d5b8439086dedb9bf0000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd20296490000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600146874a290ffa9444d417e413635eb17a610282aef640000000000000016001466d4383af71817173d87697435a67185d787eb0e000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014f3f3b2cac1d6582e72c41c7db73f4d1c8eb642500000000001011f8057ff7800000000160014f3f3b2cac1d6582e72c41c7db73f4d1c8eb64250220602bad6b8353f8ad1cc4cfc775b05ab9f834a1d132ac36e4cafdc43d8c2c99ebc46184c00739d54000080020000800a0000800000000005000000000000000022020394b3d8170d82ca3dab3feaa5846c5e659cf2f970fc535597aa96c921ed747bdd184c00739d54000080020000800a00008001000000030000000022020349e62be3b83b966bb79dfb50aac69ca112df3237b0ec5cbb069321d2dc363d84184c00739d54000080020000800a000080010000001e00000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 LTC", + "address": "LLnCCHbSzfwWquEdaS5TF2Yt7uz5Qb1SZ1" + }, + { + "type": "transaction_address", + "amount": "12.34567890 LTC", + "address": "MB1e6aUeL3Zj4s4H2ZqFBHaaHd7kvvzTco" + }, + { + "type": "transaction_address", + "amount": "0.00006000 LTC", + "address": "ltc1qxvenxvenxvenxvenxvenxvenxvenxvenwcpknh" + }, + { + "type": "transaction_address", + "amount": "0.00007000 LTC", + "address": "ltc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqwr7k5s" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 LTC", + "fee": "0.05419010 LTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 LTC", + "address": "LLnC CHbS zfwW quEd aS5T F2Yt 7uz5 Qb1S Z1" + }, + { + "type": "transaction_address", + "amount": "12.34567890 LTC", + "address": "MB1e 6aUe L3Zj 4s4H 2ZqF BHaa Hd7k vvzT co" + }, + { + "type": "transaction_address", + "amount": "0.00006000 LTC", + "address": "ltc1 qxve nxve nxve nxve nxve nxve nxve nxve nwcp knh" + }, + { + "type": "transaction_address", + "amount": "0.00007000 LTC", + "address": "ltc1 qg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z qwr7 k5s" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 LTC", + "fee": "0.05419010 LTC", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02bad6b8353f8ad1cc4cfc775b05ab9f834a1d132ac36e4cafdc43d8c2c99ebc46", + "sighash": "all" + } + ] + }, + { + "id": "high-fee-rounding", + "description": "Displays the 18.1% high-fee warning and requires the final longtouch on the warning instead of the fee screen.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff0100fdfd000100000001c38bf8f182f907c18224bd5f9cbd843e118019c68c288f5045bc4941672d30380000000000ffffffff0600e1f505000000001976a914111111111111111111111111111111111111111188acd240aa3d0000000017a91422222222222222222222222222222222222222228770170000000000001600143333333333333333333333333333333333333333581b000000000000220020444444444444444444444444444444444444444444444444444444444444444480902029000000001600140b664ee927e3e41c6f5acd135d5b31fca4ed8e4a640000000000000016001491d160f1749d830eec2b9402b0b1fcc5ccd52ab1000000000001005201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff018057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f0000000001011f8057ff7800000000160014cda0c8fb9f429f6090136946d7c928e77117847f22060218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d184c00739d54000080000000800a000080000000000500000000000000002202024ea6cfaca7a9f7689293a9b67c77e0a3e5aa8b160b5cfa12474b52767b4b94cf184c00739d54000080000000800a000080010000000300000000220202b8564af1c586c4477df4c233987002147a77987163bdc0535d364569306a74b6184c00739d54000080000000800a000080010000001e00000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 18.1%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH" + }, + { + "type": "transaction_address", + "amount": "10.34567890 BTC", + "address": "34oVnh4gNviJGMnNvgquMeLAxvXJuaRVMZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "2.05419010 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 18.1%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "1.00000000 BTC", + "address": "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH" + }, + { + "type": "transaction_address", + "amount": "10.34567890 BTC", + "address": "34oV nh4g NviJ GMnN vgqu MeLA xvXJ uaRV MZ" + }, + { + "type": "transaction_address", + "amount": "0.00006000 BTC", + "address": "bc1q xven xven xven xven xven xven xven xven 2ymj t8" + }, + { + "type": "transaction_address", + "amount": "0.00007000 BTC", + "address": "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4" + }, + { + "type": "confirm", + "title": "Warning", + "body": "There are 2\nchange outputs.\nProceed?", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "13.39999900 BTC", + "fee": "2.05419010 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 18.1%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0218f980e27a3f46860d15cbeb0aba08ed32bf5de22be5a3b80e26cfec5727eb1d", + "sighash": "all" + } + ] + }, + { + "id": "swap-payment-request", + "description": "Displays and signs a Bitcoin-to-Ethereum coin-purchase payment request.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff01007d0200000001013018e6e997b8e9b80d0244f11e3c68ba7107d8bb9a359a16a9a796d64e85550000000000ffffffff02801d2c04000000001600141133a71526b176aac7b2462bc5bff36abe68a18a002d310100000000225120da28dbf53b86ae59ae156af3745bb326f44ce309dda97345708c8d3dd45755a6000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b0000000001011f00e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b220602c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d5400008000000080000000800000000000000000002202034fc8251f3b2e939dccf75db7c9edf0e8cbc88ec662c0119e961e586ebebe3731184c00739d54000080000000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "coin_purchase", + "coin_type": 60, + "amount": "0.25 ETH", + "address": "0x416E88840Eb6353E49252Da2a2c140eA1f969D1a", + "address_keypath": "m/44'/60'/0'/0/0" + } + ], + "signature": "917d3514b2358406730f42952efe0a873255788314a9cf2906474c1e2cefd8cb0e35791ef8211463d37a007e3d9d3a0ec11736250b7e089156c53e5ebf40413d" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.0", + "outcome": "invalid_input", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "Test Merchant" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "Test Merchant" + }, + { + "type": "swap", + "title": "Swap", + "from": "0.20000000 BTC", + "to": "0.25 ETH" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048", + "sighash": "all" + } + ] + }, + { + "id": "swap-payment-request-unsupported-source", + "description": "Rejects a coin-purchase payment request whose source account is Bitcoin testnet.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000", + "options": { + "outputs": { + "1": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 20000000, + "memos": [ + { + "type": "coin_purchase", + "coin_type": 60, + "amount": "0.25 ETH", + "address": "0x416E88840Eb6353E49252Da2a2c140eA1f969D1a", + "address_keypath": "m/44'/60'/0'/0/0" + } + ], + "signature": "c231191f200c3259f71a509dc644b49ab14332fdde4b63a12aaa19b744f188f872e7c60cc6a7e5823acb1f849659e16664fbf2680749bd2101b33cdb49bb8dce" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "invalid_input", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.0", + "outcome": "invalid_input", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "Test Merchant" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "p2wpkh-p2sh", + "description": "Signs a nested SegWit input and recognizes nested SegWit change.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007e020000000118daf26c629c509a7ea9da4fa3a32393c330c526aa6466e2a2e2f880ee9cf50e0000000000ffffffff02801d2c040000000017a9147946b34fad2af6a7ed141651188fb6d7082b8ef987002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005302000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc66870000000001012000e1f5050000000017a914f566e83e9f22881747e8308ed56afd5fa0abbc6687010416001468a00bbd6de555d756e92815e523b789def6c0b022060329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0184c00739d31000080010000800000008000000000000000000001001600142f4e1eff50d5156d90c5a7d62b7ef3825d5b6db7220203a06059ec858225435fd867601375832479a21ccaef653d6a7920a4cee4797e73184c00739d31000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "0329b834de159b7b1ff5d2bcc4b10ba2611169dff448a59df7f0c84c2c54b9b7d0", + "sighash": "all" + } + ] + }, + { + "id": "high-input-address-index", + "description": "Spends an owned input at address index 100000 without treating the high spend path as a receive-address verification request.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d02000000014e52523f00352b5da9aca9fcb5dfb4a6c1e0cb8632718e187e0ffefaec0a5eb90000000000ffffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014c2e296c295380207dfdc535c8872c043d9bf52d30000000001011f00e1f50500000000160014c2e296c295380207dfdc535c8872c043d9bf52d3220603ce6f00e8e7302e323faf1f26ce2584307cb1bcef2a2b2140c6013c67bb9b991b184c00739d54000080010000800000008000000000a086010000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03ce6f00e8e7302e323faf1f26ce2584307cb1bcef2a2b2140c6013c67bb9b991b", + "sighash": "all" + } + ] + }, + { + "id": "locktime-zero", + "description": "Suppresses the locktime confirmation when locktime is zero even if the sequence signals RBF.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000fdffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "locktime-non-rbf", + "description": "Displays block locktime 10 and its non-rbf sequence semantics.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000feffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef140a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is not RBF", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is not RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is not RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "locktime-rbf", + "description": "Displays block locktime 10 and its rbf sequence semantics.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000fdffffff02801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef140a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d540000800100008000000080000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is RBF", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\nTransaction is RBF", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "locktime-litecoin-non-rbf-sequence", + "description": "Displays a Litecoin block locktime without Bitcoin-specific RBF wording.", + "coin": "ltc", + "psbt": { + "transaction": "70736274ff01007102000000018660b164e26bff1622e4172600f4011bd824211a4ef4201d92c0b0fb290f9b730000000000feffffff02801d2c04000000001600146514c2779c7ee0f8e09ae4f2999c334e5254c70e002d310100000000160014751e76e8199196d454941c45d1b3a323f1433bd60a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f160000000001011f00e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f162206032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2184c00739d5400008002000080000000800000000000000000002202039537a5fa4fa947baa7b215407914d5fcb3709c554ff5750bf0ba931b3cda24ae184c00739d54000080020000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kgmn4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1 qw50 8d6q ejxt dg4y 5r3z arva ry0c 5xw7 kgmn 4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2", + "sighash": "all" + } + ] + }, + { + "id": "locktime-litecoin-rbf-sequence", + "description": "Displays a Litecoin block locktime without Bitcoin-specific RBF wording.", + "coin": "ltc", + "psbt": { + "transaction": "70736274ff01007102000000018660b164e26bff1622e4172600f4011bd824211a4ef4201d92c0b0fb290f9b730000000000fdffffff02801d2c04000000001600146514c2779c7ee0f8e09ae4f2999c334e5254c70e002d310100000000160014751e76e8199196d454941c45d1b3a323f1433bd60a0000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f160000000001011f00e1f50500000000160014da2f19386bd475b275e76917886fc8340cce9f162206032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2184c00739d5400008002000080000000800000000000000000002202039537a5fa4fa947baa7b215407914d5fcb3709c554ff5750bf0ba931b3cda24ae184c00739d54000080020000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1qw508d6qejxtdg4y5r3zarvary0c5xw7kgmn4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 LTC", + "address": "ltc1 qw50 8d6q ejxt dg4y 5r3z arva ry0c 5xw7 kgmn 4n9" + }, + { + "type": "confirm", + "title": "", + "body": "Locktime on block:\n10\n", + "longtouch": false + }, + { + "type": "transaction_fee", + "amount": "0.30000000 LTC", + "fee": "0.10000000 LTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "032780e5412e19ac486f2be57004bc34d0f9957468bfa2b0fa1c8e689819d97df2", + "sighash": "all" + } + ] + }, + { + "id": "p2tr-output-mainnet", + "description": "Displays and signs a mainnet P2TR recipient output from a native SegWit account.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff01007d0200000001013018e6e997b8e9b80d0244f11e3c68ba7107d8bb9a359a16a9a796d64e85550000000000ffffffff02801d2c04000000001600141133a71526b176aac7b2462bc5bff36abe68a18a002d310100000000225120da28dbf53b86ae59ae156af3745bb326f44ce309dda97345708c8d3dd45755a6000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b0000000001011f00e1f50500000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b220602c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d5400008000000080000000800000000000000000002202034fc8251f3b2e939dccf75db7c9edf0e8cbc88ec662c0119e961e586ebebe3731184c00739d54000080000000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "bc1pmg5dhafms6h9nts4dtehgkanym6yeccfmk5hx3ts3jxnm4zh2knqv80ha5" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "bc1p mg5d hafm s6h9 nts4 dteh gkan ym6y eccf mk5h x3ts 3jxn m4zh 2knq v80h a5" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048", + "sighash": "all" + } + ] + }, + { + "id": "silent-payment-owned-output", + "description": "Covers version-specific handling of silent-payment metadata attached to an output owned by this device, rejected since v9.26.3.", + "coin": "btc", + "psbt": { + "transaction": "70736274ff01007d0200000001a49f3e93836b9131d84d700f422b07ff6f6ffa379499b910c0f153e18f3dac3c0000000000ffffffff02801d2c0400000000225120aaf8631d8fffabc65d283faa12aedc768bdc669c1ba6ff1166f356f13eefe089002d310100000000160014fc8c96dd45eed60d714647d787e74d2b33b5ae9b000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a29430000000001012b00e1f5050000000022512041ea70bc6cdc9e38c9faa847f0b9e8ce082b3be15fa6d137fbbab6cb138a29432116cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff0952719004c00739d5600008000000080000000800000000000000000011720cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff095270001052067426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee5210767426cdc697740fe8fcedd942ae76b85b22d73db568fc32d7ced988f387f0ee519004c00739d560000800000008000000080010000000000000000220202c6f297dd7bcc0a38a527b3f2289cf91816cae2845a605080fd5fc9af4a945048184c00739d540000800000008000000080000000000000000000", + "options": { + "outputs": { + "1": { + "silent_payment_address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + } + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.22.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "This BitBox02: sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.22.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "This BitBox (same account): sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "max_version_exclusive": "9.26.3", + "outcome": "success", + "screens": [ + { + "type": "transaction_address", + "amount": "0.20000000 BTC", + "address": "This BitBox (same account): sp1q qgst e7k9 hx0q ftg6 qmwl kqtw uy6c ycya vzmz j85c 6qdf hjdp djtd gqju exzk 6mur w56s uy3e 0rd2 cgqv ycxt tddw svgx e2us fpxu mr70 xc9p kqwv" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 BTC", + "fee": "0.10000000 BTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.3", + "outcome": "invalid_input", + "screens": [] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "cd47d5291421bd92a7dd849beb7f0d13f1c7c98067e3c23ff98de62c1ff09527", + "sighash": "default" + } + ] + }, + { + "id": "op-return-nonascii", + "description": "Displays a non-ASCII OP_RETURN payload as hexadecimal and signs the transaction.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008d0200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff030000000000000000076a050102030405801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "OP_RETURN\ndata (hex)", + "body": "0102030405", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "OP_RETURN\ndata (hex)", + "body": "0102030405", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3", + "sighash": "all" + } + ] + }, + { + "id": "op-return-nonzero-value", + "description": "Rejects a nonzero-value OP_RETURN output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100930200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff0364000000000000000d6a0b68656c6c6f20776f726c64801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000" + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "op-return-silent-payment", + "description": "Rejects silent-payment metadata on an OP_RETURN output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100930200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff0300000000000000000d6a0b68656c6c6f20776f726c64801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000", + "options": { + "outputs": { + "0": { + "silent_payment_address": "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + } + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "op-return-payment-request", + "description": "Rejects payment-request metadata on an OP_RETURN output.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100930200000001703a366633ea48d0ffd13c1dd1d33c7a0cdcb9b29fb7174ac23842ea36b897c20000000000ffffffff0300000000000000000d6a0b68656c6c6f20776f726c64801d2c04000000001600149e2597ee0b2fbffec6275bd6cab7d2b4737b6e95002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000160014fc6b321f780a2401be6884e5bf84520a278209440000000001011f00e1f50500000000160014fc6b321f780a2401be6884e5bf84520a27820944220603a9a6f11db052a402ccd0e2a2a31c4ad196b93db5b1ad1a3bdf8f23882d42b3c3184c00739d54000080010000800000008000000000000000000000220202dca870342cf7bc12c2d6c23dc6a0761160b1ff54e820be4b48c8f3ba9a8dc239184c00739d54000080010000800000008001000000000000000000", + "options": { + "outputs": { + "0": { + "payment_request_index": 0 + } + }, + "payment_requests": [ + { + "recipient_name": "Test Merchant", + "total_amount": 1, + "memos": [ + { + "type": "text", + "note": "TextMemo line1\nTextMemo line2" + } + ], + "signature": "93582744d5fab8b70cd2f1686bb7baf76e2797f095a83a6029f04c8c92d11bca4f274a71ef0ac79ce65b87241feb770823a1525d64f85e69104081d6ad397601" + } + ] + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.24.0", + "outcome": "unsupported", + "unsupported_version": "9.24.0", + "screens": [] + }, + { + "min_version": "9.24.0", + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "multisig-not-registered", + "description": "Rejects a valid 1-of-2 P2WSH multisig transaction when its account has not been registered.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff010089020000000131816c7584e8fb28ce853874eeb709394562c6e99eb9b6f3987d205dcc6933880000000000ffffffff02801d2c040000000022002049934b3c5c97bbd8e9e77ca525ddbad2addcf929f5dd3b83d2c017ab65e54382002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020276686da70f18aec8090484fc1a58b0808b2f6c7aa9f88191ec47166bd805c9e0000000001012b00e1f50500000000220020276686da70f18aec8090484fc1a58b0808b2f6c7aa9f88191ec47166bd805c9e0105475121031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad21039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde52ae2206031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad1c0dbd0cc33000008001000080000000800200008000000000000000002206039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde1c4c00739d30000080010000800000008002000080000000000000000000010147512102e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b21031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb52ae220202e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b1c0dbd0cc33000008001000080000000800200008001000000000000002202031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb1c4c00739d3000008001000080000000800200008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFX8u4cajvyZrQGikAfBcRt5EqVj4C9xB1HEQSZfN5weSUq4ULeMra6GXY8fM6hwgBX3WZ8TCP7nvMQu3hM4zvUHqWPhU9oq8HNV4ixboTz" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "outcome": "invalid_input", + "screens": [] + } + ] + }, + { + "id": "multisig-p2wsh-p2sh", + "description": "Signs and finalizes a registered 1-of-2 nested P2SH-P2WSH multisig transaction.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01007e02000000017a26adfe8f25b544220236a6516252b9eb746a620ffd210679634285fc890dd50000000000ffffffff02801d2c040000000017a914c88f04120fca1cbbd8e1a9dac997a5d99d8ca09d87002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005302000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f5050000000017a9143b3a20e6b4121c867e5a8e1bf1c3c85504b1ff13870000000001012000e1f5050000000017a9143b3a20e6b4121c867e5a8e1bf1c3c85504b1ff13870104220020cd9833d9c1ca210e8e5984f8fffa56f40770409811f431ea1c6daa0cd7cfba7f010547512102efb2f859bf983f02f6c17664426a9992985b3bdac83184b0162993d398f8ba0d2103f506dd0e67b1d3f035a6055961a3ac5510dff004069e9d025930338fd8e20c8852ae220602efb2f859bf983f02f6c17664426a9992985b3bdac83184b0162993d398f8ba0d1c0dbd0cc3300000800100008000000080010000800000000000000000220603f506dd0e67b1d3f035a6055961a3ac5510dff004069e9d025930338fd8e20c881c4c00739d3000008001000080000000800100008000000000000000000001002200209a8754cc38326f8daa604b0d1f4d803f6bd714cf428490ed6cb5407b05ea91cd01014751210235294f21973f87de509b16ef8f71bf9c353c0d450db814a428709f80a2a347f32103fd271ddb4968f17ea1aab754c4283c05e58ee5810868e845ad7d2b06ef30a87452ae22020235294f21973f87de509b16ef8f71bf9c353c0d450db814a428709f80a2a347f31c0dbd0cc3300000800100008000000080010000800100000000000000220203fd271ddb4968f17ea1aab754c4283c05e58ee5810868e845ad7d2b06ef30a8741c4c00739d3000008001000080000000800100008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDNQtJHZFjbd79dLGtC1yTcWn7WHPaCdpa8jXS8mU4y9tdYqzhrUVt3w6QR6vjx66nbUX4s2Hw5V5oer76XzKpRVMExC7zpLG", + "tpubDFX8u4cajvyZqUGS5AVgPb7GUpAJJ5nbkVMnw1m2ajrHaZmknbxuWDac2P2zyiB35UcgqwmVAeVzGEVV9qbji6tzYRCTiWwPGvtUULR4DLM" + ], + "our_xpub_index": 0, + "script_type": "p2wsh_p2sh" + }, + "keypath": "m/48'/1'/0'/1'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test sh-wsh multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test sh-wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "1-of-2\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test sh-wsh multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "multisig", + "threshold": 1, + "xpubs": [ + "xpub6EhivkawbkKDNQtJHZFjbd79dLGtC1yTcWn7WHPaCdpa8jXS8mU4y9tdYqzhrUVt3w6QR6vjx66nbUX4s2Hw5V5oer76XzKpRVMExC7zpLG", + "tpubDFX8u4cajvyZqUGS5AVgPb7GUpAJJ5nbkVMnw1m2ajrHaZmknbxuWDac2P2zyiB35UcgqwmVAeVzGEVV9qbji6tzYRCTiWwPGvtUULR4DLM" + ], + "our_xpub_index": 0, + "script_type": "p2wsh_p2sh" + }, + "keypath": "m/48'/1'/0'/1'", + "name": "test sh-wsh multisig" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "03f506dd0e67b1d3f035a6055961a3ac5510dff004069e9d025930338fd8e20c88", + "sighash": "all" + } + ] + }, + { + "id": "multisig-large", + "description": "Adds the seventh signature to a registered 7-of-15 P2WSH multisig transaction whose PSBT already contains six cosigner signatures.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001cb44be13195cb0246f334d02fc36179e5c8d03e5bb812f97b039c5920413280f0000000000ffffffff02801d2c0400000000220020472cd252fa25c59c9e7f876cca3cbf4585826ff6f66b5e465feddc94cf50a5cc002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f505000000002200208d69e9431a924d7a5cd26f9cf85b2a3bc9d91dc06266f4a24b2ea13fdd1da1780000000001012b00e1f505000000002200208d69e9431a924d7a5cd26f9cf85b2a3bc9d91dc06266f4a24b2ea13fdd1da17822020266dc638844899b98e0d23a40deb72ec14b4842fbd3e70f65f1a955b6cf9f5e50483045022100bf0316ed68454f814fe83efb4ae013339d044b003336a1c68ed80bf9123d9fb2022045ba0d2a90aadfd3b062036e03ba1f6cf8c2aa2bd5d4c5bf1728d4648b5cd87801220202fdb3657418ffef1ed8f513b9e4d9a886ce9798a6b766e5da018417f981395feb483045022100aa4344593f229829440738816c3ed451191898a1761f1e1157a008e7ba5bfb68022030f17cd5f5006baded37e56ecb2480fd3a05b44aa88fe2f870b4974326a04d52012202031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad483045022100a62043f3af8c106c3ef3d0aad0f7c36940627fa06792e19b178e5cd39c4fe9850220793fa3990ed0fa526018e835aed5cab8206827ddbea30a86cf2421c9cc3fb686012202039446d050672e4829214199332a974a5ec1ec80ab0cd4dfc395e2e37a79af87f0473044022002958b416ce5a3c9722cdd07c45addbc679ec34d63bd138207583b258031300c02204d36fd84522cd7831fc06dbd5e611e7bccfc23098a752808a5a252ba05060070012202039b319f5be7dcd91450775a73dc9f163c20ad6eb08e611f220342d53fb0d70e204730440220630b0e820c0e2e7940929a3190b374a649c2e0f0aa78077806551aad57d20b7c02202a2448562899d865f43805441331533721827925b386841818df7b5e406989e101220203ca2121456edf5d624b9bed7d7f507e40b088b313d5773f19df45e71b96278421483045022100dc2b897a4ae2ae446fb7fe7f94836f1136c7ad3c5deb4cb4ccd1eccd224c3f7a02205e4506f6edd9981ba7bb7578454b4c4250de3070db29b74c20c21f5d88e599b2010105fd01025721020faf2667c643963b00041bea043d6fd2e91f3db842b8ccfddefd0f5615bfe9b4210266dc638844899b98e0d23a40deb72ec14b4842fbd3e70f65f1a955b6cf9f5e502102bcdd4e3e30bdb4219ef1836d0f573b81574639b189225039deaefb5bd75153152102e73cddb837ae9bcb40d3f6cf72e133d77505daf9fe03a5c359822a45332391a82102eb12fd1be52ba727e325bd880e8b0ac28769c3850301f3c333c98c0d420b65ec2102fdb3657418ffef1ed8f513b9e4d9a886ce9798a6b766e5da018417f981395feb21031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad2103440396891d7d6c9f623635e4c1a4f66edca6dca79c11debe0dc58a0f10f9079421034a0ad5543e4978b96f5a06959b281298e4079fe2fd71efaa0604a93fa9305891210353983ae233c60e81243818ffa3ce635d65d666238c1e7503e31e9f2186cc714521039446d050672e4829214199332a974a5ec1ec80ab0cd4dfc395e2e37a79af87f021039b319f5be7dcd91450775a73dc9f163c20ad6eb08e611f220342d53fb0d70e2021039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde2103bbc0345e402e1f8838e0ea1669fc79f452cfcc7136a3ff91f621833fbc154f6a2103ca2121456edf5d624b9bed7d7f507e40b088b313d5773f19df45e71b962784215fae2206020faf2667c643963b00041bea043d6fd2e91f3db842b8ccfddefd0f5615bfe9b41ce9324a6c30000080010000800000008002000080000000000000000022060266dc638844899b98e0d23a40deb72ec14b4842fbd3e70f65f1a955b6cf9f5e501c73bccd56300000800100008000000080020000800000000000000000220602bcdd4e3e30bdb4219ef1836d0f573b81574639b189225039deaefb5bd75153151cd0bee5ae300000800100008000000080020000800000000000000000220602e73cddb837ae9bcb40d3f6cf72e133d77505daf9fe03a5c359822a45332391a81cc8c72c6c300000800100008000000080020000800000000000000000220602eb12fd1be52ba727e325bd880e8b0ac28769c3850301f3c333c98c0d420b65ec1c0bbe8456300000800100008000000080020000800000000000000000220602fdb3657418ffef1ed8f513b9e4d9a886ce9798a6b766e5da018417f981395feb1cac9a9e9e3000008001000080000000800200008000000000000000002206031cf0f0f1bf784a99e0c007f162d5df97563915d74004ed3fa2403f2fd1569fad1c0dbd0cc3300000800100008000000080020000800000000000000000220603440396891d7d6c9f623635e4c1a4f66edca6dca79c11debe0dc58a0f10f907941c0ed45ef03000008001000080000000800200008000000000000000002206034a0ad5543e4978b96f5a06959b281298e4079fe2fd71efaa0604a93fa93058911c9e1cb09d30000080010000800000008002000080000000000000000022060353983ae233c60e81243818ffa3ce635d65d666238c1e7503e31e9f2186cc71451c7fb8ad413000008001000080000000800200008000000000000000002206039446d050672e4829214199332a974a5ec1ec80ab0cd4dfc395e2e37a79af87f01c054f2f713000008001000080000000800200008000000000000000002206039b319f5be7dcd91450775a73dc9f163c20ad6eb08e611f220342d53fb0d70e201c41559f4f3000008001000080000000800200008000000000000000002206039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde1c4c00739d300000800100008000000080020000800000000000000000220603bbc0345e402e1f8838e0ea1669fc79f452cfcc7136a3ff91f621833fbc154f6a1c5d3e97e0300000800100008000000080020000800000000000000000220603ca2121456edf5d624b9bed7d7f507e40b088b313d5773f19df45e71b962784211c1a34b3e5300000800100008000000080020000800000000000000000000101fd01025721021f7e9e9befaa3424398f347b5b2f78d5f893498f6f8ec488dd2f9f22ea18c7bc21029c20cd5acd30db6bf7aeaf7f83cc171adc87cba7498bd7e673304db6e0a86b5b2102b29e8ea9305c52a928175756d9c979db1768ce8643badface43e1c4661aa1fec2102d63ffb041633c0c721c9dd16fabdcd0a413a5d20786fd798e9ea5458a9d0145f2102e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b21031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb21031fd8d9ae7b5cdd43427ff940ee8b485d7b0e79e2cbb53c111f736db8309dd6cb2103206d7162cc432f2b79039f839a8eb4b9155b407b9178ebf46fc29de2c23965e421034434b5c28fe2b71f01328b3af87647d7729a9ad1d1d1646d3f0b87cd0dfd06b6210347504a67f3b4bfdef20beaa5790304ce34927e740929f2caddc032ae8d653a7921035c3445de6a048ab9e905c86bc6d481890088fa7d51309a439bfeb4af77fc7650210365a176ac65d10de83778956b560630f341a6f6b43584acc0271083cff36ace35210376b63bd235b30bc9042c7b500be79e7fbebbc4f6e723c8b798aea84769acb1ea21037d33decd1fee9418357a1e419813a898f7aa006b9a59ca241fb46ad29b774fcb2103c69f4d4950ed98135ff37e631731881dd01060db8dae4ccda2f8c0a796f5c4e85fae2202021f7e9e9befaa3424398f347b5b2f78d5f893498f6f8ec488dd2f9f22ea18c7bc1c0bbe84563000008001000080000000800200008001000000000000002202029c20cd5acd30db6bf7aeaf7f83cc171adc87cba7498bd7e673304db6e0a86b5b1c0ed45ef0300000800100008000000080020000800100000000000000220202b29e8ea9305c52a928175756d9c979db1768ce8643badface43e1c4661aa1fec1c1a34b3e5300000800100008000000080020000800100000000000000220202d63ffb041633c0c721c9dd16fabdcd0a413a5d20786fd798e9ea5458a9d0145f1c41559f4f300000800100008000000080020000800100000000000000220202e48ca9c37f3f4b23010171cde63b3e22e752c5f0bda353bb1a443385b76ba38b1c0dbd0cc33000008001000080000000800200008001000000000000002202031bc4da3f6dfefcc269e08ac1edfffb1bf928790b0ed888d6047adbbd0ec27acb1c4c00739d3000008001000080000000800200008001000000000000002202031fd8d9ae7b5cdd43427ff940ee8b485d7b0e79e2cbb53c111f736db8309dd6cb1c73bccd56300000800100008000000080020000800100000000000000220203206d7162cc432f2b79039f839a8eb4b9155b407b9178ebf46fc29de2c23965e41c9e1cb09d3000008001000080000000800200008001000000000000002202034434b5c28fe2b71f01328b3af87647d7729a9ad1d1d1646d3f0b87cd0dfd06b61cac9a9e9e30000080010000800000008002000080010000000000000022020347504a67f3b4bfdef20beaa5790304ce34927e740929f2caddc032ae8d653a791cc8c72c6c3000008001000080000000800200008001000000000000002202035c3445de6a048ab9e905c86bc6d481890088fa7d51309a439bfeb4af77fc76501c7fb8ad4130000080010000800000008002000080010000000000000022020365a176ac65d10de83778956b560630f341a6f6b43584acc0271083cff36ace351c054f2f7130000080010000800000008002000080010000000000000022020376b63bd235b30bc9042c7b500be79e7fbebbc4f6e723c8b798aea84769acb1ea1c5d3e97e03000008001000080000000800200008001000000000000002202037d33decd1fee9418357a1e419813a898f7aa006b9a59ca241fb46ad29b774fcb1ce9324a6c300000800100008000000080020000800100000000000000220203c69f4d4950ed98135ff37e631731881dd01060db8dae4ccda2f8c0a796f5c4e81cd0bee5ae3000008001000080000000800200008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "multisig", + "threshold": 7, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFX8u4cajvyZrQGikAfBcRt5EqVj4C9xB1HEQSZfN5weSUq4ULeMra6GXY8fM6hwgBX3WZ8TCP7nvMQu3hM4zvUHqWPhU9oq8HNV4ixboTz", + "tpubDFCqK5T7J3FUasUXA1xsminXkeaPSxe1id2K4KCEaszuTNKERymaiZHTnUcjGDf4b57XinAHauwSh1uDRmNVMtfhiStJWsf83MZLA1tC5qi", + "tpubDEKEr89i7dBFXrrpkK2KZQy9aMw6Dg8idr1ysbZJ94mWk6uaFAy4DmYMw7LMDSEaeSJdAcgshdLATcbr6T9hQoouRphfHC9aBQqiWAh8wRF", + "tpubDERfkonU8krEq7knZXFPFrS5bnbhsyXhV7wdM9agZuzD6YgM95LgBWKzAkPeZMseTWRj2PPcbw2kEjcrBctJEzLNUu2VLz1Fixehoc5z4aq", + "tpubDECMwLTAdWk5RcgKaaLKaP83R586xMFtBhH5osdzdF6VbEwFpQp3uiiaHrKGPnqmfDUKFtSQE5ForxoBYgJWEHA5B2T9i5GP5ZAuScWKXgH", + "tpubDEuxzYbnkbY2XHQZjzcDV2Vk4DH4oZwLefGso1jXfFsTyhVV5HxZvVUrSdGLc9AVBTNcCDZh5qw7RgPiibMfMVWtHpjzoErqJcvukzFSpbf", + "tpubDEccsjksu3ddEUnx5FkNADMy9YkLi3p6vQGpdgFDSypMfYZs7amRL94ucqyHGZrVDMrBc937ptj4SKihNiYJtnRgiZ742hpxfnAgynaQno5", + "tpubDF1U1F2s28Rv71bukRRTLUN3hiTgKDGjxGVrFAYXYo1qaWFNsUnA4MoyFtKoytQyLHfmE267HDo3VDgDUfk2hQwb3ary3aNojzkT1sTe5oi", + "tpubDF8LvKCNR3JMayQDTRRqBD3PLhVng5wk6bYvCpsrhe5Mffon2sSFrLBz9J3WCdLqsoeUjMiG1gqsiiM8RVNw6eSfqbRyFsSt2sZPd54GoVi", + "tpubDEgfQ9SqcnKyEkVmEEkx9AaQ3pZN1nSZQjZxtCcTVytAk62DggB6FiuH12cFeygFLgDPsYtBYDp8FCQDrdgZLrpp2cFhnsFur55kWid1DYg", + "tpubDEHvZYZJ5N3jGxHTJJ1LSWdoD3SUSSz7zUhUPLrTMC7SoEkthZVzfFz8qSYAYF1Rg6LjKTsBZMeNJivd4iDyjbQQ6JcyZe2eHB3WENiQECP", + "tpubDEjJyDs2yboyzkczJ5ADEmU9yJNqCgmpUPBqW5CVSPCxg8Q59ZwDqRCBwUrDmR29iY8BJGfBa5bq9MBhYpnqjaQvNQdFHqGX99W6ocPX63z", + "tpubDFFcidKjrkaTSz4R4s9Y5ijp3dzHkJ9XZ2JkLanjkUskBBbFvjxfQMeU5L52ANJtgpiMRyjK4WzJ6qkK22KQsScHbiQBEsstjVKLoxZynZm", + "tpubDF3ds3VKs6p4YjvSLY848WwW21fxiaYGdn5cZEUZ5CwKwMveavxSNA3GhJHqZkFkXa5DTeT1ZKjThtFY8KDfNAuvF95SDcXEoGXNq5bs9nT" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "7-of-15\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test large multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "7-of-15\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test large multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "7-of-15\nBTC Testnet multisig", + "longtouch": false + }, + { + "type": "confirm", + "title": "Spend from", + "body": "test large multisig", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "multisig", + "threshold": 7, + "xpubs": [ + "xpub6EhivkawbkKDRT9Z59WwqhAzZZe7NQhxPgYc67nHbagRxoSbKEiZ6x4P4bE6s48GHvAHaGH8hsQfJ4RSSeNbVdYp7Kii2UGWKn8x2bQDtiR", + "tpubDFX8u4cajvyZrQGikAfBcRt5EqVj4C9xB1HEQSZfN5weSUq4ULeMra6GXY8fM6hwgBX3WZ8TCP7nvMQu3hM4zvUHqWPhU9oq8HNV4ixboTz", + "tpubDFCqK5T7J3FUasUXA1xsminXkeaPSxe1id2K4KCEaszuTNKERymaiZHTnUcjGDf4b57XinAHauwSh1uDRmNVMtfhiStJWsf83MZLA1tC5qi", + "tpubDEKEr89i7dBFXrrpkK2KZQy9aMw6Dg8idr1ysbZJ94mWk6uaFAy4DmYMw7LMDSEaeSJdAcgshdLATcbr6T9hQoouRphfHC9aBQqiWAh8wRF", + "tpubDERfkonU8krEq7knZXFPFrS5bnbhsyXhV7wdM9agZuzD6YgM95LgBWKzAkPeZMseTWRj2PPcbw2kEjcrBctJEzLNUu2VLz1Fixehoc5z4aq", + "tpubDECMwLTAdWk5RcgKaaLKaP83R586xMFtBhH5osdzdF6VbEwFpQp3uiiaHrKGPnqmfDUKFtSQE5ForxoBYgJWEHA5B2T9i5GP5ZAuScWKXgH", + "tpubDEuxzYbnkbY2XHQZjzcDV2Vk4DH4oZwLefGso1jXfFsTyhVV5HxZvVUrSdGLc9AVBTNcCDZh5qw7RgPiibMfMVWtHpjzoErqJcvukzFSpbf", + "tpubDEccsjksu3ddEUnx5FkNADMy9YkLi3p6vQGpdgFDSypMfYZs7amRL94ucqyHGZrVDMrBc937ptj4SKihNiYJtnRgiZ742hpxfnAgynaQno5", + "tpubDF1U1F2s28Rv71bukRRTLUN3hiTgKDGjxGVrFAYXYo1qaWFNsUnA4MoyFtKoytQyLHfmE267HDo3VDgDUfk2hQwb3ary3aNojzkT1sTe5oi", + "tpubDF8LvKCNR3JMayQDTRRqBD3PLhVng5wk6bYvCpsrhe5Mffon2sSFrLBz9J3WCdLqsoeUjMiG1gqsiiM8RVNw6eSfqbRyFsSt2sZPd54GoVi", + "tpubDEgfQ9SqcnKyEkVmEEkx9AaQ3pZN1nSZQjZxtCcTVytAk62DggB6FiuH12cFeygFLgDPsYtBYDp8FCQDrdgZLrpp2cFhnsFur55kWid1DYg", + "tpubDEHvZYZJ5N3jGxHTJJ1LSWdoD3SUSSz7zUhUPLrTMC7SoEkthZVzfFz8qSYAYF1Rg6LjKTsBZMeNJivd4iDyjbQQ6JcyZe2eHB3WENiQECP", + "tpubDEjJyDs2yboyzkczJ5ADEmU9yJNqCgmpUPBqW5CVSPCxg8Q59ZwDqRCBwUrDmR29iY8BJGfBa5bq9MBhYpnqjaQvNQdFHqGX99W6ocPX63z", + "tpubDFFcidKjrkaTSz4R4s9Y5ijp3dzHkJ9XZ2JkLanjkUskBBbFvjxfQMeU5L52ANJtgpiMRyjK4WzJ6qkK22KQsScHbiQBEsstjVKLoxZynZm", + "tpubDF3ds3VKs6p4YjvSLY848WwW21fxiaYGdn5cZEUZ5CwKwMveavxSNA3GhJHqZkFkXa5DTeT1ZKjThtFY8KDfNAuvF95SDcXEoGXNq5bs9nT" + ], + "our_xpub_index": 0, + "script_type": "p2wsh" + }, + "keypath": "m/48'/1'/0'/2'", + "name": "test large multisig" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "039fb11f1394842eeeadd04ec165f713c49cb875443da2bc05d994ec118d7d1fde", + "sighash": "all" + } + ] + }, + { + "id": "policy-tr-keyspend-with-script-tree", + "description": "Signs a Taproot policy key path whose tweak commits to a script tree.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001481b25e956e13b122e0155ee1066ff2620345d94c51e6c2b7889e0daac298db00000000000ffffffff02801d2c0400000000225120916ffdd6fe3d0adf420c6d71702a944b8f22db3277d4b24721f2f1deb96e8a46002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120d06244555fe882d1190e968e20f053018fe20114014338e89149d314ef4352d92215c13a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2232034c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de2acc0211634c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de23d01d9c7b0c5dc86c4594427111e20854229a2bded47788886c3b3e5093c5d9d9f56c5e4fe1a30000080010000800000008003000080000000000000000021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21d004c00739d3000008001000080000000800300008000000000000000000117203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2011820d9c7b0c5dc86c4594427111e20854229a2bded47788886c3b3e5093c5d9d9f56000105208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a01062500c022203df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563bac21073df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563b3d016a4a33d1169c5d57685ea4668790c7ce35fc9af5f8002aea692c3296287f548fc5e4fe1a30000080010000800000008003000080010000000000000021078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1d004c00739d3000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/**,pk(@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr tweaked keyspend", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/**,pk(@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test tr tweaked keyspend", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/**,pk(@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/**,pk(@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test tr tweaked keyspend" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_key", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "sighash": "default" + } + ] + }, + { + "id": "policy-tr-unspendable-internal-key", + "description": "Signs a Taproot script path with a provably unspendable internal key and displays that property in the policy review.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff0100890200000001a8346bd79715029f26a2628a872d2299934cfc774bb3c4e52209ef404f07278f0000000000ffffffff02801d2c0400000000225120f191a4cb0a3d8f8b47251bc375159b8ff3aac672264cb0e0c9c5adeba37a09e4002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120c5340ec5fb5662a6ef7290f9dffedf9c727d503c2f69377cb4c8560e9f02e45b4114bc0eab168b76abf60a0a6f3c197ea0f9b7973e27ca1aba50daa09c62de813c68e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c08558405d9d7da26d848dd9562ab95b348ac6414e1bc28a2f8ccd839b7dd5b452abc513d099a0f0efcd011fdcc9b5f18b3c69cb9122391b751e274a67dc74b61757ab5d2215c01ab64872242fff67a9cc2429d50bd615532e55122665e448ae0e53a3f4a2ce594720bc0eab168b76abf60a0a6f3c197ea0f9b7973e27ca1aba50daa09c62de813c68ac203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2ba529cc021161ab64872242fff67a9cc2429d50bd615532e55122665e448ae0e53a3f4a2ce590d007c461e5d000000000000000021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b23d01e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c085584c00739d3000008001000080000000800300008000000000000000002116bc0eab168b76abf60a0a6f3c197ea0f9b7973e27ca1aba50daa09c62de813c683d01e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c0855808f56e293000008001000080000000800300008000000000000000000117201ab64872242fff67a9cc2429d50bd615532e55122665e448ae0e53a3f4a2ce59011820e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c0855800010520349092f96b1dd44a23e6a05986e7934bae5d6a87b9a050c1e13b38a0ae4e52c101064900c04620dbe4edc0edbcb5f212c9fa1d1bcbdb4b3f6e875a5ea52ac3cafed4057379302bac208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aba529c2107349092f96b1dd44a23e6a05986e7934bae5d6a87b9a050c1e13b38a0ae4e52c10d007c461e5d010000000000000021078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a3d014b40a0f212ea2f428474b933288cec2f2a50121ba995173e8d601dbb1b2d9a784c00739d3000008001000080000000800300008001000000000000002107dbe4edc0edbcb5f212c9fa1d1bcbdb4b3f6e875a5ea52ac3cafed4057379302b3d014b40a0f212ea2f428474b933288cec2f2a50121ba995173e8d601dbb1b2d9a7808f56e293000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8" + }, + { + "xpub": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test unspendable policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test unspendable policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,multi_a(2,@1/<0;1>/*,@2/<0;1>/*))", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4XnyFMCH9qsN7VhcUSwyj5MN4juKrFBbgE9e3qCpEQsHSmq4zHA2FAkSwBQEzFGKjvXtb2xrRLioqRdqAkqN3Y9WrBf7yNj8" + }, + { + "xpub": "tpubDF93VcTYKK2grTowjbqJMst4gS19G6rtWTNKcBnxCpQfs2BaFShNYoBDisxqhTsBWj4YhigxVArhhZN43NyWBH4E3puXS7tCrHsbjb2dvBi" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test unspendable policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_script", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "leaf_hash": "e0ed23b292365b738059122f0e9f6887887b474a1e9593cb55e872c7a0c08558", + "sighash": "default" + } + ] + }, + { + "id": "policy-tr-unspendable-internal-key-complex", + "description": "Signs the satisfiable branch of a multi-leaf Taproot policy with a provably unspendable internal key, distinct multipaths and a relative-timelock sibling branch.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000017079ce1d2dffec9e935d2a74cd4a702f722e651939daeb27faf3953f14fa5c3a0000000000ffffffff02801d2c04000000002251203ad5b7757d2dc6cd5e5ec17a0603352d401de5d69fc48f9c6ec1a3ac2b8e194f002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001012b00e1f50500000000225120eaef7e275cce42763c22a441d0e5b254c794d51e8123f5562441346dac40848c4114eeab1c2d28079589eb9e55bbd86765ffce87514a1feca661ee3d88f0e04643c9b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a404eb58edfa3eec62bf0fccefc4e73dafb7e74009a81e22a24ea6f1dae95e558273eb0f851610819384de73f327e2ffe0d3751a8b785f9db8648d2bb074559eaef4215c159165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed3309e075b5976239a09b6b08d128ec95a42f328a625c26213f9568d7768d853f4974720eeab1c2d28079589eb9e55bbd86765ffce87514a1feca661ee3d88f0e04643c9ac203a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2ba529cc04215c159165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed330b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a4920ca89423a6e0a0673b51bfd05212d8963c14a8db6cd3d7cba5dabaff97d4e3ed8ac201d7d773d42ed516b73555c715929badf8ddb135b25779b475157a49ed946af79ba519d52b2c021163a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b23d01b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a4c00739d300000800100008000000080030000800000000000000000211659165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed3300d007c461e5d00000000000000002116eeab1c2d28079589eb9e55bbd86765ffce87514a1feca661ee3d88f0e04643c93d01b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a4040280c30000080010000800000008002000080000000000000000001172059165ae2e78cafa08859f5d42370b903e931f4d0d76247a3ecdcdeffc3eed3300118207754188d95aa29ae86dcf87406dfb5ed0e1d6da02a9ad5104b95f5f81abf79a4000105209ba26f33424bc7330bf3f683129516387fdc20f5baf09046ea9eaa2ed71516d401069401c0482006048aec624d548af178a98ec862e0efaa5c2fcf8fb07f5146f410918eb21eefac20c621b132507c49d0f974e71d89e3017fe4f4ca8aa7c73c291d7a4edd1a6399a6ba519d52b201c04620ddad6824617b6a1bd330aab5c263f039b7ebe04196792fe90d203752e9c37809ac208c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44aba529c21078c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a3d01d3064571589c8c4e858bc4dd2efe190b4e2d9d3da7d96306eb9702e5401d2f264c00739d30000080010000800000008003000080010000000000000021079ba26f33424bc7330bf3f683129516387fdc20f5baf09046ea9eaa2ed71516d40d007c461e5d01000000000000002107ddad6824617b6a1bd330aab5c263f039b7ebe04196792fe90d203752e9c378093d01d3064571589c8c4e858bc4dd2efe190b4e2d9d3da7d96306eb9702e5401d2f264040280c3000008001000080000000800200008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm" + }, + { + "root_fingerprint": "4040280c", + "keypath": "m/48'/1'/0'/2'", + "xpub": "tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": false, + "expectations": [ + { + "max_version_exclusive": "9.21.0", + "outcome": "unsupported", + "unsupported_version": "9.21.0", + "screens": [] + }, + { + "min_version": "9.21.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test complex unspendable", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "[4040280c/48'/1'/0'/2']tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n3 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test complex unspendable", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/3", + "body": "Provably unspendable: tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/3", + "body": "[4040280c/48'/1'/0'/2']tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 3/3", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "tr(@0/<0;1>/*,{and_v(v:multi_a(1,@1/<2;3>/*,@2/<2;3>/*),older(2)),multi_a(2,@1/<0;1>/*,@2/<0;1>/*)})", + "keys": [ + { + "xpub": "tpubD6NzVbkrYhZ4YRYppNDw8BpfEz6cSU8qt45Eoqpj8YtPLFXoZsb3dYdAyLFp32GeY3b5ccVdtr45ajBGBDHgapb53xT9kMBcpHSF7yA8kXm" + }, + { + "root_fingerprint": "4040280c", + "keypath": "m/48'/1'/0'/2'", + "xpub": "tpubDEWR7iVng7GH7BhbVBa6avnELDTSWEWHtAethvSrYYLEFm1XB1qT4Fb9xkPGZ9S7zU1FFrpVc6wRSo5BRNYvDAP7Edr5a6FJSjeCt7tWgQR" + }, + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + } + ] + }, + "name": "test complex unspendable" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "taproot_script", + "pubkey": "3a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2", + "leaf_hash": "b2edc93766200490b5d77842a9424fd5686d22c4c14fb13dec8784164160521a", + "sighash": "default" + } + ] + }, + { + "id": "policy-different-multipath-derivations", + "description": "Signs and finalizes a registered WSH policy whose two keys use different receive and change branches.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff010089020000000149aa85e2c639e52c4984cba528c60ef347f47f35917519ecba279514841928470000000000ffffffff02801d2c0400000000220020d0a6b11cf5a05594e6fad40948646011c6307c84906d318f348f5effaaa97b02002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020e267e95e105bd40e8fbf3bdda795da8bc943949f4accec407d45b469f514b4830000000001012b00e1f50500000000220020e267e95e105bd40e8fbf3bdda795da8bc943949f4accec407d45b469f514b483220203e7d88a65b1ca31ccc898193ab72acdce9b0c0076a9ba8f4ef06bd0ccd4d01d034730440220182d262d790be3d24e15dd8e74057bacf77f678ef0e82bdecce4386ab3252a7c02205651ad4a74158357b08c05bd88dec8c2522bb91013332f963374b45722c842a401010547522102b95c8bf277d3e7d7de1ad4fbb4921c5bd1cefeb3ebf38987bdd4688b9cfcbb722103e7d88a65b1ca31ccc898193ab72acdce9b0c0076a9ba8f4ef06bd0ccd4d01d0352ae220602b95c8bf277d3e7d7de1ad4fbb4921c5bd1cefeb3ebf38987bdd4688b9cfcbb721c4c00739d300000800100008000000080030000800a00000000000000220603e7d88a65b1ca31ccc898193ab72acdce9b0c0076a9ba8f4ef06bd0ccd4d01d031cc5e4fe1a300000800100008000000080030000801400000000000000000101475221034c130edc7e1574821af949c79d64052475e85f7f659cbbbd4b99c79bdf840dc92102627713c28bf52eb7012ac66a69b333fca5f9c8794fb5751ceb0cb50484546b3952ae220202627713c28bf52eb7012ac66a69b333fca5f9c8794fb5751ceb0cb50484546b391cc5e4fe1a3000008001000080000000800300008015000000000000002202034c130edc7e1574821af949c79d64052475e85f7f659cbbbd4b99c79bdf840dc91c4c00739d300000800100008000000080030000800b000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "max_version_exclusive": "9.20.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test multipath policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.20.0", + "max_version_exclusive": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test multipath policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1pff8vkq80pu2cgtu7ttgad2znw62v2lguhw6ptrppwns6nrpqau2qcuz37d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + }, + { + "min_version": "9.26.0", + "outcome": "success", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test multipath policy", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + }, + { + "type": "transaction_address", + "amount": "0.20000000 TBTC", + "address": "tb1p ff8v kq80 pu2c gtu7 ttga d2zn w62v 2lgu hw6p trpp wns6 nrpq au2q cuz3 7d" + }, + { + "type": "transaction_fee", + "amount": "0.30000000 TBTC", + "fee": "0.10000000 TBTC", + "longtouch": false + }, + { + "type": "confirm", + "title": "High fee", + "body": "The fee is 50.0%\nthe send amount.\nProceed?", + "longtouch": true + }, + { + "type": "status", + "title": "Transaction", + "body": "confirmed" + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/<10;11>/*,@1/<20;21>/*))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test multipath policy" + } + ], + "expected_signatures": [ + { + "input_index": 0, + "kind": "ecdsa", + "pubkey": "02b95c8bf277d3e7d7de1ad4fbb4921c5bd1cefeb3ebf38987bdd4688b9cfcbb72", + "sighash": "all" + } + ] + }, + { + "id": "policy-wrong-account-keypath", + "description": "Rejects a registered policy when the signing account keypath does not match the device key in the policy.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000011b6e58cffa2f032e498aa516a97410d53fc282bee867fb9653192aa223bd02de0000000000ffffffff02801d2c040000000022002032001963e09213e7387b90c3374944bdbbaf69f554060bb040626f0b428f1850002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0000000001012b00e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0105475221033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2210234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de252ae22060234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de21cc5e4fe1a3000008001000080000000800300008000000000000000002206033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21c4c00739d300000800100008000000080030000800000000000000000000101475221028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a21023df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563b52ae2202023df70ec9451b9dbf9b910e974fc8f060abfe662091836e09f3acd9017c04563b1cc5e4fe1a3000008001000080000000800300008001000000000000002202028c35dddf5bd951db2bf053d944200d11f029f37af9b5255025338f7eb9aae44a1c4c00739d3000008001000080000000800300008001000000000000000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/4'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "outcome": "invalid_input", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test policy account", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/**,@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test policy account" + } + ] + }, + { + "id": "policy-change-index-too-high", + "description": "Rejects a registered policy change output at address index 10000.", + "coin": "tbtc", + "psbt": { + "transaction": "70736274ff01008902000000011b6e58cffa2f032e498aa516a97410d53fc282bee867fb9653192aa223bd02de0000000000ffffffff02801d2c0400000000220020bd6a0b3bc28769ca2a7ef1aa12f7c052beb977fd61a1c55a9e5a3d6cecce0cbb002d3101000000002251204a4ecb00ef0f15842f9e5ad1d6a8537694c57d1cbbb4158c2174e1a98c20ef14000000000001005e02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff0100e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0000000001012b00e1f50500000000220020f5531a34c3dfbcded4d3717e6019b88c4878f964df8c9bf54b3f0256d591e7aa0105475221033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b2210234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de252ae22060234c5ed02e3e62b122655859db72a9a896aa261e8b56dc26c5ce7459ffcc08de21cc5e4fe1a3000008001000080000000800300008000000000000000002206033a1712b41eb509147fbbd80c3fac4af4ad0f0eaeaef36e6e08918002afd8e9b21c4c00739d3000008001000080000000800300008000000000000000000001014752210252631b8aca4b08e95acacae7daf3bfdad38a8651821a1009008fdd04a90d17bc210275db5c17fe98b071caf5e724752d268263106509e2bc2d3fa468168702329e9452ae22020252631b8aca4b08e95acacae7daf3bfdad38a8651821a1009008fdd04a90d17bc1c4c00739d30000080010000800000008003000080010000001027000022020275db5c17fe98b071caf5e724752d268263106509e2bc2d3fa468168702329e941cc5e4fe1a3000008001000080000000800300008001000000102700000000", + "options": { + "force_script_config": { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "keypath": "m/48'/1'/0'/3'" + } + } + }, + "expected_needs_prevtxs": true, + "expectations": [ + { + "outcome": "invalid_input", + "screens": [ + { + "type": "confirm", + "title": "Spend from", + "body": "BTC Testnet\npolicy with\n2 keys", + "longtouch": false + }, + { + "type": "confirm", + "title": "Name", + "body": "test policy account", + "longtouch": false + }, + { + "type": "confirm", + "title": "", + "body": "Show policy\ndetails?", + "longtouch": false + }, + { + "type": "confirm", + "title": "Policy", + "body": "wsh(multi(2,@0/**,@1/**))", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 1/2", + "body": "This device: [4c00739d/48'/1'/0'/3']tpubDF5MSzQdK2GfjmkNvrCZzpJhFt3if1HmrAdimugmGqWDCXYpkjxHpFZYuDxYYDAnnFMLMjLkMGvij2XV8pLtHBejgGy5RvNW4875nFGBDWv", + "longtouch": false + }, + { + "type": "confirm", + "title": "Key 2/2", + "body": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y", + "longtouch": false + } + ] + } + ], + "registrations": [ + { + "script_config": { + "type": "policy", + "policy": "wsh(multi(2,@0/**,@1/**))", + "keys": [ + { + "root_fingerprint": "4c00739d", + "keypath": "m/48'/1'/0'/3'", + "xpub": "xpub6EhivkawbkKDTyZXUfDUntyKvkx4fcniJY3RRMdcvvkKTEt7gX7CfotifBsm1iDYzLpRk9pspB6g2r2sjxVePkCScgDmkPiGUAWg2ESMKNA" + }, + { + "xpub": "tpubDFfyBnQAERJZWHv4W5rrhkpKJnPjycGzBpv8H1WgFjpkK7bVpuKw6JkeNvXqJix6csrQP3QgPZ19Yed2kbc7sniVHtNiQkX3cHsBxEqnz7Y" + } + ] + }, + "name": "test policy account" + } + ] + } + ] +}