github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/wallet/stuff.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/piotrnar/gocoin/lib/btc"
    12  	"github.com/piotrnar/gocoin/lib/others/ltc"
    13  	"github.com/piotrnar/gocoin/lib/others/sys"
    14  )
    15  
    16  // loadedTxs is a cache for txs from already loaded from balance/ folder.
    17  var loadedTxs map[[32]byte]*btc.Tx = make(map[[32]byte]*btc.Tx)
    18  
    19  // getline reads a line from stdin.
    20  func getline() string {
    21  	li, _, _ := bufio.NewReader(os.Stdin).ReadLine()
    22  	return string(li)
    23  }
    24  
    25  func ask_yes_no(msg string) bool {
    26  	for {
    27  		fmt.Print(msg, " (y/n) : ")
    28  		l := strings.ToLower(getline())
    29  		if l == "y" {
    30  			return true
    31  		} else if l == "n" {
    32  			return false
    33  		}
    34  	}
    35  }
    36  
    37  func getpass() []byte {
    38  	var pass [1024]byte
    39  	var n int
    40  	var e error
    41  	var f *os.File
    42  
    43  	if stdin {
    44  		if *ask4pass {
    45  			fmt.Println("ERROR: Both -p and -stdin switches are not allowed at the same time")
    46  			return nil
    47  		}
    48  		d, er := ioutil.ReadAll(os.Stdin)
    49  		if er != nil {
    50  			fmt.Println("Reading from stdin:", e.Error())
    51  			return nil
    52  		}
    53  		n = len(d)
    54  		if n <= 0 {
    55  			return nil
    56  		}
    57  		copy(pass[:n], d)
    58  		sys.ClearBuffer(d)
    59  		goto check_pass
    60  	}
    61  
    62  	if !*ask4pass {
    63  		f, e = os.Open(PassSeedFilename)
    64  		if e == nil {
    65  			n, _ = f.Read(pass[:])
    66  			f.Close()
    67  			if n <= 0 {
    68  				return nil
    69  			}
    70  			goto check_pass
    71  		}
    72  
    73  		fmt.Println("Seed file", PassSeedFilename, "not found")
    74  	}
    75  
    76  	fmt.Print("Enter your wallet's seed password: ")
    77  	n = sys.ReadPassword(pass[:])
    78  	if n <= 0 {
    79  		return nil
    80  	}
    81  
    82  	if *list {
    83  		if !*singleask {
    84  			fmt.Print("Re-enter the seed password (to be sure): ")
    85  			var pass2 [1024]byte
    86  			p2len := sys.ReadPassword(pass2[:])
    87  			if p2len != n || !bytes.Equal(pass[:n], pass2[:p2len]) {
    88  				sys.ClearBuffer(pass[:n])
    89  				sys.ClearBuffer(pass2[:p2len])
    90  				println("The two passwords you entered do not match")
    91  				return nil
    92  			}
    93  			sys.ClearBuffer(pass2[:p2len])
    94  		}
    95  		if *list {
    96  			// Maybe he wants to save the password?
    97  			if ask_yes_no("Save the password on disk, so you won't be asked for it later?") {
    98  				e = ioutil.WriteFile(PassSeedFilename, pass[:n], 0600)
    99  				if e != nil {
   100  					fmt.Println("WARNING: Could not save the password", e.Error())
   101  				} else {
   102  					fmt.Println("The seed password has been stored in", PassSeedFilename)
   103  				}
   104  			}
   105  		}
   106  	}
   107  check_pass:
   108  	for i := 0; i < n; i++ {
   109  		if pass[i] < ' ' || pass[i] > 126 {
   110  			fmt.Println("WARNING: Your secret contains non-printable characters")
   111  			break
   112  		}
   113  	}
   114  	outpass := make([]byte, n+len(secret_seed))
   115  	if len(secret_seed) > 0 {
   116  		copy(outpass, secret_seed)
   117  	}
   118  	copy(outpass[len(secret_seed):], pass[:n])
   119  	sys.ClearBuffer(pass[:n])
   120  	return outpass
   121  }
   122  
   123  // get_change_addr returns the change addrress or nil if there will be no change.
   124  func get_change_addr() (chng *btc.BtcAddr) {
   125  	if *change != "" {
   126  		var e error
   127  		chng, e = btc.NewAddrFromString(*change)
   128  		if e != nil {
   129  			println("Change address:", e.Error())
   130  			cleanExit(1)
   131  		}
   132  		assert_address_version(chng)
   133  		return
   134  	}
   135  
   136  	// If change address not specified, send it back to the first input
   137  	for idx := range unspentOuts {
   138  		uo := getUO(&unspentOuts[idx].TxPrevOut)
   139  		if k := pkscr_to_key(uo.Pk_script); k != nil {
   140  			chng = btc.NewAddrFromPkScript(uo.Pk_script, testnet)
   141  			//chng = k.BtcAddr
   142  			return
   143  		}
   144  	}
   145  
   146  	fmt.Println("ERROR: Could not determine address where to send change. Add -change switch")
   147  	cleanExit(1)
   148  	return
   149  }
   150  
   151  func raw_tx_from_file(fn string) *btc.Tx {
   152  	dat := sys.GetRawData(fn)
   153  	if dat == nil {
   154  		fmt.Println("Cannot fetch raw transaction data")
   155  		return nil
   156  	}
   157  	tx, txle := btc.NewTx(dat)
   158  	if tx != nil {
   159  		tx.SetHash(dat)
   160  		if txle != len(dat) {
   161  			fmt.Println("WARNING: Raw transaction length mismatch", txle, len(dat))
   162  		}
   163  	}
   164  	return tx
   165  }
   166  
   167  // tx_from_balance gets the tx for the given ID from the balance folder, or from cache.
   168  func tx_from_balance(txid *btc.Uint256, error_is_fatal bool) (tx *btc.Tx) {
   169  	if tx = loadedTxs[txid.Hash]; tx != nil {
   170  		return // we have it in cache already
   171  	}
   172  	fn := "balance/" + txid.String() + ".tx"
   173  	buf, er := ioutil.ReadFile(fn)
   174  	if er == nil && buf != nil {
   175  		tx, _ = btc.NewTx(buf)
   176  		if error_is_fatal && tx == nil {
   177  			println("Transaction is corrupt:", txid.String())
   178  			cleanExit(1)
   179  		}
   180  		tx.SetHash(buf)
   181  		if txid.Hash != tx.Hash.Hash {
   182  			println("Transaction ID mismatch:", txid.String(), tx.Hash.String())
   183  			cleanExit(1)
   184  		}
   185  	} else if error_is_fatal {
   186  		println("Error reading transaction file:", fn)
   187  		if er != nil {
   188  			println(er.Error())
   189  		}
   190  		cleanExit(1)
   191  	}
   192  	loadedTxs[txid.Hash] = tx // store it in the cache
   193  	return
   194  }
   195  
   196  // getUO looks for specific TxPrevOut in the balance folder.
   197  func getUO(pto *btc.TxPrevOut) *btc.TxOut {
   198  	if _, ok := loadedTxs[pto.Hash]; !ok {
   199  		loadedTxs[pto.Hash] = tx_from_balance(btc.NewUint256(pto.Hash[:]), true)
   200  	}
   201  	return loadedTxs[pto.Hash].TxOut[pto.Vout]
   202  }
   203  
   204  // ver_pubkey returns the version byte for P2KH addresses.
   205  func ver_pubkey() byte {
   206  	if litecoin {
   207  		return ltc.AddrVerPubkey(testnet)
   208  	} else {
   209  		return btc.AddrVerPubkey(testnet)
   210  	}
   211  }
   212  
   213  // ver_script returns the version byte for P2SH addresses.
   214  func ver_script() byte {
   215  	if litecoin {
   216  		return ltc.AddrVerScript(testnet)
   217  	} else {
   218  		return btc.AddrVerScript(testnet)
   219  	}
   220  }
   221  
   222  // ver_secret returns the version byte for private key addresses.
   223  func ver_secret() byte {
   224  	return ver_pubkey() + 0x80
   225  }
   226  
   227  // addr_from_pkscr gets the BtcAddr from pk_script.
   228  func addr_from_pkscr(scr []byte) *btc.BtcAddr {
   229  	if litecoin {
   230  		return ltc.NewAddrFromPkScript(scr, testnet)
   231  	} else {
   232  		return btc.NewAddrFromPkScript(scr, testnet)
   233  	}
   234  }
   235  
   236  // assert_address_version makes sure the version byte in the given address is what we expect.
   237  func assert_address_version(a *btc.BtcAddr) {
   238  	if a.SegwitProg != nil {
   239  		if a.SegwitProg.HRP != btc.GetSegwitHRP(testnet) {
   240  			println("Sending address", a.String(), "has an incorrect HRP string", a.SegwitProg.HRP)
   241  			cleanExit(1)
   242  		}
   243  	} else if a.Version != ver_pubkey() && a.Version != ver_script() {
   244  		println("Sending address", a.String(), "has an incorrect version", a.Version)
   245  		cleanExit(1)
   246  	}
   247  }