// validateHandler consumes items to validate from the internal validate channel // and returns the result of the validation on the internal result channel. It // must be run as a goroutine. func (v *txValidator) validateHandler() { out: for { select { case txVI := <-v.validateChan: // Ensure the referenced input transaction is available. //txIn := txVI.tx.MsgTx().TxIn[txVI.txInIdx] txIn := txVI.txIn txInHash := &txIn.PreviousOutpoint.Hash originTx, exists := v.txStore[*txInHash] if !exists || originTx.Err != nil || originTx.Tx == nil { err := fmt.Errorf("unable to find input "+ "transaction %v referenced from "+ "transaction %v", txInHash, txVI.tx.Sha()) v.sendResult(err) break out } originMsgTx := originTx.Tx.MsgTx() // Ensure the output index in the referenced transaction // is available. originTxIndex := txIn.PreviousOutpoint.Index if originTxIndex >= uint32(len(originMsgTx.TxOut)) { err := fmt.Errorf("out of bounds "+ "input index %d in transaction %v "+ "referenced from transaction %v", originTxIndex, txInHash, txVI.tx.Sha()) v.sendResult(err) break out } // Create a new script engine for the script pair. sigScript := txIn.SignatureScript pkScript := originMsgTx.TxOut[originTxIndex].PkScript engine, err := btcscript.NewScript(sigScript, pkScript, txVI.txInIndex, txVI.tx.MsgTx(), v.flags) if err != nil { v.sendResult(err) break out } // Execute the script pair. if err := engine.Execute(); err != nil { err := fmt.Errorf("validate of input "+ "%d failed: %v", txVI.txInIndex, err) v.sendResult(err) break out } // Validation succeeded. v.sendResult(nil) case <-v.quitChan: break out } } }
// Returns the "Author" who signed the first txin of the transaction func getAuthor(tx *btcwire.MsgTx, net *btcnet.Params) (string, error) { sigScript := tx.TxIn[0].SignatureScript dummyTx := btcwire.NewMsgTx() // Setup a script executor to parse the raw bytes of the signature script. script, err := btcscript.NewScript(sigScript, make([]byte, 0), 0, dummyTx, 0) if err != nil { return "", err } // Step twice due to <sig> <pubkey> format of pay 2pubkeyhash script.Step() script.Step() // Pull off the <pubkey> pkBytes := script.GetStack()[1] addrPubKey, err := btcutil.NewAddressPubKey(pkBytes, net) if err != nil { return "", err } return addrPubKey.EncodeAddress(), nil }
// txToPairs creates a raw transaction sending the amounts for each // address/amount pair and fee to each address and the miner. minconf // specifies the minimum number of confirmations required before an // unspent output is eligible for spending. Leftover input funds not sent // to addr or as a fee for the miner are sent to a newly generated // address. If change is needed to return funds back to an owned // address, changeUtxo will point to a unconfirmed (height = -1, zeroed // block hash) Utxo. ErrInsufficientFunds is returned if there are not // enough eligible unspent outputs to create the transaction. func (a *Account) txToPairs(pairs map[string]btcutil.Amount, minconf int) (*CreatedTx, error) { // Wallet must be unlocked to compose transaction. if a.IsLocked() { return nil, wallet.ErrWalletLocked } // Create a new transaction which will include all input scripts. msgtx := btcwire.NewMsgTx() // Calculate minimum amount needed for inputs. var amt btcutil.Amount for _, v := range pairs { // Error out if any amount is negative. if v <= 0 { return nil, ErrNonPositiveAmount } amt += v } // Add outputs to new tx. for addrStr, amt := range pairs { addr, err := btcutil.DecodeAddress(addrStr, cfg.Net()) if err != nil { return nil, fmt.Errorf("cannot decode address: %s", err) } // Add output to spend amt to addr. pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } txout := btcwire.NewTxOut(int64(amt), pkScript) msgtx.AddTxOut(txout) } // Get current block's height and hash. bs, err := GetCurBlock() if err != nil { return nil, err } // Make a copy of msgtx before any inputs are added. This will be // used as a starting point when trying a fee and starting over with // a higher fee if not enough was originally chosen. txNoInputs := msgtx.Copy() unspent, err := a.TxStore.UnspentOutputs() if err != nil { return nil, err } var selectedInputs []*txstore.Credit // These are nil/zeroed until a change address is needed, and reused // again in case a change utxo has already been chosen. var changeAddr btcutil.Address // Get the number of satoshis to increment fee by when searching for // the minimum tx fee needed. fee := btcutil.Amount(0) for { msgtx = txNoInputs.Copy() // Select unspent outputs to be used in transaction based on the amount // neededing to sent, and the current fee estimation. inputs, btcin, err := selectInputs(unspent, amt+fee, minconf) if err != nil { return nil, err } // Check if there are leftover unspent outputs, and return coins back to // a new address we own. // // TODO: change needs to be inserted into a random txout index, or else // this is a privacy risk. change := btcin - amt - fee if change > 0 { // Get a new change address if one has not already been found. if changeAddr == nil { changeAddr, err = a.ChangeAddress(&bs, cfg.KeypoolSize) if err != nil { return nil, fmt.Errorf("failed to get next address: %s", err) } // Mark change address as belonging to this account. AcctMgr.MarkAddressForAccount(changeAddr, a) } // Spend change. pkScript, err := btcscript.PayToAddrScript(changeAddr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript)) } // Selected unspent outputs become new transaction's inputs. for _, ip := range inputs { msgtx.AddTxIn(btcwire.NewTxIn(ip.OutPoint(), nil)) } for i, input := range inputs { _, addrs, _, _ := input.Addresses(cfg.Net()) if len(addrs) != 1 { continue } apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { continue // don't handle inputs to this yes } ai, err := a.Address(apkh) if err != nil { return nil, fmt.Errorf("cannot get address info: %v", err) } pka := ai.(wallet.PubKeyAddress) privkey, err := pka.PrivKey() if err == wallet.ErrWalletLocked { return nil, wallet.ErrWalletLocked } else if err != nil { return nil, fmt.Errorf("cannot get address key: %v", err) } sigscript, err := btcscript.SignatureScript(msgtx, i, input.TxOut().PkScript, btcscript.SigHashAll, privkey, ai.Compressed()) if err != nil { return nil, fmt.Errorf("cannot create sigscript: %s", err) } msgtx.TxIn[i].SignatureScript = sigscript } noFeeAllowed := false if !cfg.DisallowFree { noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize()) } if minFee := minimumFee(msgtx, noFeeAllowed); fee < minFee { fee = minFee } else { selectedInputs = inputs break } } // Validate msgtx before returning the raw transaction. flags := btcscript.ScriptCanonicalSignatures bip16 := time.Now().After(btcscript.Bip16Activation) if bip16 { flags |= btcscript.ScriptBip16 } for i, txin := range msgtx.TxIn { engine, err := btcscript.NewScript(txin.SignatureScript, selectedInputs[i].TxOut().PkScript, i, msgtx, flags) if err != nil { return nil, fmt.Errorf("cannot create script engine: %s", err) } if err = engine.Execute(); err != nil { return nil, fmt.Errorf("cannot validate transaction: %s", err) } } buf := bytes.NewBuffer(nil) buf.Grow(msgtx.SerializeSize()) msgtx.BtcEncode(buf, btcwire.ProtocolVersion) info := &CreatedTx{ tx: btcutil.NewTx(msgtx), inputs: selectedInputs, changeAddr: changeAddr, } return info, nil }
// validateHandler consumes items to validate from the internal validate channel // and returns the result of the validation on the internal result channel. It // must be run as a goroutine. func (v *txValidator) validateHandler() { out: for { select { case txVI := <-v.validateChan: // Ensure the referenced input transaction is available. txIn := txVI.txIn originTxHash := &txIn.PreviousOutPoint.Hash originTx, exists := v.txStore[*originTxHash] if !exists || originTx.Err != nil || originTx.Tx == nil { str := fmt.Sprintf("unable to find input "+ "transaction %v referenced from "+ "transaction %v", originTxHash, txVI.tx.Sha()) err := ruleError(ErrMissingTx, str) v.sendResult(err) break out } originMsgTx := originTx.Tx.MsgTx() // Ensure the output index in the referenced transaction // is available. originTxIndex := txIn.PreviousOutPoint.Index if originTxIndex >= uint32(len(originMsgTx.TxOut)) { str := fmt.Sprintf("out of bounds "+ "input index %d in transaction %v "+ "referenced from transaction %v", originTxIndex, originTxHash, txVI.tx.Sha()) err := ruleError(ErrBadTxInput, str) v.sendResult(err) break out } // Create a new script engine for the script pair. sigScript := txIn.SignatureScript pkScript := originMsgTx.TxOut[originTxIndex].PkScript engine, err := btcscript.NewScript(sigScript, pkScript, txVI.txInIndex, txVI.tx.MsgTx(), v.flags) if err != nil { str := fmt.Sprintf("failed to parse input "+ "%s:%d which references output %s:%d - "+ "%v (input script bytes %x, prev output "+ "script bytes %x)", txVI.tx.Sha(), txVI.txInIndex, originTxHash, originTxIndex, err, sigScript, pkScript) err := ruleError(ErrScriptMalformed, str) v.sendResult(err) break out } // Execute the script pair. if err := engine.Execute(); err != nil { str := fmt.Sprintf("failed to validate input "+ "%s:%d which references output %s:%d - "+ "%v (input script bytes %x, prev output "+ "script bytes %x)", txVI.tx.Sha(), txVI.txInIndex, originTxHash, originTxIndex, err, sigScript, pkScript) err := ruleError(ErrScriptValidation, str) v.sendResult(err) break out } // Validation succeeded. v.sendResult(nil) case <-v.quitChan: break out } } }
// txToPairs creates a raw transaction sending the amounts for each // address/amount pair and fee to each address and the miner. minconf // specifies the minimum number of confirmations required before an // unspent output is eligible for spending. Leftover input funds not sent // to addr or as a fee for the miner are sent to a newly generated // address. If change is needed to return funds back to an owned // address, changeUtxo will point to a unconfirmed (height = -1, zeroed // block hash) Utxo. ErrInsufficientFunds is returned if there are not // enough eligible unspent outputs to create the transaction. func (a *Account) txToPairs(pairs map[string]btcutil.Amount, minconf int) (*CreatedTx, error) { // Wallet must be unlocked to compose transaction. if a.IsLocked() { return nil, wallet.ErrWalletLocked } // Create a new transaction which will include all input scripts. msgtx := btcwire.NewMsgTx() // Calculate minimum amount needed for inputs. var amt btcutil.Amount for _, v := range pairs { // Error out if any amount is negative. if v <= 0 { return nil, ErrNonPositiveAmount } amt += v } // Add outputs to new tx. for addrStr, amt := range pairs { addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) if err != nil { return nil, fmt.Errorf("cannot decode address: %s", err) } // Add output to spend amt to addr. pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } txout := btcwire.NewTxOut(int64(amt), pkScript) msgtx.AddTxOut(txout) } // Get current block's height and hash. bs, err := GetCurBlock() if err != nil { return nil, err } // Make a copy of msgtx before any inputs are added. This will be // used as a starting point when trying a fee and starting over with // a higher fee if not enough was originally chosen. txNoInputs := msgtx.Copy() unspent, err := a.TxStore.UnspentOutputs() if err != nil { return nil, err } // Filter out unspendable outputs, that is, remove those that (at this // time) are not P2PKH outputs. Other inputs must be manually included // in transactions and sent (for example, using createrawtransaction, // signrawtransaction, and sendrawtransaction). eligible := make([]txstore.Credit, 0, len(unspent)) for i := range unspent { switch btcscript.GetScriptClass(unspent[i].TxOut().PkScript) { case btcscript.PubKeyHashTy: if !unspent[i].Confirmed(minconf, bs.Height) { continue } // Coinbase transactions must have have reached maturity // before their outputs may be spent. if unspent[i].IsCoinbase() { target := btcchain.CoinbaseMaturity if !unspent[i].Confirmed(target, bs.Height) { continue } } // Locked unspent outputs are skipped. if a.LockedOutpoint(*unspent[i].OutPoint()) { continue } eligible = append(eligible, unspent[i]) } } // Sort eligible inputs, as selectInputs expects these to be sorted // by amount in reverse order. sort.Sort(sort.Reverse(ByAmount(eligible))) var selectedInputs []txstore.Credit // changeAddr is nil/zeroed until a change address is needed, and reused // again in case a change utxo has already been chosen. var changeAddr btcutil.Address // Get the number of satoshis to increment fee by when searching for // the minimum tx fee needed. fee := btcutil.Amount(0) for { msgtx = txNoInputs.Copy() // Select eligible outputs to be used in transaction based on the amount // neededing to sent, and the current fee estimation. inputs, btcin, err := selectInputs(eligible, amt, fee, minconf) if err != nil { return nil, err } // Check if there are leftover unspent outputs, and return coins back to // a new address we own. change := btcin - amt - fee if change > 0 { // Get a new change address if one has not already been found. if changeAddr == nil { changeAddr, err = a.ChangeAddress(&bs, cfg.KeypoolSize) if err != nil { return nil, fmt.Errorf("failed to get next address: %s", err) } // Mark change address as belonging to this account. AcctMgr.MarkAddressForAccount(changeAddr, a) } // Spend change. pkScript, err := btcscript.PayToAddrScript(changeAddr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript)) // Randomize index of the change output. rng := badrand.New(badrand.NewSource(time.Now().UnixNano())) r := rng.Int31n(int32(len(msgtx.TxOut))) // random index c := len(msgtx.TxOut) - 1 // change index msgtx.TxOut[r], msgtx.TxOut[c] = msgtx.TxOut[c], msgtx.TxOut[r] } // Selected unspent outputs become new transaction's inputs. for _, ip := range inputs { msgtx.AddTxIn(btcwire.NewTxIn(ip.OutPoint(), nil)) } for i, input := range inputs { // Errors don't matter here, as we only consider the // case where len(addrs) == 1. _, addrs, _, _ := input.Addresses(activeNet.Params) if len(addrs) != 1 { continue } apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { continue // don't handle inputs to this yes } ai, err := a.Address(apkh) if err != nil { return nil, fmt.Errorf("cannot get address info: %v", err) } pka := ai.(wallet.PubKeyAddress) privkey, err := pka.PrivKey() if err == wallet.ErrWalletLocked { return nil, wallet.ErrWalletLocked } else if err != nil { return nil, fmt.Errorf("cannot get address key: %v", err) } sigscript, err := btcscript.SignatureScript(msgtx, i, input.TxOut().PkScript, btcscript.SigHashAll, privkey, ai.Compressed()) if err != nil { return nil, fmt.Errorf("cannot create sigscript: %s", err) } msgtx.TxIn[i].SignatureScript = sigscript } noFeeAllowed := false if !cfg.DisallowFree { noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize()) } if minFee := minimumFee(msgtx, noFeeAllowed); fee < minFee { fee = minFee } else { selectedInputs = inputs break } } // Validate msgtx before returning the raw transaction. flags := btcscript.ScriptCanonicalSignatures bip16 := time.Now().After(btcscript.Bip16Activation) if bip16 { flags |= btcscript.ScriptBip16 } for i, txin := range msgtx.TxIn { engine, err := btcscript.NewScript(txin.SignatureScript, selectedInputs[i].TxOut().PkScript, i, msgtx, flags) if err != nil { return nil, fmt.Errorf("cannot create script engine: %s", err) } if err = engine.Execute(); err != nil { return nil, fmt.Errorf("cannot validate transaction: %s", err) } } buf := bytes.Buffer{} buf.Grow(msgtx.SerializeSize()) if err := msgtx.BtcEncode(&buf, btcwire.ProtocolVersion); err != nil { // Hitting OOM by growing or writing to a bytes.Buffer already // panics, and all returned errors are unexpected. panic(err) } info := &CreatedTx{ tx: btcutil.NewTx(msgtx), inputs: selectedInputs, changeAddr: changeAddr, } return info, nil }
// txToPairs creates a raw transaction sending the amounts for each // address/amount pair and fee to each address and the miner. minconf // specifies the minimum number of confirmations required before an // unspent output is eligible for spending. Leftover input funds not sent // to addr or as a fee for the miner are sent to a newly generated // address. If change is needed to return funds back to an owned // address, changeUtxo will point to a unconfirmed (height = -1, zeroed // block hash) Utxo. ErrInsufficientFunds is returned if there are not // enough eligible unspent outputs to create the transaction. func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, error) { // Create a new transaction which will include all input scripts. msgtx := btcwire.NewMsgTx() // Calculate minimum amount needed for inputs. var amt int64 for _, v := range pairs { // Error out if any amount is negative. if v <= 0 { return nil, ErrNonPositiveAmount } amt += v } // outputs is a tx.Pair slice representing each output that is created // by the transaction. outputs := make([]tx.Pair, 0, len(pairs)+1) // Add outputs to new tx. for addrStr, amt := range pairs { addr, err := btcutil.DecodeAddr(addrStr) if err != nil { return nil, fmt.Errorf("cannot decode address: %s", err) } // Add output to spend amt to addr. pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } txout := btcwire.NewTxOut(int64(amt), pkScript) msgtx.AddTxOut(txout) // Create amount, address pair and add to outputs. out := tx.Pair{ Amount: amt, PubkeyHash: addr.ScriptAddress(), } outputs = append(outputs, out) } // Get current block's height and hash. bs, err := GetCurBlock() if err != nil { return nil, err } // Make a copy of msgtx before any inputs are added. This will be // used as a starting point when trying a fee and starting over with // a higher fee if not enough was originally chosen. txNoInputs := msgtx.Copy() // These are nil/zeroed until a change address is needed, and reused // again in case a change utxo has already been chosen. var changeAddr *btcutil.AddressPubKeyHash var btcspent int64 var selectedInputs []*tx.Utxo var finalChangeUtxo *tx.Utxo // Get the number of satoshis to increment fee by when searching for // the minimum tx fee needed. fee := int64(0) for { msgtx = txNoInputs.Copy() // Select unspent outputs to be used in transaction based on the amount // neededing to sent, and the current fee estimation. inputs, btcin, err := selectInputs(a.UtxoStore, uint64(amt+fee), minconf) if err != nil { return nil, err } // Check if there are leftover unspent outputs, and return coins back to // a new address we own. var changeUtxo *tx.Utxo change := btcin - uint64(amt+fee) if change > 0 { // Create a new address to spend leftover outputs to. // Get a new change address if one has not already been found. if changeAddr == nil { changeAddr, err = a.ChangeAddress(&bs, cfg.KeypoolSize) if err != nil { return nil, fmt.Errorf("failed to get next address: %s", err) } // Mark change address as belonging to this account. MarkAddressForAccount(changeAddr.EncodeAddress(), a.Name()) } // Spend change. pkScript, err := btcscript.PayToAddrScript(changeAddr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript)) changeUtxo = &tx.Utxo{ Amt: change, Out: tx.OutPoint{ // Hash is unset (zeroed) here and must be filled in // with the transaction hash of the complete // transaction. Index: uint32(len(pairs)), }, Height: -1, Subscript: pkScript, } copy(changeUtxo.AddrHash[:], changeAddr.ScriptAddress()) } // Selected unspent outputs become new transaction's inputs. for _, ip := range inputs { msgtx.AddTxIn(btcwire.NewTxIn((*btcwire.OutPoint)(&ip.Out), nil)) } for i, ip := range inputs { // Error is ignored as the length and network checks can never fail // for these inputs. addr, _ := btcutil.NewAddressPubKeyHash(ip.AddrHash[:], a.Wallet.Net()) privkey, err := a.AddressKey(addr) if err == wallet.ErrWalletLocked { return nil, wallet.ErrWalletLocked } else if err != nil { return nil, fmt.Errorf("cannot get address key: %v", err) } ai, err := a.AddressInfo(addr) if err != nil { return nil, fmt.Errorf("cannot get address info: %v", err) } sigscript, err := btcscript.SignatureScript(msgtx, i, ip.Subscript, btcscript.SigHashAll, privkey, ai.Compressed) if err != nil { return nil, fmt.Errorf("cannot create sigscript: %s", err) } msgtx.TxIn[i].SignatureScript = sigscript } noFeeAllowed := false if !cfg.DisallowFree { noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize()) } if minFee := minimumFee(msgtx, noFeeAllowed); fee < minFee { fee = minFee } else { // Fill Tx hash of change outpoint with transaction hash. if changeUtxo != nil { txHash, err := msgtx.TxSha() if err != nil { return nil, fmt.Errorf("cannot create transaction hash: %s", err) } copy(changeUtxo.Out.Hash[:], txHash[:]) // Add change to outputs. out := tx.Pair{ Amount: int64(change), PubkeyHash: changeAddr.ScriptAddress(), Change: true, } outputs = append(outputs, out) finalChangeUtxo = changeUtxo } selectedInputs = inputs btcspent = int64(btcin) break } } // Validate msgtx before returning the raw transaction. flags := btcscript.ScriptCanonicalSignatures bip16 := time.Now().After(btcscript.Bip16Activation) if bip16 { flags |= btcscript.ScriptBip16 } for i, txin := range msgtx.TxIn { engine, err := btcscript.NewScript(txin.SignatureScript, selectedInputs[i].Subscript, i, msgtx, flags) if err != nil { return nil, fmt.Errorf("cannot create script engine: %s", err) } if err = engine.Execute(); err != nil { return nil, fmt.Errorf("cannot validate transaction: %s", err) } } txid, err := msgtx.TxSha() if err != nil { return nil, fmt.Errorf("cannot create txid for created tx: %v", err) } buf := new(bytes.Buffer) msgtx.BtcEncode(buf, btcwire.ProtocolVersion) info := &CreatedTx{ rawTx: buf.Bytes(), txid: txid, time: time.Now(), inputs: selectedInputs, outputs: outputs, btcspent: btcspent, fee: fee, changeAddr: changeAddr, changeUtxo: finalChangeUtxo, } return info, nil }
func processPubKeyHash(db btcdb.Db, rd *rData) error { sigScript := rd.txIn.SignatureScript pkScript := rd.txPrevOut.PkScript script, err := btcscript.NewScript(sigScript, pkScript, rd.txInIndex, rd.tx.MsgTx(), 0) if err != nil { return fmt.Errorf("failed btcscript.NewScript - h %v: %v\n", rd.in.H, err) } for script.Next() != btcscript.OP_CHECKSIG { _, err := script.Step() if err != nil { return fmt.Errorf("Failed Step - in %v: %v\n", rd.in, err) } } data := script.GetStack() rd.sigStr = data[0] rd.pkStr = data[1] aPubKey, err := btcutil.NewAddressPubKey(rd.pkStr, &btcnet.MainNetParams) if err != nil { return fmt.Errorf("Pubkey parse error: %v", err) } rd.address = aPubKey.EncodeAddress() rd.compressed = aPubKey.Format() == btcutil.PKFCompressed // From github.com/conformal/btcscript/opcode.go // Signature actually needs needs to be longer than this, but we need // at least 1 byte for the below. btcec will check full length upon // parsing the signature. if len(rd.sigStr) < 1 { return fmt.Errorf("OP_CHECKSIG ERROR") } // Trim off hashtype from the signature string. hashType := rd.sigStr[len(rd.sigStr)-1] sigStr := rd.sigStr[:len(rd.sigStr)-1] // Get script from the last OP_CODESEPARATOR and without any subsequent // OP_CODESEPARATORs subScript := script.SubScript() // Unlikely to hit any cases here, but remove the signature from // the script if present. subScript = btcscript.RemoveOpcodeByData(subScript, sigStr) hash := btcscript.CalcScriptHash(subScript, hashType, rd.tx.MsgTx(), rd.txInIndex) pubKey, err := btcec.ParsePubKey(rd.pkStr, btcec.S256()) if err != nil { return fmt.Errorf("OP_CHECKSIG ERROR") } signature, err := btcec.ParseSignature(sigStr, btcec.S256()) if err != nil { return fmt.Errorf("OP_CHECKSIG ERROR") } // log.Printf("op_checksig\n"+ // "pubKey:\n%v"+ // "pubKey.X: %v\n"+ // "pubKey.Y: %v\n"+ // "signature.R: %v\n"+ // "signature.S: %v\n"+ // "checkScriptHash:\n%v", // hex.Dump(pkStr), pubKey.X, pubKey.Y, // signature.R, signature.S, hex.Dump(hash)) if ok := ecdsa.Verify(pubKey.ToECDSA(), hash, signature.R, signature.S); !ok { return fmt.Errorf("OP_CHECKSIG FAIL") } rd.signature = signature rd.pubKey = pubKey rd.hash = hash return nil }