Teach you to use go language to realize bitcoin transaction function

Time:2021-7-27

Bitcoin transaction

Transaction is the core of bitcoin, and the only purpose of blockchain is to store transactions safely and reliably. Once it is created or deleted in the blockchain, no one can delete it.
For each new transaction, its input will refer to the output of the previous transaction (with an exception, coinbase transaction). Reference means cost. The so-called reference to the previous output means that the previous output is included in the input of another transaction, that is, the transaction output before spending. The output of the transaction is where the currency is actually stored. The following diagram illustrates the correlation between transactions:

这里写图片描述 

be careful:

Some outputs are not associated with an input

The input of a transaction can reference the output of multiple previous transactions

An input must reference an output

Throughout this article, we will use words like “money”, “coin”, “spend”, “send”, “account”, and so on. But in bitcoin, there is no such concept. Transactions simply lock some values through a script, and these values can only be unlocked by the person who locks them.

Every bitcoin transaction will create output, and the output will be recorded by the blockchain. Sending bitcoin to someone actually means creating a new utxo and registering it with that person’s address for his use.
Main function of transaction:

func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) {
    if !ValidateAddress(from) {   
        log.Panic("ERROR: Sender address is not valid")
    }
    if !ValidateAddress(to) {
        log.Panic("ERROR: Recipient address is not valid")
    }
    BC: = newblockchain (nodeid) // get blockchain instance
    Utxoset: = utxoset {bc} // create utxo set
    defer bc.Db.Close()
    wallets, err := NewWallets(nodeID)
    if err != nil {
        log.Panic(err)
    }
    wallet := wallets.GetWallet(from)
    tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet)
    if mineNow {    
        cbTx := NewCoinbaseTX(from, "")
        txs := []*Transaction{cbTx, tx}
        newBlock := bc.MineBlock(txs)
        UTXOSet.Update(newBlock)
    } else {
        sendTx(knownNodes[0], tx)
    }

    fmt.Println("Success!")
}

We analyze the whole transaction process from scratchValidateAddress()Method to judge whether the input address is a valid bitcoin address, and then obtain the blockchain instance from our blockdb database (we use a database to store blockchain data, which readers can ignore here). The code to read the database is as follows

func NewBlockchain(nodeID string) *Blockchain {
    dbFile := fmt.Sprintf(dbFile, nodeID)
    if dbExists(dbFile) == false {
        fmt.Println("No existing blockchain found. Create one first.")
        os.Exit(1)
    }
    var tip []byte
    DB, err: = bolt. Open (dbfile, 0600, Nil) // open the database
    if err != nil {
        log.Panic(err)
    }
    err = db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        Tip = b.get ([] byte ("L") // read the latest blockchain
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    bc := Blockchain{tip, db}
    return &bc
}

The basic prototype of our blockchain is


type Blockchain struct {
    tip []byte
    Db  *bolt.DB
}
type Block struct {
    Timestamp     int64
    Transactions  []*Transaction
    PrevBlockHash []byte
    Hash          []byte
    Nonce         int
    Height        int
}

After obtaining the blockchain instance, we create a utxo collection with a data structure of


type UTXOSet struct {
    Blockchain *Blockchain
}

Then we get our from the wallet fileWallets,Then call our transfer function.

func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction {
    var inputs []TXInput
    var outputs []TXOutput
    pubKeyHash := HashPubKey(wallet.PublicKey)
    ACC, validoutputs: = utxoset. Findspendable outputs (pubkeyhash, amount) // find the output that can be used
    If ACC < amount {// if the available output is less than the target value, an error is returned
        log.Panic("ERROR: Not enough funds")
    }
    // Build a list of inputs
    for txid, outs := range validOutputs {         
        txID, err := hex.DecodeString(txid)
        if err != nil {
            log.Panic(err)
        }
        for _, out := range outs {
            input := TXInput{txID, out, nil, wallet.PublicKey}
            inputs = append(inputs, input)
        }
    }
    // Build a list of outputs
    from := fmt.Sprintf("%s", wallet.GetAddress())
    Outputs = append (outputs, * newtxoutput (amount, to)) // create a new transaction output
    if acc > amount {
        Outputs = append (outputs, * newtxoutput (ACC amount, from)) // a change // change output
    }
    tx := Transaction{nil, inputs, outputs}
    Tx.id = tx.hash() // create a transaction
    Utxoset. Blockchain. Signtransaction (& TX, wallet. Privatekey) // sign the transaction
    return &tx
}

For a transaction, its data structure is


type Transaction struct {
    ID   []byte
    Vin  []TXInput
    Vout []TXOutput
}
type TXInput struct {
    Txid      []byte
    Vout      int
    Signature []byte
    PubKey    []byte
}
type TXOutput struct {
    Value      int
    PubKeyHash []byte
}
type UTXOSet struct {
    Blockchain *Blockchain
}

For a transaction, the output mainly includes two parts: a certain amount of bitcoin (value) and a script pubkey. To spend this money, you must unlock the script. An input refers to an output of a previous transaction: txid stores the ID of the previous transaction, Vout stores the index of all outputs of the output in that transaction (because a transaction may have multiple outputs, information is required to indicate which one is specific). Signature is a signature, and pubkey is a public key. Both ensure that users cannot spend money belonging to others.

func HashPubKey(pubKey []byte) []byte {  // RIPEMD160(SHA256(PubKey))
    publicSHA256 := sha256.Sum256(pubKey)
    RIPEMD160Hasher := ripemd160.New()
    _, err := RIPEMD160Hasher.Write(publicSHA256[:])
    if err != nil {
        log.Panic(err)
    }
    publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
    return publicRIPEMD160
}
func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) {
    Unspentoutputs: = make (map [string] [] int) // open up a memory space for output
    accumulated := 0       
    DB: = u.blockchain. DB // get the database accessing the blockchain
    Err: = dB. View (func (TX * bolt. TX) error {// read the database
        b := tx.Bucket([]byte(utxoBucket))
        c := b.Cursor()
        for k, v := c.First();  k !=  nil;  k. V = c.next() {// traversing the database
            txID := hex.EncodeToString(k)
            outs := DeserializeOutputs(v)
            for outIdx, out := range outs.Outputs {
                If out. Islockedwithkey (pubkeyhash) & & accumulated < amount {// if the output can be unlocked, it means that the owner of the output in the utxo set is the person corresponding to the public key
                    Accumulated + = out. Value // accumulated value
                    Unspentoutputs [txid] = append (unspentoutputs [txid], outidx) // add to the array
                }
            }
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    return accumulated, unspentOutputs
}
Func (out * txoutput) islockedwithkey (pubkeyhash [] byte) bool {// judge whether the output can be unlocked by a public key
    return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
} 
func NewTXOutput(value int, address string) *TXOutput {
    TXO: = & txoutput {value, nil} // register an output
    TXO. Lock ([] byte (address)) // set the output pubhashkey
    return txo
}
func (out *TXOutput) Lock(address []byte) {
    pubKeyHash := Base58Decode(address)
    pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
    out.PubKeyHash = pubKeyHash
}

When creating new output, we must find all the output for cost and ensure that they have enough value, which isFindSpendableOutputsThen, for each output found, an input referencing that output is created. Next, we create two outputs:

  1. One is locked by the recipient address. This is the currency actually transferred to other addresses.
  2. One is locked by the sender’s address. This is a change. Generated only when the unspent output exceeds the demand of the new transaction. Remember: output is indivisible.
func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
    prevTXs := make(map[string]Transaction)
    for _, vin := range tx.Vin {
        prevTX, err := bc.FindTransaction(vin.Txid)
        if err != nil {
            log.Panic(err)
        }
        prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
    }
    tx.Sign(privKey, prevTXs)
}
Func (TX * transaction) sign (privatkey ECDSA. Privatekey, prevtxs map [string] transaction) {// the method accepts a private key and the map of the previous transaction
    if tx.IsCoinbase() {
        return
    }//Judge whether it is a currency issue transaction. Because the currency issue transaction is not entered, signature is not required

    for _, vin := range tx.Vin {
        if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
            log.Panic("ERROR: Previous transaction is not correct")
        }
    }

    Txcopy: = tx.trimmedcopy() // the trimmed transaction copy will be signed instead of a complete transaction

    for inID, vin := range txCopy.Vin {
        prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
        txCopy.Vin[inID].Signature = nil
        txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
//For each input in the iteration copy, pubkey is set as the pubkeyhash of the referenced output in each input
/
        dataToSign := fmt.Sprintf("%x\n", txCopy)
        r. S, err: = ECDSA. Sign (rand. Reader, & privkey, [] byte (datatosign)) // we sign txcopy through private, connect these numbers and store them in signature
        if err != nil {
            log.Panic(err)
        }
        signature := append(r.Bytes(), s.Bytes()...)
        tx.Vin[inID].Signature = signature
        txCopy.Vin[inID].PubKey = nil
    }
}


func (tx *Transaction) TrimmedCopy() Transaction {  
    var inputs []TXInput
    var outputs []TXOutput

    for _,  Vin: = range tx.vin {// set the input txinput.signature and txiput.pubkey to null
        inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil})
    }

    for _, vout := range tx.Vout {
        outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash})
    }
    txCopy := Transaction{tx.ID, inputs, outputs}
    return txCopy
}

The transaction must be signed because this is the only way to ensure that the sender will not spend other people’s money. If a signature is invalid, the transaction will also be considered invalid because the transaction cannot be added to the blockchain. Considering that the transaction unlocks the previous output, redistributes the value in it, and locks the new output, you must sign the data

  • The public key hash stored in the unlocked output identifies the sender of a transaction
  • The public key hash stored in the new lock output, which identifies the recipient of a transaction
  • New output value

Therefore, in bitcoin, what is signed is not a transaction, but a copy of the input with part of the signature removed, in which the referenced output is storedScriptPubKey

If mining has been carried out now

cbTx := NewCoinbaseTX(from, "")
        txs := []*Transaction{cbTx, tx}
        newBlock := bc.MineBlock(txs)
        UTXOSet.Update(newBlock)



func NewCoinbaseTX(to, data string) *Transaction {
    If data = = "" {// if the data is empty, a random data is generated
        randData := make([]byte, 20)
        _, err := rand.Read(randData)
        if err != nil {
            log.Panic(err)
        }
        data = fmt.Sprintf("%x", randData)
    }//Generate a mining transaction
    txin := TXInput{[]byte{}, -1, nil, []byte(data)}
    txout := NewTXOutput(subsidy, to)
    tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}}
    tx.ID = tx.Hash()
    return &tx
}

Func (BC * blockchain) mineblock (transactions [] * transaction) * block {// start mining
    var lastHash []byte
    var lastHeight int
    for _, tx := range transactions {
        // TODO: ignore transaction if it's not valid
        if bc.VerifyTransaction(tx) != true {
            Log. Panic ("error: invalid transaction") // authenticate transactions packaged in blocks
        }
    }

    err := bc.db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        Lasthash = b.get ([] byte ("L") // get the hash value of the latest block
        blockData := b.Get(lastHash)
        Block: = deserializeblock (blockdata) // de sequence the latest block
        lastHeight = block.Height
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    newBlock := NewBlock(transactions, lastHash, lastHeight+1)
    Err = bc.db.update (func (TX * bolt. TX) error {// update blockchain database
        b := tx.Bucket([]byte(blocksBucket))
        err := b.Put(newBlock.Hash, newBlock.Serialize())
        if err != nil {
            log.Panic(err)
        }
        err = b.Put([]byte("l"), newBlock.Hash)
        if err != nil {
            log.Panic(err)
        }
        bc.tip = newBlock.Hash
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    return newBlock
}
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
    if tx.IsCoinbase() {
        return true
    }
    prevTXs := make(map[string]Transaction)
    for _, vin := range tx.Vin {
        prevTX, err := bc.FindTransaction(vin.Txid)
        if err != nil {
            log.Panic(err)
        }
        prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
    }
    return tx.Verify(prevTXs)
}
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
    If tx.iscoinbase() {// judge whether it is a large transaction
        return true
    }
    for _, vin := range tx.Vin {
        if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
            Log. Panic ("error: previous transaction is not correct") // judge the validity of the input address
        }
    }
    Txcopy: = tx.trimmedcopy() // create a transaction copy of the cropped version
    Curve: = elliptic. P256() // we need the same block to generate the key pair
    for inID, vin := range tx.Vin {
        prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
        txCopy.Vin[inID].Signature = nil
        txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
        r := big.Int{}
        s := big.Int{}
        sigLen := len(vin.Signature)
        r.SetBytes(vin.Signature[:(sigLen / 2)])
        s.SetBytes(vin.Signature[(sigLen / 2):])
        x := big.Int{}
        y := big.Int{}
        keyLen := len(vin.PubKey)
        x.SetBytes(vin.PubKey[:(keyLen / 2)])
        y.SetBytes(vin.PubKey[(keyLen / 2):])
//Here, we unpack the values stored in txinput.signature and txinput.pubkey, because a signature is a pair of numbers and a public key is a pair of coordinates. We used to connect them together for storage. Now we need to unpack them and use them in the crypto / ECDSA function
        dataToVerify := fmt.Sprintf("%x\n", txCopy)
        rawPubKey := ecdsa.PublicKey{curve, &x, &y}
        If ECDSA. Verify (& rawpubkey, [] byte (datatoverify), & R, & S) = = false {// verify
            return false
        }
        txCopy.Vin[inID].PubKey = nil
    }

    return true
}
Func newblock (transactions [] * transaction, prevblockhash [] byte, height int) * block {// generate a new block
    Block: = & block {time. Now(). Unix(), transactions, prevblockhash, [] byte {}, 0, height} // define the data structure
    POW: = newproofofwork (block) // define the data structure of workload proof
    Nonce, hash: = pow. Run() // Mining
    block.Hash = hash[:]
    block.Nonce = nonce
    return block
}
func (pow *ProofOfWork) Run() (int, []byte) {
    var hashInt big.Int
    var hash [32]byte
    nonce := 0
    fmt.Printf("Mining a new block")
    for nonce < maxNonce {
        data := pow.prepareData(nonce)
        hash = sha256.Sum256(data)
        fmt.Printf("\r%x", hash)
        hashInt.SetBytes(hash[:])
        if hashInt.Cmp(pow.target) == -1 {
            break
        } else {
            nonce++
        }
    }
    fmt.Print("\n\n")

    return nonce, hash[:]
}
func (pow *ProofOfWork) prepareData(nonce int) []byte {
    data := bytes.Join(
        [][]byte{
            pow.block.PrevBlockHash,
            pow.block.HashTransactions(),
            IntToHex(pow.block.Timestamp),
            IntToHex(int64(targetBits)),
            IntToHex(int64(nonce)),
        },
        []byte{},
    )

    return data
}
func (u UTXOSet) Update(block *Block) {
    db := u.Blockchain.db
    err := db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoBucket))
        for _, tx := range block.Transactions {
            if tx.IsCoinbase() == false {
                for _, vin := range tx.Vin {
                    updatedOuts := TXOutputs{}
                    outsBytes := b.Get(vin.Txid)
                    outs := DeserializeOutputs(outsBytes)

                    for outIdx, out := range outs.Outputs {
                        if outIdx != vin.Vout {
                            updatedOuts.Outputs = append(updatedOuts.Outputs, out)
                        }
                    }

                    if len(updatedOuts.Outputs) == 0 {
                        err := b.Delete(vin.Txid)
                        if err != nil {
                            log.Panic(err)
                        }
                    } else {
                        err := b.Put(vin.Txid, updatedOuts.Serialize())
                        if err != nil {
                            log.Panic(err)
                        }
                    }

                }
            }
            newOutputs := TXOutputs{}
            for _, out := range tx.Vout {
                newOutputs.Outputs = append(newOutputs.Outputs, out)
            }
            err := b.Put(tx.ID, newOutputs.Serialize())
            if err != nil {
                log.Panic(err)
            }
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
}

reference resources

https://jeiwan.cc/

This is the end of this article about using go language to realize bitcoin transaction. For more information about bitcoin transaction in go language, please search the previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!

Recommended Today

Implementation example of go operation etcd

etcdIt is an open-source, distributed key value pair data storage system, which provides shared configuration, service registration and discovery. This paper mainly introduces the installation and use of etcd. Etcdetcd introduction etcdIt is an open source and highly available distributed key value storage system developed with go language, which can be used to configure sharing […]