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

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  	"math/big"
    10  	"os"
    11  
    12  	"github.com/piotrnar/gocoin/lib/btc"
    13  	"github.com/piotrnar/gocoin/lib/secp256k1"
    14  )
    15  
    16  // sign_tx prepares a signed transaction.
    17  func sign_tx(tx *btc.Tx) (all_signed bool) {
    18  	var too_big big.Int
    19  	var multisig_done bool
    20  	all_signed = true
    21  
    22  	if minsig {
    23  		too_big.SetBytes([]byte{
    24  			0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    25  			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
    26  	}
    27  
    28  	// go through each input
    29  	for in := range tx.TxIn {
    30  		if ms, _ := btc.NewMultiSigFromScript(tx.TxIn[in].ScriptSig); ms != nil {
    31  			hash := tx.SignatureHash(ms.P2SH(), in, btc.SIGHASH_ALL)
    32  			for ki := range ms.PublicKeys {
    33  				k := public_to_key(ms.PublicKeys[ki])
    34  				if k != nil {
    35  				resigna:
    36  					r, s, e := btc.EcdsaSign(k.Key, hash)
    37  					if minsig && (r.Cmp(&too_big) >= 0 || s.Cmp(&too_big) >= 0) {
    38  						//fmt.Println("re-signing A...")
    39  						goto resigna
    40  					}
    41  					if e != nil {
    42  						println("ERROR in sign_tx:", e.Error())
    43  						all_signed = false
    44  					} else {
    45  						btcsig := &btc.Signature{HashType: 0x01}
    46  						btcsig.R.Set(r)
    47  						btcsig.S.Set(s)
    48  
    49  						ms.Signatures = append(ms.Signatures, btcsig)
    50  						tx.TxIn[in].ScriptSig = ms.Bytes()
    51  						multisig_done = true
    52  					}
    53  				}
    54  			}
    55  		} else {
    56  			var uo *btc.TxOut
    57  			if in < len(tx.Spent_outputs) {
    58  				uo = tx.Spent_outputs[in]
    59  			}
    60  			if uo == nil {
    61  				println("ERROR: Unkown input:", tx.TxIn[in].Input.String(), "- missing balance folder?")
    62  				all_signed = false
    63  				continue
    64  			}
    65  			adr := addr_from_pkscr(uo.Pk_script)
    66  			if adr == nil {
    67  				fmt.Println("WARNING: Don't know how to sign input number", in)
    68  				fmt.Println(" Pk_script:", hex.EncodeToString(uo.Pk_script))
    69  				all_signed = false
    70  				continue
    71  			}
    72  
    73  			k_idx := -1
    74  
    75  			ver, segwit_prog := btc.IsWitnessProgram(uo.Pk_script)
    76  			if segwit_prog != nil {
    77  				if len(segwit_prog) == 20 && ver == 0 {
    78  					// native segwith P2WPKH output
    79  					copy(adr.Hash160[:], segwit_prog)
    80  				} else if len(segwit_prog) == 32 && ver == 1 {
    81  					// taproot payment to public key
    82  					k_idx = public_xo_to_key_idx(segwit_prog)
    83  				} else {
    84  					fmt.Println("WARNING: Unsupported SegWit Program: ", adr.String(), "at input", in)
    85  					all_signed = false
    86  					continue
    87  				}
    88  			}
    89  
    90  			if k_idx < 0 {
    91  				k_idx = hash_to_key_idx(adr.Hash160[:])
    92  				if k_idx < 0 {
    93  					fmt.Println("WARNING: You do not have key for", adr.String(), "at input", in)
    94  					all_signed = false
    95  					continue
    96  				}
    97  			}
    98  
    99  			var er error
   100  			k := keys[k_idx]
   101  			if segwit_prog != nil {
   102  				if ver == 1 {
   103  					var rseed [64]byte
   104  					h := tx.TaprootSigHash(&btc.ScriptExecutionData{}, in, btc.SIGHASH_DEFAULT, false)
   105  					copy(rseed[:32], h)
   106  					rand.Read(rseed[32:])
   107  					rand2 := btc.Sha2Sum(rseed[:])
   108  					sig := secp256k1.SchnorrSign(h, k.Key, rand2[:])
   109  					if len(sig) == 64 {
   110  						if tx.SegWit == nil {
   111  							tx.SegWit = make([][][]byte, len(tx.TxIn))
   112  						}
   113  						tx.SegWit[in] = [][]byte{sig}
   114  					} else {
   115  						er = errors.New("SchnorrSign failed")
   116  					}
   117  				} else {
   118  				resignb:
   119  					er = tx.SignWitness(in, k.BtcAddr.OutScript(), uo.Value, btc.SIGHASH_ALL, k.BtcAddr.Pubkey, k.Key)
   120  					if minsig && len(tx.SegWit[in][0]) > 71 {
   121  						//fmt.Println("re-signing B...")
   122  						goto resignb
   123  					}
   124  				}
   125  			} else if adr.String() == segwit[k_idx].String() {
   126  				tx.TxIn[in].ScriptSig = append([]byte{22, 0, 20}, k.BtcAddr.Hash160[:]...)
   127  			resignc:
   128  				er = tx.SignWitness(in, k.BtcAddr.OutScript(), uo.Value, btc.SIGHASH_ALL, k.BtcAddr.Pubkey, k.Key)
   129  				if minsig && len(tx.SegWit[in][0]) > 71 {
   130  					//fmt.Println("re-signing C...")
   131  					goto resignc
   132  				}
   133  			} else {
   134  			resignd:
   135  				er = tx.Sign(in, uo.Pk_script, btc.SIGHASH_ALL, k.BtcAddr.Pubkey, k.Key)
   136  				if minsig && len(tx.TxIn[in].ScriptSig) > 106 {
   137  					//fmt.Println("re-signing D...")
   138  					goto resignd
   139  				}
   140  			}
   141  			if er != nil {
   142  				fmt.Println("ERROR: Sign failed for input number", in, er.Error())
   143  				all_signed = false
   144  			}
   145  		}
   146  	}
   147  
   148  	// reorder signatures if we signed any multisig inputs
   149  	if multisig_done && !multisig_reorder(tx) {
   150  		all_signed = false
   151  	}
   152  
   153  	if !all_signed {
   154  		fmt.Println("WARNING: Not all the inputs have been signed")
   155  	}
   156  
   157  	return
   158  }
   159  
   160  func write_tx_file(tx *btc.Tx) {
   161  	var signedrawtx []byte
   162  	if tx.SegWit != nil {
   163  		signedrawtx = tx.SerializeNew()
   164  	} else {
   165  		signedrawtx = tx.Serialize()
   166  	}
   167  	tx.SetHash(signedrawtx)
   168  
   169  	hs := tx.Hash.String()
   170  	fmt.Println("TxID", hs)
   171  
   172  	var fn string
   173  
   174  	if txfilename == "" {
   175  		fn = hs[:8] + ".txt"
   176  	} else {
   177  		fn = txfilename
   178  	}
   179  
   180  	f, _ := os.Create(fn)
   181  	if f != nil {
   182  		f.Write([]byte(hex.EncodeToString(signedrawtx)))
   183  		f.Close()
   184  		fmt.Println("Transaction data stored in", fn)
   185  	}
   186  }
   187  
   188  // make_signed_tx prepares a signed transaction.
   189  func make_signed_tx() {
   190  	// Make an empty transaction
   191  	tx := new(btc.Tx)
   192  	tx.Version = 1
   193  	tx.Lock_time = 0
   194  
   195  	// Select as many inputs as we need to pay the full amount (with the fee)
   196  	var btcsofar uint64
   197  	for i := range unspentOuts {
   198  		if unspentOuts[i].key == nil {
   199  			continue
   200  		}
   201  		uo := getUO(&unspentOuts[i].TxPrevOut)
   202  		// add the input to our transaction:
   203  		tin := new(btc.TxIn)
   204  		tin.Input = unspentOuts[i].TxPrevOut
   205  		tin.Sequence = uint32(*sequence)
   206  		tx.TxIn = append(tx.TxIn, tin)
   207  		tx.Spent_outputs = append(tx.Spent_outputs, uo)
   208  
   209  		btcsofar += uo.Value
   210  		unspentOuts[i].spent = true
   211  		if !*useallinputs && (btcsofar >= spendBtc+feeBtc) {
   212  			break
   213  		}
   214  	}
   215  	if btcsofar < (spendBtc + feeBtc) {
   216  		fmt.Println("ERROR: You have", btc.UintToBtc(btcsofar), "BTC, but you need",
   217  			btc.UintToBtc(spendBtc+feeBtc), "BTC for the transaction")
   218  		cleanExit(1)
   219  	}
   220  	changeBtc = btcsofar - (spendBtc + feeBtc)
   221  	if *verbose {
   222  		fmt.Printf("Spending %d out of %d outputs...\n", len(tx.TxIn), len(unspentOuts))
   223  	}
   224  
   225  	// Build transaction outputs:
   226  	for o := range sendTo {
   227  		outs, er := btc.NewSpendOutputs(sendTo[o].addr, sendTo[o].amount, testnet)
   228  		if er != nil {
   229  			fmt.Println("ERROR:", er.Error())
   230  			cleanExit(1)
   231  		}
   232  		tx.TxOut = append(tx.TxOut, outs...)
   233  	}
   234  
   235  	if changeBtc > 0 {
   236  		// Add one more output (with the change)
   237  		chad := get_change_addr()
   238  		if chad == nil {
   239  			fmt.Println("ERROR: cannot determine change address")
   240  			cleanExit(1)
   241  		}
   242  		if *verbose {
   243  			fmt.Println("Sending change", changeBtc, "to", chad.String())
   244  		}
   245  		outs, er := btc.NewSpendOutputs(chad, changeBtc, testnet)
   246  		if er != nil {
   247  			fmt.Println("ERROR:", er.Error())
   248  			cleanExit(1)
   249  		}
   250  		tx.TxOut = append(tx.TxOut, outs...)
   251  	}
   252  
   253  	if *message != "" {
   254  		// Add NULL output with an arbitrary message
   255  		scr := new(bytes.Buffer)
   256  		scr.WriteByte(0x6a) // OP_RETURN
   257  		btc.WritePutLen(scr, uint32(len(*message)))
   258  		scr.Write([]byte(*message))
   259  		tx.TxOut = append(tx.TxOut, &btc.TxOut{Value: 0, Pk_script: scr.Bytes()})
   260  	}
   261  
   262  	signed := sign_tx(tx)
   263  	write_tx_file(tx)
   264  
   265  	if apply2bal && signed {
   266  		apply_to_balance(tx)
   267  	}
   268  }
   269  
   270  // process_raw_tx signs a raw transaction with all the keys we have.
   271  func process_raw_tx() {
   272  	tx := raw_tx_from_file(*rawtx)
   273  	if tx == nil {
   274  		fmt.Println("ERROR: Cannot decode the raw transaction")
   275  		return
   276  	}
   277  
   278  	tx.Spent_outputs = make([]*btc.TxOut, len(tx.TxIn))
   279  	for i := range tx.TxIn {
   280  		tx.Spent_outputs[i] = getUO(&tx.TxIn[i].Input)
   281  	}
   282  	sign_tx(tx)
   283  	write_tx_file(tx)
   284  }