@@ -18,6 +18,7 @@ package firmware
1818import (
1919 "bytes"
2020 "errors"
21+ "slices"
2122 "testing"
2223
2324 "github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
@@ -74,6 +75,44 @@ func p2shPkScript(redeemScript []byte) []byte {
7475 return pkScript
7576}
7677
78+ // P2WSH multisig witnessScript and pubkeyScript from these xpubs, derived at /<0;1>/*.
79+ // The pubkeys will be sorted lexicographically.
80+ func multisigP2WSH (threshold int , xpubs []string , change bool , index uint32 ) ([]byte , []byte ) {
81+ pubkeys := make ([]* btcutil.AddressPubKey , len (xpubs ))
82+ for i , xpubStr := range xpubs {
83+ changeIndex := uint32 (0 )
84+ if change {
85+ changeIndex = 1
86+ }
87+ xpub := mustXpub (xpubStr , changeIndex , index )
88+ pubKey , err := xpub .ECPubKey ()
89+ if err != nil {
90+ panic (err )
91+ }
92+ addrPubKey , err := btcutil .NewAddressPubKey (pubKey .SerializeCompressed (), & chaincfg .MainNetParams )
93+ if err != nil {
94+ panic (err )
95+ }
96+ pubkeys [i ] = addrPubKey
97+ }
98+ slices .SortFunc (pubkeys , func (a , b * btcutil.AddressPubKey ) int {
99+ return bytes .Compare (a .ScriptAddress (), b .ScriptAddress ())
100+ })
101+ witnessScript , err := txscript .MultiSigScript (pubkeys , threshold )
102+ if err != nil {
103+ panic (err )
104+ }
105+ addr , err := btcutil .NewAddressWitnessScriptHash (chainhash .HashB (witnessScript ), & chaincfg .MainNetParams )
106+ if err != nil {
107+ panic (err )
108+ }
109+ pkScript , err := txscript .PayToAddrScript (addr )
110+ if err != nil {
111+ panic (err )
112+ }
113+ return witnessScript , pkScript
114+ }
115+
77116//nolint:unparam
78117func mustOutpoint (s string ) * wire.OutPoint {
79118 outPoint , err := wire .NewOutPointFromString (s )
@@ -210,14 +249,27 @@ func TestSimulatorBTCAddress(t *testing.T) {
210249 })
211250}
212251
252+ func mustXpub (xpubStr string , keypath ... uint32 ) * hdkeychain.ExtendedKey {
253+ xpub , err := hdkeychain .NewKeyFromString (xpubStr )
254+ if err != nil {
255+ panic (err )
256+ }
257+ for _ , childIndex := range keypath {
258+ xpub , err = xpub .Derive (childIndex )
259+ if err != nil {
260+ panic (err )
261+ }
262+ }
263+ return xpub
264+ }
265+
213266func simulatorPub (t * testing.T , device * Device , keypath ... uint32 ) * btcec.PublicKey {
214267 t .Helper ()
215268
216269 xpubStr , err := device .BTCXPub (messages .BTCCoin_BTC , keypath , messages .BTCPubRequest_XPUB , false )
217270 require .NoError (t , err )
218271
219- xpub , err := hdkeychain .NewKeyFromString (xpubStr )
220- require .NoError (t , err )
272+ xpub := mustXpub (xpubStr )
221273 pubKey , err := xpub .ECPubKey ()
222274 require .NoError (t , err )
223275 return pubKey
@@ -937,3 +989,207 @@ func TestSimulatorSignBTCTransactionSendSelfDifferentAccount(t *testing.T) {
937989 )
938990 })
939991}
992+
993+ // 1-of-3 multisig registration and address display/verification.
994+ func TestSimulatorBTCAddressMultisig (t * testing.T ) {
995+ testInitializedSimulators (t , func (t * testing.T , device * Device , stdOut * bytes.Buffer ) {
996+ t .Helper ()
997+
998+ coin := messages .BTCCoin_BTC
999+ keypathAccount := []uint32 {
1000+ 48 + hardenedKeyStart ,
1001+ 0 + hardenedKeyStart ,
1002+ 0 + hardenedKeyStart ,
1003+ 2 + hardenedKeyStart ,
1004+ }
1005+
1006+ receiveKeypath := append (append ([]uint32 {}, keypathAccount ... ), 0 , 0 )
1007+
1008+ ourXPub , err := device .BTCXPub (coin , keypathAccount , messages .BTCPubRequest_XPUB , false )
1009+ require .NoError (t , err )
1010+
1011+ xpubs := []string {
1012+ ourXPub ,
1013+ "xpub6Esa6esRHkbuXtbdDKqu3uWjQ1GpK39WW2hxbUAN4L3TxrwDyghEwBtUYZ8uK8LZh3tJ3pjWEpxng9tjfo7RT9BaZKV2T3EPvmZ6N1LgSdj" ,
1014+ "xpub6FJ6FAAFUzuWQAKyT98Ngs6UwsoPfPCdmepqX2aLLPT54M85ARsWzPciFd49foStMwhWgfiHP6PnMgPrWLrBJpUHgqw8vZPd5ov8uSfW2vo" ,
1015+ }
1016+ ourXPubIndex := uint32 (0 )
1017+ threshold := 1
1018+
1019+ scriptConfig , err := NewBTCScriptConfigMultisig (uint32 (threshold ), xpubs , ourXPubIndex )
1020+ require .NoError (t , err )
1021+
1022+ // The multisig account has to be registered if not already.
1023+ registered , err := device .BTCIsScriptConfigRegistered (coin , scriptConfig , keypathAccount )
1024+ require .NoError (t , err )
1025+ require .False (t , registered )
1026+
1027+ err = device .BTCRegisterScriptConfig (coin , scriptConfig , keypathAccount , "My multisig account" )
1028+ require .NoError (t , err )
1029+
1030+ address , err := device .BTCAddress (
1031+ coin ,
1032+ receiveKeypath ,
1033+ scriptConfig ,
1034+ true ,
1035+ )
1036+ require .NoError (t , err )
1037+ require .Equal (t , "bc1qdhqnu2arm9al7uv687amuesk5det5nxx0k9ed30x2u8zjsfnsfyqzlsrsu" , address )
1038+ if device .Version ().AtLeast (semver .NewSemVer (9 , 20 , 0 )) {
1039+ // Before simulator v9.20, address confirmation data was not written to stdout.
1040+ require .Contains (t ,
1041+ stdOut .String (),
1042+ `TITLE: Register
1043+ BODY: 1-of-3
1044+ Bitcoin multisig
1045+ CONFIRM SCREEN END
1046+ CONFIRM SCREEN START
1047+ TITLE: Register
1048+ BODY: My multisig account
1049+ CONFIRM SCREEN END
1050+ CONFIRM SCREEN START
1051+ TITLE: Register
1052+ BODY: p2wsh
1053+ at
1054+ m/48'/0'/0'/2'
1055+ CONFIRM SCREEN END
1056+ CONFIRM SCREEN START
1057+ TITLE: Register
1058+ BODY: Cosigner 1/3 (this device): Zpub74CYNJGx5QwGYeXket9qEbWbEhMNCL1d1Za3eXpABKXMWNVDhTZmovUkzBa74SCrZruMQLGQ6Zce9HzJUaLoF8QPkRU7CVfSSqNZ7Qy2BB5
1059+ CONFIRM SCREEN END
1060+ CONFIRM SCREEN START
1061+ TITLE: Register
1062+ BODY: Cosigner 2/3: Zpub75SBqDwhA5FEf49Epht8JA3YTjbyQdp6eXQ55XDgC7ddhF8bFQQeGS4gPg1YsNsJjoBtRMvk3N4PZtjdQR6QC6fT8TzH2GLNMwxFj3Rnnzx
1063+ CONFIRM SCREEN END
1064+ CONFIRM SCREEN START
1065+ TITLE: Register
1066+ BODY: Cosigner 3/3: Zpub75rhyjEXMKYqXKsb4XAbw7dJ1c8YkysDv9Wx15deUB3EnjKSS9avKdnv6jvoE3ydQh174CuXBdVPFREkExqA3mxAFzSPVnVbWzKJGVWvYXJ
1067+ CONFIRM SCREEN END
1068+ STATUS SCREEN START
1069+ TITLE: Multisig account
1070+ registered
1071+ STATUS SCREEN END
1072+ CONFIRM SCREEN START
1073+ TITLE: Receive to
1074+ BODY: 1-of-3
1075+ Bitcoin multisig
1076+ CONFIRM SCREEN END
1077+ CONFIRM SCREEN START
1078+ TITLE: Receive to
1079+ BODY: My multisig account
1080+ CONFIRM SCREEN END
1081+ CONFIRM SCREEN START
1082+ TITLE: Receive to
1083+ BODY: bc1qdhqnu2arm9al7uv687amuesk5det5nxx0k9ed30x2u8zjsfnsfyqzlsrsu
1084+ CONFIRM SCREEN END
1085+ ` ,
1086+ )
1087+ }
1088+ })
1089+ }
1090+
1091+ // 1-of-3 P2WSH multisig spend
1092+ func TestSimulatorBTCSignMultisig (t * testing.T ) {
1093+ testInitializedSimulators (t , func (t * testing.T , device * Device , stdOut * bytes.Buffer ) {
1094+ t .Helper ()
1095+ coin := messages .BTCCoin_BTC
1096+
1097+ keypathAccount := []uint32 {
1098+ 48 + hardenedKeyStart ,
1099+ 0 + hardenedKeyStart ,
1100+ 0 + hardenedKeyStart ,
1101+ 2 + hardenedKeyStart ,
1102+ }
1103+
1104+ changeKeypath := append (append ([]uint32 {}, keypathAccount ... ), 1 , 0 )
1105+ inputKeypath := append (append ([]uint32 {}, keypathAccount ... ), 0 , 0 )
1106+
1107+ ourXPub , err := device .BTCXPub (coin , keypathAccount , messages .BTCPubRequest_XPUB , false )
1108+ require .NoError (t , err )
1109+
1110+ xpubs := []string {
1111+ ourXPub ,
1112+ "xpub6Esa6esRHkbuXtbdDKqu3uWjQ1GpK39WW2hxbUAN4L3TxrwDyghEwBtUYZ8uK8LZh3tJ3pjWEpxng9tjfo7RT9BaZKV2T3EPvmZ6N1LgSdj" ,
1113+ "xpub6FJ6FAAFUzuWQAKyT98Ngs6UwsoPfPCdmepqX2aLLPT54M85ARsWzPciFd49foStMwhWgfiHP6PnMgPrWLrBJpUHgqw8vZPd5ov8uSfW2vo" ,
1114+ }
1115+ ourXPubIndex := uint32 (0 )
1116+ threshold := 1
1117+
1118+ _ , inputPkScript := multisigP2WSH (threshold , xpubs , false , 0 )
1119+
1120+ scriptConfig , err := NewBTCScriptConfigMultisig (uint32 (threshold ), xpubs , ourXPubIndex )
1121+ require .NoError (t , err )
1122+
1123+ // The multisig account has to be registered if not already.
1124+ registered , err := device .BTCIsScriptConfigRegistered (coin , scriptConfig , keypathAccount )
1125+ require .NoError (t , err )
1126+ require .False (t , registered )
1127+
1128+ err = device .BTCRegisterScriptConfig (coin , scriptConfig , keypathAccount , "My multisig account" )
1129+ require .NoError (t , err )
1130+
1131+ prevTx := & wire.MsgTx {
1132+ Version : 2 ,
1133+ TxIn : []* wire.TxIn {
1134+ {
1135+ PreviousOutPoint : * mustOutpoint ("3131313131313131313131313131313131313131313131313131313131313131:0" ),
1136+ Sequence : 0xFFFFFFFF ,
1137+ },
1138+ },
1139+ TxOut : []* wire.TxOut {
1140+ {
1141+ Value : 100_000_000 ,
1142+ PkScript : inputPkScript ,
1143+ },
1144+ },
1145+ LockTime : 0 ,
1146+ }
1147+ convertedPrevTx := NewBTCPrevTxFromBtcd (prevTx )
1148+
1149+ scriptConfigs := []* messages.BTCScriptConfigWithKeypath {
1150+ {
1151+ ScriptConfig : scriptConfig ,
1152+ Keypath : keypathAccount ,
1153+ },
1154+ }
1155+ require .True (t , BTCSignNeedsPrevTxs (scriptConfigs ))
1156+
1157+ prevTxHash := prevTx .TxHash ()
1158+ _ , err = device .BTCSign (
1159+ coin ,
1160+ scriptConfigs ,
1161+ nil ,
1162+ & BTCTx {
1163+ Version : 2 ,
1164+ Inputs : []* BTCTxInput {
1165+ {
1166+ Input : & messages.BTCSignInputRequest {
1167+ PrevOutHash : prevTxHash [:],
1168+ PrevOutIndex : 0 ,
1169+ PrevOutValue : uint64 (prevTx .TxOut [0 ].Value ),
1170+ Sequence : 0xFFFFFFFF ,
1171+ Keypath : inputKeypath ,
1172+ ScriptConfigIndex : 0 ,
1173+ },
1174+ PrevTx : convertedPrevTx ,
1175+ },
1176+ },
1177+ Outputs : []* messages.BTCSignOutputRequest {
1178+ {
1179+ Ours : true ,
1180+ Value : 70_000_000 ,
1181+ Keypath : changeKeypath ,
1182+ },
1183+ {
1184+ Value : 20_000_000 ,
1185+ Payload : []byte ("11111111111111111111111111111111" ),
1186+ Type : messages .BTCOutputType_P2WSH ,
1187+ },
1188+ },
1189+ Locktime : 0 ,
1190+ },
1191+ messages .BTCSignInitRequest_DEFAULT ,
1192+ )
1193+ require .NoError (t , err )
1194+ })
1195+ }
0 commit comments