btcsuite / btcwallet

A secure bitcoin wallet daemon written in Go (golang)
ISC License
1.15k stars 590 forks source link

Ask for advice about using multiple utxos for the transaction #866

Closed lenhatquang97 closed 1 year ago

lenhatquang97 commented 1 year ago

Situation

I want to merge multiple unspent inputs into an input because each inputs only contain 50 satoshis. What can I do or how to use multiple inputs and outputs in the transaction?

I am using simnet in btcd and btcwallet

Commands in btcd and btcwallet

btcwallet --simnet --username=youruser --password=SomeDecentp4ssw0rd

btcd --simnet --rpcuser=youruser --rpcpass=SomeDecentp4ssw0rd --miningaddr=SeZdpbs8WBuPHMZETPWajMeXZt1xzCJNAJ --txindex

Why do I ask

Because I meet this error related to using multiple inputs -25: TX rejected: failed to validate input 048e08a85d50ee1b493920ef70d68e344304bb63689384342860c534c1d6e931:3 which references output 44f6bed55cb1bc9ad55bc7906d5398055bcd5ac504100773d1af64bc889b2975:0 - index 0 is invalid for stack size 0 (input witness [], input script bytes , prev output script bytes 76a914bd6f166f34fa00ecc0010900ead95ba6cf9f2a8788ac)

Source code

package main

import (
    "fmt"

    "github.com/btcsuite/btcd/btcjson"
    "github.com/btcsuite/btcd/btcutil"
    "github.com/btcsuite/btcd/chaincfg"
    "github.com/btcsuite/btcd/chaincfg/chainhash"
    "github.com/btcsuite/btcd/rpcclient"
    "github.com/btcsuite/btcd/txscript"
    "github.com/btcsuite/btcd/wire"
)

func main() {
    client, err := GetBitcoinWalletRpcClient()
    if err != nil {
        fmt.Println(err)
        return
    }

    err = client.WalletPassphrase("12345", 5)
    if err != nil {
        fmt.Println(err)
        return
    }

    rawTx, wif, err := CreateTxV2("SZnK16oMnqQt8Q1qLvrTpYLpkpkFG9eVRi", 200, client)

    if err != nil {
        fmt.Println(err)
        return
    }

    hash, err := client.SendRawTransaction(rawTx, false)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("Success")

    commitTx, err := client.GetRawTransaction(hash)
    if err != nil {
        fmt.Println(err)
        return
    }

    tx, address, err := RevealTx([]byte("Hello World"), *hash, *commitTx.MsgTx().TxOut[0], 0, wif.PrivKey)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(address)
    revealTx, err := client.SendRawTransaction(tx, true)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(revealTx)
}

func NewTx() (*wire.MsgTx, error) {
    return wire.NewMsgTx(wire.TxVersion), nil
}

func GetUtxo(utxos []btcjson.ListUnspentResult, address string) (string, uint32, float64) {
    for i := 0; i < len(utxos); i++ {
        if utxos[i].Address == address {
            return utxos[i].TxID, utxos[i].Vout, utxos[i].Amount
        }
    }
    return "", 0, -1
}

type MyUtxo struct {
    TxID   string
    Vout   uint32
    Amount float64
}

func GetManyUtxo(utxos []btcjson.ListUnspentResult, address string, amount float64) []*MyUtxo {
    var myUtxos []*MyUtxo
    for i := 0; i < len(utxos); i++ {
        if utxos[i].Address == address {
            myUtxos = append(myUtxos, &MyUtxo{
                TxID:   utxos[i].TxID,
                Vout:   utxos[i].Vout,
                Amount: utxos[i].Amount,
            })
        }

        if utxos[i].TxID == "c9f48e0ccf6bccd9018944d330bb1df485560bb0a94c364d2492fd336ab63e8c" {
            fmt.Println(utxos[i])
        }
    }
    var res []*MyUtxo
    //sort.Slice(myUtxos, func(i, j int) bool {
    //  return myUtxos[i].Amount < myUtxos[j].Amount
    //})
    for _, utxo := range myUtxos {
        res = append(res, utxo)
        amount -= utxo.Amount
        if amount <= 0 {
            break
        }
    }

    return res
}

func GetActualBalance(client *rpcclient.Client, actualAddress string) (int, error) {
    utxos, err := client.ListUnspent()
    if err != nil {
        return -1, err
    }

    amount := 0

    for i := 0; i < len(utxos); i++ {
        if utxos[i].Address == actualAddress {
            amount += int(utxos[i].Amount)
        }
    }
    return amount, nil
}

func CreateTxV2(destination string, amount int64, client *rpcclient.Client) (*wire.MsgTx, *btcutil.WIF, error) {
    senderAddress := "SeZdpbs8WBuPHMZETPWajMeXZt1xzCJNAJ"

    //actualBalance, _ := GetActualBalance(client, senderAddress)

    defaultAddress, err := btcutil.DecodeAddress(senderAddress, &chaincfg.SimNetParams)
    if err != nil {
        return nil, nil, err
    }

    wif, err := client.DumpPrivKey(defaultAddress)
    if err != nil {
        return nil, nil, err
    }

    utxos, err := client.ListUnspent()
    if err != nil {
        return nil, nil, err
    }

    sendUtxos := GetManyUtxo(utxos, defaultAddress.EncodeAddress(), float64(amount))
    if len(sendUtxos) == 0 {
        return nil, nil, fmt.Errorf("no utxos")
    }

    PrintLogUtxos(sendUtxos)

    var balance float64
    for _, item := range sendUtxos {
        balance += item.Amount
    }

    pkScript, _ := txscript.PayToAddrScript(defaultAddress)

    if err != nil {
        return nil, nil, err
    }

    // checking for sufficiency of account
    if int64(balance) < amount {
        return nil, nil, fmt.Errorf("the balance of the account is not sufficient")
    }

    // extracting destination address as []byte from function argument (destination string)
    destinationAddr, err := btcutil.DecodeAddress(destination, &chaincfg.SimNetParams)
    if err != nil {
        return nil, nil, err
    }

    destinationAddrByte, err := txscript.PayToAddrScript(destinationAddr)
    if err != nil {
        return nil, nil, err
    }

    redeemTx, err := NewTx()
    if err != nil {
        return nil, nil, err
    }

    for _, utxo := range sendUtxos {
        utxoHash, err := chainhash.NewHashFromStr(utxo.TxID)
        if err != nil {
            return nil, nil, err
        }

        outPoint := wire.NewOutPoint(utxoHash, utxo.Vout)

        // making the input, and adding it to transaction
        txIn := wire.NewTxIn(outPoint, nil, nil)
        redeemTx.AddTxIn(txIn)
    }

    // adding the destination address and the amount to
    // the transaction as output
    redeemTxOut := wire.NewTxOut(amount, destinationAddrByte)
    redeemTx.AddTxOut(redeemTxOut)

    // changeAddress, err := client.GetRawChangeAddress("default")
    // if err != nil {
    //  return nil, nil, err
    // }
    // changePkScript, err := txscript.PayToAddrScript(changeAddress)
    // if err != nil {
    //  return nil, nil, err
    // }
    // changeTxOut := wire.NewTxOut(20, changePkScript)
    // redeemTx.AddTxOut(changeTxOut)

    // now sign the transaction
    finalRawTx, err := SignTx(wif, pkScript, redeemTx)
    if err != nil {
        return nil, nil, err
    }

    return finalRawTx, wif, nil
}

func CreateTx(destination string, amount int64, client *rpcclient.Client) (*wire.MsgTx, *btcutil.WIF, error) {
    defaultAddress, err := btcutil.DecodeAddress("SeTCfjeSQYevShUDEqo59GH1V5kqnP4dg5", &chaincfg.SimNetParams)
    if err != nil {
        return nil, nil, err
    }

    wif, err := client.DumpPrivKey(defaultAddress)
    if err != nil {
        return nil, nil, err
    }

    utxos, err := client.ListUnspent()
    if err != nil {
        return nil, nil, err
    }

    txid, vout, balance := GetUtxo(utxos, defaultAddress.EncodeAddress())
    if len(txid) == 0 {
        return nil, nil, fmt.Errorf("no utxos")
    }

    pkScript, _ := txscript.PayToAddrScript(defaultAddress)

    if err != nil {
        return nil, nil, err
    }

    // checking for sufficiency of account
    if int64(balance) < amount {
        return nil, nil, fmt.Errorf("the balance of the account is not sufficient")
    }

    // extracting destination address as []byte from function argument (destination string)
    destinationAddr, err := btcutil.DecodeAddress(destination, &chaincfg.SimNetParams)
    if err != nil {
        return nil, nil, err
    }

    destinationAddrByte, err := txscript.PayToAddrScript(destinationAddr)
    if err != nil {
        return nil, nil, err
    }

    redeemTx, err := NewTx()
    if err != nil {
        return nil, nil, err
    }

    utxoHash, err := chainhash.NewHashFromStr(txid)
    if err != nil {
        return nil, nil, err
    }

    outPoint := wire.NewOutPoint(utxoHash, vout)

    // making the input, and adding it to transaction
    txIn := wire.NewTxIn(outPoint, nil, nil)
    redeemTx.AddTxIn(txIn)

    // adding the destination address and the amount to
    // the transaction as output
    redeemTxOut := wire.NewTxOut(amount, destinationAddrByte)
    redeemTx.AddTxOut(redeemTxOut)

    // now sign the transaction
    finalRawTx, err := SignTx(wif, pkScript, redeemTx)
    if err != nil {
        return nil, nil, err
    }

    return finalRawTx, wif, nil
}

func SignTx(wif *btcutil.WIF, pkScript []byte, redeemTx *wire.MsgTx) (*wire.MsgTx, error) {
    signature, err := txscript.SignatureScript(redeemTx, 0, pkScript, txscript.SigHashAll, wif.PrivKey, true)
    if err != nil {
        return nil, err
    }

    redeemTx.TxIn[0].SignatureScript = signature

    return redeemTx, nil
}
lenhatquang97 commented 1 year ago

I have solved this problem by signing each inputs. Can everyone tell me that is this a good solution?

func SignTx(wif *btcutil.WIF, pkScript []byte, redeemTx *wire.MsgTx) (*wire.MsgTx, error) {
    for i, _ := range redeemTx.TxIn {
        signature, err := txscript.SignatureScript(redeemTx, i, pkScript, txscript.SigHashAll, wif.PrivKey, true)
        if err != nil {
            return nil, err
        }

        redeemTx.TxIn[i].SignatureScript = signature
    }
    return redeemTx, nil
}
guggero commented 1 year ago

Yes, each input of a transaction needs to be signed. Normally that is done in a loop :+1: