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博客作者暗黑魔君的原创作品,如需转载,请注明出处,否则将追究法律责任