Skip to content

Commit

Permalink
backend/btc: refactor pkScript encoding/decoding
Browse files Browse the repository at this point in the history
We use functions from the btcd library to convert addresses to
pubKeyScripts and vice versa.

While the address type has support for Taproot addresses, the pubkey
script functions in btcd don't handle them yet. See also:
btcsuite/btcd#1768

To speed up send-to-taproot support, refactor the pubkey script
encoding/decoding into separate unit-tested functions, to which we can
easily add taproot support in the next commit.
  • Loading branch information
benma committed Dec 20, 2021
1 parent c41cfb6 commit ba3d2b6
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 14 deletions.
3 changes: 2 additions & 1 deletion backend/coins/btc/addresses/address.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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.")
}
Expand Down
6 changes: 3 additions & 3 deletions backend/coins/btc/transaction.go
Expand Up @@ -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"
Expand All @@ -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"
)
Expand Down Expand Up @@ -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))
Expand Down
25 changes: 25 additions & 0 deletions backend/coins/btc/util/util.go
Expand Up @@ -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"
)

Expand All @@ -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
}
103 changes: 103 additions & 0 deletions 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())
}
27 changes: 17 additions & 10 deletions backend/devices/bitbox02/keystore.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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(
Expand All @@ -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,
}
}
Expand Down

0 comments on commit ba3d2b6

Please sign in to comment.