從0到1簡易區塊鏈開發手冊V0.2-創建錢包
創建錢包其實就是創建比特幣地址,在比特幣世界中,沒有賬戶概念,不需要也不會在任何地方存儲個人數據(比如姓名,×××件號碼等)。但是,我們總要有某種途徑識別出你是交易輸出的所有者(也就是說,你擁有在這些輸出上鎖定的幣),這就是比特幣地址(address)需要完成的使命。
關於錢包這個概念,我個人覺得imtoken在用戶引導那部分寫得很清楚,此處將鏈接給到大家,有興趣的可以去看看
https://www.cnblogs.com/fangbei/p/imToken-clearance.html
我們來看一下一個真實的比特幣賬戶,1FSzfZ27CVTkfNw6TWxnHPaRLRCgpWvbFC ,比特幣地址是完全公開的,如果你想要給某個人發送幣,只需要知道他的地址就可以了。但是,地址(盡管地址也是獨一無二的)並不是用來證明你是一個“錢包”所有者的信物。實際上,所謂的地址,只不過是將公鑰表示成人類可讀的形式而已,因為原生的公鑰人類很難閱讀。在比特幣中,你的身份(identity)就是一對(或者多對)保存在你的電腦(或者你能夠獲取到的地方)上的公鑰(public key)和私鑰(private key)。比特幣基於一些加密算法的組合來創建這些密鑰,並且保證了在這個世界上沒有其他人能夠取走你的幣,除非拿到你的密鑰。
關於如何創建一個錢包以及錢包集合,通過下圖進行簡單展示
圖 創建錢包與錢包集合
圖 創建錢包wallet
圖 創建錢包集合
2. 定義錢包結構體
type Wallet struct {
//1.私鑰
PrivateKey ecdsa.PrivateKey
//2.公鑰
PublickKey []byte //原始公鑰
}
定義錢包Wallet的屬性為私鑰:PrivateKey,類型為系統內置的結構體對象ecdsa.PrivateKey,公鑰:PublickKey,類型為字節數組
3. 生成錢包地址
圖 從私鑰到生成錢包地址的過程圖
3.1 通過橢圓曲線算法產生密鑰對
func newKeyPair() (ecdsa.PrivateKey, []byte) { //橢圓加密 curve := elliptic.P256() //根據橢圓加密算法,得到一個橢圓曲線值 //生成私鑰 privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) //*Private if err != nil { log.Panic(err) } //通過私鑰生成原始公鑰 publicKey := append(privateKey.PublicKey.X.Bytes(), privateKey.PublicKey.Y.Bytes()...) return *privateKey, publicKey }
橢圓曲線加密:(ECC:ellipse curve Cryptography),非對稱加密
- 根據橢圓曲線算法,產生隨機私鑰
- 根據私鑰,產生公鑰
3.2 創建錢包對象
func NewWallet() *Wallet {
privateKey, publicKey := newKeyPair()
return &Wallet{privateKey, publicKey}
}
通過newKeyPair函數將返回的私鑰與公鑰生成錢包對象Wallet
3.3 定義常量值
const version = byte(0x00)
const addressCheckSumLen = 4
-
version: 版本前綴,比特幣中固定為0
- addressCheckSumLen: 用於獲取校驗碼的長度變量,取添加版本+數據進行兩次SHA256之後的前4個字節
3.5 根據公鑰獲取地址
圖 從**公鑰**到生成錢包地址的過程圖
func PubKeyHash(publickKey []byte) []byte {
//1.sha256
hasher := sha256.New()
hasher.Write(publickKey)
hash1 := hasher.Sum(nil)
//2.ripemd160
hasher2 := ripemd160.New()
hasher2.Write(hash1)
hash2 := hasher2.Sum(nil)
//3.返回公鑰哈希
return hash2
}
通過公鑰生成公鑰哈希的步驟已完成。
func GetAddressByPubKeyHash(pubKeyHash []byte) []byte {
//添加版本號:
versioned_payload := append([]byte{version}, pubKeyHash...)
//根據versioned_payload-->兩次sha256,取前4位,得到checkSum
checkSumBytes := CheckSum(versioned_payload)
//拼接全部數據
full_payload := append(versioned_payload, checkSumBytes...)
//Base58編碼
address := Base58Encode(full_payload)
return address
}
相關函數如下
- 生成校驗碼
func CheckSum(payload [] byte) []byte {
firstHash := sha256.Sum256(payload)
secondHash := sha256.Sum256(firstHash[:])
return secondHash[:addressCheckSumLen]
}
通過兩次sha256哈希得到校驗碼,返回校驗碼前四位
-
字節數組轉Base58加密
var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") func Base58Encode(input []byte)[]byte{ var result [] byte x := big.NewInt(0).SetBytes(input) base :=big.NewInt(int64(len(b58Alphabet))) zero:=big.NewInt(0) mod:= &big.Int{} for x.Cmp(zero) !=0{ x.DivMod(x,base,mod) result = append(result,b58Alphabet[mod.Int64()]) } ReverseBytes(result) for b:=range input{ if b == 0x00{ result = append([]byte{b58Alphabet[0]},result...) }else { break } } return result }
以上功能函數定義好之後,定義Wallet的方法GetAddress返回錢包address
func (w *Wallet) GetAddress() []byte {
pubKeyHash := PubKeyHash(w.PublickKey)
address := GetAddressByPubKeyHash(pubKeyHash)
return address
}
至此,我們已經能夠生成一個比特幣地址了,可以通過https://www.blockchain.com/explorer進行錢包地址查看余額。
4.定義錢包集合結構體
type Wallets struct {
WalletMap map[string]*Wallet
}
定義錢包集合結構體Wallets,屬性為WalletMap,類型為Wallet集合
5.創建錢包集合
func (ws *Wallets) CreateNewWallets() {
wallet := NewWallet()
var address []byte
address = wallet.GetAddress()
fmt.Printf("創建的錢包地址:%s\n", address)
ws.WalletMap[string(address)] = wallet
//將錢包集合存入到本地文件中
ws.SaveFile()
}
- 創建一個錢包對象
- 通過GetAddress獲取錢包對象的地址
- 將錢包地址作為錢包集合的key,錢包對象作為value存儲至錢包集合中
- 通過SaveFile將錢包集合存入到本地文件中
5.1 定義常量存儲錢包數據
const walletsFile = "Wallets.dat" //存儲錢包數據的本地文件名
5.2 本地化存儲錢包對象
func (ws *Wallets) SaveFile() {
//1.將ws對象的數據--->byte[]
var buf bytes.Buffer
//序列化的過程中:被序列化的對象中包含了接口,那麽該接口需要註冊
gob.Register(elliptic.P256()) //Curve
encoder := gob.NewEncoder(&buf)
err := encoder.Encode(ws)
if err != nil {
log.Panic(err)
}
wsBytes := buf.Bytes()
//2.將數據存儲到文件中
err = ioutil.WriteFile(walletsFile, wsBytes, 0644)
if err != nil {
log.Panic(err)
}
}
6.獲取錢包集合
此處我們提供一個函數,用戶獲取錢包集合
- 讀取本地的錢包文件,如果文件存在,直接獲取
- 如果文件不存在,創建並返回一個空的錢包對象
func GetWallets() *Wallets {
//錢包文件不存在
if _, err := os.Stat(walletsFile); os.IsNotExist(err) {
fmt.Println("區塊鏈錢包不存在")
//創建錢包集合
wallets := &Wallets{}
wallets.WalletMap = make(map[string]*Wallet)
return wallets
}
//錢包文件存在
//讀取本地的錢包文件中的數據
wsBytes, err := ioutil.ReadFile(walletsFile)
if err != nil {
log.Panic(err)
}
gob.Register(elliptic.P256()) //Curve
//將數據反序列化變成錢包集合對象
var wallets Wallets
reader := bytes.NewReader(wsBytes)
decoder := gob.NewDecoder(reader)
err = decoder.Decode(&wallets)
if err != nil {
log.Panic(err)
}
return &wallets
}
7.命令行中調用
7.1 創建錢包
回到上一章節(二.實現命令行功能-2.1創建錢包)的命令行功能
func (cli *CLI) GetAddressLists() {
fmt.Println("錢包地址列表為:")
//獲取錢包的集合,遍歷,依次輸出
_, wallets := GetWallets() //獲取錢包集合對象
for address, _ := range wallets.WalletMap {
fmt.Printf("\t%s\n", address)
}
}
此時進行編譯運行
$ go build -o mybtc main.go
$ ./mybtc createwallet //創建第一個錢包
$ ./mybtc createwallet //創建第二個錢包
$ ./mybtc createwallet //創建第三個錢包
返回的結果:
創建的錢包地址:14A1b3Lp3hL5B7vZvT2UWk1W78m2Kh8MUB
創建的錢包地址:1G3SkYAJdWy5pd1hFpcciUoJi8zy8PdV11
創建的錢包地址:1AA2fyYdXCQMwLMu5NBvq7Fb9UiHqg2cQV
7.2 獲取錢包地址
回到上一章節(二.實現命令行功能-2.2 獲取錢包地址)的命令行功能
func (cli *CLI) GetAddressLists() {
fmt.Println("錢包地址列表為:")
//獲取錢包的集合,遍歷,依次輸出
wallets := GetWallets()
for address, _ := range wallets.WalletMap {
fmt.Printf("\t%s\n", address)
}
}
$ ./mybtc getaddresslists
返回的結果
錢包地址列表為:
1AA2fyYdXCQMwLMu5NBvq7Fb9UiHqg2cQV
14A1b3Lp3hL5B7vZvT2UWk1W78m2Kh8MUB
1G3SkYAJdWy5pd1hFpcciUoJi8zy8PdV11
上面我們提到生成的比特幣地址可以通過https://www.blockchain.com/explorer進行錢包地址查看余額,現在我們來進行簡單的查看驗證,查看該地址:1AA2fyYdXCQMwLMu5NBvq7Fb9UiHqg2cQV
圖 通過搜索框進行地址搜索
圖 錢包地址詳情
如果修改錢包地址的某個字符,如將隨後的V改為X
1AA2fyYdXCQMwLMu5NBvq7Fb9UiHqg2cQV === > 1AA2fyYdXCQMwLMu5NBvq7Fb9UiHqg2cQX
從0到1簡易區塊鏈開發手冊V0.2-創建錢包