參考:https://github.com/liuchengxu/blockchain-tutorial
引言
在比特幣中,沒有用戶賬戶,不需要也不會在任何地方存儲個人數據(比如姓名,護照號碼或者 SSN),但總要有某種途徑識別出你是交易輸出的所有者(也就是說,你擁有在這些輸出上鎖定的幣),即比特幣地址(address)需要完成的使命。
比特幣地址
比特幣地址是完全公開的,如果想要給某個人發送幣,只需要知道他的地址就可以了。但是,地址(盡管地址也是獨一無二的)並不是用來證明你是一個“錢包”所有者的信物。實際上,所謂的地址,只不過是將公鑰表示成人類可讀的形式而已,因為原生的公鑰人類很難閱讀。在比特幣中,你的身份(identity)就是一對(或者多對)保存在你的電腦(或者你能夠獲取到的地方)上的公鑰(public key)和私鑰(private key)。比特幣基於一些加密算法的組合來創建這些密鑰,並且保證了在這個世界上沒有其他人能夠取走你的幣,除非拿到你的密鑰。
涉及到以下算法:
公鑰加密
公鑰加密(public-key cryptography)算法使用的是成對的密鑰:公鑰和私鑰,其中公鑰並不是敏感信息,可以告訴其他人,但是私鑰絕對不能告訴他人,只有所有者(owner)才能擁有,其作用為識別、鑒定和證明所有者的身份。在加密貨幣的世界中,你的私鑰代表的就是你,私鑰就是一切。
私鑰和公鑰是隨機的字節序列,因此它們無法在屏幕上打印,人類也無法通過肉眼去讀取。這就是為什么比特幣使用了一個轉換算法,將公鑰轉化為一個人類可讀的字符串(也就是我們看到的地址)。
注:公鑰是通過私鑰產生
數字簽名
在數學和密碼學中,有一個數字簽名(digital signature)的概念,算法可以保證:
- 當數據從發送方傳送到接收方時,數據不會被修改;
- 數據由某一確定的發送方創建;
- 發送方無法否認發送過數據這一事實。
通過在數據上應用簽名算法(即對數據進行簽名),就會得到一個簽名,之后這個簽名會被驗證。
生成數字簽名時需要一個私鑰,驗證簽名時需要一個公鑰
簽名有點類似於印章,比方說我做了一幅畫,完了用印章一蓋,就說明了這幅畫是我的作品。給數據生成簽名,就是給數據蓋了章。
為數據進行簽名時,需要以下兩樣東西:
1、要簽名的數據
2、私鑰
應用簽名算法可以生成一個簽名,並且這個簽名會被存儲在交易輸入中。
為對一個簽名進行驗證,需要以下三種東西:
1、 被簽名的數據
2、簽名
3、公鑰
其驗證過程可以簡單地描述為:檢查簽名是由被簽名數據加上私鑰得來,並且公鑰恰好是由該私鑰生成。
數據簽名並不是加密,你無法從一個簽名重新構造出數據。這有點像哈希:你在數據上運行一個哈希算法,然后得到一個該數據的唯一表示。簽名與哈希的區別在於密鑰對:有了密鑰對,才有簽名驗證。但是密鑰對也可以被用於加密數據:私鑰用於加密,公鑰用於解密數據。不過比特幣並不使用加密算法。
在比特幣中,每一筆交易輸入都會由創建交易的人簽名。在被放入到一個塊之前,必須要對每一筆交易進行驗證。除了一些其他步驟,驗證意味着:
- 檢查交易輸入有權使用來自之前交易的輸出
- 檢查交易簽名是正確的
對數據進行簽名和對簽名進行驗證的過程大致如下:
一個交易完整的生命周期:
- 起初,創世塊里面包含了一個 coinbase 交易。在 coinbase 交易中,沒有輸入,所以也就不需要簽名。coinbase 交易的輸出包含了一個哈希過的公鑰(使用的是
RIPEMD16(SHA256(PubKey)) 算法) - 當一個人發送幣時,就會創建一筆交易。這筆交易的輸入會引用之前交易的輸出。每個輸入會存儲一個公鑰(沒有被哈希)和整個交易的一個簽名。
- 比特幣網絡中接收到交易的其他節點會對該交易進行驗證。除了一些其他事情,他們還會檢查:在一個輸入中,公鑰哈希與所引用的輸出哈希相匹配(這保證了發送方只能花費屬於自己的幣);簽名是正確的(這保證了交易是由幣的實際擁有者所創建)。
- 當一個礦工准備挖一個新塊時,他會將交易放到塊中,然后開始挖礦。
- 當新塊被挖出來以后,網絡中的所有其他節點會接收到一條消息,告訴其他人這個塊已經被挖出並被加入到區塊鏈。
- 當一個塊被加入到區塊鏈以后,交易就算完成,它的輸出就可以在新的交易中被引用。
橢圓曲線加密
公鑰和私鑰是隨機的字節序列。私鑰能夠用於證明持幣人的身份,需要有一個條件:隨機算法必須生成真正隨機的字節。因為沒有人會想要生成一個私鑰,而這個私鑰意外地也被別人所有。
比特幣使用橢圓曲線來產生私鑰,該曲線可以生成非常大的隨機數,
比特幣使用的是 ECDSA(Elliptic Curve Digital Signature Algorithm)算法來對交易進行簽名,我們也會使用該算法。
Base58
公鑰為16進制的數據,為轉換成人類可讀的形式比特幣采用了Base58算法,該算法與著名的Base64很類似,區別在於它使用了更短的字母表:為了避免一些利用字母相似性的攻擊,從字母表中移除了一些字母。也就是,沒有這些符號:0(零),O(大寫的 o),I(大寫的i),l(小寫的 L),因為這幾個字母看着很像。另外,也沒有 + 和 / 符號。
從一個公鑰獲得一個地址的過程:
對應上圖代碼實現如下:
type Wallet struct {
// 1、私鑰
privateKey ecdsa.PrivateKey
// 2、公鑰
publicKey []byte
}
const version = byte(0x00)
const addressChecksumLen = 4
type Wallet struct {
// 1、私鑰
privateKey ecdsa.PrivateKey
// 2、公鑰
publicKey []byte
}
func IsValidForAddress(address []byte) bool {
version_public_checksumBytes := Base58Decode(address)
fmt.Println(version_public_checksumBytes)
checkSumBytes := version_public_checksumBytes[len(version_public_checksumBytes)-addressChecksumLen:]
version_ripemd160 := version_public_checksumBytes[:len(version_public_checksumBytes)-addressChecksumLen]
fmt.Println(len(checkSumBytes))
fmt.Println(len(version_ripemd160))
checkBytes := CheckSum(version_ripemd160)
if bytes.Compare(checkSumBytes, checkBytes) == 0 {
return true
}
return false
}
// 獲取地址
func (w *Wallet) GetAddress() []byte {
// 1、hash160
ripemd160Hash := w.Ripemd160Hash(w.publicKey)
version_ripemd160Hash := append([]byte{version}, ripemd160Hash...)
checkSumBytes := CheckSum(version_ripemd160Hash)
bytes := append(version_ripemd160Hash, checkSumBytes...)
return Base58Encode(bytes)
}
func CheckSum(payload []byte) []byte {
hash1 := sha256.Sum256(payload)
hash2 := sha256.Sum256(hash1[:])
return hash2[:addressChecksumLen]
}
func (w *Wallet) Ripemd160Hash(publicKey []byte) []byte {
// 256
hash256 := sha256.New()
hash256.Write(publicKey)
hash := hash256.Sum(nil)
//160
ripemd160 := ripemd160.New()
ripemd160.Write(hash)
return ripemd160.Sum(nil)
}
// 創建錢包
func NewWallet() *Wallet {
privateKey, publicKey := newKeyPair()
return &Wallet{privateKey, publicKey}
}
// 通過私鑰產生公鑰
func newKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
log.Panic(err)
}
publicKey := append(private.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, publicKey
}
因此,從上圖可看出,公鑰解碼后包含三個部分:
Version | Public key hash | Checksum |
---|---|---|
00 | 62E907B15CBF27D5425399EBF6F0FB50EBB88F18 | C29B7D93 |
- base58加密
- base58 解密
最后的效果圖:
如有需要可詳看:https://github.com/NGLHarry/Blockchainer/tree/main/wallet_address
注:
在進行加密過程中需要使用到ripemd160
,其go的安裝命令為:go get "golang.org/x/crypto/ripemd160"
但是,大概率會被牆,有位好心大哥已經上傳到github上,只需將其下載到本地,並放在Go項目中的src目錄下即可
ripemd160地址