@@ -3,6 +3,15 @@ package psbt
3
3
import (
4
4
"bytes"
5
5
"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
6
15
)
7
16
8
17
// Bip32Derivation encapsulates the data for the input and output
@@ -38,21 +47,23 @@ func (s Bip32Sorter) Less(i, j int) bool {
38
47
39
48
// ReadBip32Derivation deserializes a byte slice containing chunks of 4 byte
40
49
// 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.
42
51
func ReadBip32Derivation (path []byte ) (uint32 , []uint32 , error ) {
43
52
// BIP-0174 defines the derivation path being encoded as
44
53
// "<32-bit uint> <32-bit uint>*"
45
54
// with the asterisk meaning 0 to n times. Which in turn means that an
46
55
// empty path is valid, only the key fingerprint is mandatory.
47
- if len (path )% 4 != 0 {
56
+ if len (path )% uint32Size != 0 {
48
57
return 0 , nil , ErrInvalidPsbtFormat
49
58
}
50
59
51
- masterKeyInt := binary .LittleEndian .Uint32 (path [:4 ])
60
+ masterKeyInt := binary .LittleEndian .Uint32 (path [:uint32Size ])
52
61
53
62
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
+ ))
56
67
}
57
68
58
69
return masterKeyInt , paths , nil
@@ -65,16 +76,81 @@ func ReadBip32Derivation(path []byte) (uint32, []uint32, error) {
65
76
func SerializeBIP32Derivation (masterKeyFingerprint uint32 ,
66
77
bip32Path []uint32 ) []byte {
67
78
68
- var masterKeyBytes [4 ]byte
79
+ var masterKeyBytes [uint32Size ]byte
69
80
binary .LittleEndian .PutUint32 (masterKeyBytes [:], masterKeyFingerprint )
70
81
71
- derivationPath := make ([]byte , 0 , 4 + 4 * len (bip32Path ))
82
+ derivationPath := make ([]byte , 0 , uint32Size + uint32Size * len (bip32Path ))
72
83
derivationPath = append (derivationPath , masterKeyBytes [:]... )
73
84
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 [:]... )
77
88
}
78
89
79
90
return derivationPath
80
91
}
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
+ }
0 commit comments