Skip to content

Commit 09ba026

Browse files
authored
Merge pull request #2296 from btcsuite/psbt-global-xpub
psbt: add support for PSBT_GLOBAL_XPUB type
2 parents f3bd1f5 + bda0481 commit 09ba026

File tree

4 files changed

+176
-28
lines changed

4 files changed

+176
-28
lines changed

btcutil/psbt/bip32.go

+86-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ package psbt
33
import (
44
"bytes"
55
"encoding/binary"
6+
7+
"github.com/btcsuite/btcd/btcutil/base58"
8+
"github.com/btcsuite/btcd/btcutil/hdkeychain"
9+
"github.com/btcsuite/btcd/chaincfg/chainhash"
10+
)
11+
12+
const (
13+
// uint32Size is the size of a uint32 in bytes.
14+
uint32Size = 4
615
)
716

817
// Bip32Derivation encapsulates the data for the input and output
@@ -38,21 +47,23 @@ func (s Bip32Sorter) Less(i, j int) bool {
3847

3948
// ReadBip32Derivation deserializes a byte slice containing chunks of 4 byte
4049
// little endian encodings of uint32 values, the first of which is the
41-
// masterkeyfingerprint and the remainder of which are the derivation path.
50+
// MasterKeyFingerprint and the remainder of which are the derivation path.
4251
func ReadBip32Derivation(path []byte) (uint32, []uint32, error) {
4352
// BIP-0174 defines the derivation path being encoded as
4453
// "<32-bit uint> <32-bit uint>*"
4554
// with the asterisk meaning 0 to n times. Which in turn means that an
4655
// empty path is valid, only the key fingerprint is mandatory.
47-
if len(path)%4 != 0 {
56+
if len(path)%uint32Size != 0 {
4857
return 0, nil, ErrInvalidPsbtFormat
4958
}
5059

51-
masterKeyInt := binary.LittleEndian.Uint32(path[:4])
60+
masterKeyInt := binary.LittleEndian.Uint32(path[:uint32Size])
5261

5362
var paths []uint32
54-
for i := 4; i < len(path); i += 4 {
55-
paths = append(paths, binary.LittleEndian.Uint32(path[i:i+4]))
63+
for i := uint32Size; i < len(path); i += uint32Size {
64+
paths = append(paths, binary.LittleEndian.Uint32(
65+
path[i:i+uint32Size],
66+
))
5667
}
5768

5869
return masterKeyInt, paths, nil
@@ -65,16 +76,81 @@ func ReadBip32Derivation(path []byte) (uint32, []uint32, error) {
6576
func SerializeBIP32Derivation(masterKeyFingerprint uint32,
6677
bip32Path []uint32) []byte {
6778

68-
var masterKeyBytes [4]byte
79+
var masterKeyBytes [uint32Size]byte
6980
binary.LittleEndian.PutUint32(masterKeyBytes[:], masterKeyFingerprint)
7081

71-
derivationPath := make([]byte, 0, 4+4*len(bip32Path))
82+
derivationPath := make([]byte, 0, uint32Size+uint32Size*len(bip32Path))
7283
derivationPath = append(derivationPath, masterKeyBytes[:]...)
7384
for _, path := range bip32Path {
74-
var pathbytes [4]byte
75-
binary.LittleEndian.PutUint32(pathbytes[:], path)
76-
derivationPath = append(derivationPath, pathbytes[:]...)
85+
var pathBytes [uint32Size]byte
86+
binary.LittleEndian.PutUint32(pathBytes[:], path)
87+
derivationPath = append(derivationPath, pathBytes[:]...)
7788
}
7889

7990
return derivationPath
8091
}
92+
93+
// XPub is a struct that encapsulates an extended public key, as defined in
94+
// BIP-0032.
95+
type XPub struct {
96+
// ExtendedKey is the serialized extended public key as defined in
97+
// BIP-0032.
98+
ExtendedKey []byte
99+
100+
// MasterFingerprint is the fingerprint of the master pubkey.
101+
MasterKeyFingerprint uint32
102+
103+
// Bip32Path is the derivation path of the key, with hardened elements
104+
// having the 0x80000000 offset added, as defined in BIP-0032. The
105+
// number of path elements must match the depth provided in the extended
106+
// public key.
107+
Bip32Path []uint32
108+
}
109+
110+
// ReadXPub deserializes a byte slice containing an extended public key and a
111+
// BIP-0032 derivation path.
112+
func ReadXPub(keyData []byte, path []byte) (*XPub, error) {
113+
xPub, err := DecodeExtendedKey(keyData)
114+
if err != nil {
115+
return nil, ErrInvalidPsbtFormat
116+
}
117+
numPathElements := xPub.Depth()
118+
119+
// The path also contains the master key fingerprint,
120+
expectedSize := int(uint32Size * (numPathElements + 1))
121+
if len(path) != expectedSize {
122+
return nil, ErrInvalidPsbtFormat
123+
}
124+
125+
masterKeyFingerprint, bip32Path, err := ReadBip32Derivation(path)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
return &XPub{
131+
ExtendedKey: keyData,
132+
MasterKeyFingerprint: masterKeyFingerprint,
133+
Bip32Path: bip32Path,
134+
}, nil
135+
}
136+
137+
// EncodeExtendedKey serializes an extended key to a byte slice, without the
138+
// checksum.
139+
func EncodeExtendedKey(key *hdkeychain.ExtendedKey) []byte {
140+
serializedKey := key.String()
141+
decodedKey := base58.Decode(serializedKey)
142+
return decodedKey[:len(decodedKey)-uint32Size]
143+
}
144+
145+
// DecodeExtendedKey deserializes an extended key from a byte slice that does
146+
// not contain the checksum.
147+
func DecodeExtendedKey(encodedKey []byte) (*hdkeychain.ExtendedKey, error) {
148+
checkSum := chainhash.DoubleHashB(encodedKey)[:uint32Size]
149+
serializedBytes := append(encodedKey, checkSum...)
150+
xPub, err := hdkeychain.NewKeyFromString(base58.Encode(serializedBytes))
151+
if err != nil {
152+
return nil, err
153+
}
154+
155+
return xPub, nil
156+
}

btcutil/psbt/psbt.go

+52-8
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ type Packet struct {
135135
// produced by this PSBT.
136136
Outputs []POutput
137137

138+
// XPubs is a list of extended public keys that can be used to derive
139+
// public keys used in the inputs and outputs of this transaction. It
140+
// should be the public key at the highest hardened derivation index so
141+
// that the unhardened child keys used in the transaction can be
142+
// derived.
143+
XPubs []XPub
144+
138145
// Unknowns are the set of custom types (global only) within this PSBT.
139146
Unknowns []*Unknown
140147
}
@@ -161,12 +168,14 @@ func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) {
161168

162169
inSlice := make([]PInput, len(tx.TxIn))
163170
outSlice := make([]POutput, len(tx.TxOut))
171+
xPubSlice := make([]XPub, 0)
164172
unknownSlice := make([]*Unknown, 0)
165173

166174
return &Packet{
167175
UnsignedTx: tx,
168176
Inputs: inSlice,
169177
Outputs: outSlice,
178+
XPubs: xPubSlice,
170179
Unknowns: unknownSlice,
171180
}, nil
172181
}
@@ -230,7 +239,10 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {
230239

231240
// Next we parse any unknowns that may be present, making sure that we
232241
// break at the separator.
233-
var unknownSlice []*Unknown
242+
var (
243+
xPubSlice []XPub
244+
unknownSlice []*Unknown
245+
)
234246
for {
235247
keyint, keydata, err := getKey(r)
236248
if err != nil {
@@ -247,14 +259,32 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {
247259
return nil, err
248260
}
249261

250-
keyintanddata := []byte{byte(keyint)}
251-
keyintanddata = append(keyintanddata, keydata...)
252-
253-
newUnknown := &Unknown{
254-
Key: keyintanddata,
255-
Value: value,
262+
switch GlobalType(keyint) {
263+
case XPubType:
264+
xPub, err := ReadXPub(keydata, value)
265+
if err != nil {
266+
return nil, err
267+
}
268+
269+
// Duplicate keys are not allowed
270+
for _, x := range xPubSlice {
271+
if bytes.Equal(x.ExtendedKey, keyData) {
272+
return nil, ErrDuplicateKey
273+
}
274+
}
275+
276+
xPubSlice = append(xPubSlice, *xPub)
277+
278+
default:
279+
keyintanddata := []byte{byte(keyint)}
280+
keyintanddata = append(keyintanddata, keydata...)
281+
282+
newUnknown := &Unknown{
283+
Key: keyintanddata,
284+
Value: value,
285+
}
286+
unknownSlice = append(unknownSlice, newUnknown)
256287
}
257-
unknownSlice = append(unknownSlice, newUnknown)
258288
}
259289

260290
// Next we parse the INPUT section.
@@ -286,6 +316,7 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) {
286316
UnsignedTx: msgTx,
287317
Inputs: inSlice,
288318
Outputs: outSlice,
319+
XPubs: xPubSlice,
289320
Unknowns: unknownSlice,
290321
}
291322

@@ -325,6 +356,19 @@ func (p *Packet) Serialize(w io.Writer) error {
325356
return err
326357
}
327358

359+
// Serialize the global xPubs.
360+
for _, xPub := range p.XPubs {
361+
pathBytes := SerializeBIP32Derivation(
362+
xPub.MasterKeyFingerprint, xPub.Bip32Path,
363+
)
364+
err := serializeKVPairWithType(
365+
w, uint8(XPubType), xPub.ExtendedKey, pathBytes,
366+
)
367+
if err != nil {
368+
return err
369+
}
370+
}
371+
328372
// Unknown is a special case; we don't have a key type, only a key and
329373
// a value field
330374
for _, kv := range p.Unknowns {

0 commit comments

Comments
 (0)