1. 程式人生 > >從0到1簡易區塊鏈開發手冊V0.2-創建錢包

從0到1簡易區塊鏈開發手冊V0.2-創建錢包

alt 不存在 引導 shadow ado 校驗 圖片 vat tps

1.概念

創建錢包其實就是創建比特幣地址,在比特幣世界中,沒有賬戶概念,不需要也不會在任何地方存儲個人數據(比如姓名,×××件號碼等)。但是,我們總要有某種途徑識別出你是交易輸出的所有者(也就是說,你擁有在這些輸出上鎖定的幣),這就是比特幣地址(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-創建錢包