1. 练习:比特币测试网地址的生成

// 具体说明已经在代码中详细注释,实现HASH160、HASH256和Base58编码
// 代码如下

/*比特币测试网地址的生成使用RIPEMD-160、SHA-256哈希算法以及Base58编码对给定公钥生成地址给定公钥:public key 1:02b1ebcdbac723f7444fdfb8e83b13bd14fe679c59673a519df6a1038c07b719c6public key 2:036e69a3e7c303935403d5b96c47b7c4fa8a80ca569735284a91d930f0f49afa86提示:比特币中有两种复合式的哈希函数,分别为:HASH160,即先对输入做一次SHA256,再做一次RIPEMD160;HASH256,即先对输入做一次SHA256,再做一次SHA256。本练习要求的version byte为0x6f(测试网)。*/package mainimport ("crypto/sha256""encoding/hex""fmt""github.com/shengdoushi/base58""golang.org/x/crypto/ripemd160")const version byte = 0x6fconst addressChecksumLen = 4 // 定义Checksum长度为4个字节var PublicKey1 = "02b1ebcdbac723f7444fdfb8e83b13bd14fe679c59673a519df6a1038c07b719c6"var PublicKey2 = "036e69a3e7c303935403d5b96c47b7c4fa8a80ca569735284a91d930f0f49afa86"func Hash160(publicKey []byte) []byte {// 先对输入做一次SHA256hash256 := sha256.New()hash256.Write(publicKey)hash := hash256.Sum(nil) // hash256得到的结果hash// 再做一次RIPEMD160ripemd160 := ripemd160.New()ripemd160.Write(hash) // 对hash再ripemd160计算得到的结果return ripemd160.Sum(nil)}// HASH256,返回CheckSum变量func CheckSum(payload []byte) []byte {hash1 := sha256.Sum256(payload) // HASH256,即先对输入做一次SHA256hash2 := sha256.Sum256(hash1[:])// 再做一次SHA256。return hash2[:addressChecksumLen] // 切片取前4字节}// 返回Addressfunc GetAddress(PublicKey string) string {pubkey, _ := hex.DecodeString(PublicKey) // 返回数组ripemd160Hash := Hash160((pubkey)) // HASH160计算,得到Fingerprintversion_hash := append([]byte{version}, ripemd160Hash...) // 将version byte加到Fingerprint前面checkbytes := CheckSum(version_hash) // 将version_hash即version byte+Fingerprint进行HASH256后取前4字节,得到Checksumbytes := 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)}

//运行截图如下

求得结果,地址是合法符合要求的

  1. 练习:Merkle Tree

// 具体说明已经在代码中详细注释
// 代码如下

/*请用Go语言实现一棵叶子节点数为16的Merkle Tree,并在叶子节点存储任意字符串,并在所有非叶节点计算相应Hash值请将上一步生成的Merkle Tree任一叶子节点数据进行更改,并重新生成其余Hash值。利用Merkle Tree的特点对该修改位置进行快速定位即设计函数func compareMerkleTree(*MTree tree1, *MTree tree2) (int index) { }*/package mainimport ("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 {// 计算哈希sha256hash := sha256.Sum256(data)mNode.Data = hash[:] // 以切片的类型,数据给Data} else { // 有孩子的情况,非叶子节点存储该节点两个孩子节点的Hash值prevHash := append(left.Data, right.Data...) // 将左右子树的数据集合在一起// 计算哈希sha256hash := sha256.Sum256(prevHash)mNode.Data = hash[:] // 以切片的类型,数据给Data}// 给mNode节点的左右子树赋值mNode.Left = leftmNode.Right = rightreturn &mNode // 返回mNode节点地址}// 对最开始的数据初始化,将输入的16个字符串转化为节点,以便于新建树func initial_datas(data [][]byte) []MerkleNode { // 输入二维数组,返回[]MerkleNode数组var nodes []MerkleNodefor _, datum := range data { // 得到字符串数据node := NewMerkleNode(nil, nil, datum) // 左右孩子空,将datum数据传入进去,返回一个地址nodes = append(nodes, *node) // 取这个node的值,加到nodes数组里面去}return nodes // 返回nodes数组}// 新建Merkle Treefunc 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 = newLevelif 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.Lefttmp_comparenode2 := comparenode2.Leftif !bytes.Equal((*tmp_comparenode1).Data, (*tmp_comparenode2).Data) { // 确实是由于左孩子的问题,继续在左孩子的路径上找下去comparenode1 = comparenode1.Leftcomparenode2 = comparenode2.Left} else { // 是由于右孩子的问题,则继续在右孩子的路径上找下去comparenode1 = comparenode1.Rightcomparenode2 = comparenode2.Right}}}// 对被修改的叶子节点进行定位var i intfor 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)// 打印输出}

//运行截图如下
//求得结果,找到了预计的被修改数据的位置

  1. 练习:(拓展实验)生成比特币靓号地址

// 具体说明已经在代码中详细注释
// 代码如下

/*靓号,泛指阿拉伯数字组成的连续相同的易于记忆的号码。车牌靓号多为四个连号或8899、5566之类的号码。在本实验的比特币地址中,由于采用了Base58编码,所以可能存在连续出现3个拉丁字母的“靓号地址”。请完成以下实验:使用Go语言编写一段程序,程序的输出为一个合法的比特币测试网地址(version byte为0x6f),且要求:1. 地址中包含3个连续的小写字母c。2. 生成公钥时,使用安全的随机数生成器crpyto/rand。使用浏览器访问任意水龙头网站,输入刚刚生成的地址,获取小额的测试用比特币,记录下交易ID。如果领取成功,网页将显示如下交易信息。*/package mainimport ("crypto/rand""crypto/sha256""fmt""github.com/shengdoushi/base58""golang.org/x/crypto/ripemd160")const version byte = 0x6f// version byte为0x6fconst addressChecksumLen = 4 // 定义Checksum长度为4个字节func Hash160(publicKey []byte) []byte {// 先对输入做一次SHA256hash256 := sha256.New()hash256.Write(publicKey)hash := hash256.Sum(nil) // hash256得到的结果hash// 再做一次RIPEMD160ripemd160 := ripemd160.New()ripemd160.Write(hash) // 对hash再ripemd160计算得到的结果return ripemd160.Sum(nil)}// HASH256,返回CheckSum变量func CheckSum(payload []byte) []byte {hash1 := sha256.Sum256(payload) // HASH256,即先对输入做一次SHA256hash2 := sha256.Sum256(hash1[:])// 再做一次SHA256。return hash2[:addressChecksumLen] // 切片取前4字节}// 返回Addressfunc GetAddress(pubkey []byte) string {ripemd160Hash := Hash160((pubkey)) // HASH160计算,得到Fingerprintversion_hash := append([]byte{version}, ripemd160Hash...) // 将version byte加到Fingerprint前面checkbytes := CheckSum(version_hash) // 将version_hash即version byte+Fingerprint进行HASH256后取前4字节,得到Checksumbytes := 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/randbAddress := GetAddress(pubKey)// 遍历查询,是否有连续三个c,也就是“靓号地址”中包含3个连续的小写字母cfor i := 0; i <= len(bAddress)-3; i++ {if bAddress[i] == 'c' && bAddress[i+1] == 'c' && bAddress[i+2] == 'c' { // 连续三个cfmt.Printf("已得到比特币测试网“靓号地址”,是:%s\n", bAddress)return}}// 如果没有找到满足条件的地址,则重新生成一个地址,重新查找main()}

//关键部分如下截图:

//运行截图如下

使用浏览器访问任意水龙头网站,输入刚刚生成的地址,获取小额的测
试用比特币