github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/btc/wallethd.go (about) 1 /* 2 This code originates from: 3 * https://github.com/WeMeetAgain/go-hdwallet 4 */ 5 6 package btc 7 8 import ( 9 "bytes" 10 "crypto/hmac" 11 "crypto/sha512" 12 "encoding/binary" 13 "errors" 14 "fmt" 15 16 "github.com/piotrnar/gocoin/lib/secp256k1" 17 ) 18 19 const ( 20 Public = uint32(0x0488B21E) // xpub 21 Private = uint32(0x0488ADE4) // xprv 22 PublicY = uint32(0x049d7cb2) // ypub 23 PrivateY = uint32(0x049d7878) // yprv 24 PublicZ = uint32(0x04b24746) // zpub 25 PrivateZ = uint32(0x04b2430c) // zprv 26 27 TestPublic = uint32(0x043587cf) // tpub 28 TestPrivate = uint32(0x04358394) // tprv 29 TestPublicY = uint32(0x044a5262) // upub 30 TestPrivateY = uint32(0x044a4e28) // uprv 31 TestPublicZ = uint32(0x045f1cf6) // vpub 32 TestPrivateZ = uint32(0x045f18bc) // vpub 33 ) 34 35 // HDWallet defines the components of a hierarchical deterministic wallet 36 type HDWallet struct { 37 Prefix uint32 38 Depth byte 39 Checksum [4]byte 40 I uint32 41 ChCode []byte //32 bytes 42 Key []byte //33 bytes 43 } 44 45 func IsPublicHDPrefix(p uint32) bool { 46 return p == Public || p == PublicY || p == PublicZ || 47 p == TestPublic || p == TestPublicY || p == TestPublicZ 48 } 49 50 func IsPrivateHDPrefix(p uint32) bool { 51 return p == Private || p == PrivateY || p == PrivateZ || 52 p == TestPrivate || p == TestPrivateY || p == TestPrivateZ 53 } 54 55 func IsTestnetHDPrefix(p uint32) bool { 56 return p == TestPublic || p == TestPublicY || p == TestPublicZ || 57 p == TestPrivate || p == TestPrivateY || p == TestPrivateZ 58 } 59 60 func PublishHDPrefix(p uint32) uint32 { 61 if p == Private { 62 return Public 63 } 64 if p == PrivateY { 65 return PublicY 66 } 67 if p == PrivateZ { 68 return PublicZ 69 } 70 if p == TestPrivate { 71 return TestPublic 72 } 73 if p == TestPrivateY { 74 return TestPublicY 75 } 76 if p == TestPrivateZ { 77 return TestPublicZ 78 } 79 return p 80 } 81 82 // Child returns the ith child of wallet w. Values of i >= 2^31 83 // signify private key derivation. Attempting private key derivation 84 // with a public key will throw an error. 85 func (w *HDWallet) Child(i uint32) (res *HDWallet) { 86 var ha, newkey []byte 87 var chksum [20]byte 88 89 if IsPrivateHDPrefix(w.Prefix) { 90 pub := PublicFromPrivate(w.Key[1:], true) 91 mac := hmac.New(sha512.New, w.ChCode) 92 if i >= uint32(0x80000000) { 93 mac.Write(w.Key) 94 } else { 95 mac.Write(pub) 96 } 97 binary.Write(mac, binary.BigEndian, i) 98 ha = mac.Sum(nil) 99 newkey = append([]byte{0}, DeriveNextPrivate(ha[:32], w.Key[1:])...) 100 RimpHash(pub, chksum[:]) 101 } else if IsPublicHDPrefix(w.Prefix) { 102 mac := hmac.New(sha512.New, w.ChCode) 103 if i >= uint32(0x80000000) { 104 panic("HDWallet.Child(): Private derivation on Public key") 105 } 106 mac.Write(w.Key) 107 binary.Write(mac, binary.BigEndian, i) 108 ha = mac.Sum(nil) 109 newkey = DeriveNextPublic(w.Key, ha[:32]) 110 RimpHash(w.Key, chksum[:]) 111 } else { 112 panic("HDWallet.Child(): Unexpected Prefix") 113 } 114 res = new(HDWallet) 115 res.Prefix = w.Prefix 116 res.Depth = w.Depth + 1 117 copy(res.Checksum[:], chksum[:4]) 118 res.I = i 119 res.ChCode = ha[32:] 120 res.Key = newkey 121 return 122 } 123 124 // Serialize returns the serialized form of the wallet. 125 // vbytes || depth || fingerprint || i || chaincode || key 126 func (w *HDWallet) Serialize() []byte { 127 var tmp [32]byte 128 b := new(bytes.Buffer) 129 binary.Write(b, binary.BigEndian, w.Prefix) 130 b.WriteByte(w.Depth) 131 b.Write(w.Checksum[:]) 132 binary.Write(b, binary.BigEndian, w.I) 133 b.Write(w.ChCode) 134 b.Write(w.Key) 135 ShaHash(b.Bytes(), tmp[:]) 136 return append(b.Bytes(), tmp[:4]...) 137 } 138 139 // String returns the base58-encoded string form of the wallet. 140 func (w *HDWallet) String() string { 141 return Encodeb58(w.Serialize()) 142 } 143 144 // StringWallet returns a wallet given a base58-encoded extended key 145 func StringWallet(data string) (*HDWallet, error) { 146 dbin := Decodeb58(data) 147 if err := ByteCheck(dbin); err != nil { 148 return &HDWallet{}, err 149 } 150 var res [32]byte 151 ShaHash(dbin[:(len(dbin)-4)], res[:]) 152 if !bytes.Equal(res[:4], dbin[(len(dbin)-4):]) { 153 return &HDWallet{}, errors.New("StringWallet: Invalid checksum") 154 } 155 r := new(HDWallet) 156 r.Prefix = binary.BigEndian.Uint32(dbin[0:4]) 157 r.Depth = dbin[4] 158 copy(r.Checksum[:], dbin[5:9]) 159 r.I = binary.BigEndian.Uint32(dbin[9:13]) 160 r.ChCode = dbin[13:45] 161 r.Key = dbin[45:78] 162 return r, nil 163 } 164 165 // Pub returns a new wallet which is the public key version of w. 166 // If w is a public key, Pub returns a copy of w 167 func (w *HDWallet) Pub() *HDWallet { 168 if IsPublicHDPrefix(w.Prefix) { 169 r := new(HDWallet) 170 *r = *w 171 return r 172 } else { 173 return &HDWallet{Prefix: PublishHDPrefix(w.Prefix), Depth: w.Depth, Checksum: w.Checksum, 174 I: w.I, ChCode: w.ChCode, Key: PublicFromPrivate(w.Key[1:], true)} 175 } 176 } 177 178 // StringChild returns the ith base58-encoded extended key of a base58-encoded extended key. 179 func StringChild(data string, i uint32) string { 180 w, err := StringWallet(data) 181 if err != nil { 182 return "" 183 } else { 184 w = w.Child(i) 185 return w.String() 186 } 187 } 188 189 // StringToAddress returns the Bitcoin address of a base58-encoded extended key. 190 func StringAddress(data string) (string, error) { 191 w, err := StringWallet(data) 192 if err != nil { 193 return "", err 194 } 195 196 return NewAddrFromPubkey(w.Key, AddrVerPubkey(w.Prefix == TestPublic || w.Prefix == TestPrivate)).String(), nil 197 } 198 199 // PublicAddress returns the Base58 or bech32 encoded public address of the given HD key. 200 func (w *HDWallet) PubAddr() *BtcAddr { 201 var pub []byte 202 if IsPrivateHDPrefix(w.Prefix) { 203 pub = PublicFromPrivate(w.Key[1:], true) 204 } else { 205 pub = w.Key 206 } 207 208 var h160 [20]byte 209 testnet := IsTestnetHDPrefix(w.Prefix) 210 RimpHash(pub, h160[:]) 211 switch w.Prefix { 212 case PrivateZ, PublicZ, TestPrivateZ, TestPublicZ: 213 return NewAddrFromPkScript(append([]byte{0, 20}, h160[:]...), testnet) 214 case PrivateY, PublicY, TestPrivateY, TestPublicY: 215 tmp := Rimp160AfterSha256(append([]byte{0, 20}, h160[:]...)) 216 return NewAddrFromHash160(tmp[:], AddrVerScript(testnet)) 217 default: 218 return NewAddrFromHash160(h160[:], AddrVerPubkey(testnet)) 219 } 220 } 221 222 // MasterKey returns a new wallet given a random seed. 223 func MasterKey(seed []byte, testnet bool) *HDWallet { 224 key := []byte("Bitcoin seed") 225 mac := hmac.New(sha512.New, key) 226 mac.Write(seed) 227 I := mac.Sum(nil) 228 res := &HDWallet{ChCode: I[len(I)/2:], Key: append([]byte{0}, I[:len(I)/2]...)} 229 if testnet { 230 res.Prefix = TestPrivate 231 } else { 232 res.Prefix = Private 233 } 234 return res 235 } 236 237 // StringCheck is a validation check of a Base58-encoded extended key. 238 func StringCheck(key string) error { 239 return ByteCheck(Decodeb58(key)) 240 } 241 242 // ByteCheck verifies the consistency of a serialized HD address. 243 func ByteCheck(dbin []byte) error { 244 // check proper length 245 if len(dbin) != 82 { 246 return errors.New("ByteCheck: Unexpected length") 247 } 248 249 // check for correct Public or Private Prefix 250 vb := binary.BigEndian.Uint32(dbin[:4]) 251 if !IsPrivateHDPrefix(vb) && !IsPublicHDPrefix(vb) { 252 return fmt.Errorf("ByteCheck: Unexpected Prefix 0x%08x", vb) 253 } 254 255 // if Public, check x coord is on curve 256 if IsPublicHDPrefix(vb) { 257 var xy secp256k1.XY 258 xy.ParsePubkey(dbin[45:78]) 259 if !xy.IsValid() { 260 return errors.New("ByteCheck: Invalid public key") 261 } 262 } 263 return nil 264 } 265 266 // HDKeyPrefix returns the first 32 bits, as expected for sepcific HD address. 267 func HDKeyPrefix(private, testnet bool) uint32 { 268 if private { 269 if testnet { 270 return TestPrivate 271 } else { 272 return Private 273 } 274 } else { 275 if testnet { 276 return TestPublic 277 } else { 278 return Public 279 } 280 } 281 }