github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/btc/addr.go (about) 1 package btc 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "math/big" 9 "strings" 10 11 "github.com/piotrnar/gocoin/lib/others/bech32" 12 ) 13 14 type BtcAddr struct { 15 Version byte 16 Hash160 [20]byte 17 Checksum []byte 18 Pubkey []byte 19 Enc58str string 20 21 *SegwitProg // if this is not nil, means that this is a native segwit address 22 23 // This is used only by the client 24 Extra struct { 25 Label string 26 Wallet string 27 Virgin bool 28 } 29 } 30 31 type SegwitProg struct { 32 HRP string 33 Version int 34 Program []byte 35 } 36 37 func NewAddrFromString(hs string) (a *BtcAddr, e error) { 38 if len(hs) < 4 { 39 e = errors.New("Address '" + hs + "' is too short") 40 return 41 } 42 43 prefix := strings.ToLower(hs[:3]) 44 if prefix == "bc1" || prefix == "tb1" { 45 var sw = &SegwitProg{HRP: prefix[:2]} 46 sw.Version, sw.Program, e = bech32.SegwitDecode(sw.HRP, hs) 47 if sw.Program != nil { 48 a = &BtcAddr{SegwitProg: sw} 49 } else { 50 if e == nil { 51 e = errors.New("INCORRECT bech32/segwit address") 52 } 53 } 54 return 55 } 56 57 dec := Decodeb58(hs) 58 if dec == nil { 59 e = errors.New("Cannot decode b58 string '" + hs + "'") 60 return 61 } 62 if len(dec) < 25 { 63 e = errors.New("Address too short " + hex.EncodeToString(dec)) 64 return 65 } 66 if len(dec) == 25 { 67 sh := Sha2Sum(dec[0:21]) 68 if !bytes.Equal(sh[:4], dec[21:25]) { 69 e = errors.New("CHECKSUM error in base58") 70 } else { 71 a = new(BtcAddr) 72 a.Version = dec[0] 73 copy(a.Hash160[:], dec[1:21]) 74 a.Checksum = make([]byte, 4) 75 copy(a.Checksum, dec[21:25]) 76 a.Enc58str = hs 77 } 78 } else { 79 e = errors.New("Unrecognized address payload " + hex.EncodeToString(dec)) 80 } 81 return 82 } 83 84 func NewAddrFromHash160(in []byte, ver byte) (a *BtcAddr) { 85 a = new(BtcAddr) 86 a.Version = ver 87 copy(a.Hash160[:], in[:]) 88 return 89 } 90 91 func NewAddrFromPubkey(in []byte, ver byte) (a *BtcAddr) { 92 a = new(BtcAddr) 93 a.Pubkey = make([]byte, len(in)) 94 copy(a.Pubkey[:], in[:]) 95 a.Version = ver 96 RimpHash(in, a.Hash160[:]) 97 return 98 } 99 100 func AddrVerPubkey(testnet bool) byte { 101 if testnet { 102 return 111 103 } else { 104 return 0 105 } 106 } 107 108 func AddrVerScript(testnet bool) byte { 109 if testnet { 110 return 196 111 } else { 112 return 5 113 } 114 } 115 116 func NewAddrFromPkScript(scr []byte, testnet bool) *BtcAddr { 117 // check segwit bech32: 118 if len(scr) == 0 { 119 return nil 120 } 121 122 if version, program := IsWitnessProgram(scr); program != nil { 123 sw := &SegwitProg{HRP: GetSegwitHRP(testnet), Version: version, Program: program} 124 125 str := bech32.SegwitEncode(sw.HRP, version, program) 126 if str == "" { 127 return nil 128 } 129 130 ad := new(BtcAddr) 131 ad.Enc58str = str 132 ad.SegwitProg = sw 133 134 return ad 135 } 136 137 if len(scr) == 25 && scr[0] == 0x76 && scr[1] == 0xa9 && scr[2] == 0x14 && scr[23] == 0x88 && scr[24] == 0xac { 138 return NewAddrFromHash160(scr[3:23], AddrVerPubkey(testnet)) 139 } else if len(scr) == 67 && scr[0] == 0x41 && scr[66] == 0xac { 140 return NewAddrFromPubkey(scr[1:66], AddrVerPubkey(testnet)) 141 } else if len(scr) == 35 && scr[0] == 0x21 && scr[34] == 0xac { 142 return NewAddrFromPubkey(scr[1:34], AddrVerPubkey(testnet)) 143 } else if len(scr) == 23 && scr[0] == 0xa9 && scr[1] == 0x14 && scr[22] == 0x87 { 144 return NewAddrFromHash160(scr[2:22], AddrVerScript(testnet)) 145 } 146 return nil 147 } 148 149 // String returns the Base58 encoded address. 150 func (a *BtcAddr) String() string { 151 if a.Enc58str == "" { 152 if a.SegwitProg != nil { 153 a.Enc58str = a.SegwitProg.String() 154 } else { 155 var ad [25]byte 156 ad[0] = a.Version 157 copy(ad[1:21], a.Hash160[:]) 158 if a.Checksum == nil { 159 sh := Sha2Sum(ad[0:21]) 160 a.Checksum = make([]byte, 4) 161 copy(a.Checksum, sh[:4]) 162 } 163 copy(ad[21:25], a.Checksum[:]) 164 a.Enc58str = Encodeb58(ad[:]) 165 } 166 } 167 return a.Enc58str 168 } 169 170 func (a *BtcAddr) IsCompressed() bool { 171 if len(a.Pubkey) == 33 { 172 return true 173 } 174 if len(a.Pubkey) != 65 { 175 panic("Cannot determine whether the key was compressed") 176 } 177 return false 178 } 179 180 // String with a label 181 func (a *BtcAddr) Label() (s string) { 182 if a.Extra.Wallet != "" { 183 s += " " + a.Extra.Wallet + ":" 184 } 185 if a.Extra.Label != "" { 186 s += " " + a.Extra.Label 187 } 188 if a.Extra.Virgin { 189 s += " ***" 190 } 191 return 192 } 193 194 // Owns checks if a pk_script send coins to this address. 195 func (a *BtcAddr) Owns(scr []byte) (yes bool) { 196 // The most common spend script 197 if len(scr) == 25 && scr[0] == 0x76 && scr[1] == 0xa9 && scr[2] == 0x14 && scr[23] == 0x88 && scr[24] == 0xac { 198 yes = bytes.Equal(scr[3:23], a.Hash160[:]) 199 return 200 } 201 202 // Spend script with an entire public key 203 if len(scr) == 67 && scr[0] == 0x41 && scr[1] == 0x04 && scr[66] == 0xac { 204 if a.Pubkey == nil { 205 h := Rimp160AfterSha256(scr[1:66]) 206 if h == a.Hash160 { 207 a.Pubkey = make([]byte, 65) 208 copy(a.Pubkey, scr[1:66]) 209 yes = true 210 } 211 return 212 } 213 yes = bytes.Equal(scr[1:34], a.Pubkey[:33]) 214 return 215 } 216 217 // Spend script with a compressed public key 218 if len(scr) == 35 && scr[0] == 0x21 && (scr[1] == 0x02 || scr[1] == 0x03) && scr[34] == 0xac { 219 if a.Pubkey == nil { 220 h := Rimp160AfterSha256(scr[1:34]) 221 if h == a.Hash160 { 222 a.Pubkey = make([]byte, 33) 223 copy(a.Pubkey, scr[1:34]) 224 yes = true 225 } 226 return 227 } 228 yes = bytes.Equal(scr[1:34], a.Pubkey[:33]) 229 return 230 } 231 232 return 233 } 234 235 func (a *BtcAddr) OutScript() (res []byte) { 236 if a.SegwitProg != nil { 237 res = make([]byte, 2+len(a.SegwitProg.Program)) 238 if a.SegwitProg.Version == 0 { 239 res[0] = OP_0 240 } else if a.SegwitProg.Version <= 16 { 241 res[0] = byte(a.SegwitProg.Version - 1 + OP_1) 242 } else { 243 panic(fmt.Sprint("Cannot create OutScript for SegwitProg version ", a.SegwitProg.Version)) 244 } 245 res[1] = byte(len(a.SegwitProg.Program)) 246 copy(res[2:], a.SegwitProg.Program) 247 } else if a.Version == AddrVerPubkey(false) || a.Version == AddrVerPubkey(true) || a.Version == 48 /*Litecoin*/ { 248 res = make([]byte, 25) 249 res[0] = 0x76 250 res[1] = 0xa9 251 res[2] = 20 252 copy(res[3:23], a.Hash160[:]) 253 res[23] = 0x88 254 res[24] = 0xac 255 } else if a.Version == AddrVerScript(false) || a.Version == AddrVerScript(true) { 256 res = make([]byte, 23) 257 res[0] = 0xa9 258 res[1] = 20 259 copy(res[2:22], a.Hash160[:]) 260 res[22] = 0x87 261 } else { 262 panic(fmt.Sprint("Cannot create OutScript for address version ", a.Version)) 263 } 264 return 265 } 266 267 var b58set []byte = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") 268 269 func b58chr2int(chr byte) int { 270 for i := range b58set { 271 if b58set[i] == chr { 272 return i 273 } 274 } 275 return -1 276 } 277 278 var bn0 *big.Int = big.NewInt(0) 279 var bn58 *big.Int = big.NewInt(58) 280 281 func Encodeb58(a []byte) (s string) { 282 idx := len(a)*138/100 + 1 283 buf := make([]byte, idx) 284 bn := new(big.Int).SetBytes(a) 285 var mo *big.Int 286 for bn.Cmp(bn0) != 0 { 287 bn, mo = bn.DivMod(bn, bn58, new(big.Int)) 288 idx-- 289 buf[idx] = b58set[mo.Int64()] 290 } 291 for i := range a { 292 if a[i] != 0 { 293 break 294 } 295 idx-- 296 buf[idx] = b58set[0] 297 } 298 299 s = string(buf[idx:]) 300 301 return 302 } 303 304 func Decodeb58(s string) (res []byte) { 305 bn := big.NewInt(0) 306 for i := range s { 307 v := b58chr2int(byte(s[i])) 308 if v < 0 { 309 return nil 310 } 311 bn = bn.Mul(bn, bn58) 312 bn = bn.Add(bn, big.NewInt(int64(v))) 313 } 314 315 // We want to "restore leading zeros" as satoshi's implementation does: 316 var i int 317 for i < len(s) && s[i] == b58set[0] { 318 i++ 319 } 320 if i > 0 { 321 res = make([]byte, i) 322 } 323 res = append(res, bn.Bytes()...) 324 return 325 } 326 327 func (sw *SegwitProg) String() (res string) { 328 res = bech32.SegwitEncode(sw.HRP, sw.Version, sw.Program) 329 return 330 } 331 332 func GetSegwitHRP(testnet bool) string { 333 if testnet { 334 return "tb" 335 } else { 336 return "bc" 337 } 338 }