Introduction
在上一篇文章,我們開始實施交易。您還了解了交易的非個人性質:沒有使用者帳戶,您的個人資料(例如,姓名,護照號碼或SSN)不是必需的,也不儲存在比特幣的任何地方。但仍然必須有一些東西可以確定您是交易輸出的所有者(即鎖定在這些輸出上的硬幣的所有者)。這就是比特幣需要的地址。到目前為止,我們已經使用任意使用者定義的字串作為地址,現在是實現真實地址的時候了,因為它們是在比特幣中實現的。
這部分介紹了重要的程式碼更改,因此在這裡解釋所有這些都沒有意義。請參閱this page檢視自上一篇文章以來的所有更改。
Bitcoin Address
以下是比特幣地址的示例:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。這是第一個比特幣地址,據稱屬於Satoshi Nakamoto。比特幣地址是公開的。如果您想向某人傳送硬幣,您需要知道他們的地址。但地址(儘管是獨一無二的)並不能確定您是“錢包”的擁有者。實際上,這樣的地址是公鑰的人類可讀表示。在比特幣中,您的身份是儲存在您計算機上的一對(或多對)私鑰和公鑰(或儲存在您有權訪問的其他地方)。比特幣依靠加密演算法的組合來建立這些金鑰,並保證世界上沒有其他人可以訪問您的硬幣而無需實際訪問您的金鑰。我們來討論一下這些演算法是什麼。
公鑰加密
公鑰加密演算法使用金鑰對:公鑰和私鑰。公鑰不敏感,可以向任何人透露。相反,私鑰不應該被披露:除了所有者之外,沒有人可以訪問它們,因為它是用作所有者識別符號的私鑰。你是私人金鑰(當然是加密貨幣世界)。
從本質上講,比特幣錢包只是一對這樣的鑰匙。當您安裝錢包應用程式或使用比特幣客戶端生成新地址時,會為您生成一對金鑰。控制私鑰的人控制比特幣中傳送給該金鑰的所有硬幣。
私鑰和公鑰只是位元組的隨機序列,因此它們不能在螢幕上列印並由人讀取。這就是比特幣使用演算法將公鑰轉換為人類可讀字串的原因。
如果你曾經使用過比特幣錢包應用程式,很可能會為你生成一個助記符密碼短語。使用這些短語代替私鑰,並且可以用於生成它們。這個機制是在BIP-039.
好的,我們現在知道用比特幣識別使用者的是什麼。但比特幣如何檢查交易輸出(以及儲存在其上的硬幣)的所有權?
Digital Signatures
在數學和密碼學中,有一個數字簽名的概念 - 演算法可以保證:
- 從發件人轉移到收件人時,資料未被修改;
- 該資料是由某個發件人建立的;
- 發件人不能拒絕傳送資料。
通過將簽名演算法應用於資料(即,對資料進行簽名),可以獲得簽名,稍後可以對其進行驗證。使用私鑰進行數字簽名,驗證需要公鑰。
為了簽署資料,我們需要以下內容:
- 需要簽名的資料;
- 私鑰
簽名操作產生簽名,該簽名儲存在事務輸入中。為了驗證簽名,需要以下內容:
- 已簽署的資料;
- 簽名;
- 公鑰.
簡單來說,驗證過程可以描述為:檢查此簽名是否使用用於生成公鑰的私鑰從此資料中獲取。
數字簽名不是加密,您無法從簽名重建資料。這類似於雜湊:您通過雜湊演算法執行資料並獲得資料的唯一表示。簽名和雜湊之間的區別是金鑰對:它們使簽名驗證成為可能。
但金鑰對也可用於加密資料:私鑰用於加密,公鑰用於解密資料。比特幣不使用加密演算法。
比特幣中的每個交易輸入都由建立交易的人簽名。比特幣中的每筆交易必須經過驗證才能進入區塊。驗證手段(除了其他程式):
- 檢查輸入是否有權使用先前事務的輸出。
- 檢查事務簽名是否正確。
示意性地,簽署資料和驗證簽名的過程看起來像這樣:
現在讓我們回顧一下交易的整個生命週期:
- 最初,有一個包含coinbase交易的創世塊。在coinbase交易中沒有實際輸入,因此不需要簽名。 coinbase事務的輸出包含一個雜湊的公鑰(
RIPEMD16(SHA256(PubKey))
使用演算法)。 - 當一個人傳送硬幣時,就會建立一個交易。交易的輸入將參考先前交易的輸出。每個輸入都將儲存一個公鑰(不是雜湊)和整個交易的簽名。
- 比特幣網路中接收交易的其他節點將對其進行驗證。除了其他東西,他們還會檢查:輸入中公鑰的雜湊值與引用輸出的雜湊值匹配(這可以確保傳送者只花費屬於它們的硬幣);簽名是正確的(這確保了交易是由硬幣的真正所有者建立的)。
- 當礦工節點準備挖掘新塊時,它會將事務放入塊中並開始挖掘它。
- 當阻塞被挖掘時,網路中的每個其他節點都會收到一條訊息,說明該塊已被挖掘,並將該塊新增到區塊鏈中。
- 將塊新增到區塊鏈後,事務完成,其輸出可在新事務中引用。
橢圓曲線密碼學
如上所述,公鑰和私鑰是隨機位元組的序列。由於它是用於識別硬幣所有者的私鑰,因此存在必需條件:隨機性演算法必須產生真正的隨機位元組。我們不希望意外生成其他人擁有的私鑰。
比特幣使用橢圓曲線生成私鑰。橢圓曲線是一個複雜的數學概念,我們在這裡不會詳細解釋(如果你很好奇,請檢視this gentle introduction to elliptic curves警告:數學公式!)。我們需要知道的是,這些曲線可用於生成非常大且隨機的數字。比特幣使用的曲線可以隨機選取0到2²之間的數字(當可見宇宙中有10⁷⁸到10⁸之間的原子時,這個數字約為10⁷⁷)。如此巨大的上限意味著幾乎不可能兩次生成相同的私鑰。
此外,比特幣使用(我們將)ECDSA(橢圓曲線數字簽名演算法)演算法來簽署交易。
Base58
現在讓我們回到上面提到的比特幣地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。現在我們知道這是一個人類可讀的公鑰表示。如果我們解碼它,這就是公鑰的樣子(作為十六進位制系統中寫入的位元組序列):
0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93
複製程式碼
比特幣使用Base58演算法將公鑰轉換為人類可讀格式。該演算法與著名的Base64非常相似,但它使用較短的字母表:從字母表中刪除了一些字母,以避免一些使用字母相似性的攻擊。因此,沒有這些符號:0(零),O(大寫o),I(大寫i),l(小寫L),因為它們看起來相似。此外,沒有+和/符號。
讓我們示意地顯示從公鑰獲取地址的過程:
因此,上述解碼公鑰由三部分組成:
Version Public key hash Checksum
00 62E907B15CBF27D5425399EBF6F0FB50EBB88F18 C29B7D93
複製程式碼
由於雜湊函式是一種方式(即,它們不能被反轉),因此無法從雜湊中提取公鑰。但我們可以通過執行它來檢查是否使用了公鑰來獲取雜湊,以及儲存雜湊函式和比較雜湊值。
好的,現在我們已經完成了所有部分,讓我們編寫一些程式碼。當用程式碼編寫時,一些概念應該更清楚。
實現地址
我們將從開始Wallet
structure:
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
type Wallets struct {
Wallets map[string]*Wallet
}
func NewWallet() *Wallet {
private, public := newKeyPair()
wallet := Wallet{private, public}
return &wallet
}
func newKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, pubKey
}
複製程式碼
錢包只不過是一對鑰匙。我們還需要Wallets
鍵入以儲存錢包集合,將它們儲存到檔案中,然後從中載入它們。在建築功能Wallet
生成新金鑰對。該newKeyPair
功能很簡單:ECDSA基於橢圓曲線,所以我們需要一個。接下來,使用該曲線生成私鑰,並且從私鑰生成公鑰。需要注意的一點是:在基於橢圓曲線的演算法中,公鑰是曲線上的點。因此,公鑰是X,Y座標的組合。在比特幣中,這些座標被連線起來並形成公鑰。
現在,讓我們生成一個地址:
func (w Wallet) GetAddress() []byte {
pubKeyHash := HashPubKey(w.PublicKey)
versionedPayload := append([]byte{version}, pubKeyHash...)
checksum := checksum(versionedPayload)
fullPayload := append(versionedPayload, checksum...)
address := Base58Encode(fullPayload)
return address
}
func HashPubKey(pubKey []byte) []byte {
publicSHA256 := sha256.Sum256(pubKey)
RIPEMD160Hasher := ripemd160.New()
_, err := RIPEMD160Hasher.Write(publicSHA256[:])
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
func checksum(payload []byte) []byte {
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
return secondSHA[:addressChecksumLen]
}
複製程式碼
以下是將公鑰轉換為Base58地址的步驟:
- 使用公鑰並將其雜湊兩次
RIPEMD160(SHA256(PubKey))
雜湊演算法。 - 將地址生成演算法的版本新增到雜湊。
- 通過雜湊步驟2的結果來計算校驗和
SHA256(SHA256(payload))
。校驗和是結果雜湊的前四個位元組。 - 將校驗和附加到
version+PubKeyHash
. - 用Base58為
version+PubKeyHash+checksum
進行編碼。
結果,你會得到一個真實的比特幣地址,你甚至可以檢視其餘額blockchain.info。但我可以向您保證,無論您生成新地址多少次並檢查其餘額,餘額為0。這就是選擇合適的公鑰加密演算法如此重要的原因:考慮私鑰是隨機數,生成相同數字的機會必須儘可能低。理想情況下,它必須與“從不”一樣低。
另外,請注意您不需要連線到比特幣節點來獲取地址。地址生成演算法利用在許多程式語言和庫中實現的開放演算法的組合。
現在我們需要修改它們的輸入和輸出以使用地址:
type TXInput struct {
Txid []byte
Vout int
Signature []byte
PubKey []byte
}
func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
lockingHash := HashPubKey(in.PubKey)
return bytes.Compare(lockingHash, pubKeyHash) == 0
}
type TXOutput struct {
Value int
PubKeyHash []byte
}
func (out *TXOutput) Lock(address []byte) {
pubKeyHash := Base58Decode(address)
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
out.PubKeyHash = pubKeyHash
}
func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}
複製程式碼
請注意,我們已不再使用ScriptPubKey
和 ScriptSig
欄位,因為我們不打算實現指令碼語言。代替,ScriptSig
被分為 Signature
和 PubKey
欄位,並且 ScriptPubKey被重新命名為
PubKeyHash
。我們將實現與比特幣相同的輸出鎖定/解鎖和輸入簽名邏輯,但我們將在方法中執行此操作。
UsesKey
方法檢查輸入是否使用特定鍵來解鎖輸出。請注意,輸入儲存原始公鑰(即,不進行雜湊處理),但該函式採用雜湊值。IsLockedWithKey
檢查是否提供了公鑰雜湊用於鎖定輸出。這是一個補充功能UsesKey
,它們都被用於FindUnspentTransactions
在事務之間建立連線。
Lock
只需鎖定輸出。當我們向某人傳送硬幣時,我們只知道他們的地址,因此該函式將地址作為唯一的引數。然後解碼該地址,並從中提取公鑰雜湊並儲存在PubKeyHash
欄位.
現在,讓我們檢查一切是否正常:
$ blockchain_go createwallet
Your new address: 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
$ blockchain_go createwallet
Your new address: 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
$ blockchain_go createwallet
Your new address: 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
$ blockchain_go createblockchain -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
0000005420fbfdafa00c093f56e033903ba43599fa7cd9df40458e373eee724d
Done!
$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 10
$ blockchain_go send -from 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -to 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -amount 5
2017/09/12 13:08:56 ERROR: Not enough funds
$ blockchain_go send -from 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -to 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -amount 6
00000019afa909094193f64ca06e9039849709f5948fbac56cae7b1b8f0ff162
Success!
$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 4
$ blockchain_go getbalance -address 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
Balance of '15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h': 6
$ blockchain_go getbalance -address 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
Balance of '1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy': 0
複製程式碼
太好了!現在讓我們實現事務簽名。
實施簽名
必須簽署交易,因為這是比特幣保證不能花錢屬於其他人的唯一方式。如果簽名無效,則該交易也被視為無效,因此無法新增到區塊鏈中。
我們擁有實現交易簽名的所有部分,除了一件事:要簽署的資料。交易的哪些部分實際簽署了?或者一個交易是整體簽署的?選擇要簽名的資料非常重要。問題是要簽名的資料必須包含以獨特方式標識資料的資訊。例如,僅簽署輸出值是沒有意義的,因為此類簽名不會考慮發件人和收件人。
考慮到事務解鎖以前的輸出,重新分配它們的值並鎖定新輸出,必須簽署以下資料:
- 公鑰雜湊儲存在未鎖定的輸出中。這標識了交易的“發件人”。
- 公鑰雜湊儲存在新的鎖定輸出中。這標識了交易的“收件人”。
- 新產出的價值。
如您所見,我們不需要對儲存在輸入中的公鑰進行簽名。因此,在比特幣中,它不是一個已簽名的交易,而是帶有輸入儲存的修剪副本ScriptPubKey
來自參考輸出。
描述了獲取修剪的事務副本的詳細過程here。它可能已經過時,但我沒有找到更可靠的資訊來源。
好吧,它看起來很複雜,所以讓我們開始編碼。我們將從Sign
方法開始:
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
if tx.IsCoinbase() {
return
}
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
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID)
signature := append(r.Bytes(), s.Bytes()...)
tx.Vin[inID].Signature = signature
}
}
複製程式碼
該方法採用私鑰和先前事務的對映。如上所述,為了對事務進行簽名,我們需要訪問事務輸入中引用的輸出,因此我們需要儲存這些輸出的事務。
讓我們一步一步地回顧這個方法:
if tx.IsCoinbase() {
return
}
複製程式碼
Coinbase事務未簽名,因為它們中沒有實際輸入。
txCopy := tx.TrimmedCopy()
複製程式碼
修剪後的副本將被簽名,而非完整交易:
func (tx *Transaction) TrimmedCopy() Transaction {
var inputs []TXInput
var outputs []TXOutput
for _, vin := range tx.Vin {
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
}
複製程式碼
副本將包括所有輸入和輸出,但是TXInput.Signature
和 TXInput.PubKey
會被設定為 nil.
接下來,我們遍歷副本中的每個輸入:
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
複製程式碼
對於美國輸入, Signature
被設定為 nil
(只是仔細檢查)和PubKey
被設定為 PubKeyHash
引用的輸出。此時,除當前交易之外的所有交易都是“空的”,即他們的交易Signature
和 PubKey
欄位設定為nil。從而,輸入單獨簽名雖然這對我們的應用程式來說不是必需的,但比特幣允許事務包含引用不同地址的輸入。
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
複製程式碼
Hash
方法序列化事務並使用SHA-256演算法對其進行雜湊處理。結果雜湊是我們要簽署的資料。獲得雜湊後我們應該重置PubKey
欄位,因此它不會影響進一步的迭代。
現在,中心部分:
r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID)
signature := append(r.Bytes(), s.Bytes()...)
tx.Vin[inID].Signature = signature
複製程式碼
我們用 privKey
對 txCopy.ID
進行簽名。 ECDSA簽名是一對數字,我們將它們連線並儲存在輸入中Signature
欄位.
現在,驗證功能:
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
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
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
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):])
rawPubKey := ecdsa.PublicKey{curve, &x, &y}
if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false {
return false
}
}
return true
}
複製程式碼
該方法非常簡單。首先,我們需要相同的交易副本:
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
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
複製程式碼
這件作品與之相同Sign
方法,因為在驗證過程中我們需要簽署的相同資料。
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
方法.
rawPubKey := ecdsa.PublicKey{curve, &x, &y}
if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false {
return false
}
}
return true
複製程式碼
這是:我們創造了一個ecdsa.PublicKey
使用從輸入中提取的公鑰並執行ecdsa.Verify
傳遞從輸入中提取的簽名。如果驗證了所有輸入,則返回true;如果至少有一個輸入驗證失敗,則返回false。
現在,我們需要一個函式來獲取以前的事務。由於這需要與區塊鏈互動,我們將把它作為一種方法Blockchain
:
func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
bci := bc.Iterator()
for {
block := bci.Next()
for _, tx := range block.Transactions {
if bytes.Compare(tx.ID, ID) == 0 {
return *tx, nil
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return Transaction{}, errors.New("Transaction is not found")
}
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)
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
tx.Sign(privKey, prevTXs)
}
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin {
prevTX, err := bc.FindTransaction(vin.Txid)
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
return tx.Verify(prevTXs)
}
複製程式碼
這些功能很簡單:FindTransaction
按ID查詢事務(這需要迭代區塊鏈中的所有塊);SignTransaction
接受一個事務,查詢它引用的事務並簽名;VerifyTransaction
做同樣的事情,但改為驗證交易。
現在,我們需要實際簽署和驗證交易。簽名發生在NewUTXOTransaction
:
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
...
tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash()
bc.SignTransaction(&tx, wallet.PrivateKey)
return &tx
}
複製程式碼
在將事務放入塊之前進行驗證:
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
var lastHash []byte
for _, tx := range transactions {
if bc.VerifyTransaction(tx) != true {
log.Panic("ERROR: Invalid transaction")
}
}
...
}
複製程式碼
就是這樣!讓我們再一次檢查一切:
$ blockchain_go createwallet
Your new address: 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR
$ blockchain_go createwallet
Your new address: 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab
$ blockchain_go createblockchain -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR
000000122348da06c19e5c513710340f4c307d884385da948a205655c6a9d008
Done!
$ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 6
0000000f3dbb0ab6d56c4e4b9f7479afe8d5a5dad4d2a8823345a1a16cf3347b
Success!
$ blockchain_go getbalance -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR
Balance of '1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR': 4
$ blockchain_go getbalance -address 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab
Balance of '1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab': 6
複製程式碼
什麼都沒有打破。真棒!
我們也要評論一下bc.SignTransaction(&tx, wallet.PrivateKey)
確保無法挖掘未簽名的交易:
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
...
tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash()
// bc.SignTransaction(&tx, wallet.PrivateKey)
return &tx
}
複製程式碼
$ go install
$ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 1
2017/09/12 16:28:15 ERROR: Invalid transaction
複製程式碼
Conclusion
到目前為止我們真的很棒,實現了比特幣的許多關鍵功能!我們已經實現了網路外的幾乎所有內容,在下一部分中,我們將完成交易。
英文原文:https://jeiwan.cc/posts/building-blockchain-in-go-part-5/
更多文章歡迎訪問: http://www.apexyun.com
聯絡郵箱:public@space-explore.com
(未經同意,請勿轉載)