diff --git a/backend/coins/btc/addresses/address.go b/backend/coins/btc/addresses/address.go index a7662cd95d..a754bb54d7 100644 --- a/backend/coins/btc/addresses/address.go +++ b/backend/coins/btc/addresses/address.go @@ -23,6 +23,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/blockchain" + "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/util" "github.com/digitalbitbox/bitbox-wallet-app/backend/signing" "github.com/sirupsen/logrus" ) @@ -126,7 +127,7 @@ func (address *AccountAddress) isUsed() bool { // PubkeyScript returns the pubkey script of this address. Use this in a tx output to receive funds. func (address *AccountAddress) PubkeyScript() []byte { - script, err := txscript.PayToAddrScript(address.Address) + script, err := util.PkScriptFromAddress(address.Address) if err != nil { address.log.WithError(err).Panic("Failed to get the pubkey script for an address.") } diff --git a/backend/coins/btc/transaction.go b/backend/coins/btc/transaction.go index f70d825d22..cde8027da8 100644 --- a/backend/coins/btc/transaction.go +++ b/backend/coins/btc/transaction.go @@ -20,7 +20,6 @@ import ( "strconv" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/digitalbitbox/bitbox-wallet-app/backend/accounts" @@ -29,6 +28,7 @@ import ( "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/blockchain" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/maketx" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/transactions" + "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/util" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin" "github.com/digitalbitbox/bitbox-wallet-app/util/errp" ) @@ -80,9 +80,9 @@ func (account *Account) newTx(args *accounts.TxProposalArgs) ( if err != nil { return nil, nil, err } - pkScript, err := txscript.PayToAddrScript(address) + pkScript, err := util.PkScriptFromAddress(address) if err != nil { - return nil, nil, errp.WithStack(err) + return nil, nil, err } utxo := account.transactions.SpendableOutputs() wireUTXO := make(map[wire.OutPoint]maketx.UTXO, len(utxo)) diff --git a/backend/coins/btc/util/util.go b/backend/coins/btc/util/util.go index c8273dc8f4..e92e4f9e5b 100644 --- a/backend/coins/btc/util/util.go +++ b/backend/coins/btc/util/util.go @@ -18,8 +18,11 @@ import ( "strconv" "strings" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/digitalbitbox/bitbox-wallet-app/util/errp" ) @@ -39,3 +42,25 @@ func ParseOutPoint(outPointBytes []byte) (*wire.OutPoint, error) { } return wire.NewOutPoint(txHash, uint32(index)), nil } + +// PkScriptFromAddress decodes an address into the pubKeyScript that can be used in a transaction +// output. +func PkScriptFromAddress(address btcutil.Address) ([]byte, error) { + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + return nil, errp.WithStack(err) + } + return pkScript, nil +} + +// AddressFromPkScript decodes a pkScript into an Address instance. +func AddressFromPkScript(pkScript []byte, net *chaincfg.Params) (btcutil.Address, error) { + _, addresses, _, err := txscript.ExtractPkScriptAddrs(pkScript, net) + if err != nil { + return nil, errp.WithStack(err) + } + if len(addresses) != 1 { + return nil, errp.New("couldn't parse pkScript") + } + return addresses[0], nil +} diff --git a/backend/coins/btc/util/util_test.go b/backend/coins/btc/util/util_test.go new file mode 100644 index 0000000000..40652e16c6 --- /dev/null +++ b/backend/coins/btc/util/util_test.go @@ -0,0 +1,103 @@ +// Copyright 2021 Shift Crypto AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "testing" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil" + "github.com/stretchr/testify/require" +) + +func TestPkScriptFromAddress(t *testing.T) { + hash := []byte("\x92\x95\x3b\x69\x91\x29\x70\x02\xfa\xa6\x2a\x1d\xd2\x43\x13\xff\x62\x1e\x10\xab") + net := &chaincfg.MainNetParams + + var address btcutil.Address + + address, err := btcutil.NewAddressPubKeyHash(hash, net) + require.NoError(t, err) + pkScript, err := PkScriptFromAddress(address) + require.NoError(t, err) + require.Equal(t, + []byte("\x76\xa9\x14\x92\x95\x3b\x69\x91\x29\x70\x02\xfa\xa6\x2a\x1d\xd2\x43\x13\xff\x62\x1e\x10\xab\x88\xac"), + pkScript) + + address, err = btcutil.NewAddressWitnessPubKeyHash(hash, net) + require.NoError(t, err) + pkScript, err = PkScriptFromAddress(address) + require.NoError(t, err) + require.Equal(t, + []byte("\x00\x14\x92\x95\x3b\x69\x91\x29\x70\x02\xfa\xa6\x2a\x1d\xd2\x43\x13\xff\x62\x1e\x10\xab"), + pkScript) + + address, err = btcutil.NewAddressScriptHashFromHash(hash, net) + require.NoError(t, err) + pkScript, err = PkScriptFromAddress(address) + require.NoError(t, err) + require.Equal(t, + []byte("\xa9\x14\x92\x95\x3b\x69\x91\x29\x70\x02\xfa\xa6\x2a\x1d\xd2\x43\x13\xff\x62\x1e\x10\xab\x87"), + pkScript) + + scriptHash := []byte("\x4a\xf2\xe4\x54\x9a\x5c\xbb\x73\x6e\x77\xce\xf5\x2f\xe3\x0b\x9d\xf8\x12\x1d\x73\x56\xab\x20\x05\x46\x3e\xcb\x08\x97\x23\x45\x8d") + address, err = btcutil.NewAddressWitnessScriptHash(scriptHash, net) + require.NoError(t, err) + pkScript, err = PkScriptFromAddress(address) + require.NoError(t, err) + require.Equal(t, + []byte("\x00\x20\x4a\xf2\xe4\x54\x9a\x5c\xbb\x73\x6e\x77\xce\xf5\x2f\xe3\x0b\x9d\xf8\x12\x1d\x73\x56\xab\x20\x05\x46\x3e\xcb\x08\x97\x23\x45\x8d"), + pkScript) +} + +func TestAddressFromPkScript(t *testing.T) { + hash := []byte("\x92\x95\x3b\x69\x91\x29\x70\x02\xfa\xa6\x2a\x1d\xd2\x43\x13\xff\x62\x1e\x10\xab") + net := &chaincfg.MainNetParams + + var address btcutil.Address + + address, err := btcutil.NewAddressPubKeyHash(hash, net) + require.NoError(t, err) + pkScript, err := PkScriptFromAddress(address) + require.NoError(t, err) + recoveredAddres, err := AddressFromPkScript(pkScript, &chaincfg.MainNetParams) + require.NoError(t, err) + require.Equal(t, address.ScriptAddress(), recoveredAddres.ScriptAddress()) + + address, err = btcutil.NewAddressWitnessPubKeyHash(hash, net) + require.NoError(t, err) + pkScript, err = PkScriptFromAddress(address) + require.NoError(t, err) + recoveredAddres, err = AddressFromPkScript(pkScript, &chaincfg.MainNetParams) + require.NoError(t, err) + require.Equal(t, address.ScriptAddress(), recoveredAddres.ScriptAddress()) + + address, err = btcutil.NewAddressScriptHashFromHash(hash, net) + require.NoError(t, err) + pkScript, err = PkScriptFromAddress(address) + require.NoError(t, err) + recoveredAddres, err = AddressFromPkScript(pkScript, &chaincfg.MainNetParams) + require.NoError(t, err) + require.Equal(t, address.ScriptAddress(), recoveredAddres.ScriptAddress()) + + scriptHash := []byte("\x4a\xf2\xe4\x54\x9a\x5c\xbb\x73\x6e\x77\xce\xf5\x2f\xe3\x0b\x9d\xf8\x12\x1d\x73\x56\xab\x20\x05\x46\x3e\xcb\x08\x97\x23\x45\x8d") + address, err = btcutil.NewAddressWitnessScriptHash(scriptHash, net) + require.NoError(t, err) + pkScript, err = PkScriptFromAddress(address) + require.NoError(t, err) + recoveredAddres, err = AddressFromPkScript(pkScript, &chaincfg.MainNetParams) + require.NoError(t, err) + require.Equal(t, address.ScriptAddress(), recoveredAddres.ScriptAddress()) +} diff --git a/backend/devices/bitbox02/keystore.go b/backend/devices/bitbox02/keystore.go index 8944a5c7e3..98b2fb7896 100644 --- a/backend/devices/bitbox02/keystore.go +++ b/backend/devices/bitbox02/keystore.go @@ -22,9 +22,10 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc" + "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/btc/util" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin" coinpkg "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/coin" "github.com/digitalbitbox/bitbox-wallet-app/backend/coins/eth" @@ -351,16 +352,22 @@ func (keystore *keystore) signBTCTransaction(btcProposedTx *btc.ProposedTransact } outputs := make([]*messages.BTCSignOutputRequest, len(tx.TxOut)) for index, txOut := range tx.TxOut { - scriptClass, addresses, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, coin.Net()) + address, err := util.AddressFromPkScript(txOut.PkScript, coin.Net()) if err != nil { - return errp.WithStack(err) - } - if len(addresses) != 1 { - return errp.New("couldn't parse pkScript") + return err } - msgOutputType, ok := btcMsgOutputTypeMap[scriptClass] - if !ok { - return errp.Newf("unsupported output type: %d", scriptClass) + var msgOutputType messages.BTCOutputType + switch address.(type) { + case *btcutil.AddressPubKeyHash: + msgOutputType = messages.BTCOutputType_P2PKH + case *btcutil.AddressScriptHash: + msgOutputType = messages.BTCOutputType_P2SH + case *btcutil.AddressWitnessPubKeyHash: + msgOutputType = messages.BTCOutputType_P2WPKH + case *btcutil.AddressWitnessScriptHash: + msgOutputType = messages.BTCOutputType_P2WSH + default: + return errp.Newf("unsupported output type: %v", address) } changeAddress := btcProposedTx.TXProposal.ChangeAddress isChange := changeAddress != nil && bytes.Equal( @@ -375,7 +382,7 @@ func (keystore *keystore) signBTCTransaction(btcProposedTx *btc.ProposedTransact Ours: isChange, Type: msgOutputType, Value: uint64(txOut.Value), - Payload: addresses[0].ScriptAddress(), + Payload: address.ScriptAddress(), Keypath: keypath, } }