溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

使用golang怎么實現(xiàn)一個比特幣交易功能

發(fā)布時間:2021-05-13 15:56:53 來源:億速云 閱讀:139 作者:Leah 欄目:開發(fā)技術(shù)

使用golang怎么實現(xiàn)一個比特幣交易功能?針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

比特幣交易

交易(transaction)是比特幣的核心所在,而區(qū)塊鏈唯一的目的,也正是為了能夠安全可靠地存儲交易。在區(qū)塊鏈中,交易一旦被創(chuàng)建,就沒有任何人能夠再去修改或是刪除它。
對于每一筆新的交易,它的輸入會引用(reference)之前一筆交易的輸出(這里有個例外,coinbase 交易),引用就是花費的意思。所謂引用之前的一個輸出,也就是將之前的一個輸出包含在另一筆交易的輸入當(dāng)中,就是花費之前的交易輸出。交易的輸出,就是幣實際存儲的地方。下面的圖示闡釋了交易之間的互相關(guān)聯(lián):

使用golang怎么實現(xiàn)一個比特幣交易功能 

注意:

有一些輸出并沒有被關(guān)聯(lián)到某個輸入上

一筆交易的輸入可以引用之前多筆交易的輸出

一個輸入必須引用一個輸出

貫穿本文,我們將會使用像“錢(money)”,“幣(coin)”,“花費(spend)”,“發(fā)送(send)”,“賬戶(account)” 等等這樣的詞。但是在比特幣中,其實并不存在這樣的概念。交易僅僅是通過一個腳本(script)來鎖定(lock)一些值(value),而這些值只可以被鎖定它們的人解鎖(unlock)。

每一筆比特幣交易都會創(chuàng)造輸出,輸出都會被區(qū)塊鏈記錄下來。給某個人發(fā)送比特幣,實際上意味著創(chuàng)造新的 UTXO 并注冊到那個人的地址,可以為他所用。
交易的主函數(shù):

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)    //獲取區(qū)塊鏈實例
    UTXOSet := UTXOSet{bc}    //創(chuàng)建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數(shù)據(jù)庫中獲取blockchain實例(我們利用一個數(shù)據(jù)庫實現(xiàn)區(qū)塊鏈數(shù)據(jù)的存儲,這里讀者可以忽略),其中讀取數(shù)據(jù)庫的代碼如下

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)    //打開數(shù)據(jù)庫
    if err != nil {
        log.Panic(err)
    }
    err = db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        tip = b.Get([]byte("l"))  //讀取最新的區(qū)塊鏈
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    bc := Blockchain{tip, db}
    return &bc
}

其中我們的區(qū)塊鏈的基本原型為

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

獲取完成區(qū)塊鏈實例后,我們創(chuàng)建出一個utxo集合,其數(shù)據(jù)結(jié)構(gòu)為

type UTXOSet struct {
    Blockchain *Blockchain
}

然后我們從錢包文件中獲取我們的錢包集合(wallets),接著調(diào)用我們的轉(zhuǎn)賬函數(shù)。

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 {    //如果能夠使用的輸出小于目標(biāo)值,則返回錯誤
        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))    //創(chuàng)建新的交易輸出
    if acc > amount {
        outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change    //找零輸出
    }
    tx := Transaction{nil, inputs, outputs}
    tx.ID = tx.Hash()    //創(chuàng)建一筆交易
    UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey)       //對交易簽名
    return &tx
}

對于一筆交易來說,其數(shù)據(jù)結(jié)構(gòu)為

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),要花這筆錢,必須要解鎖該腳本。一個輸入引用了之前交易的一個輸出:Txid 存儲的是之前交易的 ID,Vout 存儲的是該輸出在那筆交易中所有輸出的索引(因為一筆交易可能有多個輸出,需要有信息指明是具體的哪一個)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)     //為輸出開辟一塊內(nèi)存空間
    accumulated := 0       
    db := u.Blockchain.db             //獲取存取區(qū)塊鏈的數(shù)據(jù)庫
    err := db.View(func(tx *bolt.Tx) error {               //讀取數(shù)據(jù)庫
        b := tx.Bucket([]byte(utxoBucket))
        c := b.Cursor()
        for k, v := c.First(); k != nil; k, v = c.Next() {               //遍歷數(shù)據(jù)庫
            txID := hex.EncodeToString(k)
            outs := DeserializeOutputs(v)
            for outIdx, out := range outs.Outputs {
                if out.IsLockedWithKey(pubkeyHash) && accumulated < amount {          //如果能夠解鎖輸出,代表utxo集中的輸出是的所有者是該公鑰所對應(yīng)的人
                    accumulated += out.Value     //累加值
                    unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)     //加到數(shù)組中
                }
            }
        }

        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))    //設(shè)置輸出的pubhashkey
    return txo
}
func (out *TXOutput) Lock(address []byte) {
    pubKeyHash := Base58Decode(address)
    pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
    out.PubKeyHash = pubKeyHash
}

在創(chuàng)建新的輸出時,我們必須找到所有的為花費的輸出,并且確保他們有足夠的價值(value),這就是FindSpendableOutputs 要做的事情,隨后,對于每個找到的輸出,會創(chuàng)建一個引用該輸出的輸入。接下來,我們創(chuàng)建兩個輸出:

  1. 一個由接收者地址鎖定。這是給其他地址實際轉(zhuǎn)移的幣。

  2. 一個由發(fā)送者地址鎖定。這是一個找零。只有當(dāng)未花費輸出超過新交易所需時產(chǎn)生。記?。狠敵鍪遣豢稍俜值摹?/p>

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
    }//判斷是是否為發(fā)幣交易,因為發(fā)幣交易沒有輸入,故不用進行簽名

    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 被設(shè)置為所引用輸出的PubKeyHash
/
        dataToSign := fmt.Sprintf("%x\n", txCopy)
        r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign))//我們通過private對txCopy進行簽名將這串?dāng)?shù)字連接起來儲存在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設(shè)置為空
        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
}

交易必須被簽名,因為這是保證發(fā)送方不會花費其他人的幣的唯一方式,如果一個簽名是無效的,那么這筆交易也會被認為是無效的,因為這筆交易無法被加到區(qū)塊鏈中。考慮到交易解鎖的是之前的輸出,然后重新分配里面的價值,并鎖定新的輸出,那么必須要簽名一下的數(shù)據(jù)

  • 存儲在已經(jīng)解鎖輸出的公鑰哈希,他識別了一筆交易的發(fā)送方

  • 存儲在新的鎖定輸出里面的公鑰哈希,他識別了一筆交易的接收方

  • 新的輸出值

因此,在比特幣里,所簽名的并不是一個交易,而是一個去除部分簽名的輸入的副本,輸入里面存儲了被引用輸出的ScriptPubKey

如果現(xiàn)在進行過挖礦

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



func NewCoinbaseTX(to, data string) *Transaction {
    if data == "" {  //如果數(shù)據(jù)為空生成一個隨機數(shù)據(jù)
        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")   //對打包在區(qū)塊中的交易進行認證
        }
    }

    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 {    //更新區(qū)塊鏈數(shù)據(jù)庫
        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()    //創(chuàng)建一個裁剪版本的交易副本
    curve := elliptic.P256()    //我們需要相同區(qū)塊用于生成密鑰對
    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 中的值,因為一個簽名就是一對數(shù)字,一個公鑰就是一對坐標(biāo)。我們之前為了存儲將它們連接在一起,現(xiàn)在我們需要對它們進行解包在 crypto/ecdsa 函數(shù)中使用
        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 {//產(chǎn)生一個新的塊
    block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height}//定義數(shù)據(jù)結(jié)構(gòu)
    pow := NewProofOfWork(block)    //定義工作量證明的數(shù)據(jù)結(jié)構(gòu)
    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可以做服務(wù)器端開發(fā),但golang很適合做日志處理、數(shù)據(jù)打包、虛擬機處理、數(shù)據(jù)庫代理等工作。在網(wǎng)絡(luò)編程方面,它還廣泛應(yīng)用于web應(yīng)用、API應(yīng)用等領(lǐng)域。

關(guān)于使用golang怎么實現(xiàn)一個比特幣交易功能問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI