佳木斯湛栽影视文化发展公司

主頁 > 知識庫 > 教你用go語言實現(xiàn)比特幣交易功能(Transaction)

教你用go語言實現(xiàn)比特幣交易功能(Transaction)

熱門標簽:地方門戶網(wǎng)站 鐵路電話系統(tǒng) 服務外包 網(wǎng)站排名優(yōu)化 百度競價排名 AI電銷 Linux服務器 呼叫中心市場需求

比特幣交易

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

type UTXOSet struct {
    Blockchain *Blockchain
}

然后我們從錢包文件中獲取我們的錢包集合(wallets),接著調用我們的轉賬函數(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 {    //如果能夠使用的輸出小于目標值,則返回錯誤
        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ù)結構為

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)     //為輸出開辟一塊內存空間
    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集中的輸出是的所有者是該公鑰所對應的人
                    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))    //設置輸出的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. 一個由接收者地址鎖定。這是給其他地址實際轉移的幣。
  2. 一個由發(fā)送者地址鎖定。這是一個找零。只有當未花費輸出超過新交易所需時產生。記?。狠敵鍪遣豢稍俜值?。
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 被設置為所引用輸出的PubKeyHash
/
        dataToSign := fmt.Sprintf("%x\n", txCopy)
        r, s, err := ecdsa.Sign(rand.Reader, privKey, []byte(dataToSign))//我們通過private對txCopy進行簽名將這串數(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設置為空
        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ù)字,一個公鑰就是一對坐標。我們之前為了存儲將它們連接在一起,現(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 {//產生一個新的塊
    block := Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height}//定義數(shù)據(jù)結構
    pow := NewProofOfWork(block)    //定義工作量證明的數(shù)據(jù)結構
    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)
    }
}

參考

https://jeiwan.cc/

到此這篇關于利用go語言實現(xiàn)比特幣交易(Transaction)的文章就介紹到這了,更多相關go語言比特幣交易內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • go語言實現(xiàn)簡易比特幣系統(tǒng)之交易簽名及校驗功能
  • go語言實現(xiàn)簡易比特幣系統(tǒng)錢包的原理解析
  • go語言實戰(zhàn)之實現(xiàn)比特幣地址校驗步驟

標簽:仙桃 衡水 黃山 湘潭 蘭州 銅川 崇左 湖南

巨人網(wǎng)絡通訊聲明:本文標題《教你用go語言實現(xiàn)比特幣交易功能(Transaction)》,本文關鍵詞  ;如發(fā)現(xiàn)本文內容存在版權問題,煩請?zhí)峁┫嚓P信息告之我們,我們將及時溝通與處理。本站內容系統(tǒng)采集于網(wǎng)絡,涉及言論、版權與本站無關。
  • 相關文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    乐山市| 修武县| 多伦县| 抚松县| 梁河县| 土默特左旗| 沿河| 都匀市| 永仁县| 云林县| 普安县| 云和县| 蓬莱市| 花莲县| 巨鹿县| 永昌县| 江安县| 扬中市| 平陆县| 藁城市| 神农架林区| 合江县| 安图县| 荔波县| 巴南区| 赤水市| 循化| 济南市| 扬中市| 左权县| 宁安市| 阳信县| 鄂伦春自治旗| 彰武县| 神池县| 兰州市| 凤城市| 枝江市| 勃利县| 靖宇县| 屏东县|