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 }