主页 > imtoken国际版下载 > 如何使用golang实现一个比特币交易功能

如何使用golang实现一个比特币交易功能

imtoken国际版下载 2023-06-11 05:57:34

如何使用golang实现一个比特币交易功能? 针对这个问题,本文详细介绍了相应的分析和解决方法,希望能帮助更多想要解决这个问题的朋友找到更简单更容易的方法。

比特币交易

交易是比特币的核心,区块链的唯一目的就是安全可靠地存储交易。 在区块链中,交易一旦创建,任何人都无法修改或删除。

对于每一笔新的交易,它的输入都会参考上一笔交易的输出(这里有个例外,coinbase交易),参考就是花费的意思。 所谓引用之前的输出,就是将之前的输出包含在另一笔交易的输入中,也就是花费之前的交易输出。 交易的输出是硬币实际存储的地方。 下图说明了事务之间的相互关系:

使用golang怎么实现一个比特币交易功能

注意:

有些输出与输入无关

一笔交易的输入可以参考之前多笔交易的输出

输入必须引用输出

在整篇文章中,我们将使用“金钱”、“硬币”、“花费”、“发送”、“帐户”等词。 但是在比特币中,没有这样的概念。 一个交易只是一个脚本(script)来锁定(lock)一些值(value),而这些值只能被锁定它们的人解锁(unlock)。

每笔比特币交易都会产生一个输出,并记录在区块链上。 将比特币发送给某人实际上意味着创建一个新的 UTXO 并将其注册到该人的地址,该地址可供他使用。

交易的主要功能:

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)    //获取区块链实例
    UTXOSet := UTXOSet{bc}    //创建UTXO集
    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!")
}

我们从头开始分析整个交易过程,首先使用ValidateAddress()方法判断输入地址是否为合法的比特币地址,然后从我们的blotDB数据库中获取区块链实例(我们使用一个数据库来存储区块链数据,读者这里可以忽略),其中读取数据库的代码如下

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)    //打开数据库
    if err != nil {
        log.Panic(err)
    }
    err = db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        tip = b.Get([]byte("l"))  //读取最新的区块链
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    bc := Blockchain{tip, db}
    return &bc
}

我们区块链的基本原型是

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

矿工为什么能见证比特币交易_比特币如何应用交易_比特币交易流程

}

获取区块链实例后,我们创建一个utxo集合,其数据结构为

type UTXOSet struct {
    Blockchain *Blockchain
}

然后我们从钱包文件中获取我们的钱包集合(wallets),然后调用我们的转账函数。

func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction {
    var inputs []TXInput
    var outputs []TXOutput
    pubKeyHash := HashPubKey(wallet.PublicKey)
    acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount)    //找到能够使用的输出
    if acc < amount {    //如果能够使用的输出小于目标值,则返回错误
        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))    //创建新的交易输出
    if acc > amount {
        outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change    //找零输出
    }
    tx := Transaction{nil, inputs, outputs}
    tx.ID = tx.Hash()    //创建一笔交易
    UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey)       //对交易签名
    return &tx
}

对于一个事务,它的数据结构是

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
}

对于一笔交易,其输出主要包括两部分:一定数量的比特币(Value)和锁定的脚本(ScriptPubKey)。 要花这笔钱,必须解锁剧本。 一个input是指上一笔交易的output:Txid存储了上一笔交易的ID比特币如何应用交易,Vout存储了该笔交易output的所有output的index(因为一个transaction可能有多个output,所以需要信息指明哪个是output)具体哪一个)Signature是签名,Pubkey是公钥,两者都保证用户不能花属于别人的币。

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)     //为输出开辟一块内存空间
    accumulated := 0       
    db := u.Blockchain.db             //获取存取区块链的数据库
    err := db.View(func(tx *bolt.Tx) error {               //读取数据库
        b := tx.Bucket([]byte(utxoBucket))
        c := b.Cursor()
        for k, v := c.First(); k != nil; k, v = c.Next() {               //遍历数据库

比特币如何应用交易_矿工为什么能见证比特币交易_比特币交易流程

            txID := hex.EncodeToString(k)             outs := DeserializeOutputs(v)             for outIdx, out := range outs.Outputs {                 if out.IsLockedWithKey(pubkeyHash) && accumulated < amount {          //如果能够解锁输出,代表utxo集中的输出是的所有者是该公钥所对应的人                     accumulated += out.Value     //累加值                     unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)     //加到数组中                 }             }         }         return nil     })     if err != nil {         log.Panic(err)     }     return accumulated, unspentOutputs } func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {      //判断输出是否能够被某个公钥解锁     return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0 }  func NewTXOutput(value int, address string) *TXOutput {     txo := &TXOutput{value, nil}    //注册一个输出     txo.Lock([]byte(address))    //设置输出的pubhashkey     return txo } func (out *TXOutput) Lock(address []byte) {     pubKeyHash := Base58Decode(address)     pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]     out.PubKeyHash = pubKeyHash }

在创建新的输出时,我们必须找到所有不可花费的输出,并确保它们具有足够的价值(value),这就是 FindSpendableOutputs 所做的。 然后,对于找到的每个输出,输出对输入的引用。 接下来,我们创建两个输出:

一个被收件人地址锁定。 这是将硬币实际转移到其他地址。

一个被发件人地址锁定。 这是一个变化。 只有当未花费的输出超过新交易所需时才会产生。 记住:输出是不可分割的。

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(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {//方法接受一个私钥和之前一个交易的map
    if tx.IsCoinbase() {
        return
    }//判断是是否为发币交易,因为发币交易没有输入,故不用进行签名
    for _, vin := range tx.Vin {
        if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
            log.Panic("ERROR: Previous transaction is not correct")
        }
    }
    txCopy := tx.TrimmedCopy()  //将会被签名的是修剪后的交易副本,而不是一个完整的交易
    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
//迭代副本中的每一个输入,在每个输入中,Pubkey 被设置为所引用输出的PubKeyHash
/
        dataToSign := fmt.Sprintf("%x\n", txCopy)
        r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign))//我们通过private对txCopy进行签名将这串数字连接起来储存在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 {//将输入的TXInput.Signature 和TXIput.PubKey设置为空         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 }

交易必须签名,因为只有这样才能保证发送方不会花费别人的币,如果签名无效,那么这笔交易也将被视为无效比特币如何应用交易,因为这笔交易无法添加到区块中的区域chain.考虑到交易解锁之前的输出,然后重新分配里面的值,并锁定新的输出,那么必须要签名的数据

因此,在比特币中,签名的不是交易,而是存储引用输出的 ScriptPubKey 的部分签名输入的副本

如果现在挖矿

   cbTx := NewCoinbaseTX(from, "")
        txs := []*Transaction{cbTx, tx}
        newBlock := bc.MineBlock(txs)
        UTXOSet.Update(newBlock)
func NewCoinbaseTX(to, data string) *Transaction {
    if data == "" {  //如果数据为空生成一个随机数据
        randData := make([]byte, 20)
        _, err := rand.Read(randData)
        if err != nil {
            log.Panic(err)
        }
        data = fmt.Sprintf("%x", randData)
    }//生成一笔挖矿交易
    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 {   //开始挖矿
    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")   //对打包在区块中的交易进行认证
        }
    }
    err := bc.db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        lastHash = b.Get([]byte("l"))   //获取最新的一个块的hash值
        blockData := b.Get(lastHash)
        block := DeserializeBlock(blockData)  //将最新的一个块解序列
        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 {    //更新区块链数据库
        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() {   //判断是否为大笔交易         return true     }     for _, vin := range tx.Vin {         if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {             log.Panic("ERROR: Previous transaction is not correct")   //判断输入地址的有效性         }     }     txCopy := tx.TrimmedCopy()    //创建一个裁剪版本的交易副本     curve := elliptic.P256()    //我们需要相同区块用于生成密钥对     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):]) //这里我们解包存储在 TXInput.Signature 和 TXInput.PubKey 中的值,因为一个签名就是一对数字,一个公钥就是一对坐标。我们之前为了存储将它们连接在一起,现在我们需要对它们进行解包在 crypto/ecdsa 函数中使用         dataToVerify := fmt.Sprintf("%x\n", txCopy)         rawPubKey := ecdsa.PublicKey{curve, &x, &y}         if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false {  //验证             return false         }         txCopy.Vin[inID].PubKey = nil     }     return true } func NewBlock(transactions []*Transaction, prevBlockHash []byte, height int) *Block {//产生一个新的块     block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height}//定义数据结构     pow := NewProofOfWork(block)    //定义工作量证明的数据结构     nonce, hash := pow.Run()    //挖矿     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)     } }

golang适合做什么

golang可以做服务端开发,但是golang非常适合做日志处理、数据打包、虚拟机处理、数据库代理等工作。 在网络编程方面,它也广泛应用于Web应用、API应用等领域。

关于如何使用golang实现比特币交易功能的问题的答案分享到这里。 希望以上内容能够对大家有所帮助。 如果您还有很多疑惑没有解决,可以关注易速云行业资讯频道,了解更多相关知识。