- 练习:比特币测试网地址的生成
// 具体说明已经在代码中详细注释,实现HASH160、HASH256和Base58编码
// 代码如下
/*
比特币测试网地址的生成
使用RIPEMD-160、SHA-256哈希算法以及Base58编码对给定公钥生成地址
给定公钥:
public key 1:
02b1ebcdbac723f7444fdfb8e83b13bd14fe679c59673a519df6a1038c07b719c6
public key 2:
036e69a3e7c303935403d5b96c47b7c4fa8a80ca569735284a91d930f0f49afa86
提示:
比特币中有两种复合式的哈希函数,分别为:
HASH160,即先对输入做一次SHA256,再做一次RIPEMD160;
HASH256,即先对输入做一次SHA256,再做一次SHA256。
本练习要求的version byte为0x6f(测试网)。
*/
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/shengdoushi/base58"
"golang.org/x/crypto/ripemd160"
)
const version byte = 0x6f
const addressChecksumLen = 4 // 定义Checksum长度为4个字节
var PublicKey1 = "02b1ebcdbac723f7444fdfb8e83b13bd14fe679c59673a519df6a1038c07b719c6"
var PublicKey2 = "036e69a3e7c303935403d5b96c47b7c4fa8a80ca569735284a91d930f0f49afa86"
func Hash160(publicKey []byte) []byte {
// 先对输入做一次SHA256
hash256 := sha256.New()
hash256.Write(publicKey)
hash := hash256.Sum(nil) // hash256得到的结果hash
// 再做一次RIPEMD160
ripemd160 := ripemd160.New()
ripemd160.Write(hash) // 对hash再ripemd160计算得到的结果
return ripemd160.Sum(nil)
}
// HASH256,返回CheckSum变量
func CheckSum(payload []byte) []byte {
hash1 := sha256.Sum256(payload) // HASH256,即先对输入做一次SHA256
hash2 := sha256.Sum256(hash1[:]) // 再做一次SHA256。
return hash2[:addressChecksumLen] // 切片取前4字节
}
// 返回Address
func GetAddress(PublicKey string) string {
pubkey, _ := hex.DecodeString(PublicKey) // 返回数组
ripemd160Hash := Hash160((pubkey)) // HASH160计算,得到Fingerprint
version_hash := append([]byte{version}, ripemd160Hash...) // 将version byte加到Fingerprint前面
checkbytes := CheckSum(version_hash) // 将version_hash即version byte+Fingerprint进行HASH256后取前4字节,得到Checksum
bytes := append(version_hash, checkbytes...) // 将version_hash即version byte+Fingerprint,添加到Checksum前面,得到拼接结果
// 进行base58编码
myAlphabet := base58.BitcoinAlphabet // 编码表
var encodstring string = base58.Encode(bytes, myAlphabet)
return encodstring // 得到最终结果
}
func main() {
address1 := GetAddress(PublicKey1)
address2 := GetAddress(PublicKey2)
fmt.Printf("address1:%s\n", address1)
fmt.Printf("assress2:%s\n", address2)
}
//运行截图如下
求得结果,地址是合法符合要求的
- 练习:Merkle Tree
// 具体说明已经在代码中详细注释
// 代码如下
/*
请用Go语言实现一棵叶子节点数为16的Merkle Tree,并在叶子节点存储任意字符
串,并在所有非叶节点计算相应Hash值
请将上一步生成的Merkle Tree任一叶子节点数据进行更改,并重新生成其余Hash值。利
用Merkle Tree的特点对该修改位置进行快速定位
即设计函数func compareMerkleTree(*MTree tree1, *MTree tree2) (int index) { }
*/
package main
import (
"bytes"
"crypto/sha256"
"fmt"
)
// 节点结构体定义
type MerkleNode struct {
Left *MerkleNode // 左孩子
Right *MerkleNode // 右孩子
Data []byte // 节点存储的数据,切片类型
}
// 树的结构体定义
type MerkleTree struct {
RootNode *MerkleNode // 根节点
}
// 创建节点
func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode {
mNode := MerkleNode{} // 创建一个空节点
// 如果左右子树为空,那么data就是叶子节点
if left == nil && right == nil {
// 计算哈希sha256
hash := sha256.Sum256(data)
mNode.Data = hash[:] // 以切片的类型,数据给Data
} else { // 有孩子的情况,非叶子节点存储该节点两个孩子节点的Hash值
prevHash := append(left.Data, right.Data...) // 将左右子树的数据集合在一起
// 计算哈希sha256
hash := sha256.Sum256(prevHash)
mNode.Data = hash[:] // 以切片的类型,数据给Data
}
// 给mNode节点的左右子树赋值
mNode.Left = left
mNode.Right = right
return &mNode // 返回mNode节点地址
}
// 对最开始的数据初始化,将输入的16个字符串转化为节点,以便于新建树
func initial_datas(data [][]byte) []MerkleNode { // 输入二维数组,返回[]MerkleNode数组
var nodes []MerkleNode
for _, datum := range data { // 得到字符串数据
node := NewMerkleNode(nil, nil, datum) // 左右孩子空,将datum数据传入进去,返回一个地址
nodes = append(nodes, *node) // 取这个node的值,加到nodes数组里面去
}
return nodes // 返回nodes数组
}
// 新建Merkle Tree
func NewMerkleTree(data [][]byte) *MerkleTree { // 输入二维数组,返回*MerkleTree这一棵树
var nodes []MerkleNode
// 构造节点
for _, datum := range data { // 得到字符串数据
node := NewMerkleNode(nil, nil, datum) // 左右孩子空,将datum数据传入进去,返回一个地址
nodes = append(nodes, *node) // 取这个node的值,加到nodes数组里面去
}
// 层层计算,这里树共5层,16个叶子节点
for i := 0; i < 5; i++ { // 循环五层
var newLevel []MerkleNode // 每一层的节点数组
//相邻两个节点合并
for j := 0; j < len(nodes); j += 2 { // 对每一层循环
node := NewMerkleNode(&nodes[j], &nodes[j+1], nil) // j=0:0与1形成父节点,j=2:2与3形成父节点,j=4:4与5形成父节点...
newLevel = append(newLevel, *node) // 加入newLevel数组
}
nodes = newLevel
if len(nodes) == 1 { // 1说明已经是根节点了
break
}
}
mTree := MerkleTree{&nodes[0]} // 根节点即是第一个节点
return &mTree // 返回根节点地址
}
// 比较修改后的前后两棵树,对该修改位置进行快速定位
func compareMerkleTree(tree1, tree2 *MerkleTree, initial_data []MerkleNode) int { //返回index索引
comparenode1 := (*tree1).RootNode // 第一棵树的根节点
comparenode2 := (*tree2).RootNode // 第二棵树的根节点
var key_val []byte // 该切片与修改某个叶子节点数据后的树(第二棵树)进行对比,来找到修改的位置的下标
// 开始遍历查找被修改的数据
for j := 0; j < 5; j++ {
// 无左右孩子,已经到达叶子节点
if (*comparenode2).Left == nil && (*comparenode2).Right == nil {
key_val = (*comparenode2).Data // 叶子节点值赋给key_val,也就是找到了被修改的叶子节点
break
}
// 如果comparenode1与comparenode2两棵树相同位置的节点,其数据hash值不同,则找到被修改值的路径
if !(bytes.Equal((*comparenode1).Data, (*comparenode2).Data)) {
// 假设由于这两个节点,左孩子被修改,导致这两个节点不同
tmp_comparenode1 := comparenode1.Left
tmp_comparenode2 := comparenode2.Left
if !bytes.Equal((*tmp_comparenode1).Data, (*tmp_comparenode2).Data) { // 确实是由于左孩子的问题,继续在左孩子的路径上找下去
comparenode1 = comparenode1.Left
comparenode2 = comparenode2.Left
} else { // 是由于右孩子的问题,则继续在右孩子的路径上找下去
comparenode1 = comparenode1.Right
comparenode2 = comparenode2.Right
}
}
}
// 对被修改的叶子节点进行定位
var i int
for i = 0; i < len(initial_data); i++ {
// 找到的叶子节点key_val与最开始初始化并且进行修改数据之后的数组对比,找到key_val在initial_data数组中的下标
if bytes.Equal(key_val, initial_data[i].Data) {
break
}
}
return i + 1 // 返回被修改数据下标+1,也就是第几个数据
}
func main() {
datas := [][]byte{ // 定义二维数组
[]byte("node1"),
[]byte("node2"),
[]byte("node3"),
[]byte("node4"),
[]byte("node5"),
[]byte("node6"),
[]byte("node7"),
[]byte("node8"),
[]byte("node9"),
[]byte("node10"),
[]byte("node11"),
[]byte("node12"),
[]byte("node13"),
[]byte("node14"),
[]byte("node15"),
[]byte("node16"),
}
tree1 := NewMerkleTree(datas) // 原始的第一棵树
datas[3] = []byte("node100") // 修改第4个数据
tree2 := NewMerkleTree(datas) // 修改后的第二棵树
ini_datas := initial_datas(datas) // 数据初始化
index := compareMerkleTree(tree1, tree2, ini_datas) // 查找到的位置
fmt.Printf("已找到到被修改的叶子节点位置,位置是:%d", index) // 打印输出
}
//运行截图如下
//求得结果,找到了预计的被修改数据的位置
- 练习:(拓展实验)生成比特币靓号地址
// 具体说明已经在代码中详细注释
// 代码如下
/*
靓号,泛指阿拉伯数字组成的连续相同的易于记忆的号码。车牌靓号多为四个连号或
8899、5566之类的号码。在本实验的比特币地址中,由于采用了Base58编码,所以可能存在
连续出现3个拉丁字母的“靓号地址”。
请完成以下实验:
使用Go语言编写一段程序,程序的输出为一个合法的比特币测试网地址(version byte
为0x6f),且要求:
1. 地址中包含3个连续的小写字母c。
2. 生成公钥时,使用安全的随机数生成器crpyto/rand。
使用浏览器访问任意水龙头网站,输入刚刚生成的地址,获取小额的测
试用比特币,记录下交易ID。
如果领取成功,网页将显示如下交易信息。
*/
package main
import (
"crypto/rand"
"crypto/sha256"
"fmt"
"github.com/shengdoushi/base58"
"golang.org/x/crypto/ripemd160"
)
const version byte = 0x6f // version byte为0x6f
const addressChecksumLen = 4 // 定义Checksum长度为4个字节
func Hash160(publicKey []byte) []byte {
// 先对输入做一次SHA256
hash256 := sha256.New()
hash256.Write(publicKey)
hash := hash256.Sum(nil) // hash256得到的结果hash
// 再做一次RIPEMD160
ripemd160 := ripemd160.New()
ripemd160.Write(hash) // 对hash再ripemd160计算得到的结果
return ripemd160.Sum(nil)
}
// HASH256,返回CheckSum变量
func CheckSum(payload []byte) []byte {
hash1 := sha256.Sum256(payload) // HASH256,即先对输入做一次SHA256
hash2 := sha256.Sum256(hash1[:]) // 再做一次SHA256。
return hash2[:addressChecksumLen] // 切片取前4字节
}
// 返回Address
func GetAddress(pubkey []byte) string {
ripemd160Hash := Hash160((pubkey)) // HASH160计算,得到Fingerprint
version_hash := append([]byte{version}, ripemd160Hash...) // 将version byte加到Fingerprint前面
checkbytes := CheckSum(version_hash) // 将version_hash即version byte+Fingerprint进行HASH256后取前4字节,得到Checksum
bytes := append(version_hash, checkbytes...) // 将version_hash即version byte+Fingerprint,添加到Checksum前面,得到拼接结果
// 进行base58编码
myAlphabet := base58.BitcoinAlphabet // 编码表
var encodstring string = base58.Encode(bytes, myAlphabet)
return encodstring // 得到最终结果
}
func main() {
// 生成随机的 20 字节公钥
pubKey := make([]byte, 20)
rand.Read(pubKey) // 生成公钥时,使用安全的随机数生成器crpyto/rand
bAddress := GetAddress(pubKey)
// 遍历查询,是否有连续三个c,也就是“靓号地址”中包含3个连续的小写字母c
for i := 0; i <= len(bAddress)-3; i++ {
if bAddress[i] == 'c' && bAddress[i+1] == 'c' && bAddress[i+2] == 'c' { // 连续三个c
fmt.Printf("已得到比特币测试网“靓号地址”,是:%s\n", bAddress)
return
}
}
// 如果没有找到满足条件的地址,则重新生成一个地址,重新查找
main()
}
//关键部分如下截图:
//运行截图如下
使用浏览器访问任意水龙头网站,输入刚刚生成的地址,获取小额的测
试用比特币