git代码管理

1
2
3
4
5
6
$ git clone https://gitee.com/用户个性地址/HelloGitee.git #将远程仓库克隆到本地
$ git config --global user.name "你的名字或昵称"
$ git config --global user.email "你的邮箱"
$ git add . #将当前目录所有文件添加到git暂存区
$ git commit -m "my first commit" #提交并备注提交信息
$ git push origin master #将本地提交推送到远程仓库

一个简单的区块链

block.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const genesisInfo = "第一个区块"

//定义结构
type Block struct {
PrevBlockHash []byte //前区块哈希
Hash []byte //当前区块哈希
Data []byte //数据
}

//创建区块 对Block的每一个字段填充数据
func NewBlock(data string, prevBlockHash []byte) *Block {
block := Block{
PrevBlockHash: prevBlockHash,
Hash: []byte{}, //先填充为空
Data: []byte(data), //类型转换
}
block.SetHash()
return &block
}

//为了生成区块哈希,我们生成一个简单的函数来计算哈希值
func (block *Block) SetHash() {
var data []byte
data = append(data, block.PrevBlockHash...)
data = append(data, block.Data...)
hash := sha256.Sum256(data) //hash是一个32位的数组
block.Hash = hash[:]
}

blockchain.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//创建区块链 使用Block数组模拟
type BlockChain struct {
Blocks []*Block
}

//实现创建区块链的方法
func NewBlockChain() *BlockChain {
//在创建的时候添加一个区块:创世块
genesisBlock := NewBlock(genesisInfo, []byte{0x000000000000000})

bc := BlockChain{Blocks: []*Block{genesisBlock}}
return &bc
}

//添加区块
func (bc *BlockChain) AddBlock(data string) {
//bc.Blocks的最后一个区块的Hash值就是当前新区块的前哈希
lastBlock := bc.Blocks[len(bc.Blocks)-1]
prevHash := lastBlock.Hash

//1.创建一个区块
block := NewBlock(data, prevHash)

//2.添加倒bc.Blocks数组中
bc.Blocks = append(bc.Blocks, block)
}

main.go

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
//区块实例化
//block := NewBlock(genesisInfo, []byte{0x000000000000000})
bc := NewBlockChain()
bc.AddBlock("第二个区块")
for index, block := range bc.Blocks {
fmt.Printf("第%d个区块:\n", index+1)
fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
fmt.Printf("Hash : %x\n", block.Hash)
fmt.Printf("Data : %s\n", block.Data)
}
}

升级版区块链

1. 更新补充区块字段

1
2
3
4
5
6
7
8
9
10
type Block struct {
Version uint64 //区块版本号
PrevBlockHash []byte //前区块哈希
MerkleRoot []byte //Merkle根
TimeStamp uint64 //从1970年1月1日至今的秒数
Difficulity uint64 //挖矿的难度值
Nonce uint64 //随机数
Hash []byte //当前区块哈希
Data []byte //数据,目前为字节流 后续使用交易代替
}

2.更新NewBlock函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//创建区块 对Block的每一个字段填充数据
func NewBlock(data string, prevBlockHash []byte) *Block {
block := Block{
Version: 00,
PrevBlockHash: prevBlockHash,
MerkleRoot: []byte{}, //先填充为空
TimeStamp: uint64(time.Now().Unix()),
Difficulity: 10, //随便写的
Nonce: 10, //随便写的
Hash: []byte{}, //先填充为空
Data: []byte(data), //类型转换
}
block.SetHash()
return &block
}

3.更新SetHash函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//为了生成区块哈希,我们生成一个简单的函数来计算哈希值
func (block *Block) SetHash() {
var data []byte
//uintToByte将数字转成byte
data = append(data, uintToByte(block.Version)...)
data = append(data, block.PrevBlockHash...)
data = append(data, block.MerkleRoot...)
data = append(data, uintToByte(block.TimeStamp)...)
data = append(data, uintToByte(block.Difficulity)...)
data = append(data, uintToByte(block.Nonce)...)
data = append(data, block.Data...)
//data = append(data, block.Hash...)

hash := sha256.Sum256(data) //hash是一个32位的数组
block.Hash = hash[:]
}

4.创建utils.go文件,添加函数uintToByte,将uint64类型转换为byte类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//这是一个工具函数文件
//将数字转成byte类型
func uintToByte(num uint64) []byte {
//TODO
//使用binary.Write来进行编码
var buffer bytes.Buffer
//编码要进行校验
err := binary.Write(&buffer, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
return buffer.Bytes()
}
//也可使用binary.Read()进行解码

5.将SetHash函数中的append函数修改为bytes.Jion函数,优化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//为了生成区块哈希,我们生成一个简单的函数来计算哈希值
func (block *Block) SetHash() {
var data []byte
//uintToByte将数字转成byte
//data = append(data, uintToByte(block.Version)...)
//data = append(data, block.PrevBlockHash...)
//data = append(data, block.MerkleRoot...)
//data = append(data, uintToByte(block.TimeStamp)...)
//data = append(data, uintToByte(block.Difficulity)...)
//data = append(data, uintToByte(block.Nonce)...)
//data = append(data, block.Data...)
//data = append(data, block.Hash...)

tmp := [][]byte{
uintToByte(block.Version),
block.PrevBlockHash,
block.MerkleRoot,
uintToByte(block.TimeStamp),
uintToByte(block.Difficulity),
uintToByte(block.Nonce),
block.Data,
}
data = bytes.Join(tmp, []byte{})

hash := sha256.Sum256(data) //hash是一个32位的数组
block.Hash = hash[:]
}

当前版本问题

1.随机数和难度值是随意填写的

2.区块的哈希值是无规则的

v2版本

1.创建proofofwork.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//一. 定义一个工作量证明的结构ProofOfWork
//
//1. block
//2. 目标值

type ProffOfWork struct {
block *Block

//来存储哈希值,他内置了一些方法
//SetBytes:把bytes转成big.Int类型
//SetString:把string转成big.Int类型
//Cmp:比较方法
target *big.Int //系统提供的固定的难度值
}

2.提供创建POW的函数->NewProofofWork(参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//提供创建POW的函数->NewProofofWork(参数)
func NewProofofWork(block *Block) *ProffOfWork {
pow := ProffOfWork{
block: block,
}
//写难度值,难度值应该是推导出来的,但是为了简化,先写难度值为固定的
//16进制格式的字符串
targetStr := "0000100000000000000000000000000000000000000000000000000000000000"
var bigIntTmp big.Int
bigIntTmp.SetString(targetStr, 16)
pow.target = &bigIntTmp

return &pow
}
//进行修改程序推导难度值
func NewProofOfWork(block *Block) *ProffOfWork {
pow := ProffOfWork{
block: block,
}
//程序推导难度值
//目标值
//0000100000000000000000000000000000000000000000000000000000000000
//初始化
//0000000000000000000000000000000000000000000000000000000000000001
//向左移动256位
//1 0000000000000000000000000000000000000000000000000000000000000000
//向右移动5次,一个16进制表示4个2进制,因此向右移动20位
//1 0000100000000000000000000000000000000000000000000000000000000000
bigIntTmp := big.NewInt(1)
bigIntTmp.Lsh(bigIntTmp, 256) //向左移256位
bigIntTmp.Rsh(bigIntTmp, Bits) //向右移动20位

pow.target = bigIntTmp

return &pow
}

3.提供计算不断计算hash的函数->run()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//这是pow的运算函数,为了获取挖矿的随机数,同时返回区块的哈希值
func (pow *ProffOfWork) Run() ([]byte, uint64) {
//1. 获取block数据
//2. 拼接nonce
//3. sha256
//4. 与难度值进行比较,哈希值大于难度值,nonce++,小于难度值则挖矿成功
var nonce uint64
var hash [32]byte

for {
hash = sha256.Sum256(pow.prepareData(nonce))

//将hash(数组类型)转成big.Int,然后与pow.target比较
var bigIntTmp big.Int
bigIntTmp.SetBytes(hash[:])

//func (x *Int) Cmp(y *Int) (r int)
//Cmp compares x and y and returns:
//-1 if x < y
// 0 if x == y
//+1 if x > y
if bigIntTmp.Cmp(pow.target) == -1 {
//此时x<y,挖矿成功
fmt.Printf("挖矿成功!nonce:%d,哈希值为:%x\n", nonce, hash)
break
} else {
nonce++
}
}
return hash[:], nonce
}

//prepareData用来计算hash值供run方法调用
func (pow *ProffOfWork) prepareData(nonce uint64) []byte {
block := pow.block

tmp := [][]byte{
uintToByte(block.Version),
block.PrevBlockHash,
block.MerkleRoot,
uintToByte(block.TimeStamp),
uintToByte(block.Difficulity),
uintToByte(block.Nonce),
block.Data,
}
data := bytes.Join(tmp, []byte{})
return data
}

4. 删除SetHash函数,并修改NewBlock函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//创建区块 对Block的每一个字段填充数据
func NewBlock(data string, prevBlockHash []byte) *Block {
block := Block{
Version: 00,
PrevBlockHash: prevBlockHash,
MerkleRoot: []byte{}, //先填充为空
TimeStamp: uint64(time.Now().Unix()),
Difficulity: 10, //随便写的
Nonce: 10, //随便写的
Hash: []byte{}, //先填充为空
Data: []byte(data), //类型转换
}
//block.SetHash()
pow := NewProofOfWork(&block)
hash, nonce := pow.Run()
block.Hash = hash
block.Nonce = nonce

return &block
}

5. 证明校验函数IsValid(),校验挖矿是否有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//工作量证明校验
func (pow *ProffOfWork) IsValid() bool {
//在校验的时候 block里面的数据是完整的 我们需要校验Hash,block数据和Nonce是否满足难度值要求

//获取block数据
//拼接nonce
//做sha256
//比较

//block := pow.block
data := pow.prepareData(pow.block.Nonce)
hash := sha256.Sum256(data)

var tmp big.Int
tmp.SetBytes(hash[:])

//if tmp.Cmp(pow.target) == -1 {
// return true
//}else{
// return false
//}or
return tmp.Cmp(pow.target) == -1
}

当前版本问题

1.区块存储在内存中,每次程序执行完就释放,无法重用

2.创建区块不灵活,创建区块在main函数中写死了,无法随意添加区块

V3版本

1. 安装bolt,写一个demo进行测试

在终端执行go get github.com/boltdb/bolt/…命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func main() {
db, err := bolt.Open("test.db", 0600, nil) //"test.db"是数据库名称 0600是读写
//向数据库中写入数据
//从数据库中读取数据

if err != nil {
log.Panic(err)
}
defer db.Close()

db.Update(func(tx *bolt.Tx) error {
//所有的操作都在这里

b1 := tx.Bucket([]byte("bucketName1"))
if b1 == nil {
//如果b1为空,说明这个名字不存在,需要创建
b1, err = tx.CreateBucket([]byte("bucketName1"))
if err != nil {
log.Panic(err)
}
}

//bucket创建成功,准备写入数据
//写数据使用Put,读数据使用Get
err = b1.Put([]byte("name"), []byte("Anduin"))
if err != nil {
fmt.Println("写入数据失败name:Anduin")
}

err = b1.Put([]byte("age"), []byte(strconv.Itoa(22)))
if err != nil {
fmt.Println("写入数据失败age:22")
}

//读取数据
name := b1.Get([]byte("name"))
age := b1.Get([]byte("age"))
fmt.Printf("name:%s\n", name)
fmt.Printf("age:%s\n", age)
return nil
})
}

2.更新NewBlockChain方法

​ 注意:

  1. key一定唯一

  2. 把所有的区块都写到一个bucket中

  3. 在bucket中存储两种数据:(1). 使用区块的hash值作为key,block的字节流作为value

    ​ (2). 最后一个区块的哈希值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//使用bolt进行改写,需要两个字段
//1. bolt数据库的句柄
//2. 最后一个区块的哈希值
type BlockChain struct {
db *bolt.DB //句柄

tail []byte //代表最后一个区块的哈希值
}

const blockChainName = "blockChain.db"
const blockBucketName = "blockBucket"
const lastHashKey = "lastHashKey"

//实现创建区块链的方法
func NewBlockChain() *BlockChain {
//功能分析:
//1. 获得区块链数据库的句柄,打开数据库,读写数据
db, err := bolt.Open(blockChainName, 0600, nil) //"test.db"是数据库名称 0600是读写
//向数据库中写入数据
//从数据库中读取数据

if err != nil {
log.Panic(err)
}

var tail []byte

db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockBucketName))
//判断是否有bucket,如果没有,创建bucket并写入创世块,写入lastHashKey数据
if b == nil {
//如果b1为空,说明这个名字不存在,需要创建
fmt.Println("bucket不存在,需要创建")
b, err = tx.CreateBucket([]byte(blockBucketName))
if err != nil {
log.Panic(err)
}
//开始添加创世块
genesisBlock := NewBlock(genesisInfo, []byte{})
b.Put(genesisBlock.Hash, genesisBlock.Serialize() /*将区块序列化,转成字节流*/)
b.Put([]byte(lastHashKey), genesisBlock.Hash)

//测试,读取写入的数据
//blockInfo := b.Get(genesisBlock.Hash)
//block := deSerialize(blockInfo)
//fmt.Printf("解码后的block数据:%s\n", block)

//更新tail位最后一个区块的哈希值
tail = genesisBlock.Hash
} else {
// 获取最后一个区块的哈希值,填充给tail
tail = b.Get([]byte(lastHashKey))
}
return nil
})
//返回实例
return &BlockChain{
db: db,
tail: tail,
}
}

3.在block.go中编写Serialize( )与deSerialize( )编码解码区块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//1. gob是go语言内置的编码包
//2. 他可以对任意数据类型进行编码和解码
//3. 编码时,先要创建编码器,编码器进行编码
//4. 解码时,先要创建解码器,解码器进行解码

//序列化 将区块转换成字节流
func (block *Block) Serialize() []byte {
var buffer bytes.Buffer

//定义编码器
encoder := gob.NewEncoder(&buffer)
//编码器对结构进行编码
err := encoder.Encode(&block)
if err != nil {
log.Panic(err)
}
return buffer.Bytes()
}

//反序列化
func deSerialize(data []byte) *Block {

fmt.Printf("需要解码的数据:%x\n", data)

var block Block
decoder := gob.NewDecoder(bytes.NewReader(data))
//解码器对结构进行解码
err := decoder.Decode(&block)
if err != nil {
log.Panic(err)
}
return &block
}

4. 更新AddBlock方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func (bc *BlockChain) AddBlock(data string) {
bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockBucketName))

if b == nil {
//如果b1为空,说明这个名字不存在,需要检查
fmt.Println("bucket不存在,请检查")
os.Exit(1) //退出
}

//创建一个区块
block := NewBlock(data, bc.tail)
b.Put(block.Hash, block.Serialize() /*将区块序列化,转成字节流*/)
b.Put([]byte(lastHashKey), block.Hash)

//测试,读取写入的数据
//blockInfo := b.Get(block.Hash)
//nowblock := deSerialize(blockInfo)
//fmt.Printf("解码后的block数据:%s\n", nowblock)

//更新tail位最后一个区块的哈希值
bc.tail = block.Hash

return nil
})
}

4.定义、创建迭代器

//迭代器作用:遍历容器,将数据逐个返回,防止一次性加载到内存,所以一点一点读取

//迭代器包含两个元素:1. 区块链句柄db 2. 移动的指针current,存储哈希

db: 为了遍历账本

current:为了访问每一个区块

调用Next( )方法,要做两件事情:1. 返回当前所指向的区块的数据block 2. 指针向前移动

1
2
3
4
5
6
7
8
9
10
11
12
13
//定义一个区块链的迭代器,包含db,current
type BlockChainIterator struct {
db *bolt.DB //账本
current []byte //当前所指向区块的哈希值
}

//创建迭代器,使用bc进行初始化
func (bc *BlockChain) NewIterator() *BlockChainIterator {
return &BlockChainIterator{
db: bc.db,
current: bc.tail,
}
}

5. Next函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//读取数据
func (it *BlockChainIterator) Next() *Block {
var block Block
//读取数据库
it.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockBucketName))
if b == nil {
fmt.Println("bucket不存在,请检查")
os.Exit(1)
}

//读取数据
blockInfo /*block的字节流*/ := b.Get(it.current)
block = *DeSerialize(blockInfo)

it.current = block.PrevBlockHash
return nil
})
return &block
}

6. 修改main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func main() {
//区块实例化
//block := NewBlock(genesisInfo, []byte{0x000000000000000})
bc := NewBlockChain()
defer bc.db.Close()
bc.AddBlock("the second block")
bc.AddBlock("the third block")
bc.AddBlock("the forth block")
it := bc.NewIterator()
for {
block := it.Next()

fmt.Println("-----------------------------------------------------------------")
fmt.Printf("Version : %x\n", block.Version)
fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
fmt.Printf("TimeStamp : %x\n", block.TimeStamp)
fmt.Printf("Difficulity : %d\n", block.Difficulity)
fmt.Printf("Nonce : %d\n", block.Nonce)
fmt.Printf("Hash : %x\n", block.Hash)
fmt.Printf("Data : %s\n", block.Data)
pow := NewProofOfWork(block)
fmt.Printf("IsValid:%v\n", pow.IsValid())
fmt.Println("-----------------------------------------------------------------")

if bytes.Equal(block.PrevBlockHash, []byte{}) {
fmt.Println("区块链遍历结束")
break
}
}
}

7. CLI-Run实现,创建cli.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//1. 所有的支配动作交给命令行来做
//2. 主函数只需要调用命令行结构即可
//3. 根据输入的不同命令,命令行做相应动作
// 1. addBlock
// 2. printChain

const Usage = `
./blockchain addBlock "xxxxxx" 添加数据到区块链
./blockchain printChain 打印区块链
`

//CLI: command line的缩写
type CLI struct {
bc *BlockChain
}

//给CLI提供一个方法,进行命令解析,从而执行调度
func (cli *CLI) Run() {
cmds := os.Args
if len(cmds) < 2 {
fmt.Println(Usage)
os.Exit(1)
}
switch cmds[1] {
case "addBlock":
fmt.Printf("添加区块命令被调用,数据:%s\n", cmds[2])
data := cmds[2]
cli.AddBlock(data)
case "printChain":
fmt.Printf("打印区块链命令被调用\n")
cli.PrintChain()
default:
fmt.Printf("无效的命令,请检查\n")
fmt.Println(Usage)
}
//添加区块的时候:bc.addBlock(data) ,data通过os.Args拿回来
//打印区块的时候:遍历区块链,不需要外部输入数据
}

8.创建commands.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//cli.go的配套文件,实现具体的命令

func (cli *CLI) AddBlock(data string) {
cli.bc.AddBlock(data)
fmt.Printf("添加区块成功\n")
}

func (cli *CLI) PrintChain() {
it := cli.bc.NewIterator()
for {
block := it.Next()

fmt.Println("-----------------------------------------------------------------")
fmt.Printf("Version : %x\n", block.Version)
fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
fmt.Printf("MerkleRoot : %x\n", block.MerkleRoot)
fmt.Printf("TimeStamp : %x\n", block.TimeStamp)
fmt.Printf("Difficulity : %d\n", block.Difficulity)
fmt.Printf("Nonce : %d\n", block.Nonce)
fmt.Printf("Hash : %x\n", block.Hash)
fmt.Printf("Data : %s\n", block.Data)
pow := NewProofOfWork(block)
fmt.Printf("IsValid:%v\n", pow.IsValid())

if bytes.Equal(block.PrevBlockHash, []byte{}) {
fmt.Println("区块链遍历结束")
break
}
}
}

9.修改main函数

1
2
3
4
5
6
7
func main() {

bc := NewBlockChain()
defer bc.db.Close()
cli := CLI{bc}
cli.Run()
}

10.命令行操作

1
2
3
go build -o blockchain.exe ./         //build项目,起名为blockchain
blockchain //运行项目
blockchain 。。。。 //根据提示进行操作

v4版本

交易输入(TXInput)

指明交易发起人可支付资金的来源,包含:

  1. 引用utxo所在交易的ID(知道在哪个房间)
  2. 所消费utxo在output中的索引(具体位置)
  3. 解锁脚本(签名,公钥)

交易输出(TXOutput)

包含资金接收方的相关信息,包含:

  1. 接收金额(数字)
  2. 锁定脚本(对方公钥的哈希,这个哈希可以通过地址反推出来,所以转账时知道地址即可)

交易ID

一般是交易结构的哈希值(参考block的哈希值)

1. 新建transaction.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//定义交易的结构
//定义input
type TXInput struct {
TXID []byte //交易ID
Index int64 //output的索引
Address string //解锁脚本
}

//定义output
type TXOutput struct {
Value float64 //转账金额
Address string //锁定脚本
}

//设置交易ID
type Transaction struct {
TXid []byte //交易id
TXInputs []TXInput //所有的input
TXOutputs []TXOutput //所有的output
}

//生产交易的哈希值
func (tx *Transaction) SetTXID() {
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)

err := encoder.Encode(tx)
if err != nil {
log.Panic(err)
}
hash := sha256.Sum256(buffer.Bytes())
tx.TXid = hash[:]
}

//实现挖矿交易,只有输出,没有有效输入,不需要引用id,索引,签名
//maner 为矿工
func NewCoinbaseTX(miner string) *Transaction {
//TODO
inputs := []TXInput{{
TXID: nil,
Index: -1,
Address: genesisInfo,
}} //设置特殊的值用于判断
outputs := []TXOutput{{
Value: 12.5,
Address: miner,
}}
tx := Transaction{
TXid: nil,
TXInputs: inputs,
TXOutputs: outputs,
}
tx.SetTXID()
return &tx
}

2. 修改文件

  1. 改写block结构
  2. 逐个文件修改

3. 模拟梅克尔根,在NewBlock中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//模拟梅克尔根,做简单处理
func (block *Block) HashTransactions() {
//交易的id就是交易的哈希值,将交易id拼接起来整体做哈希运算,作为梅克尔根
var hashes []byte
for _, tx := range block.Transactions {
txid := tx.TXid /*[]byte*/
hashes = append(hashes, txid...)
}
hash := sha256.Sum256(hashes)
block.MerkleRoot = hash[:]
}


//在NewBlock中调用
block.HashTransactions()

4. 添加GetBanlance、FindMuUtxos函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//实现思路:
//1. 遍历账本
//2. 遍历交易
//3. 遍历output
//4. 找到属于我的所有output
//5. 遍历input,把已经消耗的output剔除
//遍历交易输出
func (bc *BlockChain) FindMuUtxos(address string) []TXOutput {
fmt.Printf("FindMuUtxos\n")
var UTXOs []TXOutput //返回的结构
it := bc.NewIterator()

spentUTXOs := make(map[string][]int64) //标识已经消耗过的utxo的结构 ,key是交易id,value是这个id里面的output索引的数组

//遍历账本
for {
block := it.Next()
//遍历交易
for _, tx := range block.Transactions {
//遍历input
for _, input := range tx.TXInputs {
if input.Address == address {
fmt.Printf("找到了消耗过的output , index : %d\n", input.Index)
key := string(input.TXID)
spentUTXOs[key] = append(spentUTXOs[key], input.Index)
}
}

OUTPUT:
//遍历output
for i, output := range tx.TXOutputs {
key := string(tx.TXid)
indexes := spentUTXOs[key]
if len(indexes) != 0 {
fmt.Printf("找到了当前这笔交易中有被消耗过的output\n")
for _, j := range indexes {
if int64(i) == j {
fmt.Printf("i == j , 当前的output已经被消耗了,跳过不统计\n")
continue OUTPUT
}
}
}
//找到属于我的output
if address == output.Address {
fmt.Printf("找到了属于%s的output , i : %d\n", address, i)
UTXOs = append(UTXOs, output)
}
}
}
if len(block.PrevBlockHash) == 0 {
fmt.Printf("遍历区块链结束\n")
break
}
}
return UTXOs
}

//查询余额
func (bc *BlockChain) GetBalance(address string) {
utxos := bc.FindMuUtxos(address)

var total = 0.0
for _, utxo := range utxos {
total += utxo.Value //10,3,1
}

fmt.Printf("%s的余额为%f", address, total)
}

5. 在cli.go中添加getBalance命令

1
2
3
case "getBalance":
fmt.Printf("获取余额命令被调用\n")
cli.bc.GetBalance(cmds[2]) //cmd[2]为 blockchain getBalance 地址 获取地址的余额 中的地址

6. 创建普通交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
utxos := make(map[string][]int64) //标识能用的utxo
var resValue float64 //这些utxo存储的金额

//遍历账本,找到属于付款人的合适的金额,把这个output找到
utxos, resValue = bc.FindNeedUtxos(from, amount)

//如果找到的钱不足以转账,创建交易失败
if resValue < amount {
fmt.Printf("余额不足,交易失败\n")
return nil
}

var inputs []TXInput
var outputs []TXOutput

//将outputs转成inputs
for txid, indexes := range utxos {
for _, i := range indexes {
input := TXInput{
TXID: []byte(txid),
Index: i,
Address: from,
}
inputs = append(inputs, input)
}
}

//创建输出,创建一个属于收款人的output
output := TXOutput{
Value: amount,
Address: to,
}
outputs = append(outputs, output)

//如果有找零,创建属于付款人的output
if resValue > amount {
output1 := TXOutput{
Value: resValue - amount,
Address: from,
}
outputs = append(outputs, output1)
}

//创建交易
tx := Transaction{
TXid: nil,
TXInputs: inputs,
TXOutputs: outputs,
}

//设置交易id
tx.SetTXID()

//返回交易结构
return &tx
}

7.在blockchain.go中添加FindNeedUtxos方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//遍历账本,找到属于付款人的合适的金额,把这个outputs找到
func (bc *BlockChain) FindNeedUtxos(from string, amount float64) (map[string][]int64, float64) {
needUtxos := make(map[string][]int64) //标识能用的utxo
//TODO
//var UTXOs []TXOutput //返回的结构
var resValue float64 //统计的金额

it := bc.NewIterator()

spentUTXOs := make(map[string][]int64) //标识已经消耗过的utxo的结构 ,key是交易id,value是这个id里面的output索引的数组

//遍历账本
for {
block := it.Next()
//遍历交易
for _, tx := range block.Transactions {
//遍历input
for _, input := range tx.TXInputs {
if input.Address == from {
fmt.Printf("找到了消耗过的output , index : %d\n", input.Index)
key := string(input.TXID)
spentUTXOs[key] = append(spentUTXOs[key], input.Index)
}
}

OUTPUT:
//遍历output
for i, output := range tx.TXOutputs {
key := string(tx.TXid)
indexes := spentUTXOs[key]
if len(indexes) != 0 {
fmt.Printf("找到了当前这笔交易中有被消耗过的output\n")
for _, j := range indexes {
if int64(i) == j {
fmt.Printf("i == j , 当前的output已经被消耗了,跳过不统计\n")
continue OUTPUT
}
}
}
//找到属于我的output
if from == output.Address {
fmt.Printf("找到了属于%s的output , i : %d\n", from, i)
//UTXOs = append(UTXOs, output)
//在这里实现控制逻辑
//找到符合条件的output
//添加到返回结构中needUtxos
needUtxos[key] = append(needUtxos[key], int64(i))
resValue += output.Value

//判断金额是否足够,足够则直接返回,不足则继续遍历
if resValue >= amount {
return needUtxos, resValue
}
}
}
}
if len(block.PrevBlockHash) == 0 {
fmt.Printf("遍历区块链结束\n")
break
}
}
//TODO
//return utxos, 0.0
return needUtxos, resValue
}

8. 在commdans.go中添加send方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (cli *CLI) Send(from, to string, amount float64, miner, data string) {

//创建挖矿交易
coinbase := NewCoinbaseTX(miner, data)
txs := []*Transaction{coinbase}

//创建普通交易
tx := NewTransaction(from, to, amount, cli.bc)
if tx != nil {
txs = append(txs, tx)
} else {
fmt.Printf("发现无效交易,过滤\n")
}

//添加到区块
cli.bc.AddBlock(txs)
fmt.Printf("挖矿成功")
}

9. 将FindNeedUtxos和FindMuUtxos进行整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//1. FindMyUtxos:找到所有的utxo(只要output就行了)
//2. FindNeedUtxos:找到需要的utxo(需要output的定位)

type UTXOInfo struct {
TXID []byte //交易id
Index int64 //output的索引值
Output TXOutput //output本身
}

//遍历交易输出
func (bc *BlockChain) FindMyUtxos(address string) []UTXOInfo {
fmt.Printf("FindMuUtxos\n")

var UTXOInfos []UTXOInfo //新的返回结构
it := bc.NewIterator()

spentUTXOs := make(map[string][]int64) //标识已经消耗过的utxo的结构 ,key是交易id,value是这个id里面的output索引的数组

//遍历账本
for {
block := it.Next()
//遍历交易
for _, tx := range block.Transactions {
//遍历input
for _, input := range tx.TXInputs {
if input.Address == address {
fmt.Printf("找到了消耗过的output , index : %d\n", input.Index)
key := string(input.TXID)
spentUTXOs[key] = append(spentUTXOs[key], input.Index)
}
}

OUTPUT:
//遍历output
for i, output := range tx.TXOutputs {
key := string(tx.TXid)
indexes := spentUTXOs[key]
if len(indexes) != 0 {
fmt.Printf("找到了当前这笔交易中有被消耗过的output\n")
for _, j := range indexes {
if int64(i) == j {
fmt.Printf("i == j , 当前的output已经被消耗了,跳过不统计\n")
continue OUTPUT
}
}
}
//找到属于我的output
if address == output.Address {
fmt.Printf("找到了属于%s的output , i : %d\n", address, i)

utxoinfo := UTXOInfo{
TXID: tx.TXid,
Index: int64(i),
Output: output,
}
UTXOInfos = append(UTXOInfos, utxoinfo)
}
}
}
if len(block.PrevBlockHash) == 0 {
fmt.Printf("遍历区块链结束\n")
break
}
}
return UTXOInfos
}

//查询余额
func (bc *BlockChain) GetBalance(address string) {
utxoinfos := bc.FindMyUtxos(address)

var total = 0.0
//所有的output都在utxoinfos内部
//获取余额时,遍历utxoinfos获取output即可
for _, utxoinfo := range utxoinfos {
total += utxoinfo.Output.Value //10,3,1
}

fmt.Printf("%s的余额为%f", address, total)
}

//遍历账本,找到属于付款人的合适的金额,把这个outputs找到
func (bc *BlockChain) FindNeedUtxos(from string, amount float64) (map[string][]int64, float64) {
needUtxos := make(map[string][]int64) //标识能用的utxo

var resValue float64 //统计的金额

//复用FindMyUtxo函数,这个函数已经包含了所有的信息
utxoinfos := bc.FindMyUtxos(from)

for _, utxoinfo := range utxoinfos {
key := string(utxoinfo.TXID)
needUtxos[key] = append(needUtxos[key], utxoinfo.Index)
resValue += utxoinfo.Output.Value

//判断金额是否足够,足够则直接返回,不足则继续遍历
if resValue >= amount {
break
}
}
return needUtxos, resValue
}

10. 添加isCoinbase方法,判断交易是否为挖矿交易

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (tx *Transaction) IsCoinbase() bool {
inputs := tx.TXInputs
if len(inputs) == 1 && inputs[0].TXID == nil && inputs[0].Index == -1 {
return true
}
return false
}

//修改FindMyUtxos函数
//判断是否为普通交易
if tx.IsCoinbase() == false {
//是普通交易,遍历input
for _, input := range tx.TXInputs {
if input.Address == address {
fmt.Printf("找到了消耗过的output , index : %d\n", input.Index)
key := string(input.TXID)
pentUTXOs[key] = append(spentUTXOs[key], input.Index)
}
}
}

11. 添加CreateBlockChain方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//创建区块链
func CreateBlockChain(miner string) *BlockChain {
//功能分析:
//1. 获得区块链数据库的句柄,打开数据库,读写数据
db, err := bolt.Open(blockChainName, 0600, nil) //"test.db"是数据库名称 0600是读写

if err != nil {
log.Panic(err)
}

var tail []byte
//向数据库中写入数据
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte(blockBucketName))
if err != nil {
log.Panic(err)
}
//开始添加创世块
//创世块中只有一个挖矿交易,只有coinbase
coinbase := NewCoinbaseTX(miner, genesisInfo)
genesisBlock := NewBlock([]*Transaction{coinbase}, []byte{})

b.Put(genesisBlock.Hash, genesisBlock.Serialize() /*将区块序列化,转成字节流*/)
b.Put([]byte(lastHashKey), genesisBlock.Hash)

//测试,读取写入的数据
//blockInfo := b.Get(genesisBlock.Hash)
//block := DeSerialize(blockInfo)
//fmt.Printf("解码后的block数据:%s\n", block)

//更新tail位最后一个区块的哈希值
tail = genesisBlock.Hash
return nil
})
//返回实例
return &BlockChain{
db: db,
tail: tail,
}
}

12. 修改NewBlockChain函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func NewBlockChain() *BlockChain {
//功能分析:
//1. 获得区块链数据库的句柄,打开数据库,读写数据
db, err := bolt.Open(blockChainName, 0600, nil) //"test.db"是数据库名称 0600是读写
//从数据库中读取数据

if err != nil {
log.Panic(err)
}

var tail []byte
//在数据库中读取数据
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockBucketName))
//判断是否有bucket,如果没有,创建bucket并写入创世块,写入lastHashKey数据
if b == nil {
fmt.Printf("区块链bucket为空,请检查")
os.Exit(1)
}
// 获取最后一个区块的哈希值,填充给tail
tail = b.Get([]byte(lastHashKey))

return nil
})
//返回实例
return &BlockChain{
db: db,
tail: tail,
}
}

14.添加命令

1
blockchain createBlockChain "地址" 创建区块链

15.修改AddBlock为CreateBlockChain函数

1
2
3
4
5
func (cli *CLI) CreateBlockChain(addr string) {
bc := CreateBlockChain(addr)
bc.db.Close()
fmt.Printf("创建区块链成功\n")
}

16. 修改main函数

1
2
3
4
func main() {
cli := CLI{}
cli.Run()
}

17. 使用NewBlockChain改写GetBalance

1
2
//CLI中不需要保存区块链,所有的命令在自己调用之前,自己获取区块链实例
//自行进行修改代码

18. 添加函数,判断区块链是否存在

判断blockchain.db文件是否存在

1
2
3
4
5
6
7
8
9
10
11
//判断文件是否存在

func IsFileExist(Filename string) bool {
//使用os.Stat来判断
_, err := os.Stat(Filename)

if os.IsNotExist(err) {
return false
}
return true
}

19. 修改代码

添加以下语句到commands.go的每个方法中

1
2
3
4
if bc == nil {
return
}
defer bc.db.Close()

添加以下语句到NewBlockChain和CreateBlockChain方法中

1
2
3
4
if !IsFileExist(blockChainName) {
fmt.Printf("区块链不存在,请创建区块链\n")
return nil
}

当前版本存在问题:

1.地址是用字符串代替的

2.没有校验

V5版本

1. 创建秘钥对->公钥->地址

2. 由公钥生成地址

  1. 使用椭圆曲线算法生成私钥
  2. 由私钥生成公钥
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//esdsaTest.go   ecdsa签名demo

package main

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"math/big"
"os"
)

func main() {
//1. 创建私钥
//2. 创建公钥
//3. 私钥对数据的哈希进行签名
//4. 使用数据,签名,公钥进行校验

curve := elliptic.P256()
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
os.Exit(1)
}

pubKey := privateKey.PublicKey

data := "hello"

dataHash := sha256.Sum256([]byte(data))

r, s, err := ecdsa.Sign(rand.Reader, privateKey, dataHash[:])

//一般传输过程中会把r,s拼成字节流再传输
fmt.Printf("r: %x,len(r): %d\n", r.Bytes(), len(r.Bytes()))
fmt.Printf("s: %x,len(s): %d\n", s.Bytes(), len(s.Bytes()))
signagure := append(r.Bytes(), s.Bytes()...)

if err != nil {
os.Exit(1)
}

//传输中... : 数据,签名signagure,公钥
//在接收端从中间把r,s切出来
var r1, s1 big.Int
r1Data := signagure[0 : len(signagure)/2]
s1Data := signagure[len(signagure)/2:]

r1.SetBytes(r1Data)
s1.SetBytes(s1Data)

res := ecdsa.Verify(&pubKey, dataHash[:], &r1, &s1)

fmt.Printf("res : %v\n", res)
}

3. 保存私钥公钥

​ 钱包wallet.dat保存所有的公钥,私钥,地址

4. 创建wallet.go 生成新的比特币地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
type WalletKeyPair struct {
PrivateKey *ecdsa.PrivateKey
//将公钥的X,Y进行字节流拼接后传输
PublicKey []byte
}

func NewWalletKeyPair() *WalletKeyPair {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

if err != nil {
log.Panic(err)
}
publicKeyRow := privateKey.PublicKey

publicKey := append(publicKeyRow.X.Bytes(), publicKeyRow.Y.Bytes()...)
return &WalletKeyPair{
PrivateKey: privateKey,
PublicKey: publicKey,
}
}

func (w *WalletKeyPair) GetAddress() string {
hash := sha256.Sum256(w.PublicKey)

//创建一个hash160对象
//向hash160中写入数据
//做哈希运算
rip160Haher := ripemd160.New()
_, err := rip160Haher.Write(hash[:])
if err != nil {
log.Panic(err)
}
//Sum函数会把结果与Sum的参数append到一起返回,传入nil防止数据污染
publicHash := rip160Haher.Sum(nil)

version := 0x00

//21字节的数据
payload := append([]byte{byte(version)}, publicHash...)

//第一次哈希
first := sha256.Sum256(payload)
//第二次哈希
second := sha256.Sum256(first[:])

//取前四个字节作为校验码
checksum := second[0:4]

//25字节的数据
payload = append(payload, checksum...)

address := base58.Encode(payload)

return address
}
//1. 创建一个结构WalletKeyPair秘钥对,保存公钥和私钥
//2. 给这个结构提供一个方法GetAddress:私钥->公钥->地址

type WalletKeyPair struct {
PrivateKey *ecdsa.PrivateKey
//将公钥的X,Y进行字节流拼接后传输
PublicKey []byte
}

func NewWalletKeyPair() *WalletKeyPair {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

if err != nil {
log.Panic(err)
}
publicKeyRow := privateKey.PublicKey

publicKey := append(publicKeyRow.X.Bytes(), publicKeyRow.Y.Bytes()...)
return &WalletKeyPair{
PrivateKey: privateKey,
PublicKey: publicKey,
}
}

5 .克隆git://github.com/golang/crypto.git

1
git clone git://github.com/golang/crypto.git

6. 添加Wallets.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//wallets结构
//把地址和秘钥对一一对应起来
//map[address]——>walletKeyPair

type Wallets struct {
WalletsMap map[string]*WalletKeyPair
}

//创建Wallets,返回Wallets的实例

func NewWallets() *Wallets {
var ws Wallets
ws.WalletsMap = make(map[string]*WalletKeyPair)
//1. 把所有的钱包从本地加载出来
//TODO
//2. 返回实例
return &ws
}

//这个Wallets是对外的,WalletKeyPair是对内的,Wallets调用WalletKeyPair
func (ws *Wallets) CreateWallet() string {
//1. 调用NewWalletKeyPair
wallet := NewWalletKeyPair()
//2. 将返回的WalletKeyPair添加到WalletMap中
address := wallet.GetAddress()

ws.WalletsMap[address] = wallet
//3. 保存到本地文件
res := ws.SaveToFile()
if !res {
fmt.Printf("创建钱包失败\n")
return ""
}
return address
}

7.添加创建文件方法

  1. gob.Register:对于结构中的interface类型,需要明确类型
  2. iotutil.WhiteFile:实现文件写入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const WalletName = "wallet.dat"

//保存钱包到文件
func (ws *Wallets) SaveToFile() bool {
var buffer bytes.Buffer

//将接口类型明确注册 否则会报错
gob.Register(elliptic.P256())

encoder := gob.NewEncoder(&buffer)

err := encoder.Encode(ws)

if err != nil {
fmt.Printf("钱包序列化失败: %v\n", err)
return false
}

content := buffer.Bytes()
err = ioutil.WriteFile(WalletName, content, 0600)
if err != nil {
fmt.Printf("钱包创建失败\n")
return false
}
return true
}

8.添加读取文件方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func (ws *Wallets) LoadFromFile() bool {
//判断文件是否存在
if !IsFileExist(WalletName) {
fmt.Printf("钱包文件不存在,准备创建\n")
return true
}

//读取文件
content, err := ioutil.ReadFile(WalletName)
if err != nil {
return false
}

gob.Register(elliptic.P256())

//gob解码
decoder := gob.NewDecoder(bytes.NewReader(content))

var wallets Wallets

err = decoder.Decode(&wallets)
if err != nil {
return false
}
//赋值给ws
ws.WalletsMap = wallets.WalletsMap

return true
}

9.添加打印钱包地址命令

1
2
3
4
5
6
7
8
//遍历map打印地址
func (ws *Wallets) ListAddress() []string {
var addresses []string
for address, _ := range ws.WalletsMap {
addresses = append(addresses, address)
}
return addresses
}
1
2
3
4
5
6
7
func (cli *CLI) ListAddress() {
ws := NewWallets()
addresses := ws.ListAddress()
for _, address := range addresses {
fmt.Printf("address : %s\n", address)
}
}

10.改写TXInput和TXOutput的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//定义交易的结构
//定义input
type TXInput struct {
TXID []byte //交易ID
Index int64 //output的索引
//Address string //解锁脚本
Signature []byte //交易签名

pubKey []byte //公钥本身,不是公钥哈希
}

//定义output
type TXOutput struct {
Value float64 //转账金额
//Address string //锁定脚本

PubKeyHash []byte //公钥哈希
}

11.添加Lock方法和NewTXOutput方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//给定转账地址,得到该地址的公钥哈希,完成对output 的锁定
func (output *TXOutput) Lock(address string) {
//address->public key hash
//25字节
decodeInfo, err := base58.Decode(address)
if err != nil {
log.Panic(err)
}
pubKeyHash := decodeInfo[1 : len(decodeInfo)-4]

output.PubKeyHash = pubKeyHash
}

func NewTXOutput(value float64, address string) TXOutput {
output := TXOutput{
Value: value,
}
output.Lock(address)
return output
}

12.改写NewCoinbaseTX函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//挖矿奖励
const reward = 12.5

func NewCoinbaseTX(miner string, data string) *Transaction {

inputs := []TXInput{{
TXID: nil,
Index: -1,
Signature: nil,
pubKey: []byte(data),
}} //设置特殊的值用于判断
output := NewTXOutput(reward, miner)
outputs := []TXOutput{output}
tx := Transaction{
TXid: nil,
TXInputs: inputs,
TXOutputs: outputs,
}
tx.SetTXID()
return &tx
}

13.改写NewTransaction函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
//打开钱包
ws := NewWallets()

//获取秘钥对
Wallet := ws.WalletsMap[from]

if Wallet == nil {
fmt.Printf("这个地址的私钥不存在,交易创建失败\n")
return nil
}

//获取公钥,私钥
//privateKey := Wallet.PrivateKey
publicKey := Wallet.PublicKey
pubKeyHash := hashPubKey(from)

utxos := make(map[string][]int64) //标识能用的utxo
var resValue float64 //这些utxo存储的金额

//遍历账本,找到属于付款人的合适的金额,把这个outputs找到
utxos, resValue = bc.FindNeedUtxos(pubKeyHash, amount)

//如果找到的钱不足以转账,创建交易失败
if resValue < amount {
fmt.Printf("余额不足,交易失败\n")
return nil
}

var inputs []TXInput
var outputs []TXOutput

//将outputs转成inputs
for txid, indexes := range utxos {
for _, i := range indexes {
input := TXInput{
TXID: []byte(txid),
Index: i,
Signature: nil,
pubKey: publicKey,
}
inputs = append(inputs, input)
}
}

//创建输出,创建一个属于收款人的output
output := NewTXOutput(amount, to)
outputs = append(outputs, output)

//如果有找零,创建属于付款人的output
if resValue > amount {
output1 := NewTXOutput(resValue-amount, from)
outputs = append(outputs, output1)
}

//创建交易
tx := Transaction{
TXid: nil,
TXInputs: inputs,
TXOutputs: outputs,
}

//设置交易id
tx.SetTXID()

//返回交易结构
return &tx
}

14.使用HashPubKey和CheckSum函数改写GetAddress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
func (w *WalletKeyPair) GetAddress() string {
publicHash := HashPubKey(w.PublicKey)

version := 0x00

//21字节的数据
payload := append([]byte{byte(version)}, publicHash...)

checksum := CheckSum(payload)

//25字节的数据
payload = append(payload, checksum...)

address := base58.Encode(payload)

return address
}

func HashPubKey(pubKey []byte) []byte {
hash := sha256.Sum256(pubKey)

//创建一个hash160对象
//向hash160中写入数据
//做哈希运算
rip160Haher := ripemd160.New()
_, err := rip160Haher.Write(hash[:])
if err != nil {
log.Panic(err)
}
//Sum函数会把结果与Sum的参数append到一起返回,传入nil防止数据污染
publicHash := rip160Haher.Sum(nil)
return publicHash
}

func CheckSum(payload []byte) []byte {
//第一次哈希
first := sha256.Sum256(payload)
//第二次哈希
second := sha256.Sum256(first[:])

//取前四个字节作为校验码
checksum := second[0:4]
return checksum
}

15.改写blockchain中相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package main

import (
"./base58"
"bytes"
"fmt"
"github.com/boltdb/bolt"
"log"
"os"
)

//使用bolt进行改写,需要两个字段
//1. bolt数据库的句柄
//2. 最后一个区块的哈希值
type BlockChain struct {
db *bolt.DB //句柄

tail []byte //代表最后一个区块的哈希值
}

const blockChainName = "blockChain.db"
const blockBucketName = "blockBucket"
const lastHashKey = "lastHashKey"

//创建区块链
func CreateBlockChain(miner string) *BlockChain {

if IsFileExist(blockChainName) {
fmt.Printf("区块链已经存在,不需要重复创建\n")
return nil
}

//功能分析:
//1. 获得区块链数据库的句柄,打开数据库,读写数据
db, err := bolt.Open(blockChainName, 0600, nil) //"test.db"是数据库名称 0600是读写
//从数据库中读取数据

if err != nil {
log.Panic(err)
}

var tail []byte
//向数据库中写入数据
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte(blockBucketName))
if err != nil {
log.Panic(err)
}
//开始添加创世块
//创世块中只有一个挖矿交易,只有coinbase
coinbase := NewCoinbaseTX(miner, genesisInfo)
genesisBlock := NewBlock([]*Transaction{coinbase}, []byte{})

b.Put(genesisBlock.Hash, genesisBlock.Serialize() /*将区块序列化,转成字节流*/)
b.Put([]byte(lastHashKey), genesisBlock.Hash)

//测试,读取写入的数据
//blockInfo := b.Get(genesisBlock.Hash)
//block := DeSerialize(blockInfo)
//fmt.Printf("解码后的block数据:%s\n", block)

//更新tail位最后一个区块的哈希值
tail = genesisBlock.Hash
return nil
})
//返回实例
return &BlockChain{
db: db,
tail: tail,
}
}

//读取区块链,返回区块链实例
func NewBlockChain() *BlockChain {
if !IsFileExist(blockChainName) {
fmt.Printf("区块链不存在,请创建区块链\n")
return nil
}
//功能分析:
//1. 获得区块链数据库的句柄,打开数据库,读写数据
db, err := bolt.Open(blockChainName, 0600, nil) //"test.db"是数据库名称 0600是读写

if err != nil {
log.Panic(err)
}

var tail []byte
//在数据库中读取数据
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockBucketName))
//判断是否有bucket,如果没有,创建bucket并写入创世块,写入lastHashKey数据
if b == nil {
fmt.Printf("区块链bucket为空,请检查")
os.Exit(1)
}
// 获取最后一个区块的哈希值,填充给tail
tail = b.Get([]byte(lastHashKey))

return nil
})
//返回实例
return &BlockChain{
db: db,
tail: tail,
}
}

//添加区块
func (bc *BlockChain) AddBlock(txs []*Transaction) {
bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockBucketName))

if b == nil {
//如果b1为空,说明这个名字不存在,需要检查
fmt.Println("bucket不存在,请检查")
os.Exit(1) //退出
}

//创建一个区块
block := NewBlock(txs, bc.tail)
b.Put(block.Hash, block.Serialize() /*将区块序列化,转成字节流*/)
b.Put([]byte(lastHashKey), block.Hash)

//测试,读取写入的数据
//blockInfo := b.Get(block.Hash)
//nowblock := DeSerialize(blockInfo)
//fmt.Printf("解码后的block数据:%s\n", nowblock)

//更新tail位最后一个区块的哈希值
bc.tail = block.Hash

return nil
})
}

//迭代器作用:遍历容器,将数据逐个返回,防止一次性加载到内存,所以一点一点读取
//迭代器包含两个元素:1. 区块链句柄db 2. 移动的指针current,存储哈希
//db: 为了遍历账本
//current:为了访问每一个区块
//调用Next( )方法,要做两件事情:1. 返回当前所指向的区块的数据block 2. 指针向前移动

//定义一个区块链的迭代器,包含db,current
type BlockChainIterator struct {
db *bolt.DB //账本
current []byte //当前所指向区块的哈希值
}

//创建迭代器,使用bc进行初始化
func (bc *BlockChain) NewIterator() *BlockChainIterator {
return &BlockChainIterator{
db: bc.db,
current: bc.tail,
}
}

//读取数据
func (it *BlockChainIterator) Next() *Block {
var block Block
//读取数据库
it.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blockBucketName))
if b == nil {
fmt.Println("bucket不存在,请检查")
os.Exit(1)
}

//读取数据
blockInfo /*block的字节流*/ := b.Get(it.current)
block = *DeSerialize(blockInfo)

it.current = block.PrevBlockHash
return nil
})
return &block
}

type UTXOInfo struct {
TXID []byte //交易id
Index int64 //output的索引值
Output TXOutput //output本身
}

//遍历交易输出
func (bc *BlockChain) FindMyUtxos(pubKeyHash []byte) []UTXOInfo {
fmt.Printf("FindMuUtxos\n")

var UTXOInfos []UTXOInfo //新的返回结构
it := bc.NewIterator()

spentUTXOs := make(map[string][]int64) //标识已经消耗过的utxo的结构 ,key是交易id,value是这个id里面的output索引的数组

//遍历账本
for {
block := it.Next()
//遍历交易
for _, tx := range block.Transactions {

//判断是否为普通交易
if tx.IsCoinbase() == false {
//是普通交易,遍历input
for _, input := range tx.TXInputs {
//判断当前被使用的input是否为目标地址所有
if bytes.Equal(HashPubKey(input.pubKey), pubKeyHash) {
fmt.Printf("找到了消耗过的output , index : %d\n", input.Index)
key := string(input.TXID)
spentUTXOs[key] = append(spentUTXOs[key], input.Index)
}
}
}

OUTPUT:
//遍历output
for i, output := range tx.TXOutputs {
key := string(tx.TXid)
indexes := spentUTXOs[key]
if len(indexes) != 0 {
fmt.Printf("找到了当前这笔交易中有被消耗过的output\n")
for _, j := range indexes {
if int64(i) == j {
fmt.Printf("i == j , 当前的output已经被消耗了,跳过不统计\n")
continue OUTPUT
}
}
}
//找到属于我的output
if bytes.Equal(pubKeyHash, output.PubKeyHash) {

utxoinfo := UTXOInfo{
TXID: tx.TXid,
Index: int64(i),
Output: output,
}
UTXOInfos = append(UTXOInfos, utxoinfo)
}
}
}
if len(block.PrevBlockHash) == 0 {
fmt.Printf("遍历区块链结束\n")
break
}
}
return UTXOInfos
}

//查询余额
func (bc *BlockChain) GetBalance(address string) {
decodeInfo, err := base58.Decode(address)
if err != nil {
log.Panic(err)
}
pubKeyHash := decodeInfo[1 : len(decodeInfo)-4]

utxoinfos := bc.FindMyUtxos(pubKeyHash)

var total = 0.0
//所有的output都在utxoinfos内部
//获取余额时,遍历utxoinfos获取output即可
for _, utxoinfo := range utxoinfos {
total += utxoinfo.Output.Value
}

fmt.Printf("%s的余额为%f", address, total)
}

//遍历账本,找到属于付款人的合适的金额,把这个outputs找到
func (bc *BlockChain) FindNeedUtxos(pubKeyHash []byte, amount float64) (map[string][]int64, float64) {
needUtxos := make(map[string][]int64) //标识能用的utxo

var resValue float64 //统计的金额

//复用FindMyUtxo函数,这个函数已经包含了所有的信息
utxoinfos := bc.FindMyUtxos(pubKeyHash)

for _, utxoinfo := range utxoinfos {
key := string(utxoinfo.TXID)
needUtxos[key] = append(needUtxos[key], utxoinfo.Index)
resValue += utxoinfo.Output.Value

//判断金额是否足够,足够则直接返回,不足则继续遍历
if resValue >= amount {
break
}
}
return needUtxos, resValue
}

16.地址有效性校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func IsValidAddress(address string) bool {
//1. 将输入的地址进行解码,得到25个字节
//2. 取出前21个字节,运行CheckSum函数,得到checksum1,
//3. 取出后4个字节,得到checksum2
//4. 比较checksum1和checksum2,如果相同则地址有效,反之无效
decodeInfo,_:= base58.Decode(address)

if len(decodeInfo) != 25{
return false
}

payload := decodeInfo[0:len(decodeInfo) - 4]

//自己求出来的校验码
checksum1 := CheckSum(payload)
//解出来的校验码
checksum2 := decodeInfo[len(decodeInfo)-4 : 0]

return bytes.Equal(checksum1,checksum2)
}

17.对交易进行签名

我们对一笔交易签名,要填充每一个所引用的Input的sig,N个input要有N个签名,签名内容包括:

  1. 所引用的Output的公钥哈希
  2. 所新生成的Output的公钥哈希
  3. 所新生成的Output的Value

签名时需要找到这个Input对应的Output,或者说找到这笔交易

在创建交易后,对交易进行一次签名

做签名时,会创建一个交易的副本,会对这个副本进行替换,生成的sig放到原始交易中,因此所引用的Output放到Input的pubKey不会覆盖原有Input的pubkey,

1
2
3
4
5
6
7
func (bc *BlockChain) SignTransaction(tx *Transaction , privateKey *ecdsa.PrivateKey)  {
//遍历账本,找到所有引用的交易

prevTXs := make(map[string]Transaction)

tx.Sign(privateKey,prevTXs)
}
1
2
3
4
//第一个参数是私钥,第二个参数是这个交易的Input所引用的所有的交易
func (tx *Transaction) Sign(privKey *ecdsa.PrivateKey , prevTXs map[string]Transaction) {
fmt.Printf("对交易进行签名\n")
}

在NewTransaction后调用

1
2
3
4
5
//把查找,引用,交易的环节放到blockchain中,同时在blockchain中进行调用签名
//付款人在创建交易时,已经得到了所有引用的output的详细信息
//但是我们不去引用,因为在矿工校验的时候,矿工是没有这部分信息的,矿工需要遍历账本,找到所有引用的交易
//为了统一操作,所以再查询一次,进行签名
bc.SignTransaction(&tx,privateKey)

18.实现FindTransaction函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (bc *BlockChain) FindTransaction(txid []byte) *Transaction {
//遍历区块链的交易
//通过对比id来识别

it := bc.NewIterator()
for {
block := it.Next()
for _,tx := range block.Transactions {
//如果找到相同的id交易,直接返回交易即可
if bytes.Equal(tx.TXid , txid){
return tx
}
}
if len(block.PrevBlockHash) == 0{
break
}
}

return nil
}

19. 实现VerifyTransaction函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//矿工校验流程
//1. 找到交易input所引用的所有的交易prevTXs
//2. 对交易进行校验
func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool {
//遍历账本,找到所有引用的交易

prevTXs := make(map[string]Transaction)

//遍历tx的inputs,通过ID取查找所引用的交易

for _, input := range tx.TXInputs{
prevTX := bc.FindTransaction(input.TXID)

if prevTX == nil{
fmt.Printf("没有找到交易, %x\n" ,input.TXID)
}else {
//把找到的引用交易保存起来
prevTXs[string(input.TXID)] = *prevTX
}
}

return tx.Verify(prevTXs)
}

20.在AddBlock进行交易校验

1
2
3
4
5
6
7
8
9
10
11
12
func (bc *BlockChain) AddBlock(txs []*Transaction) {
//矿工得到交易时,第一时间对交易进行验证
//矿工如果不验证,即使挖矿成功,广播区块后,其他的验证矿工,仍然会校验每一笔交易
validTXs := []*Transaction{}
for _ , tx := range txs{
if bc.VerifyTransaction(tx){
fmt.Printf("该交易有效 : %x\n " , tx.TXid)
validTXs = append(validTXs , tx)
}else{
fmt.Printf("发现无效的交易 : %x\n",tx.TXid)
}
}

21. 实现TrimmedCopy函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//裁剪copy
func (tx *Transaction) TrimmedCopy() Transaction {
var inputs []TXInput
var outputs []TXOutput

for _ , input := range tx.TXInputs{
input1 := TXInput{
TXID: input.TXID,
Index: input.Index,
Signature: nil,
PubKey: nil,
}
inputs = append(inputs,input1)
}
outputs = tx.TXOutputs
tx1 := Transaction{
TXid: tx.TXid,
TXInputs: inputs,
TXOutputs: outputs,
}
return tx1
}

22.添加Sign函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//第一个参数是私钥,第二个参数是这个交易的Input所引用的所有的交易
func (tx *Transaction) Sign(privKey *ecdsa.PrivateKey , prevTXs map[string]Transaction) {
fmt.Printf("对交易进行签名\n")
//1. 拷贝一份交易txCopy,做相应裁剪,把每一个Input的pubKey和Sig设置为空,output不做改变
txCopy := tx.TrimmedCopy()
//2. 遍历txCopy.inputs,把这个input所引用的output的公钥哈希拿出来,赋值给pubKey
for i , input:=range txCopy.TXInputs{
//找到引用交易
prevTX := prevTXs[string(input.TXID)]
output := prevTX.TXOutputs[input.Index]
//input.PubKey = output.PubKeyHash
//for循环迭代出来的数据是一个副本,对这个副本进行修改不会影响原始数据,因此需要使用下标方式进行修改
txCopy.TXInputs[i].PubKey = output.PubKeyHash

//签名要对数据的哈希值进行签名
//数据都在交易中,要求交易的哈希
//Transaction的SetTXID就是对交易的哈希
//可以使用交易id作为签名的内容
//3. 生成要签名的数据(哈希)
txCopy.SetTXID()
signData := txCopy.TXid

//请理,以供下笔交易信息使用,理由同上
txCopy.TXInputs[i].PubKey = nil

fmt.Printf("要签名的数据, signData : %x\n",signData)

//4. 对数据进行签名r,s
r,s,err := ecdsa.Sign(rand.Reader,privKey,signData)
if err !=nil{
fmt.Printf("交易签名失败 , err : %v\n" , err)
}
//5. 拼接r,s为字节流,赋值给原始的交易的Sig字段
signature := append(r.Bytes() , s.Bytes()...)
tx.TXInputs[i].Signature = signature
}
}

23.添加Verify函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
func (tx *Transaction) Verify(prevTXs map[string]Transaction)  bool {
fmt.Printf("对交易进行校验\n")
//1. 拷贝修剪的副本
txCopy := tx.TrimmedCopy()
//2. 遍历原始交易
for i , input := range tx.TXInputs{
//3. 遍历原始交易的inputs所引用的前交易prevTX
prevTX := prevTXs[string(input.TXID)]
output := prevTX.TXOutputs[input.Index]
//4. 找到output的公钥哈希,赋值给txCopy对应的input
txCopy.TXInputs[i].PubKey = output.PubKeyHash
//5. 还原签名数据
txCopy.SetTXID()
verifyData := txCopy.TXid
fmt.Printf("verifyData : %x\n",verifyData)
//6. 校验

//公钥字节流
pubKeyBytes := input.PubKey
//还原签名为r,s
signature := input.Signature
r := big.Int{}
s := big.Int{}
rData := signature[0:len(signature)/2]
sData := signature[len(signature)/2:]
r.SetBytes(rData)
s.SetBytes(sData)
//还原公钥为Cruve,X,Y
x := big.Int{}
y := big.Int{}
xData := pubKeyBytes[0:len(pubKeyBytes)/2]
yData := signature[len(pubKeyBytes)/2:]
x.SetBytes(xData)
y.SetBytes(yData)

cruve := elliptic.P256()

publicKey := ecdsa.PublicKey{
Curve: cruve,
X: &x,
Y: &y,
}
//数据、签名、公钥准备完毕,开始校验
if !ecdsa.Verify(&publicKey,verifyData,&r,&s){
return false
}
}
return true
}