手记

从0到1简易区块链开发手册V0.2-创建钱包


Author: brucefeng

Email: brucefeng@brucefeng.com

编程语言:Golang

从0到1简易区块链开发手册V0.2-创建钱包

1.概念

创建钱包其实就是创建比特币地址,在比特币世界中,没有账户概念,不需要也不会在任何地方存储个人数据(比如姓名,×××件号码等)。但是,我们总要有某种途径识别出你是交易输出的所有者(也就是说,你拥有在这些输出上锁定的币),这就是比特币地址(address)需要完成的使命。

关于钱包这个概念,我个人觉得imtoken在用户引导那部分写得很清楚,此处将链接给到大家,有兴趣的可以去看看

https://www.cnblogs.com/fangbei/p/imToken-clearance.html

我们来看一下一个真实的比特币账户,1FSzfZ27CVTkfNw6TWxnHPaRLRCgpWvbFC ,比特币地址是完全公开的,如果你想要给某个人发送币,只需要知道他的地址就可以了。但是,地址(尽管地址也是独一无二的)并不是用来证明你是一个“钱包”所有者的信物。实际上,所谓的地址,只不过是将公钥表示成人类可读的形式而已,因为原生的公钥人类很难阅读。在比特币中,你的身份(identity)就是一对(或者多对)保存在你的电脑(或者你能够获取到的地方)上的公钥(public key)和私钥(private key)。比特币基于一些加密算法的组合来创建这些密钥,并且保证了在这个世界上没有其他人能够取走你的币,除非拿到你的密钥。

关于如何创建一个钱包以及钱包集合,通过下图进行简单展示

从0到1简易区块链开发手册V0.2-创建钱包

                        图 创建钱包与钱包集合

从0到1简易区块链开发手册V0.2-创建钱包

                            图 创建钱包wallet

从0到1简易区块链开发手册V0.2-创建钱包

                            图 创建钱包集合

2. 定义钱包结构体

type Wallet struct {

    //1.私钥

    PrivateKey ecdsa.PrivateKey

    //2.公钥

    PublickKey []byte //原始公钥

}

定义钱包Wallet的属性为私钥:PrivateKey,类型为系统内置的结构体对象ecdsa.PrivateKey,公钥:PublickKey,类型为字节数组

3. 生成钱包地址

从0到1简易区块链开发手册V0.2-创建钱包

                        图   从私钥到生成钱包地址的过程图      

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 根据公钥获取地址

从0到1简易区块链开发手册V0.2-创建钱包

                            图 从**公钥**到生成钱包地址的过程图

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

从0到1简易区块链开发手册V0.2-创建钱包

                            图  通过搜索框进行地址搜索

从0到1简易区块链开发手册V0.2-创建钱包

                            图  钱包地址详情

如果修改钱包地址的某个字符,如将随后的V改为X

1AA2fyYdXCQMwLMu5NBvq7Fb9UiHqg2cQV === > 1AA2fyYdXCQMwLMu5NBvq7Fb9UiHqg2cQX

从0到1简易区块链开发手册V0.2-创建钱包

下一篇文章将介绍如何实现创世区块的功能。

©著作权归作者所有:来自51CTO博客作者暗黑魔君的原创作品,如需转载,请注明出处,否则将追究法律责任


0人推荐
随时随地看视频
慕课网APP