比特幣交易
交易(transaction)是比特幣的核心所在,而區(qū)塊鏈唯一的目的,也正是為了能夠安全可靠地存儲交易。在區(qū)塊鏈中,交易一旦被創(chuàng)建,就沒有任何人能夠再去修改或是刪除它。
對于每一筆新的交易,它的輸入會引用(reference)之前一筆交易的輸出(這里有個例外,coinbase 交易),引用就是花費的意思。所謂引用之前的一個輸出,也就是將之前的一個輸出包含在另一筆交易的輸入當(dāng)中,就是花費之前的交易輸出。交易的輸出,就是幣實際存儲的地方。下面的圖示闡釋了交易之間的互相關(guān)聯(liá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)建兩個輸出:
- 一個由接收者地址鎖定。這是給其他地址實際轉(zhuǎn)移的幣。
- 一個由發(fā)送者地址鎖定。這是一個找零。只有當(dāng)未花費輸出超過新交易所需時產(chǎn)生。記?。狠敵鍪遣豢稍俜值?。
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ā)送方不會花費其他人的幣的唯一方式,如果一個簽名是無效的,那么這筆交易也會被認(rèn)為是無效的,因為這筆交易無法被加到區(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ū)塊中的交易進行認(rèn)證
}
}
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)
}
}
參考
https://jeiwan.cc/
到此這篇關(guān)于利用go語言實現(xiàn)比特幣交易(Transaction)的文章就介紹到這了,更多相關(guān)go語言比特幣交易內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- go語言實現(xiàn)簡易比特幣系統(tǒng)之交易簽名及校驗功能
- go語言實現(xiàn)簡易比特幣系統(tǒng)錢包的原理解析
- go語言實戰(zhàn)之實現(xiàn)比特幣地址校驗步驟