github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/wallet/wallet.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "crypto/sha256" 7 "encoding/hex" 8 "fmt" 9 "os" 10 "regexp" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/piotrnar/gocoin/lib/btc" 16 "github.com/piotrnar/gocoin/lib/others/bip39" 17 "github.com/piotrnar/gocoin/lib/others/scrypt" 18 "github.com/piotrnar/gocoin/lib/others/sys" 19 ) 20 21 var ( 22 // set in make_wallet(): 23 keys []*btc.PrivateAddr 24 segwit []*btc.BtcAddr 25 curFee uint64 26 hd_wallet_xtra []string 27 ) 28 29 // load_others loads private keys of .others file. 30 func load_others() { 31 f, e := os.Open(RawKeysFilename) 32 if e == nil { 33 defer f.Close() 34 td := bufio.NewReader(f) 35 for { 36 li, _, _ := td.ReadLine() 37 if li == nil { 38 break 39 } 40 if len(li) == 0 { 41 continue 42 } 43 pk := strings.SplitN(strings.Trim(string(li), " "), " ", 2) 44 if pk[0][0] == '#' { 45 continue // Just a comment-line 46 } 47 48 rec, er := btc.DecodePrivateAddr(pk[0]) 49 if er != nil { 50 println("DecodePrivateAddr error:", er.Error()) 51 if *verbose { 52 println(pk[0]) 53 } 54 continue 55 } 56 if rec.Version != ver_secret() { 57 println(pk[0][:6], "has version", rec.Version, "while we expect", ver_secret()) 58 fmt.Println("You may want to play with -t or -ltc switch") 59 } 60 if len(pk) > 1 { 61 rec.BtcAddr.Extra.Label = pk[1] 62 } else { 63 rec.BtcAddr.Extra.Label = fmt.Sprint("Other ", len(keys)) 64 } 65 keys = append(keys, rec) 66 } 67 if *verbose { 68 fmt.Println(len(keys), "keys imported from", RawKeysFilename) 69 } 70 } else { 71 if *verbose { 72 fmt.Println("You can also have some dumped (b58 encoded) Key keys in file", RawKeysFilename) 73 } 74 } 75 } 76 77 func hdwal_private_prefix() uint32 { 78 if !testnet { 79 if segwit_mode { 80 if bech32_mode { 81 return btc.PrivateZ 82 } else { 83 return btc.PrivateY 84 } 85 } else { 86 return btc.Private 87 } 88 } else { 89 if segwit_mode { 90 if bech32_mode { 91 return btc.TestPrivateZ 92 } else { 93 return btc.TestPrivateY 94 } 95 } else { 96 return btc.TestPrivate 97 } 98 } 99 } 100 101 // make_wallet gets the secret seed and generates "keycnt" key pairs (both private and public). 102 func make_wallet() { 103 var seed_key []byte 104 var hdwal, prvwal *btc.HDWallet 105 var hdpath_x []uint32 106 var hdpath_last, prvidx uint32 107 var hd_label_prefix string 108 var hd_hardend bool 109 var currhdsub uint // default 0 110 var aes_key []byte 111 112 load_others() 113 defer func() { 114 sys.ClearBuffer(seed_key) 115 if hdwal != nil { 116 sys.ClearBuffer(hdwal.Key) 117 sys.ClearBuffer(hdwal.ChCode) 118 } 119 }() 120 121 if waltype < 1 || waltype > 4 { 122 println("ERROR: Unsupported wallet type", waltype) 123 os.Exit(1) 124 } 125 126 if waltype < 3 { 127 println("Wallets Type", waltype, " are no longer supported. Use Gocoin wallet 1.9.8 or earlier.") 128 os.Exit(1) 129 } 130 131 if waltype == 4 { 132 // parse hdpath 133 ts := strings.Split(hdpath, "/") 134 if len(ts) < 2 || ts[0] != "m" { 135 println("hdpath - top level syntax error:", hdpath, len(ts)) 136 os.Exit(1) 137 } 138 hd_label_prefix = "m" 139 for i := 1; i < len(ts); i++ { 140 var xval uint32 141 ti := ts[i] 142 if strings.HasSuffix(ti, "'") { 143 xval = 0x80000000 // hardened 144 hd_hardend = true 145 } 146 if v, e := strconv.ParseInt(strings.TrimSuffix(ti, "'"), 10, 32); e != nil || v < 0 { 147 println("hdpath - syntax error. non-negative integer expected:", ti) 148 os.Exit(1) 149 } else { 150 xval |= uint32(v) 151 } 152 hdpath_x = append(hdpath_x, xval) 153 if i < len(ts)-1 { 154 hd_label_prefix += fmt.Sprint("/", xval&0x7fffffff) 155 if (xval & 0x80000000) != 0 { 156 hd_label_prefix += "'" 157 } 158 } 159 } 160 hdpath_last = hdpath_x[len(hdpath_x)-1] 161 } 162 163 if bip39wrds != 0 { 164 if bip39wrds == -1 { 165 fmt.Println("The seed password is considered to be BIP39 mnemonic") 166 } else if bip39wrds < 12 || bip39wrds > 24 || (bip39wrds%3) != 0 { 167 println("ERROR: Incorrect value for BIP39 words count", bip39wrds) 168 os.Exit(1) 169 } 170 if waltype != 4 { 171 fmt.Println("WARNING: Not HD-Wallet type. BIP39 mode ignored.") 172 } 173 } 174 175 pass := getpass() 176 if pass == nil { 177 cleanExit(0) 178 } 179 180 if usescrypt != 0 { 181 if bip39wrds == -1 { 182 println("ERROR: Cannot use scrypt function in BIP39 mnemonic mode") 183 cleanExit(1) 184 } 185 fmt.Print("Running scrypt function with complexity ", 1<<usescrypt, " ... ") 186 sta := time.Now() 187 dk, er := scrypt.Key(pass, []byte("Gocoin scrypt password salt"), 1<<usescrypt, 8, 1, 32) 188 tim := time.Since(sta) 189 sys.ClearBuffer(pass) 190 if len(dk) != 32 || er != nil { 191 println("scrypt.Key failed") 192 cleanExit(0) 193 } 194 pass = dk 195 fmt.Println("took", tim.String()) 196 } 197 if *encrypt != "" || *decrypt != "" { 198 aes_key = make([]byte, 32) 199 } 200 if waltype == 3 { 201 seed_key = make([]byte, 32) 202 btc.ShaHash(pass, seed_key) 203 sys.ClearBuffer(pass) 204 if aes_key != nil { 205 btc.ShaHash(seed_key, aes_key) 206 } 207 } else /*if waltype==4*/ { 208 if bip39wrds != 0 { 209 var er error 210 var mnemonic string 211 if bip39wrds == -1 { 212 mnemonic = strings.ToLower(string(pass)) 213 sys.ClearBuffer(pass) 214 re := regexp.MustCompile("[^a-z]") 215 a := re.ReplaceAll([]byte(mnemonic), []byte(" ")) 216 lns := strings.Split(string(a), " ") 217 mnemonic = "" 218 for _, l := range lns { 219 if l != "" { 220 if mnemonic != "" { 221 mnemonic = mnemonic + " " + l 222 } else { 223 mnemonic = l 224 } 225 } 226 } 227 } else { 228 hd_wallet_xtra = append(hd_wallet_xtra, fmt.Sprint("Based on ", bip39wrds, " BIP39 words")) 229 s := sha256.New() 230 s.Write(pass) 231 s.Write([]byte("|gocoin|")) 232 s.Write(pass) 233 s.Write([]byte{byte(bip39wrds / 3 * 32)}) 234 seed_key = s.Sum(nil) 235 sys.ClearBuffer(pass) 236 mnemonic, er = bip39.NewMnemonic(seed_key[:(bip39wrds/3)*4]) 237 sys.ClearBuffer(seed_key) 238 if er != nil { 239 println(er.Error()) 240 cleanExit(1) 241 } 242 } 243 if *dumpwords { 244 fmt.Println("BIP39:", mnemonic) 245 } 246 seed_key, er = bip39.NewSeedWithErrorChecking(mnemonic, "") 247 sys.ClearBuffer([]byte(mnemonic)) 248 if er != nil { 249 println(er.Error()) 250 cleanExit(1) 251 } 252 hdwal = btc.MasterKey(seed_key, testnet) 253 sys.ClearBuffer(seed_key) 254 } else { 255 hdwal = btc.MasterKey(pass, testnet) 256 sys.ClearBuffer(pass) 257 } 258 if aes_key != nil { 259 btc.ShaHash(hdwal.Key, aes_key) 260 } 261 hdwal.Prefix = hdwal_private_prefix() 262 if *dumpxprv { 263 fmt.Println("Root:", hdwal.String()) 264 } 265 if !hd_hardend { 266 // list root xpub... 267 hd_wallet_xtra = append(hd_wallet_xtra, "Root: "+hdwal.Pub().String()) 268 } 269 for _, x := range hdpath_x[:len(hdpath_x)-1] { 270 prvwal = hdwal 271 prvidx = x 272 hdwal = hdwal.Child(x) 273 } 274 if *dumpxprv { 275 fmt.Println("Leaf:", hdwal.String()) 276 } 277 if (hdpath_last & 0x80000000) == 0 { 278 // if non-hardend, list xpub... 279 if prvwal != nil { 280 hd_wallet_xtra = append(hd_wallet_xtra, "Prnt: "+prvwal.Pub().String()) 281 } 282 hd_wallet_xtra = append(hd_wallet_xtra, "Leaf: "+hdwal.Pub().String()) 283 } 284 } 285 286 if *encrypt != "" { 287 fmt.Println("Encryped file saved as", encrypt_file(*encrypt, aes_key)) 288 cleanExit(0) 289 } 290 291 if *decrypt != "" { 292 fmt.Println("Decryped file saved as", decrypt_file(*decrypt, aes_key)) 293 cleanExit(0) 294 } 295 296 if *verbose { 297 fmt.Println("Generating", keycnt, "keys, version", ver_pubkey(), "...") 298 } 299 300 do_it_again: 301 for i := uint32(0); i < uint32(keycnt); { 302 prv_key := make([]byte, 32) 303 if waltype == 3 { 304 btc.ShaHash(seed_key, prv_key) 305 seed_key = append(seed_key, byte(i)) 306 } else /*if waltype==4*/ { 307 // HD wallet 308 _hd := hdwal.Child(uint32(i) + hdpath_last) 309 copy(prv_key, _hd.Key[1:]) 310 sys.ClearBuffer(_hd.Key) 311 sys.ClearBuffer(_hd.ChCode) 312 } 313 314 rec := btc.NewPrivateAddr(prv_key, ver_secret(), !uncompressed) 315 316 if waltype == 3 { 317 rec.BtcAddr.Extra.Label = fmt.Sprint("TypC ", i+1) 318 } else { 319 if (hdpath_last & 0x80000000) != 0 { 320 rec.BtcAddr.Extra.Label = fmt.Sprint(hd_label_prefix, "/", i+(hdpath_last&0x7fffffff), "'") 321 } else { 322 rec.BtcAddr.Extra.Label = fmt.Sprint(hd_label_prefix, "/", i+(hdpath_last&0x7fffffff)) 323 } 324 } 325 keys = append(keys, rec) 326 i++ 327 } 328 if prvwal != nil { 329 currhdsub++ 330 if currhdsub < hdsubs { 331 sys.ClearBuffer(hdwal.ChCode) 332 sys.ClearBuffer(hdwal.Key) 333 hdwal = prvwal.Child(prvidx + uint32(currhdsub)) 334 var ii int 335 for ii = len(hd_label_prefix); ii > 0 && hd_label_prefix[ii-1] != '/'; ii-- { 336 } 337 hd_label_prefix = fmt.Sprint(hd_label_prefix[:ii], (prvidx&0x7fffffff)+uint32(currhdsub)) 338 if (prvidx & 0x80000000) != 0 { 339 hd_label_prefix = hd_label_prefix + "'" 340 } 341 goto do_it_again 342 } 343 sys.ClearBuffer(prvwal.ChCode) 344 sys.ClearBuffer(prvwal.Key) 345 prvwal = nil 346 } 347 if hdwal != nil { 348 sys.ClearBuffer(hdwal.ChCode) 349 sys.ClearBuffer(hdwal.Key) 350 hdwal = nil 351 } 352 if *verbose { 353 fmt.Println("Private keys re-generated") 354 } 355 356 // Calculate SegWit addresses 357 segwit = make([]*btc.BtcAddr, len(keys)) 358 for i, pk := range keys { 359 if len(pk.Pubkey) != 33 { 360 continue 361 } 362 if bech32_mode { 363 if taproot_mode { 364 segwit[i] = btc.NewAddrFromPkScript(append([]byte{btc.OP_1, 32}, pk.Pubkey[1:]...), testnet) 365 } else { 366 segwit[i] = btc.NewAddrFromPkScript(append([]byte{0, 20}, pk.Hash160[:]...), testnet) 367 } 368 } else { 369 h160 := btc.Rimp160AfterSha256(append([]byte{0, 20}, pk.Hash160[:]...)) 370 segwit[i] = btc.NewAddrFromHash160(h160[:], ver_script()) 371 } 372 373 if *pubkey != "" && (*pubkey == pk.BtcAddr.String() || *pubkey == segwit[i].String()) { 374 if segwit_mode { 375 fmt.Println("Public address:", segwit[i].String()) 376 } else { 377 fmt.Println("Public address:", pk.BtcAddr.String()) 378 } 379 fmt.Println("Public hexdump:", hex.EncodeToString(pk.BtcAddr.Pubkey)) 380 return 381 } 382 383 } 384 } 385 386 // dump_addrs prints all the public addresses. 387 func dump_addrs() { 388 f, _ := os.Create("wallet.txt") 389 390 fmt.Fprintln(f, "# Deterministic Walet Type", waltype) 391 for _, x := range hd_wallet_xtra { 392 fmt.Println("#", x) 393 fmt.Fprintln(f, "#", x) 394 } 395 for i := range keys { 396 if !*noverify { 397 if er := btc.VerifyKeyPair(keys[i].Key, keys[i].BtcAddr.Pubkey); er != nil { 398 println("Something wrong with key at index", i, " - abort!", er.Error()) 399 cleanExit(1) 400 } 401 } 402 var pubaddr string 403 label := keys[i].BtcAddr.Extra.Label 404 if pubkeys_mode { 405 pubaddr = hex.EncodeToString(keys[i].Pubkey) 406 } else if segwit_mode { 407 if segwit[i] == nil { 408 pubaddr = "-=CompressedKey=-" 409 } else { 410 pubaddr = segwit[i].String() 411 } 412 p2kh_adr := keys[i].BtcAddr.String() 413 if len(p2kh_adr) > 20 { 414 label += " (" + p2kh_adr + ")" 415 } else { 416 label += "-?????" 417 } 418 } else { 419 pubaddr = keys[i].BtcAddr.String() 420 } 421 fmt.Println(pubaddr, label) 422 if f != nil { 423 fmt.Fprintln(f, pubaddr, label) 424 } 425 } 426 if f != nil { 427 f.Close() 428 fmt.Println("You can find all the addresses in wallet.txt file") 429 } 430 } 431 432 func public_to_key_idx(pubkey []byte) int { 433 for i := range keys { 434 if bytes.Equal(pubkey, keys[i].BtcAddr.Pubkey) { 435 return i 436 } 437 } 438 return -1 439 } 440 441 func public_xo_to_key_idx(pubkey []byte) int { 442 for i := range keys { 443 if bytes.Equal(pubkey, keys[i].BtcAddr.Pubkey[1:33]) { 444 return i 445 } 446 } 447 return -1 448 } 449 450 func public_to_key(pubkey []byte) *btc.PrivateAddr { 451 idx := public_to_key_idx(pubkey) 452 if idx < 0 { 453 return nil 454 } 455 return keys[idx] 456 } 457 458 func public_xo_to_key(pubkey []byte) *btc.PrivateAddr { 459 idx := public_xo_to_key_idx(pubkey) 460 if idx < 0 { 461 return nil 462 } 463 return keys[idx] 464 } 465 466 func hash_to_key_idx(h160 []byte) (res int) { 467 for i := range keys { 468 if bytes.Equal(keys[i].BtcAddr.Hash160[:], h160) { 469 return i 470 } 471 if segwit[i] != nil && bytes.Equal(segwit[i].Hash160[:], h160) { 472 return i 473 } 474 } 475 return -1 476 } 477 478 func hash_to_key(h160 []byte) *btc.PrivateAddr { 479 if i := hash_to_key_idx(h160); i >= 0 { 480 return keys[i] 481 } 482 return nil 483 } 484 485 func address_to_key(addr string) *btc.PrivateAddr { 486 a, e := btc.NewAddrFromString(addr) 487 if e != nil { 488 println("Cannot Decode address", addr) 489 println(e.Error()) 490 cleanExit(1) 491 } 492 if a.SegwitProg != nil { 493 if len(a.SegwitProg.Program) == 20 { 494 return hash_to_key(a.SegwitProg.Program) 495 } 496 if len(a.SegwitProg.Program) == 32 { 497 return public_xo_to_key(a.SegwitProg.Program) 498 } 499 println("Cannot show private key for this address type", addr) 500 cleanExit(1) 501 } 502 return hash_to_key(a.Hash160[:]) 503 } 504 505 // pkscr_to_key supports only P2KH scripts. 506 func pkscr_to_key(scr []byte) *btc.PrivateAddr { 507 if len(scr) == 25 && scr[0] == 0x76 && scr[1] == 0xa9 && scr[2] == 0x14 && scr[23] == 0x88 && scr[24] == 0xac { 508 return hash_to_key(scr[3:23]) 509 } 510 // P2SH(WPKH) 511 if len(scr) == 23 && scr[0] == 0xa9 && scr[22] == 0x87 { 512 return hash_to_key(scr[2:22]) 513 } 514 // P2WPKH 515 if len(scr) == 22 && scr[0] == 0x00 && scr[1] == 0x14 { 516 return hash_to_key(scr[2:]) 517 } 518 // TAPROOT 519 if len(scr) == 34 && scr[0] == btc.OP_1 && scr[1] == 32 { 520 return public_xo_to_key(scr[2:]) 521 } 522 return nil 523 } 524 525 func dump_prvkey() { 526 if *dumppriv == "*" { 527 // Dump all private keys 528 for i := range keys { 529 fmt.Println(keys[i].String(), keys[i].BtcAddr.String(), keys[i].BtcAddr.Extra.Label) 530 } 531 } else { 532 // single key 533 k := address_to_key(*dumppriv) 534 if k != nil { 535 fmt.Println("Public address:", k.BtcAddr.String(), k.BtcAddr.Extra.Label) 536 fmt.Println("Public hexdump:", hex.EncodeToString(k.BtcAddr.Pubkey)) 537 fmt.Println("Public compressed:", k.BtcAddr.IsCompressed()) 538 fmt.Println("Private encoded:", k.String()) 539 fmt.Println("Private hexdump:", hex.EncodeToString(k.Key)) 540 } else { 541 println("Dump Private Key:", *dumppriv, "not found it the wallet") 542 } 543 } 544 }