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  }