// addHTLC... // NOTE: This MUST be called with stateMtx held. func (lc *LightningChannel) addHTLC(ourCommitTx, theirCommitTx *wire.MsgTx, paymentDesc *PaymentDescriptor) error { // If the HTLC is going to us, then we're the sender, otherwise they // are. var senderKey, receiverKey *btcec.PublicKey var senderRevocation, receiverRevocation []byte if paymentDesc.PayToUs { receiverKey = lc.channelState.OurCommitKey.PubKey() receiverRevocation = paymentDesc.OurRevocation[:] senderKey = lc.channelState.TheirCommitKey senderRevocation = paymentDesc.TheirRevocation[:] } else { senderKey = lc.channelState.OurCommitKey.PubKey() senderRevocation = paymentDesc.OurRevocation[:] receiverKey = lc.channelState.TheirCommitKey receiverRevocation = paymentDesc.TheirRevocation[:] } // Generate the proper redeem scripts for the HTLC output for both the // sender and the receiver. timeout := paymentDesc.Timeout rHash := paymentDesc.RHash delay := lc.channelState.CsvDelay senderPKScript, err := senderHTLCScript(timeout, delay, senderKey, receiverKey, senderRevocation[:], rHash[:]) if err != nil { return nil } receiverPKScript, err := receiverHTLCScript(timeout, delay, senderKey, receiverKey, receiverRevocation[:], rHash[:]) if err != nil { return nil } // Now that we have the redeem scripts, create the P2SH public key // script for each. senderP2SH, err := scriptHashPkScript(senderPKScript) if err != nil { return nil } receiverP2SH, err := scriptHashPkScript(receiverPKScript) if err != nil { return nil } // Add the new HTLC outputs to the respective commitment transactions. amountPending := int64(paymentDesc.Value) if paymentDesc.PayToUs { ourCommitTx.AddTxOut(wire.NewTxOut(amountPending, receiverP2SH)) theirCommitTx.AddTxOut(wire.NewTxOut(amountPending, senderP2SH)) } else { ourCommitTx.AddTxOut(wire.NewTxOut(amountPending, senderP2SH)) theirCommitTx.AddTxOut(wire.NewTxOut(amountPending, receiverP2SH)) } return nil }
// getFundingParams pulls the relevant transaction information from the json returned by blockchain.info // To generate a new valid transaction all of the parameters of the TxOut we are // spending from must be used. func getFundingParams(rawtx *blockChainInfoTx, vout uint32) (*wire.TxOut, *wire.OutPoint) { blkChnTxOut := rawtx.Outputs[vout] hash, err := wire.NewShaHashFromStr(rawtx.Hash) if err != nil { log.Fatal(err) } // Then convert it to a btcutil amount amnt := btcutil.Amount(int64(blkChnTxOut.Value)) if err != nil { log.Fatal(err) } outpoint := wire.NewOutPoint(hash, vout) subscript, err := hex.DecodeString(blkChnTxOut.ScriptHex) if err != nil { log.Fatal(err) } oldTxOut := wire.NewTxOut(int64(amnt), subscript) return oldTxOut, outpoint }
func p2pkhOutputs(amounts ...btcutil.Amount) []*wire.TxOut { v := make([]*wire.TxOut, 0, len(amounts)) for _, a := range amounts { outScript := make([]byte, txsizes.P2PKHOutputSize) v = append(v, wire.NewTxOut(int64(a), outScript)) } return v }
// createTxOut generates a TxOut that can be added to a transaction. func createTxOut(outCoins uint64, addr btcutil.Address) *wire.TxOut { // Take the address and generate a PubKeyScript out of it script, err := txscript.PayToAddrScript(addr) if err != nil { log.Fatal(err) } txout := wire.NewTxOut(int64(outCoins), script) return txout }
// NewUnsignedTransaction creates an unsigned transaction paying to one or more // non-change outputs. An appropriate transaction fee is included based on the // transaction size. // // Transaction inputs are chosen from repeated calls to fetchInputs with // increasing targets amounts. // // If any remaining output value can be returned to the wallet via a change // output without violating mempool dust rules, a P2PKH change output is // appended to the transaction outputs. Since the change output may not be // necessary, fetchChange is called zero or one times to generate this script. // This function must return a P2PKH script or smaller, otherwise fee estimation // will be incorrect. // // If successful, the transaction, total input value spent, and all previous // output scripts are returned. If the input source was unable to provide // enough input value to pay for every output any any necessary fees, an // InputSourceError is returned. // // BUGS: Fee estimation may be off when redeeming non-compressed P2PKH outputs. func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { targetAmount := h.SumOutputValues(outputs) estimatedSize := txsizes.EstimateSerializeSize(1, outputs, true) targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize) for { inputAmount, inputs, scripts, err := fetchInputs(targetAmount + targetFee) if err != nil { return nil, err } if inputAmount < targetAmount+targetFee { return nil, insufficientFundsError{} } maxSignedSize := txsizes.EstimateSerializeSize(len(inputs), outputs, true) maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize) remainingAmount := inputAmount - targetAmount if remainingAmount < maxRequiredFee { targetFee = maxRequiredFee continue } unsignedTransaction := &wire.MsgTx{ Version: wire.TxVersion, TxIn: inputs, TxOut: outputs, LockTime: 0, } changeIndex := -1 changeAmount := inputAmount - targetAmount - maxRequiredFee if changeAmount != 0 && !txrules.IsDustAmount(changeAmount, txsizes.P2PKHPkScriptSize, relayFeePerKb) { changeScript, err := fetchChange() if err != nil { return nil, err } if len(changeScript) > txsizes.P2PKHPkScriptSize { return nil, errors.New("fee estimation requires change " + "scripts no larger than P2PKH output scripts") } change := wire.NewTxOut(int64(changeAmount), changeScript) l := len(outputs) unsignedTransaction.TxOut = append(outputs[:l:l], change) changeIndex = l } return &AuthoredTx{ Tx: unsignedTransaction, PrevScripts: scripts, TotalInput: inputAmount, ChangeIndex: changeIndex, }, nil } }
// createTxOut generates a TxOut that can be added to a transaction. func createTxOut(inCoin int64, addr btcutil.Address) *wire.TxOut { // Pay the minimum network fee so that nodes will broadcast the tx. outCoin := inCoin - TX_FEE // Take the address and generate a PubKeyScript out of it script, err := txscript.PayToAddrScript(addr) if err != nil { log.Fatal(err) } txout := wire.NewTxOut(outCoin, script) return txout }
func TestStoreTransactionsWithChangeOutput(t *testing.T) { tearDown, pool, store := TstCreatePoolAndTxStore(t) defer tearDown() wtx := createWithdrawalTxWithStoreCredits(t, store, pool, []int64{5e6}, []int64{1e6, 1e6}) wtx.changeOutput = wire.NewTxOut(int64(3e6), []byte{}) msgtx := wtx.toMsgTx() tx := &changeAwareTx{MsgTx: msgtx, changeIdx: int32(len(msgtx.TxOut) - 1)} if err := storeTransactions(store, []*changeAwareTx{tx}); err != nil { t.Fatal(err) } sha := msgtx.TxSha() txDetails, err := store.TxDetails(&sha) if err != nil { t.Fatal(err) } if txDetails == nil { t.Fatal("The new tx doesn't seem to have been stored") } storedTx := txDetails.TxRecord.MsgTx outputTotal := int64(0) for i, txOut := range storedTx.TxOut { if int32(i) != tx.changeIdx { outputTotal += txOut.Value } } if outputTotal != int64(2e6) { t.Fatalf("Unexpected output amount; got %v, want %v", outputTotal, int64(2e6)) } inputTotal := btcutil.Amount(0) for _, debit := range txDetails.Debits { inputTotal += debit.Amount } if inputTotal != btcutil.Amount(5e6) { t.Fatalf("Unexpected input amount; got %v, want %v", inputTotal, btcutil.Amount(5e6)) } credits, err := store.UnspentOutputs() if err != nil { t.Fatal(err) } if len(credits) != 1 { t.Fatalf("Unexpected number of credits in txstore; got %d, want 1", len(credits)) } changeOutpoint := wire.OutPoint{Hash: sha, Index: uint32(tx.changeIdx)} if credits[0].OutPoint != changeOutpoint { t.Fatalf("Credit's outpoint (%v) doesn't match the one from change output (%v)", credits[0].OutPoint, changeOutpoint) } }
// addChange adds a change output if there are any satoshis left after paying // all the outputs and network fees. It returns true if a change output was // added. // // This method must be called only once, and no extra inputs/outputs should be // added after it's called. Also, callsites must make sure adding a change // output won't cause the tx to exceed the size limit. func (tx *withdrawalTx) addChange(pkScript []byte) bool { tx.fee = tx.calculateFee() change := tx.inputTotal() - tx.outputTotal() - tx.fee log.Debugf("addChange: input total %v, output total %v, fee %v", tx.inputTotal(), tx.outputTotal(), tx.fee) if change > 0 { tx.changeOutput = wire.NewTxOut(int64(change), pkScript) log.Debugf("Added change output with amount %v", change) } return tx.hasChange() }
// createSpendTx generates a basic spending transaction given the passed // signature and public key scripts. func createSpendingTx(sigScript, pkScript []byte) *wire.MsgTx { coinbaseTx := wire.NewMsgTx() outPoint := wire.NewOutPoint(&wire.ShaHash{}, ^uint32(0)) txIn := wire.NewTxIn(outPoint, []byte{OP_0, OP_0}) txOut := wire.NewTxOut(0, pkScript) coinbaseTx.AddTxIn(txIn) coinbaseTx.AddTxOut(txOut) spendingTx := wire.NewMsgTx() coinbaseTxSha := coinbaseTx.TxSha() outPoint = wire.NewOutPoint(&coinbaseTxSha, 0) txIn = wire.NewTxIn(outPoint, sigScript) txOut = wire.NewTxOut(0, nil) spendingTx.AddTxIn(txIn) spendingTx.AddTxOut(txOut) return spendingTx }
// createSpendTx generates a basic spending transaction given the passed // signature and public key scripts. func createSpendingTx(sigScript, pkScript []byte) *wire.MsgTx { coinbaseTx := wire.NewMsgTx(wire.TxVersion) outPoint := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0)) txIn := wire.NewTxIn(outPoint, []byte{OP_0, OP_0}) txOut := wire.NewTxOut(0, pkScript) coinbaseTx.AddTxIn(txIn) coinbaseTx.AddTxOut(txOut) spendingTx := wire.NewMsgTx(wire.TxVersion) coinbaseTxHash := coinbaseTx.TxHash() outPoint = wire.NewOutPoint(&coinbaseTxHash, 0) txIn = wire.NewTxIn(outPoint, sigScript) txOut = wire.NewTxOut(0, nil) spendingTx.AddTxIn(txIn) spendingTx.AddTxOut(txOut) return spendingTx }
func TestWithdrawalTxOutputTotal(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() tx := createWithdrawalTx(t, pool, []int64{}, []int64{4}) tx.changeOutput = wire.NewTxOut(int64(1), []byte{}) if tx.outputTotal() != btcutil.Amount(4) { t.Fatalf("Wrong total output; got %v, want %v", tx.outputTotal(), btcutil.Amount(4)) } }
func TestWithdrawalTxToMsgTxWithInputButNoOutputsWithChange(t *testing.T) { tearDown, pool, _ := TstCreatePoolAndTxStore(t) defer tearDown() tx := createWithdrawalTx(t, pool, []int64{1}, []int64{}) tx.changeOutput = wire.NewTxOut(int64(1), []byte{}) msgtx := tx.toMsgTx() compareMsgTxAndWithdrawalTxOutputs(t, msgtx, tx) compareMsgTxAndWithdrawalTxInputs(t, msgtx, tx) }
// createCommitTx... // TODO(roasbeef): fix inconsistency of 32 vs 20 byte revocation hashes everywhere... func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey, revokeHash []byte, csvTimeout uint32, amountToSelf, amountToThem btcutil.Amount) (*wire.MsgTx, error) { // First, we create the script for the delayed "pay-to-self" output. ourRedeemScript, err := commitScriptToSelf(csvTimeout, selfKey, theirKey, revokeHash) if err != nil { return nil, err } payToUsScriptHash, err := scriptHashPkScript(ourRedeemScript) if err != nil { return nil, err } // Next, we create the script paying to them. This is just a regular // P2PKH-like output, without any added CSV delay. However, we instead // use P2SH. theirRedeemScript, err := commitScriptUnencumbered(theirKey) if err != nil { return nil, err } payToThemScriptHash, err := scriptHashPkScript(theirRedeemScript) if err != nil { return nil, err } // Now that both output scripts have been created, we can finally create // the transaction itself. commitTx := wire.NewMsgTx() commitTx.AddTxIn(fundingOutput) // TODO(roasbeef): we default to blocks, make configurable as part of // channel reservation. commitTx.TxIn[0].Sequence = lockTimeToSequence(false, csvTimeout) commitTx.AddTxOut(wire.NewTxOut(int64(amountToSelf), payToUsScriptHash)) commitTx.AddTxOut(wire.NewTxOut(int64(amountToThem), payToThemScriptHash)) return commitTx, nil }
// createCommitTx... // TODO(roasbeef): fix inconsistency of 32 vs 20 byte revocation hashes everywhere... func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey, revokeHash []byte, csvTimeout uint32, amountToSelf, amountToThem btcutil.Amount) (*wire.MsgTx, error) { // First, we create the script for the delayed "pay-to-self" output. ourRedeemScript, err := commitScriptToSelf(csvTimeout, selfKey, theirKey, revokeHash) if err != nil { return nil, err } payToUsScriptHash, err := scriptHashPkScript(ourRedeemScript) if err != nil { return nil, err } // Next, we create the script paying to them. This is just a regular // P2PKH-like output, without any added CSV delay. However, we instead // use P2SH. theirRedeemScript, err := commitScriptUnencumbered(theirKey) if err != nil { return nil, err } payToThemScriptHash, err := scriptHashPkScript(theirRedeemScript) if err != nil { return nil, err } // Now that both output scripts have been created, we can finally create // the transaction itself. We use a transaction version of 2 since CSV // will fail unless the tx version is >= 2. commitTx := wire.NewMsgTx() commitTx.Version = 2 commitTx.AddTxIn(fundingOutput) commitTx.AddTxOut(wire.NewTxOut(int64(amountToSelf), payToUsScriptHash)) commitTx.AddTxOut(wire.NewTxOut(int64(amountToThem), payToThemScriptHash)) return commitTx, nil }
// addChange adds a new output with the given amount and address, and // randomizes the index (and returns it) of the newly added output. func addChange(msgtx *wire.MsgTx, change btcutil.Amount, changeAddr btcutil.Address) (int, error) { pkScript, err := txscript.PayToAddrScript(changeAddr) if err != nil { return 0, fmt.Errorf("cannot create txout script: %s", err) } msgtx.AddTxOut(wire.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] return int(r), nil }
// toMsgTx generates a btcwire.MsgTx with this tx's inputs and outputs. func (tx *withdrawalTx) toMsgTx() *wire.MsgTx { msgtx := wire.NewMsgTx(wire.TxVersion) for _, o := range tx.outputs { msgtx.AddTxOut(wire.NewTxOut(int64(o.amount), o.pkScript())) } if tx.hasChange() { msgtx.AddTxOut(tx.changeOutput) } for _, i := range tx.inputs { msgtx.AddTxIn(wire.NewTxIn(&i.OutPoint, []byte{})) } return msgtx }
// fundMultiSigOut create the redeemScript for the funding transaction, and // also a TxOut paying to the p2sh of the multi-sig redeemScript. Give it the // two pubkeys and it'll give you the p2sh'd txout. You don't have to remember // the p2sh preimage, as long as you remember the pubkeys involved. func fundMultiSigOut(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, error) { if amt < 0 { return nil, nil, fmt.Errorf("can't create FundTx script with " + "negative coins") } // p2shify redeemScript, err := genFundingPkScript(aPub, bPub) if err != nil { return nil, nil, err } pkScript, err := scriptHashPkScript(redeemScript) if err != nil { return nil, nil, err } return redeemScript, wire.NewTxOut(amt, pkScript), nil }
// createTxOut generates a TxOut that can be added to a transaction. func createTxOut(inCoin int64, addr btcutil.Address) *wire.TxOut { // Pay the minimum network fee so that nodes will broadcast the tx. outCoin := *amount if outCoin == 0 { log.Fatal("You probably don't want to transfer 0 satoshis") } if outCoin > inCoin-TX_FEE { log.Fatal("Can't complete transaction--the request amount" + " is larger than available funds after transaction fee") } // Take the address and generate a PubKeyScript out of it script, err := txscript.PayToAddrScript(addr) if err != nil { log.Fatal(err) } txout := wire.NewTxOut(outCoin, script) return txout }
// newBobNode generates a test "ln node" to interact with Alice (us). For the // funding transaction, bob has a single output totaling 7BTC. For our basic // test, he'll fund the channel with 5BTC, leaving 2BTC to the change output. // TODO(roasbeef): proper handling of change etc. func newBobNode() (*bobNode, error) { // First, parse Bob's priv key in order to obtain a key he'll use for the // multi-sig funding transaction. privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey) // Next, generate an output redeemable by bob. bobAddr, err := btcutil.NewAddressPubKey(privKey.PubKey().SerializeCompressed(), ActiveNetParams) if err != nil { return nil, err } bobAddrScript, err := txscript.PayToAddrScript(bobAddr.AddressPubKeyHash()) if err != nil { return nil, err } prevOut := wire.NewOutPoint(&wire.ShaHash{}, ^uint32(0)) // TODO(roasbeef): When the chain rpc is hooked in, assert bob's output // actually exists and it unspent in the chain. bobTxIn := wire.NewTxIn(prevOut, nil) // Using bobs priv key above, create a change address he can spend. bobChangeOutput := wire.NewTxOut(2*1e8, bobAddrScript) // Bob's initial revocation hash is just his private key with the first // byte changed... var revocation [20]byte copy(revocation[:], bobsPrivKey) revocation[0] = 0xff // His ID is just as creative... var id [wire.HashSize]byte id[0] = 0xff return &bobNode{ id: id, privKey: privKey, channelKey: pubKey, deliveryAddress: bobAddr, revocation: revocation, delay: 5, availableOutputs: []*wire.TxIn{bobTxIn}, changeOutputs: []*wire.TxOut{bobChangeOutput}, }, nil }
func createTxRemainder(inCoin int64) *wire.TxOut { remainder := inCoin - *amount - TX_FEE // Put the remainder back in our wallet pkBytes, err := hex.DecodeString(*privkey) if err != nil { log.Fatal(err) } // Get pubkey object _, pubkey := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes) // Get an address object from WIF string changeAddress := generateAddr(pubkey) script, err := txscript.PayToAddrScript(changeAddress) if err != nil { log.Fatal(err) } remtx := wire.NewTxOut(remainder, script) return remtx }
func createMsgTx(pkScript []byte, amts []int64) *wire.MsgTx { msgtx := &wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{ { PreviousOutPoint: wire.OutPoint{ Hash: chainhash.Hash{}, Index: 0xffffffff, }, SignatureScript: []byte{txscript.OP_NOP}, Sequence: 0xffffffff, }, }, LockTime: 0, } for _, amt := range amts { msgtx.AddTxOut(wire.NewTxOut(amt, pkScript)) } return msgtx }
// addOutputs adds the given address/amount pairs as outputs to msgtx, // returning their total amount. func addOutputs(msgtx *wire.MsgTx, pairs map[string]btcutil.Amount, chainParams *chaincfg.Params) (btcutil.Amount, error) { var minAmount btcutil.Amount for addrStr, amt := range pairs { if amt <= 0 { return minAmount, ErrNonPositiveAmount } minAmount += amt addr, err := btcutil.DecodeAddress(addrStr, chainParams) if err != nil { return minAmount, fmt.Errorf("cannot decode address: %s", err) } // Add output to spend amt to addr. pkScript, err := txscript.PayToAddrScript(addr) if err != nil { return minAmount, fmt.Errorf("cannot create txout script: %s", err) } txout := wire.NewTxOut(int64(amt), pkScript) msgtx.AddTxOut(txout) } return minAmount, nil }
func testMemWalletLockedOutputs(r *Harness, t *testing.T) { // Obtain the initial balance of the wallet at this point. startingBalance := r.ConfirmedBalance() // First, create a signed transaction spending some outputs. addr, err := r.NewAddress() if err != nil { t.Fatalf("unable to generate new address: %v", err) } pkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("unable to create script: %v", err) } outputAmt := btcutil.Amount(50 * btcutil.SatoshiPerBitcoin) output := wire.NewTxOut(int64(outputAmt), pkScript) tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10) if err != nil { t.Fatalf("unable to create transaction: %v", err) } // The current wallet balance should now be at least 50 BTC less // (accounting for fees) than the period balance currentBalance := r.ConfirmedBalance() if !(currentBalance <= startingBalance-outputAmt) { t.Fatalf("spent outputs not locked: previous balance %v, "+ "current balance %v", startingBalance, currentBalance) } // Now unlocked all the spent inputs within the unbroadcast signed // transaction. The current balance should now be exactly that of the // starting balance. r.UnlockOutputs(tx.TxIn) currentBalance = r.ConfirmedBalance() if currentBalance != startingBalance { t.Fatalf("current and starting balance should now match: "+ "expected %v, got %v", startingBalance, currentBalance) } }
// Test the sigscript generation for valid and invalid inputs, all // hashTypes, and with and without compression. This test creates // sigscripts to spend fake coinbase inputs, as sigscripts cannot be // created for the MsgTxs in txTests, since they come from the blockchain // and we don't have the private keys. func TestSignatureScript(t *testing.T) { t.Parallel() privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyD) nexttest: for i := range sigScriptTests { tx := wire.NewMsgTx(wire.TxVersion) output := wire.NewTxOut(500, []byte{OP_RETURN}) tx.AddTxOut(output) for range sigScriptTests[i].inputs { txin := wire.NewTxIn(coinbaseOutPoint, nil) tx.AddTxIn(txin) } var script []byte var err error for j := range tx.TxIn { var idx int if sigScriptTests[i].inputs[j].indexOutOfRange { t.Errorf("at test %v", sigScriptTests[i].name) idx = len(sigScriptTests[i].inputs) } else { idx = j } script, err = SignatureScript(tx, idx, sigScriptTests[i].inputs[j].txout.PkScript, sigScriptTests[i].hashType, privKey, sigScriptTests[i].compress) if (err == nil) != sigScriptTests[i].inputs[j].sigscriptGenerates { if err == nil { t.Errorf("passed test '%v' incorrectly", sigScriptTests[i].name) } else { t.Errorf("failed test '%v': %v", sigScriptTests[i].name, err) } continue nexttest } if !sigScriptTests[i].inputs[j].sigscriptGenerates { // done with this test continue nexttest } tx.TxIn[j].SignatureScript = script } // If testing using a correct sigscript but for an incorrect // index, use last input script for first input. Requires > 0 // inputs for test. if sigScriptTests[i].scriptAtWrongIndex { tx.TxIn[0].SignatureScript = script sigScriptTests[i].inputs[0].inputValidates = false } // Validate tx input scripts scriptFlags := ScriptBip16 | ScriptVerifyDERSignatures for j := range tx.TxIn { vm, err := NewEngine(sigScriptTests[i].inputs[j].txout. PkScript, tx, j, scriptFlags, nil) if err != nil { t.Errorf("cannot create script vm for test %v: %v", sigScriptTests[i].name, err) continue nexttest } err = vm.Execute() if (err == nil) != sigScriptTests[i].inputs[j].inputValidates { if err == nil { t.Errorf("passed test '%v' validation incorrectly: %v", sigScriptTests[i].name, err) } else { t.Errorf("failed test '%v' validation: %v", sigScriptTests[i].name, err) } continue nexttest } } } }
0xeb, 0xa4, 0x02, 0xcb, 0x68, 0xe0, 0x69, 0x56, 0xbf, 0x32, 0x53, 0x90, 0x0e, 0x0a, 0x88, 0xac} uncompressedAddrStr = "1L6fd93zGmtzkK6CsZFVVoCwzZV3MUtJ4F" compressedAddrStr = "14apLppt9zTq6cNw8SDfiJhk9PhkZrQtYZ" ) // Pretend output amounts. const coinbaseVal = 2500000000 const fee = 5000000 var sigScriptTests = []tstSigScript{ { name: "one input uncompressed", inputs: []tstInput{ { txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), sigscriptGenerates: true, inputValidates: true, indexOutOfRange: false, }, }, hashType: SigHashAll, compress: false, scriptAtWrongIndex: false, }, { name: "two inputs uncompressed", inputs: []tstInput{ { txout: wire.NewTxOut(coinbaseVal, uncompressedPkScript), sigscriptGenerates: true,
func loadTestCredits(w *LightningWallet, numOutputs, btcPerOutput int) error { // Import the priv key (converting to WIF) above that controls all our // available outputs. privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) if err := w.Unlock(privPass, time.Duration(0)); err != nil { return err } bs := &waddrmgr.BlockStamp{Hash: *genBlockHash(1), Height: 1} wif, err := btcutil.NewWIF(privKey, ActiveNetParams, true) if err != nil { return err } if _, err := w.ImportPrivateKey(wif, bs, false); err != nil { return nil } if err := w.Manager.SetSyncedTo(&waddrmgr.BlockStamp{int32(1), *genBlockHash(1)}); err != nil { return err } blk := wtxmgr.BlockMeta{wtxmgr.Block{Hash: *genBlockHash(2), Height: 2}, time.Now()} // Create a simple P2PKH pubkey script spendable by Alice. For simplicity // all of Alice's spendable funds will reside in this output. satosihPerOutput := int64(btcPerOutput * 1e8) walletAddr, err := btcutil.NewAddressPubKey(privKey.PubKey().SerializeCompressed(), ActiveNetParams) if err != nil { return err } walletScriptCredit, err := txscript.PayToAddrScript(walletAddr.AddressPubKeyHash()) if err != nil { return err } // Create numOutputs outputs spendable by our wallet each holding btcPerOutput // in satoshis. tx := wire.NewMsgTx() prevOut := wire.NewOutPoint(genBlockHash(999), 1) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}) tx.AddTxIn(txIn) for i := 0; i < numOutputs; i++ { tx.AddTxOut(wire.NewTxOut(satosihPerOutput, walletScriptCredit)) } txCredit, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now()) if err != nil { return err } if err := addTestTx(w, txCredit, &blk); err != nil { return err } if err := w.Manager.SetSyncedTo(&waddrmgr.BlockStamp{int32(2), *genBlockHash(2)}); err != nil { return err } // Make the wallet think it's been synced to block 10. This way the // outputs we added above will have sufficient confirmations // (hard coded to 6 atm). for i := 3; i < 10; i++ { sha := *genBlockHash(i) if err := w.Manager.SetSyncedTo(&waddrmgr.BlockStamp{int32(i), sha}); err != nil { return err } } return nil }
// This example demonstrates manually creating and signing a redeem transaction. func ExampleSignTxOutput() { // Ordinarily the private key would come from whatever storage mechanism // is being used, but for this example just hard code it. privKeyBytes, err := hex.DecodeString("22a47fa09a223f2aa079edf85a7c2" + "d4f8720ee63e502ee2869afab7de234b80c") if err != nil { fmt.Println(err) return } privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes) pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed()) addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, &chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } // For this example, create a fake transaction that represents what // would ordinarily be the real transaction that is being spent. It // contains a single output that pays to address in the amount of 1 BTC. originTx := wire.NewMsgTx() prevOut := wire.NewOutPoint(&wire.ShaHash{}, ^uint32(0)) txIn := wire.NewTxIn(prevOut, []byte{txscript.OP_0, txscript.OP_0}) originTx.AddTxIn(txIn) pkScript, err := txscript.PayToAddrScript(addr) if err != nil { fmt.Println(err) return } txOut := wire.NewTxOut(100000000, pkScript) originTx.AddTxOut(txOut) originTxHash, err := originTx.TxSha() if err != nil { fmt.Println(err) return } // Create the transaction to redeem the fake transaction. redeemTx := wire.NewMsgTx() // Add the input(s) the redeeming transaction will spend. There is no // signature script at this point since it hasn't been created or signed // yet, hence nil is provided for it. prevOut = wire.NewOutPoint(&originTxHash, 0) txIn = wire.NewTxIn(prevOut, nil) redeemTx.AddTxIn(txIn) // Ordinarily this would contain that actual destination of the funds, // but for this example don't bother. txOut = wire.NewTxOut(0, nil) redeemTx.AddTxOut(txOut) // Sign the redeeming transaction. lookupKey := func(a btcutil.Address) (*btcec.PrivateKey, bool, error) { // Ordinarily this function would involve looking up the private // key for the provided address, but since the only thing being // signed in this example uses the address associated with the // private key from above, simply return it with the compressed // flag set since the address is using the associated compressed // public key. // // NOTE: If you want to prove the code is actually signing the // transaction properly, uncomment the following line which // intentionally returns an invalid key to sign with, which in // turn will result in a failure during the script execution // when verifying the signature. // // privKey.D.SetInt64(12345) // return privKey, true, nil } // Notice that the script database parameter is nil here since it isn't // used. It must be specified when pay-to-script-hash transactions are // being signed. sigScript, err := txscript.SignTxOutput(&chaincfg.MainNetParams, redeemTx, 0, originTx.TxOut[0].PkScript, txscript.SigHashAll, txscript.KeyClosure(lookupKey), nil, nil) if err != nil { fmt.Println(err) return } redeemTx.TxIn[0].SignatureScript = sigScript // Prove that the transaction has been validly signed by executing the // script pair. flags := txscript.ScriptBip16 | txscript.ScriptVerifyDERSignatures | txscript.ScriptStrictMultiSig | txscript.ScriptDiscourageUpgradableNops s, err := txscript.NewScript(redeemTx.TxIn[0].SignatureScript, originTx.TxOut[0].PkScript, 0, redeemTx, flags) if err != nil { fmt.Println(err) return } if err := s.Execute(); err != nil { fmt.Println(err) return } fmt.Println("Transaction successfully signed") // Output: // Transaction successfully signed }
func TestLimitAndSkipFetchTxsForAddr(t *testing.T) { testDb, err := setUpTestDb(t, "tstdbtxaddr") if err != nil { t.Errorf("Failed to open test database %v", err) return } defer testDb.cleanUpFunc() // Insert a block with some fake test transactions. The block will have // 10 copies of a fake transaction involving same address. addrString := "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" targetAddr, err := btcutil.DecodeAddress(addrString, &chaincfg.MainNetParams) if err != nil { t.Fatalf("Unable to decode test address: %v", err) } outputScript, err := txscript.PayToAddrScript(targetAddr) if err != nil { t.Fatalf("Unable make test pkScript %v", err) } fakeTxOut := wire.NewTxOut(10, outputScript) var emptyHash wire.ShaHash fakeHeader := wire.NewBlockHeader(&emptyHash, &emptyHash, 1, 1) msgBlock := wire.NewMsgBlock(fakeHeader) for i := 0; i < 10; i++ { mtx := wire.NewMsgTx() mtx.AddTxOut(fakeTxOut) msgBlock.AddTransaction(mtx) } // Insert the test block into the DB. testBlock := btcutil.NewBlock(msgBlock) newheight, err := testDb.db.InsertBlock(testBlock) if err != nil { t.Fatalf("Unable to insert block into db: %v", err) } // Create and insert an address index for out test addr. txLoc, _ := testBlock.TxLoc() index := make(database.BlockAddrIndex) for i := range testBlock.Transactions() { var hash160 [ripemd160.Size]byte scriptAddr := targetAddr.ScriptAddress() copy(hash160[:], scriptAddr[:]) index[hash160] = append(index[hash160], &txLoc[i]) } blkSha := testBlock.Sha() err = testDb.db.UpdateAddrIndexForBlock(blkSha, newheight, index) if err != nil { t.Fatalf("UpdateAddrIndexForBlock: failed to index"+ " addrs for block #%d (%s) "+ "err %v", newheight, blkSha, err) return } // Try skipping the first 4 results, should get 6 in return. txReply, err := testDb.db.FetchTxsForAddr(targetAddr, 4, 100000) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReply) != 6 { t.Fatalf("Did not correctly skip forward in txs for address reply"+ " got %v txs, expected %v", len(txReply), 6) } // Limit the number of results to 3. txReply, err = testDb.db.FetchTxsForAddr(targetAddr, 0, 3) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReply) != 3 { t.Fatalf("Did not correctly limit in txs for address reply"+ " got %v txs, expected %v", len(txReply), 3) } // Skip 1, limit 5. txReply, err = testDb.db.FetchTxsForAddr(targetAddr, 1, 5) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReply) != 5 { t.Fatalf("Did not correctly limit in txs for address reply"+ " got %v txs, expected %v", len(txReply), 5) } }
func TestInsertsCreditsDebitsRollbacks(t *testing.T) { t.Parallel() // Create a double spend of the received blockchain transaction. dupRecvTx, _ := btcutil.NewTxFromBytes(TstRecvSerializedTx) // Switch txout amount to 1 BTC. Transaction store doesn't // validate txs, so this is fine for testing a double spend // removal. TstDupRecvAmount := int64(1e8) newDupMsgTx := dupRecvTx.MsgTx() newDupMsgTx.TxOut[0].Value = TstDupRecvAmount TstDoubleSpendTx := btcutil.NewTx(newDupMsgTx) TstDoubleSpendSerializedTx := serializeTx(TstDoubleSpendTx) // Create a "signed" (with invalid sigs) tx that spends output 0 of // the double spend. spendingTx := wire.NewMsgTx() spendingTxIn := wire.NewTxIn(wire.NewOutPoint(TstDoubleSpendTx.Hash(), 0), []byte{0, 1, 2, 3, 4}) spendingTx.AddTxIn(spendingTxIn) spendingTxOut1 := wire.NewTxOut(1e7, []byte{5, 6, 7, 8, 9}) spendingTxOut2 := wire.NewTxOut(9e7, []byte{10, 11, 12, 13, 14}) spendingTx.AddTxOut(spendingTxOut1) spendingTx.AddTxOut(spendingTxOut2) TstSpendingTx := btcutil.NewTx(spendingTx) TstSpendingSerializedTx := serializeTx(TstSpendingTx) var _ = TstSpendingTx tests := []struct { name string f func(*Store) (*Store, error) bal, unc btcutil.Amount unspents map[wire.OutPoint]struct{} unmined map[chainhash.Hash]struct{} }{ { name: "new store", f: func(s *Store) (*Store, error) { return s, nil }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[chainhash.Hash]struct{}{}, }, { name: "txout insert", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, false) return s, err }, bal: 0, unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Hash(): {}, }, }, { name: "insert duplicate unconfirmed", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, false) return s, err }, bal: 0, unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Hash(): {}, }, }, { name: "confirmed txout insert", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "insert duplicate confirmed", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "rollback confirmed credit", f: func(s *Store) (*Store, error) { err := s.Rollback(TstRecvTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstRecvTx.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstRecvTx.Hash(): {}, }, }, { name: "insert confirmed double spend", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstDoubleSpendTx.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "insert unconfirmed debit", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) return s, err }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { name: "insert unconfirmed debit again", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstDoubleSpendSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) return s, err }, bal: 0, unc: 0, unspents: map[wire.OutPoint]struct{}{}, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { name: "insert change (index 0)", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 0, true) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 0, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { name: "insert output back to this own wallet (index 1)", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, nil) if err != nil { return nil, err } err = s.AddCredit(rec, nil, 1, true) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { name: "confirm signed tx", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstSpendingSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstSignedTxBlockDetails) return s, err }, bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "rollback after spending tx", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height + 1) return s, err }, bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]struct{}{}, }, { name: "rollback spending tx block", f: func(s *Store) (*Store, error) { err := s.Rollback(TstSignedTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 0, }: {}, wire.OutPoint{ Hash: *TstSpendingTx.Hash(), Index: 1, }: {}, }, unmined: map[chainhash.Hash]struct{}{ *TstSpendingTx.Hash(): {}, }, }, { name: "rollback double spend tx block", f: func(s *Store) (*Store, error) { err := s.Rollback(TstRecvTxBlockDetails.Height) return s, err }, bal: 0, unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstSpendingTx.Hash(), 0): {}, *wire.NewOutPoint(TstSpendingTx.Hash(), 1): {}, }, unmined: map[chainhash.Hash]struct{}{ *TstDoubleSpendTx.Hash(): {}, *TstSpendingTx.Hash(): {}, }, }, { name: "insert original recv txout", f: func(s *Store) (*Store, error) { rec, err := NewTxRecord(TstRecvSerializedTx, time.Now()) if err != nil { return nil, err } err = s.InsertTx(rec, TstRecvTxBlockDetails) if err != nil { return nil, err } err = s.AddCredit(rec, TstRecvTxBlockDetails, 0, false) return s, err }, bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), unc: 0, unspents: map[wire.OutPoint]struct{}{ *wire.NewOutPoint(TstRecvTx.Hash(), 0): {}, }, unmined: map[chainhash.Hash]struct{}{}, }, } s, teardown, err := testStore() defer teardown() if err != nil { t.Fatal(err) } for _, test := range tests { tmpStore, err := test.f(s) if err != nil { t.Fatalf("%s: got error: %v", test.name, err) } s = tmpStore bal, err := s.Balance(1, TstRecvCurrentHeight) if err != nil { t.Fatalf("%s: Confirmed Balance failed: %v", test.name, err) } if bal != test.bal { t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal) } unc, err := s.Balance(0, TstRecvCurrentHeight) if err != nil { t.Fatalf("%s: Unconfirmed Balance failed: %v", test.name, err) } unc -= bal if unc != test.unc { t.Fatalf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc) } // Check that unspent outputs match expected. unspent, err := s.UnspentOutputs() if err != nil { t.Fatalf("%s: failed to fetch unspent outputs: %v", test.name, err) } for _, cred := range unspent { if _, ok := test.unspents[cred.OutPoint]; !ok { t.Errorf("%s: unexpected unspent output: %v", test.name, cred.OutPoint) } delete(test.unspents, cred.OutPoint) } if len(test.unspents) != 0 { t.Fatalf("%s: missing expected unspent output(s)", test.name) } // Check that unmined txs match expected. unmined, err := s.UnminedTxs() if err != nil { t.Fatalf("%s: cannot load unmined transactions: %v", test.name, err) } for _, tx := range unmined { txHash := tx.TxHash() if _, ok := test.unmined[txHash]; !ok { t.Fatalf("%s: unexpected unmined tx: %v", test.name, txHash) } delete(test.unmined, txHash) } if len(test.unmined) != 0 { t.Fatalf("%s: missing expected unmined tx(s)", test.name) } } }
// SendCoins does send coins, but it's very rudimentary func SendCoins(s uspv.SPVCon, adr btcutil.Address, sendAmt int64) error { var err error var score int64 allUtxos, err := s.TS.GetAllUtxos() if err != nil { return err } for _, utxo := range allUtxos { score += utxo.Value } // important rule in bitcoin, output total > input total is invalid. if sendAmt > score { return fmt.Errorf("trying to send %d but %d available.", sendAmt, score) } tx := wire.NewMsgTx() // make new tx // make address script 76a914...88ac adrScript, err := txscript.PayToAddrScript(adr) if err != nil { return err } // make user specified txout and add to tx txout := wire.NewTxOut(sendAmt, adrScript) tx.AddTxOut(txout) nokori := sendAmt // nokori is how much is needed on input side for _, utxo := range allUtxos { // generate pkscript to sign prevPKscript, err := txscript.PayToAddrScript( s.TS.Adrs[utxo.KeyIdx].PkhAdr) if err != nil { return err } // make new input from this utxo thisInput := wire.NewTxIn(&utxo.Op, prevPKscript) tx.AddTxIn(thisInput) nokori -= utxo.Value if nokori < -10000 { // minimum overage / fee is 1K now break } } // there's enough left to make a change output if nokori < -200000 { change, err := s.TS.NewAdr() if err != nil { return err } changeScript, err := txscript.PayToAddrScript(change) if err != nil { return err } changeOut := wire.NewTxOut((-100000)-nokori, changeScript) tx.AddTxOut(changeOut) } // use txstore method to sign err = s.TS.SignThis(tx) if err != nil { return err } fmt.Printf("tx: %s", uspv.TxToString(tx)) buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) tx.Serialize(buf) fmt.Printf("tx: %x\n", buf.Bytes()) // send it out on the wire. hope it gets there. // we should deal with rejects. Don't yet. err = s.NewOutgoingTx(tx) if err != nil { return err } return nil }