区块链工程实验与实践实验1

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

// 具体说明已经在代码中详细注释,实现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)
}

//运行截图如下
在这里插入图片描述
求得结果,地址是合法符合要求的

  1. 练习: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)          // 打印输出
}

//运行截图如下
//求得结果,找到了预计的被修改数据的位置
在这里插入图片描述

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

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

/*
靓号,泛指阿拉伯数字组成的连续相同的易于记忆的号码。车牌靓号多为四个连号或
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()
}

//关键部分如下截图:
在这里插入图片描述
//运行截图如下
在这里插入图片描述
使用浏览器访问任意水龙头网站,输入刚刚生成的地址,获取小额的测
试用比特币

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值