国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

用 Go 構(gòu)建一個(gè)區(qū)塊鏈 -- Part 5: 地址

KunMinX / 2492人閱讀

摘要:比特幣地址這就是一個(gè)真實(shí)的比特幣地址。這是史上第一個(gè)比特幣地址,據(jù)說(shuō)屬于中本聰。當(dāng)你安裝一個(gè)錢包應(yīng)用,或是使用一個(gè)比特幣客戶端來(lái)生成一個(gè)新地址時(shí),它就會(huì)為你生成一對(duì)密鑰。在被放入到一個(gè)塊之前,必須要對(duì)每一筆交易進(jìn)行驗(yàn)證。

翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉(cāng)庫(kù),進(jìn)入 src 目錄執(zhí)行 make 即可。


引言

在上一篇文章中,我們已經(jīng)初步實(shí)現(xiàn)了交易。相信你應(yīng)該了解了交易中的一些天然屬性,這些屬性沒(méi)有絲毫“個(gè)人”色彩的存在:在比特幣中,沒(méi)有用戶賬戶,不需要也不會(huì)在任何地方存儲(chǔ)個(gè)人數(shù)據(jù)(比如姓名,護(hù)照號(hào)碼或者 SSN)。但是,我們總要有某種途徑識(shí)別出你是交易輸出的所有者(也就是說(shuō),你擁有在這些輸出上鎖定的幣)。這就是比特幣地址(address)需要完成的使命。在上一篇中,我們把一個(gè)由用戶定義的任意字符串當(dāng)成是地址,現(xiàn)在我們將要實(shí)現(xiàn)一個(gè)跟比特幣一樣的真實(shí)地址。

本文的代碼實(shí)現(xiàn)變化很大,請(qǐng)點(diǎn)擊 這里 查看所有的代碼更改。
比特幣地址

這就是一個(gè)真實(shí)的比特幣地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa。這是史上第一個(gè)比特幣地址,據(jù)說(shuō)屬于中本聰。比特幣地址是完全公開的,如果你想要給某個(gè)人發(fā)送幣,只需要知道他的地址就可以了。但是,地址(盡管地址也是獨(dú)一無(wú)二的)并不是用來(lái)證明你是一個(gè)“錢包”所有者的信物。實(shí)際上,所謂的地址,只不過(guò)是將公鑰表示成人類可讀的形式而已,因?yàn)樵墓€人類很難閱讀。在比特幣中,你的身份(identity)就是一對(duì)(或者多對(duì))保存在你的電腦(或者你能夠獲取到的地方)上的公鑰(public key)和私鑰(private key)。比特幣基于一些加密算法的組合來(lái)創(chuàng)建這些密鑰,并且保證了在這個(gè)世界上沒(méi)有其他人能夠取走你的幣,除非拿到你的密鑰。下面,讓我們來(lái)討論一下這些算法到底是什么。

公鑰加密

公鑰加密(public-key cryptography)算法使用的是成對(duì)的密鑰:公鑰和私鑰。公鑰并不是敏感信息,可以告訴其他人。但是,私鑰絕對(duì)不能告訴其他人:只有所有者(owner)才能知道私鑰,能夠識(shí)別,鑒定和證明所有者身份的就是私鑰。在加密貨幣的世界中,你的私鑰代表的就是你,私鑰就是一切。

本質(zhì)上,比特幣錢包也只不過(guò)是這樣的密鑰對(duì)而已。當(dāng)你安裝一個(gè)錢包應(yīng)用,或是使用一個(gè)比特幣客戶端來(lái)生成一個(gè)新地址時(shí),它就會(huì)為你生成一對(duì)密鑰。在比特幣中,誰(shuí)擁有了私鑰,誰(shuí)就可以控制所以發(fā)送到這個(gè)公鑰的幣。

私鑰和公鑰只不過(guò)是隨機(jī)的字節(jié)序列,因此它們無(wú)法在屏幕上打印,人類也無(wú)法通過(guò)肉眼去讀取。這就是為什么比特幣使用了一個(gè)轉(zhuǎn)換算法,將公鑰轉(zhuǎn)化為一個(gè)人類可讀的字符串(也就是我們看到的地址)。

如果你用過(guò)比特幣錢包應(yīng)用,很可能它會(huì)為你生成一個(gè)助記符。這樣的助記符可以用來(lái)替代私鑰,并且可以被用于生成私鑰。BIP-039 已經(jīng)實(shí)現(xiàn)了這個(gè)機(jī)制。

好了,現(xiàn)在我們已經(jīng)知道了在比特幣中證明用戶身份的是私鑰。那么,比特幣如何檢查交易輸出(和存儲(chǔ)在里面的幣)的所有權(quán)呢?

數(shù)字簽名

在數(shù)學(xué)和密碼學(xué)中,有一個(gè)數(shù)字簽名(digital signature)的概念,算法可以保證:

當(dāng)數(shù)據(jù)從發(fā)送方傳送到接收方時(shí),數(shù)據(jù)不會(huì)被修改;

數(shù)據(jù)由某一確定的發(fā)送方創(chuàng)建;

發(fā)送方無(wú)法否認(rèn)發(fā)送過(guò)數(shù)據(jù)這一事實(shí)。

通過(guò)在數(shù)據(jù)上應(yīng)用簽名算法(也就是對(duì)數(shù)據(jù)進(jìn)行簽名),你就可以得到一個(gè)簽名,這個(gè)簽名晚些時(shí)候會(huì)被驗(yàn)證。生成數(shù)字簽名需要一個(gè)私鑰,而驗(yàn)證簽名需要一個(gè)公鑰。簽名有點(diǎn)類似于印章,比方說(shuō)我做了一幅畫,完了用印章一蓋,就說(shuō)明了這幅畫是我的作品。給數(shù)據(jù)生成簽名,就是給數(shù)據(jù)蓋了章。

為了對(duì)數(shù)據(jù)進(jìn)行簽名,我們需要下面兩樣?xùn)|西:

要簽名的數(shù)據(jù)

私鑰

應(yīng)用簽名算法可以生成一個(gè)簽名,并且這個(gè)簽名會(huì)被存儲(chǔ)在交易輸入中。為了對(duì)一個(gè)簽名進(jìn)行驗(yàn)證,我們需要以下三樣?xùn)|西:

被簽名的數(shù)據(jù)

簽名

公鑰

簡(jiǎn)單來(lái)說(shuō),驗(yàn)證過(guò)程可以被描述為:檢查簽名是由被簽名數(shù)據(jù)加上私鑰得來(lái),并且公鑰恰好是由該私鑰生成。

數(shù)據(jù)簽名并不是加密,你無(wú)法從一個(gè)簽名重新構(gòu)造出數(shù)據(jù)。這有點(diǎn)像哈希:你在數(shù)據(jù)上運(yùn)行一個(gè)哈希算法,然后得到一個(gè)該數(shù)據(jù)的唯一表示。簽名與哈希的區(qū)別在于密鑰對(duì):有了密鑰對(duì),才有簽名驗(yàn)證。但是密鑰對(duì)也可以被用于加密數(shù)據(jù):私鑰用于加密,公鑰用于解密數(shù)據(jù)。不過(guò)比特幣并不使用加密算法。

在比特幣中,每一筆交易輸入都會(huì)由創(chuàng)建交易的人簽名。在被放入到一個(gè)塊之前,必須要對(duì)每一筆交易進(jìn)行驗(yàn)證。除了一些其他步驟,驗(yàn)證意味著:

檢查交易輸入有權(quán)使用來(lái)自之前交易的輸出

檢查交易簽名是正確的

如圖,對(duì)數(shù)據(jù)進(jìn)行簽名和對(duì)簽名進(jìn)行驗(yàn)證的過(guò)程大致如下:

現(xiàn)在來(lái)回顧一個(gè)交易完整的生命周期:

起初,創(chuàng)世塊里面包含了一個(gè) coinbase 交易。在 coinbase 交易中,沒(méi)有輸入,所以也就不需要簽名。coinbase 交易的輸出包含了一個(gè)哈希過(guò)的公鑰(使用的是
RIPEMD16(SHA256(PubKey)) 算法)

當(dāng)一個(gè)人發(fā)送幣時(shí),就會(huì)創(chuàng)建一筆交易。這筆交易的輸入會(huì)引用之前交易的輸出。每個(gè)輸入會(huì)存儲(chǔ)一個(gè)公鑰(沒(méi)有被哈希)和整個(gè)交易的一個(gè)簽名。

比特幣網(wǎng)絡(luò)中接收到交易的其他節(jié)點(diǎn)會(huì)對(duì)該交易進(jìn)行驗(yàn)證。除了一些其他事情,他們還會(huì)檢查:在一個(gè)輸入中,公鑰哈希與所引用的輸出哈希相匹配(這保證了發(fā)送方只能花費(fèi)屬于自己的幣);簽名是正確的(這保證了交易是由幣的實(shí)際擁有者所創(chuàng)建)。

當(dāng)一個(gè)礦工準(zhǔn)備挖一個(gè)新塊時(shí),他會(huì)將交易放到塊中,然后開始挖礦。

當(dāng)新塊被挖出來(lái)以后,網(wǎng)絡(luò)中的所有其他節(jié)點(diǎn)會(huì)接收到一條消息,告訴其他人這個(gè)塊已經(jīng)被挖出并被加入到區(qū)塊鏈。

當(dāng)一個(gè)塊被加入到區(qū)塊鏈以后,交易就算完成,它的輸出就可以在新的交易中被引用。

橢圓曲線加密

正如之前提到的,公鑰和私鑰是隨機(jī)的字節(jié)序列。私鑰能夠用于證明持幣人的身份,需要有一個(gè)條件:隨機(jī)算法必須生成真正隨機(jī)的字節(jié)。因?yàn)闆](méi)有人會(huì)想要生成一個(gè)私鑰,而這個(gè)私鑰意外地也被別人所有。

比特幣使用橢圓曲線來(lái)產(chǎn)生私鑰。橢圓曲線是一個(gè)復(fù)雜的數(shù)學(xué)概念,我們并不打算在這里作太多解釋(如果你真的十分好奇,可以查看這篇文章,注意:有很多數(shù)學(xué)公式!)我們只要知道這些曲線可以生成非常大的隨機(jī)數(shù)就夠了。在比特幣中使用的曲線可以隨機(jī)選取在 0 與 2 ^ 2 ^ 56(大概是 10^77, 而整個(gè)可見(jiàn)的宇宙中,原子數(shù)在 10^78 到 10^82 之間) 的一個(gè)數(shù)。有如此高的一個(gè)上限,意味著幾乎不可能發(fā)生有兩次生成同一個(gè)私鑰的事情。

比特幣使用的是 ECDSA(Elliptic Curve Digital Signature Algorithm)算法來(lái)對(duì)交易進(jìn)行簽名,我們也會(huì)使用該算法。

Base58

回到上面提到的比特幣地址:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa ?,F(xiàn)在,我們已經(jīng)知道了這是公鑰用人類可讀的形式表示而已。如果我們對(duì)它進(jìn)行解碼,就會(huì)看到公鑰的本來(lái)面目(16 進(jìn)制表示的字節(jié)):

0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93

比特幣使用 Base58 算法將公鑰轉(zhuǎn)換成人類可讀的形式。這個(gè)算法跟著名的 Base64 很類似,區(qū)別在于它使用了更短的字母表:為了避免一些利用字母相似性的攻擊,從字母表中移除了一些字母。也就是,沒(méi)有這些符號(hào):0(零),O(大寫的 o),I(大寫的i),l(小寫的 L),因?yàn)檫@幾個(gè)字母看著很像。另外,也沒(méi)有 + 和 / 符號(hào)。

下圖是從一個(gè)公鑰獲得一個(gè)地址的過(guò)程:

因此,上面提到的公鑰解碼后包含三個(gè)部分:

Version  Public key hash                           Checksum
00       62E907B15CBF27D5425399EBF6F0FB50EBB88F18  C29B7D93

由于哈希函數(shù)是單向的(也就說(shuō)無(wú)法逆轉(zhuǎn)回去),所以不可能從一個(gè)哈希中提取公鑰。不過(guò)通過(guò)執(zhí)行哈希函數(shù)并進(jìn)行哈希比較,我們可以檢查一個(gè)公鑰是否被用于哈希的生成。

好了,所有細(xì)節(jié)都已就緒,來(lái)寫代碼吧。很多概念只有當(dāng)寫代碼的時(shí)候,才能理解地更透徹。

實(shí)現(xiàn)地址

我們先從錢包 Wallet 結(jié)構(gòu)開始:

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
}

一個(gè)錢包只有一個(gè)密鑰對(duì)而已。我們需要 Wallets 類型來(lái)保存多個(gè)錢包的組合,將它們保存到文件中,或者從文件中進(jìn)行加載。Wallet 的構(gòu)造函數(shù)會(huì)生成一個(gè)新的密鑰對(duì)。newKeyPair 函數(shù)非常直觀:ECDSA 基于橢圓曲線,所以我們需要一個(gè)橢圓曲線。接下來(lái),使用橢圓生成一個(gè)私鑰,然后再?gòu)乃借€生成一個(gè)公鑰。有一點(diǎn)需要注意:在基于橢圓曲線的算法中,公鑰是曲線上的點(diǎn)。因此,公鑰是 X,Y 坐標(biāo)的組合。在比特幣中,這些坐標(biāo)會(huì)被連接起來(lái),然后形成一個(gè)公鑰。

現(xiàn)在,來(lái)生成一個(gè)地址:

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]
}

將一個(gè)公鑰轉(zhuǎn)換成一個(gè) Base58 地址需要以下步驟:

使用 RIPEMD160(SHA256(PubKey)) 哈希算法,取公鑰并對(duì)其哈希兩次

給哈希加上地址生成算法版本的前綴

對(duì)于第二步生成的結(jié)果,使用 SHA256(SHA256(payload)) 再哈希,計(jì)算校驗(yàn)和。校驗(yàn)和是結(jié)果哈希的前四個(gè)字節(jié)。

將校驗(yàn)和附加到 version+PubKeyHash 的組合中。

使用 Base58 對(duì) version+PubKeyHash+checksum 組合進(jìn)行編碼。

至此,就可以得到一個(gè)真實(shí)的比特幣地址,你甚至可以在?blockchain.info 查看它的余額。不過(guò)我可以負(fù)責(zé)任地說(shuō),無(wú)論生成一個(gè)新的地址多少次,檢查它的余額都是 0。這就是為什么選擇一個(gè)合適的公鑰加密算法是如此重要:考慮到私鑰是隨機(jī)數(shù),生成同一個(gè)數(shù)字的概率必須是盡可能地低。理想情況下,必須是低到“永遠(yuǎn)”不會(huì)重復(fù)。

另外,注意:你并不需要連接到一個(gè)比特幣節(jié)點(diǎn)來(lái)獲得一個(gè)地址。地址生成算法使用的多種開源算法可以通過(guò)很多編程語(yǔ)言和庫(kù)實(shí)現(xiàn)。

現(xiàn)在我們需要修改輸入和輸出來(lái)使用地址:

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
}

注意,現(xiàn)在我們已經(jīng)不再需要 ScriptPubKeyScriptSig 字段,因?yàn)槲覀儾粫?huì)實(shí)現(xiàn)一個(gè)腳本語(yǔ)言。相反,ScriptSig 會(huì)被分為 SignaturePubKey 字段,ScriptPubKey 被重命名為 PubKeyHash。我們會(huì)實(shí)現(xiàn)跟比特幣里一樣的輸出鎖定/解鎖和輸入簽名邏輯,不同的是我們會(huì)通過(guò)方法(method)來(lái)實(shí)現(xiàn)。

UsesKey 方法檢查輸入使用了指定密鑰來(lái)解鎖一個(gè)輸出。注意到輸入存儲(chǔ)的是原生的公鑰(也就是沒(méi)有被哈希的公鑰),但是這個(gè)函數(shù)要求的是哈希后的公鑰。IsLockedWithKey 檢查是否提供的公鑰哈希被用于鎖定輸出。這是一個(gè) UsesKey 的輔助函數(shù),并且它們都被用于 FindUnspentTransactions 來(lái)形成交易之間的聯(lián)系。

Lock 只是簡(jiǎn)單地鎖定了一個(gè)輸出。當(dāng)我們給某個(gè)人發(fā)送幣時(shí),我們只知道他的地址,因?yàn)檫@個(gè)函數(shù)使用一個(gè)地址作為唯一的參數(shù)。然后,地址會(huì)被解碼,從中提取出公鑰哈希并保存在 PubKeyHash 字段。

現(xiàn)在,來(lái)檢查一下是否都能如期工作:

$ 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

很好!現(xiàn)在我們來(lái)實(shí)現(xiàn)交易簽名。

實(shí)現(xiàn)簽名

交易必須被簽名,因?yàn)檫@是比特幣里面保證發(fā)送方不會(huì)花費(fèi)屬于其他人的幣的唯一方式。如果一個(gè)簽名是無(wú)效的,那么這筆交易就會(huì)被認(rèn)為是無(wú)效的,因此,這筆交易也就無(wú)法被加到區(qū)塊鏈中。

我們現(xiàn)在離實(shí)現(xiàn)交易簽名還差一件事情:用于簽名的數(shù)據(jù)。一筆交易的哪些部分需要簽名?又或者說(shuō),要對(duì)完整的交易進(jìn)行簽名?選擇簽名的數(shù)據(jù)相當(dāng)重要。因?yàn)橛糜诤灻倪@個(gè)數(shù)據(jù),必須要包含能夠唯一識(shí)別數(shù)據(jù)的信息。比如,如果僅僅對(duì)輸出值進(jìn)行簽名并沒(méi)有什么意義,因?yàn)楹灻粫?huì)考慮發(fā)送方和接收方。

考慮到交易解鎖的是之前的輸出,然后重新分配里面的價(jià)值,并鎖定新的輸出,那么必須要簽名以下數(shù)據(jù):

存儲(chǔ)在已解鎖輸出的公鑰哈希。它識(shí)別了一筆交易的“發(fā)送方”。

存儲(chǔ)在新的鎖定輸出里面的公鑰哈希。它識(shí)別了一筆交易的“接收方”。

新的輸出值。

在比特幣中,鎖定/解鎖邏輯被存儲(chǔ)在腳本中,它們被分別存儲(chǔ)在輸入和輸出的 ScriptSigScriptPubKey 字段。由于比特幣允許這樣不同類型的腳本,它對(duì) ScriptPubKey 的整個(gè)內(nèi)容進(jìn)行了簽名。

可以看到,我們不需要對(duì)存儲(chǔ)在輸入里面的公鑰簽名。因此,在比特幣里, 所簽名的并不是一個(gè)交易,而是一個(gè)去除部分內(nèi)容的輸入副本,輸入里面存儲(chǔ)了被引用輸出的 ScriptPubKey

獲取修剪后的交易副本的詳細(xì)過(guò)程在這里. 雖然它可能已經(jīng)過(guò)時(shí)了,但是我并沒(méi)有找到另一個(gè)更可靠的來(lái)源。

看著有點(diǎn)復(fù)雜,來(lái)開始寫代碼吧。先從 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
    }
}

這個(gè)方法接受一個(gè)私鑰和一個(gè)之前交易的 map。正如上面提到的,為了對(duì)一筆交易進(jìn)行簽名,我們需要獲取交易輸入所引用的輸出,因?yàn)槲覀冃枰鎯?chǔ)這些輸出的交易。

來(lái)一步一步地分析該方法:

if tx.IsCoinbase() {
    return
}

coinbase 交易因?yàn)闆](méi)有實(shí)際輸入,所以沒(méi)有被簽名。

txCopy := tx.TrimmedCopy()

將會(huì)被簽署的是修剪后的交易副本,而不是一個(gè)完整交易:

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
}

這個(gè)副本包含了所有的輸入和輸出,但是 TXInput.SignatureTXIput.PubKey 被設(shè)置為 nil

接下來(lái),我們會(huì)迭代副本中每一個(gè)輸入:

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

在每個(gè)輸入中,Signature 被設(shè)置為 nil (僅僅是一個(gè)雙重檢驗(yàn)),PubKey 被設(shè)置為所引用輸出的 PubKeyHash?,F(xiàn)在,除了當(dāng)前交易,其他所有交易都是“空的”,也就是說(shuō)他們的 SignaturePubKey 字段被設(shè)置為 nil。因此,輸入是被分開簽名的,盡管這對(duì)于我們的應(yīng)用并不十分緊要,但是比特幣允許交易包含引用了不同地址的輸入。

    txCopy.ID = txCopy.Hash()
    txCopy.Vin[inID].PubKey = nil

Hash 方法對(duì)交易進(jìn)行序列化,并使用 SHA-256 算法進(jìn)行哈希。哈希后的結(jié)果就是我們要簽名的數(shù)據(jù)。在獲取完哈希,我們應(yīng)該重置 PubKey 字段,以便于它不會(huì)影響后面的迭代。

現(xiàn)在,關(guān)鍵點(diǎn):

    r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID)
    signature := append(r.Bytes(), s.Bytes()...)

    tx.Vin[inID].Signature = signature

我們通過(guò) privKey 對(duì) txCopy.ID 進(jìn)行簽名。一個(gè) ECDSA 簽名就是一對(duì)數(shù)字,我們對(duì)這對(duì)數(shù)字連接起來(lái),并存儲(chǔ)在輸入的 Signature 字段。

現(xiàn)在,驗(yàn)證函數(shù):

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
}

這個(gè)方法十分直觀。首先,我們需要同一筆交易的副本:

txCopy := tx.TrimmedCopy()

然后,我們需要相同的區(qū)塊用于生成密鑰對(duì):

curve := elliptic.P256()

接下來(lái),我們檢查每個(gè)輸入中的簽名:

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

這個(gè)部分跟 Sign 方法一模一樣,因?yàn)樵隍?yàn)證階段,我們需要的是與簽名相同的數(shù)據(jù)。

    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):])

這里我們解包存儲(chǔ)在 TXInput.SignatureTXInput.PubKey 中的值,因?yàn)橐粋€(gè)簽名就是一對(duì)數(shù)字,一個(gè)公鑰就是一對(duì)坐標(biāo)。我們之前為了存儲(chǔ)將它們連接在一起,現(xiàn)在我們需要對(duì)它們進(jìn)行解包在 crypto/ecdsa 函數(shù)中使用。

    rawPubKey := ecdsa.PublicKey{curve, &x, &y}
    if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false {
        return false
    }
}

return true

在這里:我們使用從輸入提取的公鑰創(chuàng)建了一個(gè) ecdsa.PublicKey,通過(guò)傳入輸入中提取的簽名執(zhí)行了 ecdsa.Verify。如果所有的輸入都被驗(yàn)證,返回 true;如果有任何一個(gè)驗(yàn)證失敗,返回 false.

現(xiàn)在,我們需要一個(gè)函數(shù)來(lái)獲得之前的交易。由于這需要與區(qū)塊鏈進(jìn)行交互,我們將它放在了 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)
}

這幾個(gè)比較簡(jiǎn)單:FindTransaction 通過(guò) ID 找到一筆交易(這需要在區(qū)塊鏈上迭代所有區(qū)塊);SignTransaction 傳入一筆交易,找到它引用的交易,然后對(duì)它進(jìn)行簽名;VerifyTransaction 做的是相同的事情,不過(guò)是對(duì)交易進(jìn)行驗(yàn)證。

現(xiàn)在,我們需要實(shí)際簽名和驗(yàn)證交易。簽名在 NewUTXOTransaction 中進(jìn)行:

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
}

在一筆交易被放入一個(gè)塊之前進(jìn)行驗(yàn)證:

func (bc *Blockchain) MineBlock(transactions []*Transaction) {
    var lastHash []byte

    for _, tx := range transactions {
        if bc.VerifyTransaction(tx) != true {
            log.Panic("ERROR: Invalid transaction")
        }
    }
    ...
}

就是這些了!讓我們?cè)賮?lái)檢查一下所有內(nèi)容:

$ 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

一切正常!

現(xiàn)在來(lái)注釋掉 NewUTXOTransaction 里面的bc.SignTransaction(&tx, wallet.PrivateKey) 的調(diào)用,因?yàn)槲春灻慕灰谉o(wú)法被打包:

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
總結(jié)

到目前為止,我們已經(jīng)完成了比特幣中的許多關(guān)鍵特性!除了網(wǎng)絡(luò)外的所有事情都已基本完成,在下一篇文章中,我們將會(huì)完成交易部分。

鏈接:

Full source codes

Public-key cryptography

Digital signatures

Elliptic curve

Elliptic curve cryptography

ECDSA

Technical background of Bitcoin addresses

Address

Base58

A gentle introduction to elliptic curve cryptography

原文:Building Blockchain in Go. Part 5: Addresses

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/23938.html

相關(guān)文章

  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 7: 網(wǎng)絡(luò)

    摘要:盡管我們不會(huì)實(shí)現(xiàn)一個(gè)真實(shí)的網(wǎng)絡(luò),但是我們會(huì)實(shí)現(xiàn)一個(gè)真是,也是比特幣最常見(jiàn)最重要的用戶場(chǎng)景。不過(guò),這并不是處于禮貌用于找到一個(gè)更長(zhǎng)的區(qū)塊鏈。意為給我看一下你有什么區(qū)塊在比特幣中,這會(huì)更加復(fù)雜。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHu...

    lingdududu 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 4: 交易(1)

    摘要:引言交易是比特幣的核心所在,而區(qū)塊鏈的唯一目的,也正是為了能夠安全可靠地存儲(chǔ)交易。比特幣使用了一個(gè)更加復(fù)雜的技術(shù)它將一個(gè)塊里面包含的所有交易表示為一個(gè),然后在工作量證明系統(tǒng)中使用樹的根哈希。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHu...

    graf 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 6: 交易(2)

    摘要:到目前為止,我們幾乎已經(jīng)實(shí)現(xiàn)了一個(gè)區(qū)塊鏈數(shù)據(jù)庫(kù)的所有元素。使用根據(jù)在區(qū)塊鏈中找到一筆交易。是一個(gè)比特幣輕節(jié)點(diǎn),它不需要下載整個(gè)區(qū)塊鏈,也不需要驗(yàn)證區(qū)塊和交易。到目前為止,我們只是將一個(gè)塊里面的每筆交易哈希連接了起來(lái),將在上面應(yīng)用了算法。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果...

    spacewander 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 ---- Part 1: 基本原型

    摘要:在區(qū)塊鏈中,存儲(chǔ)有效信息的是區(qū)塊。存儲(chǔ)的是前一個(gè)塊的哈希。正是由于這個(gè)特性,才使得區(qū)塊鏈?zhǔn)前踩?。這樣的結(jié)構(gòu),能夠讓我們快速地獲取鏈上的最新塊,并且高效地通過(guò)哈希來(lái)檢索一個(gè)塊。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉(cāng)...

    ZoomQuiet 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 3: 持久化和命令行接口

    摘要:引言到目前為止,我們已經(jīng)構(gòu)建了一個(gè)有工作量證明機(jī)制的區(qū)塊鏈。在今天的內(nèi)容中,我們會(huì)將區(qū)塊鏈持久化到一個(gè)數(shù)據(jù)庫(kù)中,然后會(huì)提供一個(gè)簡(jiǎn)單的命令行接口,用來(lái)完成一些與區(qū)塊鏈的交互操作。這同樣也意味著,一個(gè)也就是區(qū)塊鏈的一種標(biāo)識(shí)符。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼...

    felix0913 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 2: 工作量證明

    摘要:哈希函數(shù)被廣泛用于檢測(cè)數(shù)據(jù)的一致性。在區(qū)塊鏈中,哈希被用于保證一個(gè)塊的一致性。比特幣使用,一個(gè)最初用來(lái)防止垃圾郵件的工作量證明算法。下面是與前面例子哈希的形式化比較第一個(gè)哈希基于計(jì)算比目標(biāo)要大,因此它并不是一個(gè)有效的工作量證明。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)...

    suxier 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<