Skip to content

Commit 383ad4b

Browse files
committed
Merge branch 'btctests'
2 parents 0eefc39 + 788a939 commit 383ad4b

File tree

3 files changed

+298
-39
lines changed

3 files changed

+298
-39
lines changed

api/firmware/btc.go

+28
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/BitBoxSwiss/bitbox02-api-go/util/errp"
2525
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
2626
"github.com/btcsuite/btcd/btcutil/base58"
27+
"github.com/btcsuite/btcd/wire"
2728
"google.golang.org/protobuf/proto"
2829
)
2930

@@ -222,6 +223,33 @@ type BTCPrevTx struct {
222223
Locktime uint32
223224
}
224225

226+
// NewBTCPrevTxFromBtcd converts a btcd transaction to a BTCPrevTx.
227+
func NewBTCPrevTxFromBtcd(tx *wire.MsgTx) *BTCPrevTx {
228+
prevTxInputs := make([]*messages.BTCPrevTxInputRequest, len(tx.TxIn))
229+
for prevInputIndex, prevTxIn := range tx.TxIn {
230+
prevTxInputs[prevInputIndex] = &messages.BTCPrevTxInputRequest{
231+
PrevOutHash: prevTxIn.PreviousOutPoint.Hash[:],
232+
PrevOutIndex: prevTxIn.PreviousOutPoint.Index,
233+
SignatureScript: prevTxIn.SignatureScript,
234+
Sequence: prevTxIn.Sequence,
235+
}
236+
}
237+
prevTxOuputs := make([]*messages.BTCPrevTxOutputRequest, len(tx.TxOut))
238+
for prevOutputIndex, prevTxOut := range tx.TxOut {
239+
prevTxOuputs[prevOutputIndex] = &messages.BTCPrevTxOutputRequest{
240+
Value: uint64(prevTxOut.Value),
241+
PubkeyScript: prevTxOut.PkScript,
242+
}
243+
}
244+
245+
return &BTCPrevTx{
246+
Version: uint32(tx.Version),
247+
Inputs: prevTxInputs,
248+
Outputs: prevTxOuputs,
249+
Locktime: tx.LockTime,
250+
}
251+
}
252+
225253
// BTCTxInput contains the data needed to sign an input.
226254
type BTCTxInput struct {
227255
Input *messages.BTCSignInputRequest

api/firmware/btc_test.go

+261-15
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,26 @@ import (
2323
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
2424
"github.com/btcsuite/btcd/btcec/v2"
2525
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
26+
"github.com/btcsuite/btcd/btcutil"
2627
"github.com/btcsuite/btcd/btcutil/hdkeychain"
28+
"github.com/btcsuite/btcd/chaincfg"
2729
"github.com/btcsuite/btcd/chaincfg/chainhash"
30+
"github.com/btcsuite/btcd/txscript"
31+
"github.com/btcsuite/btcd/wire"
2832
"github.com/stretchr/testify/require"
2933
"google.golang.org/protobuf/proto"
3034
)
3135

3236
const hardenedKeyStart = 0x80000000
3337

38+
func mustOutpoint(s string) *wire.OutPoint {
39+
outPoint, err := wire.NewOutPointFromString(s)
40+
if err != nil {
41+
panic(err)
42+
}
43+
return outPoint
44+
}
45+
3446
func parseECDSASignature(t *testing.T, sig []byte) *ecdsa.Signature {
3547
t.Helper()
3648
require.Len(t, sig, 64)
@@ -87,36 +99,32 @@ func TestBTCAddress(t *testing.T) {
8799
})
88100
}
89101

90-
func parseXPub(t *testing.T, xpubStr string, keypath ...uint32) *hdkeychain.ExtendedKey {
102+
func simulatorPub(t *testing.T, device *Device, keypath ...uint32) *btcec.PublicKey {
91103
t.Helper()
92-
xpub, err := hdkeychain.NewKeyFromString(xpubStr)
104+
105+
xpubStr, err := device.BTCXPub(messages.BTCCoin_BTC, keypath, messages.BTCPubRequest_XPUB, false)
93106
require.NoError(t, err)
94107

95-
for _, child := range keypath {
96-
xpub, err = xpub.Derive(child)
97-
require.NoError(t, err)
98-
}
99-
return xpub
108+
xpub, err := hdkeychain.NewKeyFromString(xpubStr)
109+
require.NoError(t, err)
110+
pubKey, err := xpub.ECPubKey()
111+
require.NoError(t, err)
112+
return pubKey
100113
}
101114

102115
func TestSimulatorBTCSignMessage(t *testing.T) {
103116
testInitializedSimulators(t, func(t *testing.T, device *Device) {
104117
t.Helper()
105118
coin := messages.BTCCoin_BTC
106-
accountKeypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart}
107-
108-
xpubStr, err := device.BTCXPub(coin, accountKeypath, messages.BTCPubRequest_XPUB, false)
109-
require.NoError(t, err)
119+
keypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 10}
110120

111-
xpub := parseXPub(t, xpubStr, 0, 10)
112-
pubKey, err := xpub.ECPubKey()
113-
require.NoError(t, err)
121+
pubKey := simulatorPub(t, device, keypath...)
114122

115123
sig, _, _, err := device.BTCSignMessage(
116124
coin,
117125
&messages.BTCScriptConfigWithKeypath{
118126
ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH_P2SH),
119-
Keypath: append(accountKeypath, 0, 10),
127+
Keypath: keypath,
120128
},
121129
[]byte("message"),
122130
)
@@ -321,3 +329,241 @@ func TestBTCSignMessage(t *testing.T) {
321329
}
322330
})
323331
}
332+
333+
func makeTaprootOutput(t *testing.T, pubkey *btcec.PublicKey) []byte {
334+
t.Helper()
335+
outputKey := txscript.ComputeTaprootKeyNoScript(pubkey)
336+
outputPkScript, err := txscript.PayToTaprootScript(outputKey)
337+
require.NoError(t, err)
338+
return outputPkScript
339+
}
340+
341+
// Test signing; all inputs are BIP86 Taproot keyspends.
342+
func TestSimulatorBTCSignTaprootKeySpend(t *testing.T) {
343+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
344+
t.Helper()
345+
coin := messages.BTCCoin_BTC
346+
accountKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart}
347+
inputKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0}
348+
input2Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 1}
349+
changeKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 1, 0}
350+
351+
input1PkScript := makeTaprootOutput(t, simulatorPub(t, device, inputKeypath...))
352+
input2PkScript := makeTaprootOutput(t, simulatorPub(t, device, input2Keypath...))
353+
354+
prevTx := &wire.MsgTx{
355+
Version: 2,
356+
TxIn: []*wire.TxIn{
357+
{
358+
PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"),
359+
Sequence: 0xFFFFFFFF,
360+
},
361+
},
362+
TxOut: []*wire.TxOut{
363+
{
364+
Value: 60_000_000,
365+
PkScript: input1PkScript,
366+
},
367+
{
368+
Value: 40_000_000,
369+
PkScript: input2PkScript,
370+
},
371+
},
372+
LockTime: 0,
373+
}
374+
375+
scriptConfigs := []*messages.BTCScriptConfigWithKeypath{
376+
{
377+
ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR),
378+
Keypath: accountKeypath,
379+
},
380+
}
381+
require.False(t, BTCSignNeedsPrevTxs(scriptConfigs))
382+
383+
prevTxHash := prevTx.TxHash()
384+
_, err := device.BTCSign(
385+
coin,
386+
scriptConfigs,
387+
&BTCTx{
388+
Version: 2,
389+
Inputs: []*BTCTxInput{
390+
{
391+
Input: &messages.BTCSignInputRequest{
392+
PrevOutHash: prevTxHash[:],
393+
PrevOutIndex: 0,
394+
PrevOutValue: uint64(prevTx.TxOut[0].Value),
395+
Sequence: 0xFFFFFFFF,
396+
Keypath: inputKeypath,
397+
ScriptConfigIndex: 0,
398+
},
399+
},
400+
{
401+
Input: &messages.BTCSignInputRequest{
402+
PrevOutHash: prevTxHash[:],
403+
PrevOutIndex: 1,
404+
PrevOutValue: uint64(prevTx.TxOut[1].Value),
405+
Sequence: 0xFFFFFFFF,
406+
Keypath: input2Keypath,
407+
ScriptConfigIndex: 0,
408+
},
409+
},
410+
},
411+
Outputs: []*messages.BTCSignOutputRequest{
412+
{
413+
Ours: true,
414+
Value: 70_000_000,
415+
Keypath: changeKeypath,
416+
},
417+
{
418+
Value: 20_000_000,
419+
Payload: []byte("11111111111111111111111111111111"),
420+
Type: messages.BTCOutputType_P2WSH,
421+
},
422+
},
423+
Locktime: 0,
424+
},
425+
messages.BTCSignInitRequest_DEFAULT,
426+
)
427+
require.NoError(t, err)
428+
})
429+
}
430+
431+
// Test signing; mixed input types (p2wpkh, p2wpkh-p2sh, p2tr)
432+
func TestSimulatorBTCSignMixed(t *testing.T) {
433+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
434+
t.Helper()
435+
coin := messages.BTCCoin_BTC
436+
changeKeypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 1, 0}
437+
input0Keypath := []uint32{86 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0}
438+
input1Keypath := []uint32{84 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0}
439+
input2Keypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart, 0, 0}
440+
441+
net := &chaincfg.MainNetParams
442+
443+
prevTx := &wire.MsgTx{
444+
Version: 2,
445+
TxIn: []*wire.TxIn{
446+
{
447+
PreviousOutPoint: *mustOutpoint("3131313131313131313131313131313131313131313131313131313131313131:0"),
448+
Sequence: 0xFFFFFFFF,
449+
},
450+
},
451+
TxOut: []*wire.TxOut{
452+
{
453+
Value: 100_000_000,
454+
PkScript: func() []byte {
455+
return makeTaprootOutput(t, simulatorPub(t, device, input0Keypath...))
456+
}(),
457+
},
458+
{
459+
Value: 100_000_000,
460+
PkScript: func() []byte {
461+
inputPub := simulatorPub(t, device, input1Keypath...)
462+
463+
publicKeyHash := btcutil.Hash160(inputPub.SerializeCompressed())
464+
address, err := btcutil.NewAddressWitnessPubKeyHash(publicKeyHash, net)
465+
require.NoError(t, err)
466+
pkScript, err := txscript.PayToAddrScript(address)
467+
require.NoError(t, err)
468+
return pkScript
469+
}(),
470+
},
471+
{
472+
Value: 100_000_000,
473+
PkScript: func() []byte {
474+
inputPub := simulatorPub(t, device, input2Keypath...)
475+
publicKeyHash := btcutil.Hash160(inputPub.SerializeCompressed())
476+
477+
segwitAddress, err := btcutil.NewAddressWitnessPubKeyHash(publicKeyHash, net)
478+
require.NoError(t, err)
479+
redeemScript, err := txscript.PayToAddrScript(segwitAddress)
480+
require.NoError(t, err)
481+
address, err := btcutil.NewAddressScriptHash(redeemScript, net)
482+
require.NoError(t, err)
483+
pkScript, err := txscript.PayToAddrScript(address)
484+
require.NoError(t, err)
485+
return pkScript
486+
}(),
487+
},
488+
},
489+
LockTime: 0,
490+
}
491+
convertedPrevTx := NewBTCPrevTxFromBtcd(prevTx)
492+
493+
scriptConfigs := []*messages.BTCScriptConfigWithKeypath{
494+
{
495+
ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2TR),
496+
Keypath: input0Keypath[:3],
497+
},
498+
{
499+
ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH),
500+
Keypath: input1Keypath[:3],
501+
},
502+
503+
{
504+
ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH_P2SH),
505+
Keypath: input2Keypath[:3],
506+
},
507+
}
508+
require.True(t, BTCSignNeedsPrevTxs(scriptConfigs))
509+
510+
prevTxHash := prevTx.TxHash()
511+
_, err := device.BTCSign(
512+
coin,
513+
scriptConfigs,
514+
&BTCTx{
515+
Version: 2,
516+
Inputs: []*BTCTxInput{
517+
{
518+
Input: &messages.BTCSignInputRequest{
519+
PrevOutHash: prevTxHash[:],
520+
PrevOutIndex: 0,
521+
PrevOutValue: uint64(prevTx.TxOut[0].Value),
522+
Sequence: 0xFFFFFFFF,
523+
Keypath: input0Keypath,
524+
ScriptConfigIndex: 0,
525+
},
526+
PrevTx: convertedPrevTx,
527+
},
528+
{
529+
Input: &messages.BTCSignInputRequest{
530+
PrevOutHash: prevTxHash[:],
531+
PrevOutIndex: 1,
532+
PrevOutValue: uint64(prevTx.TxOut[1].Value),
533+
Sequence: 0xFFFFFFFF,
534+
Keypath: input1Keypath,
535+
ScriptConfigIndex: 1,
536+
},
537+
PrevTx: convertedPrevTx,
538+
},
539+
{
540+
Input: &messages.BTCSignInputRequest{
541+
PrevOutHash: prevTxHash[:],
542+
PrevOutIndex: 2,
543+
PrevOutValue: uint64(prevTx.TxOut[2].Value),
544+
Sequence: 0xFFFFFFFF,
545+
Keypath: input2Keypath,
546+
ScriptConfigIndex: 2,
547+
},
548+
PrevTx: convertedPrevTx,
549+
},
550+
},
551+
Outputs: []*messages.BTCSignOutputRequest{
552+
{
553+
Ours: true,
554+
Value: 270_000_000,
555+
Keypath: changeKeypath,
556+
},
557+
{
558+
Value: 20_000_000,
559+
Payload: []byte("11111111111111111111111111111111"),
560+
Type: messages.BTCOutputType_P2WSH,
561+
},
562+
},
563+
Locktime: 0,
564+
},
565+
messages.BTCSignInitRequest_DEFAULT,
566+
)
567+
require.NoError(t, err)
568+
})
569+
}

0 commit comments

Comments
 (0)