Skip to content

Commit d58dda7

Browse files
committed
backend/btc: use PSBT as the internal format for passing unsigned tx
PSBT BIP: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki bitbox02-api-go now has a `BTCSignPSBT()` function, which simplifies the signing code in the app significantly. Benefits include: - Standardized, so easier for devs to understand and maintain - Future-proofing (interopable, extensible) - Clear lifecycle: - Create (unsigned) - Update (add metadata) - Sign (BitBox) - Extract (create sigscripts and witnesses, create final signed tx) The btcd/psbt lib is used and contains a function to extract the tx, which makes our own functions to create sigscripts/witnesses redundant. godot is removed as a linter b/c of false positives and it not being worth wasting time over not finishing a docstring with a dot.
1 parent c476bae commit d58dda7

File tree

12 files changed

+225
-351
lines changed

12 files changed

+225
-351
lines changed

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ linters:
120120
- predeclared
121121
- tenv
122122
- recvcheck
123+
- godot
123124
disable-all: false
124125

125126
issues:

backend/coins/btc/addresses/address.go

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/types"
2222
ourbtcutil "github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/util"
2323
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/signing"
24-
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
2524
"github.com/btcsuite/btcd/btcec/v2"
2625
"github.com/btcsuite/btcd/btcec/v2/schnorr"
2726
"github.com/btcsuite/btcd/btcutil"
@@ -38,12 +37,13 @@ type AccountAddress struct {
3837

3938
// AccountConfiguration is the account level configuration from which this address was derived.
4039
AccountConfiguration *signing.Configuration
41-
// publicKey is the public key of a single-sig address.
42-
publicKey *btcec.PublicKey
40+
// PublicKey is the public key of a single-sig address.
41+
PublicKey *btcec.PublicKey
4342
Derivation types.Derivation
4443

45-
// redeemScript stores the redeem script of a BIP16 P2SH output or nil if address type is P2PKH.
46-
redeemScript []byte
44+
// redeemScript stores the redeem script of a BIP16 P2SH output or nil if address type is not
45+
// P2SH.
46+
RedeemScript []byte
4747

4848
log *logrus.Entry
4949
}
@@ -116,9 +116,9 @@ func NewAccountAddress(
116116
return &AccountAddress{
117117
Address: address,
118118
AccountConfiguration: accountConfiguration,
119-
publicKey: publicKey,
119+
PublicKey: publicKey,
120120
Derivation: derivation,
121-
redeemScript: redeemScript,
121+
RedeemScript: redeemScript,
122122
log: log,
123123
}
124124
}
@@ -128,23 +128,6 @@ func (address *AccountAddress) ID() string {
128128
return string(address.PubkeyScriptHashHex())
129129
}
130130

131-
// BIP352Pubkey returns the pubkey used for silent payments:
132-
// - 33 byte compressed public key for p2pkh, p2wpkh, p2wpkh-p2sh.
133-
// - 32 byte x-only public key for p2tr
134-
// See https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki#user-content-Inputs_For_Shared_Secret_Derivation.
135-
func (address *AccountAddress) BIP352Pubkey() ([]byte, error) {
136-
publicKey := address.publicKey
137-
switch address.AccountConfiguration.ScriptType() {
138-
case signing.ScriptTypeP2PKH, signing.ScriptTypeP2WPKHP2SH, signing.ScriptTypeP2WPKH:
139-
return publicKey.SerializeCompressed(), nil
140-
case signing.ScriptTypeP2TR:
141-
outputKey := txscript.ComputeTaprootKeyNoScript(publicKey)
142-
return schnorr.SerializePubKey(outputKey), nil
143-
default:
144-
return nil, errp.New("unsupported script type for silent payments")
145-
}
146-
}
147-
148131
// EncodeForHumans implements accounts.Address.
149132
func (address *AccountAddress) EncodeForHumans() string {
150133
return address.EncodeAddress()
@@ -180,7 +163,7 @@ func (address *AccountAddress) ScriptForHashToSign() (bool, []byte) {
180163
case signing.ScriptTypeP2PKH:
181164
return false, address.PubkeyScript()
182165
case signing.ScriptTypeP2WPKHP2SH:
183-
return true, address.redeemScript
166+
return true, address.RedeemScript
184167
case signing.ScriptTypeP2WPKH:
185168
return true, address.PubkeyScript()
186169
default:
@@ -194,7 +177,7 @@ func (address *AccountAddress) ScriptForHashToSign() (bool, []byte) {
194177
func (address *AccountAddress) SignatureScript(
195178
signature types.Signature,
196179
) ([]byte, wire.TxWitness) {
197-
publicKey := address.publicKey
180+
publicKey := address.PublicKey
198181
switch address.AccountConfiguration.ScriptType() {
199182
case signing.ScriptTypeP2PKH:
200183
signatureScript, err := txscript.NewScriptBuilder().
@@ -207,7 +190,7 @@ func (address *AccountAddress) SignatureScript(
207190
return signatureScript, nil
208191
case signing.ScriptTypeP2WPKHP2SH:
209192
signatureScript, err := txscript.NewScriptBuilder().
210-
AddData(address.redeemScript).
193+
AddData(address.RedeemScript).
211194
Script()
212195
if err != nil {
213196
address.log.WithError(err).Panic("Failed to build segwit signature script.")

backend/coins/btc/addresses/address_export_test.go

Lines changed: 0 additions & 22 deletions
This file was deleted.

backend/coins/btc/addresses/addresschain_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func (s *addressChainTestSuite) TestEnsureAddresses() {
143143
s.Require().Len(newAddresses, s.gapLimit)
144144
for index, address := range newAddresses {
145145
s.Require().Equal(uint32(index), address.AbsoluteKeypath().ToUInt32()[1])
146-
s.Require().Equal(getPubKey(index), address.TstPublicKey())
146+
s.Require().Equal(getPubKey(index), address.PublicKey)
147147
}
148148
// Address statuses are still the same, so calling it again won't produce more addresses.
149149
addrs, err := s.addresses.EnsureAddresses()

backend/coins/btc/maketx/maketx.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/signing"
2929
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
3030
"github.com/btcsuite/btcd/btcutil"
31+
"github.com/btcsuite/btcd/btcutil/psbt"
3132
"github.com/btcsuite/btcd/chaincfg/chainhash"
3233
"github.com/btcsuite/btcd/txscript"
3334
"github.com/btcsuite/btcd/wire"
@@ -49,8 +50,7 @@ type TxProposal struct {
4950
// Amount is the amount that is sent out. The fee is not included and is deducted on top.
5051
Amount btcutil.Amount
5152
// Fee is the mining fee used.
52-
Fee btcutil.Amount
53-
Transaction *wire.MsgTx
53+
Fee btcutil.Amount
5454
// ChangeAddress is the address of the wallet to which the change of the transaction is sent.
5555
ChangeAddress *addresses.AccountAddress
5656
PreviousOutputs PreviousOutputs
@@ -60,11 +60,12 @@ type TxProposal struct {
6060
SilentPaymentAddress string
6161
// OutIndex is the index of the output we send to.
6262
OutIndex int
63+
Psbt *psbt.Packet
6364
}
6465

6566
// SigHashes computes the hashes cache to speed up per-input sighash computations.
6667
func (txProposal *TxProposal) SigHashes() *txscript.TxSigHashes {
67-
return txscript.NewTxSigHashes(txProposal.Transaction, txProposal.PreviousOutputs)
68+
return txscript.NewTxSigHashes(txProposal.Psbt.UnsignedTx, txProposal.PreviousOutputs)
6869
}
6970

7071
// Total is amount+fee.
@@ -209,15 +210,19 @@ func NewTxSpendAll(
209210
log.WithField("fee", maxRequiredFee).Debug("Preparing transaction to spend all outputs")
210211

211212
setRBF(coin, unsignedTransaction)
213+
psbt, err := psbt.NewFromUnsignedTx(unsignedTransaction)
214+
if err != nil {
215+
return nil, err
216+
}
212217
return &TxProposal{
213218
Coin: coin,
214219
Amount: btcutil.Amount(output.Value),
215220
Fee: maxRequiredFee,
216-
Transaction: unsignedTransaction,
217221
PreviousOutputs: spendableOutputs,
218222
SilentPaymentAddress: outputInfo.silentPaymentAddress,
219223
// Only one output in send-all
220224
OutIndex: 0,
225+
Psbt: psbt,
221226
}, nil
222227
}
223228

@@ -307,15 +312,20 @@ func NewTx(
307312
}
308313

309314
setRBF(coin, unsignedTransaction)
315+
psbt, err := psbt.NewFromUnsignedTx(unsignedTransaction)
316+
if err != nil {
317+
return nil, err
318+
}
319+
310320
return &TxProposal{
311321
Coin: coin,
312322
Amount: targetAmount,
313323
Fee: finalFee,
314-
Transaction: unsignedTransaction,
315324
ChangeAddress: changeAddress,
316325
PreviousOutputs: previousOutputs,
317326
SilentPaymentAddress: outputInfo.silentPaymentAddress,
318327
OutIndex: outIndex,
328+
Psbt: psbt,
319329
}, nil
320330
}
321331
}

backend/coins/btc/maketx/maketx_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func (s *newTxSuite) check(
165165
s.Require().NoError(err)
166166
}
167167

168-
tx := txProposal.Transaction
168+
tx := txProposal.Psbt.UnsignedTx
169169

170170
// Check invariants independent of the particular coin selection algorithm.
171171
s.Require().Equal(s.coin, txProposal.Coin)

0 commit comments

Comments
 (0)