1.block.go
package main
import (
"time"
"bytes"
"encoding/binary"
"log"
"encoding/gob"
"crypto/sha256"
)
//0. 定义结构
type Block struct {
//1.版本号
Version uint64
//2. 前区块哈希
PrevHash []byte
//3. Merkel根(梅克尔根,
MerkelRoot []byte
//4. 时间戳
TimeStamp uint64
//5. 难度值
Difficulty uint64
//6. 随机数,也就是挖矿要找的数据
Nonce uint64
//a. 当前区块哈希,正常比特币区块中没有当前区块的哈希
Hash []byte
//b. 数据
//Data []byte
//真实的交易数组
Transactions []*Transaction
}
//1. 补充区块字段
//2. 更新计算哈希函数
//3. 优化代码
//实现一个辅助函数,功能是将uint64转成[]byte
func Uint64ToByte(num uint64) []byte {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
return buffer.Bytes()
}
//2. 创建区块
func NewBlock(txs []*Transaction, prevBlockHash []byte) *Block {
block := Block{
Version: 00,
PrevHash: prevBlockHash,
MerkelRoot: []byte{},
TimeStamp: uint64(time.Now().Unix()),
Difficulty: 0, //
Nonce: 0, //同上
Hash: []byte{},
//Data: []byte(data),
Transactions: txs,
}
block.MerkelRoot = block.MakeMerkelRoot()
//block.SetHash()
//创建一个pow对象
pow := NewProofOfWork(&block)
//查找随机数,不停的进行哈希运算
hash, nonce := pow.Run()
//根据挖矿结果对区块数据进行更新
block.Hash = hash
block.Nonce = nonce
return &block
}
//序列化
func (block *Block) Serialize() []byte {
var buffer bytes.Buffer
//- 使用gob进行序列化(编码)得到字节流
//1. 定义一个编码器
//2. 使用编码器进行编码
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(&block)
if err != nil {
log.Panic("编码出错!")
}
//fmt.Printf("编码后的小明:%v\n", buffer.Bytes())
return buffer.Bytes()
}
//反序列化
func Deserialize(data []byte) Block {
decoder := gob.NewDecoder(bytes.NewReader(data))
var block Block
//2. 使用解码器进行解码
err := decoder.Decode(&block)
if err != nil {
log.Panic("解码出错!", err)
}
return block
}
/*
//3. 生成哈希
func (block *Block) SetHash() {
//var blockInfo []byte
//1. 拼装数据
/*
blockInfo = append(blockInfo, Uint64ToByte(block.Version)...)
blockInfo = append(blockInfo, block.PrevHash...)
blockInfo = append(blockInfo, block.MerkelRoot...)
blockInfo = append(blockInfo, Uint64ToByte(block.TimeStamp)...)
blockInfo = append(blockInfo, Uint64ToByte(block.Difficulty)...)
blockInfo = append(blockInfo, Uint64ToByte(block.Nonce)...)
blockInfo = append(blockInfo, block.Data...)
*/
/*
tmp := [][]byte{
Uint64ToByte(block.Version),
block.PrevHash,
block.MerkelRoot,
Uint64ToByte(block.TimeStamp),
Uint64ToByte(block.Difficulty),
Uint64ToByte(block.Nonce),
block.Data,
}
//将二维的切片数组链接起来,返回一个一维的切片
blockInfo := bytes.Join(tmp, []byte{})
//2. sha256
//func Sum256(data []byte) [Size]byte {
hash := sha256.Sum256(blockInfo)
block.Hash = hash[:]
}
*/
//模拟梅克尔根,只是对交易的数据做简单的拼接,而不做二叉树处理!
func (block *Block) MakeMerkelRoot() []byte {
var info []byte
//var finalInfo [][]byte
for _, tx := range block.Transactions {
//将交易的哈希值拼接起来,再整体做哈希处理
info = append(info, tx.TXID...)
//finalInfo = [][]byte{tx.TXID}
}
hash := sha256.Sum256(info)
return hash[:]
}
02.blockchain.go
package main
import (
"./lib/bolt"
"log"
"fmt"
"bytes"
)
//4. 引入区块链
//使用数据库代替数组
type BlockChain struct {
//定一个区块链数组
//blocks []*Block
db *bolt.DB
tail []byte //存储最后一个区块的哈希
}
const blockChainDb = "blockChain.db"
const blockBucket = "blockBucket"
//5. 定义一个区块链
func NewBlockChain(address string) *BlockChain {
//return &BlockChain{
// blocks: []*Block{genesisBlock},
//}
//最后一个区块的哈希, 从数据库中读出来的
var lastHash []byte
//1. 打开数据库
db, err := bolt.Open(blockChainDb, 0600, nil)
//defer db.Close()
if err != nil {
log.Panic("打开数据库失败!")
}
//将要操作数据库(改写)
db.Update(func(tx *bolt.Tx) error {
//2. 找到抽屉bucket(如果没有,就创建)
bucket := tx.Bucket([]byte(blockBucket))
if bucket == nil {
//没有抽屉,我们需要创建
bucket, err = tx.CreateBucket([]byte(blockBucket))
if err != nil {
log.Panic("创建bucket(b1)失败")
}
//创建一个创世块,并作为第一个区块添加到区块链中
genesisBlock := GenesisBlock(address)
//fmt.Printf("genesisBlock :%s\n", genesisBlock)
//3. 写数据
//hash作为key, block的字节流作为value,尚未实现
bucket.Put(genesisBlock.Hash, genesisBlock.Serialize())
bucket.Put([]byte("LastHashKey"), genesisBlock.Hash)
lastHash = genesisBlock.Hash
} else {
lastHash = bucket.Get([]byte("LastHashKey"))
}
return nil
})
return &BlockChain{db, lastHash}
}
//定义一个创世块
func GenesisBlock(address string) *Block {
coinbase := NewCoinbaseTX(address, "大家好")
return NewBlock([]*Transaction{coinbase}, []byte{})
}
//5. 添加区块
func (bc *BlockChain) AddBlock(txs []*Transaction) {
//如何获取前区块的哈希呢??
db := bc.db //区块链数据库
lastHash := bc.tail //最后一个区块的哈希
db.Update(func(tx *bolt.Tx) error {
//完成数据添加
bucket := tx.Bucket([]byte(blockBucket))
if bucket == nil {
log.Panic("bucket 不应该为空,请检查!")
}
//a. 创建新的区块
block := NewBlock(txs, lastHash)
//b. 添加到区块链db中
//hash作为key, block的字节流作为value
bucket.Put(block.Hash, block.Serialize())
bucket.Put([]byte("LastHashKey"), block.Hash)
//c. 更新一下内存中的区块链
bc.tail = block.Hash
return nil
})
}
func (bc *BlockChain) Printchain() {
blockHeight := 0
bc.db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("blockBucket"))
//从第一个key-> value 进行遍历,到最后一个固定的key时直接返回
b.ForEach(func(k, v []byte) error {
if bytes.Equal(k, []byte("LastHashKey")) {
return nil
}
block := Deserialize(v)
//fmt.Printf("key=%x, value=%s\n", k, v)
fmt.Printf("=============== 区块高度: %d ==============\n", blockHeight)
blockHeight++
fmt.Printf("版本号: %d\n", block.Version)
fmt.Printf("前区块哈希值: %x\n", block.PrevHash)
fmt.Printf("梅克尔根: %x\n", block.MerkelRoot)
fmt.Printf("时间戳: %d\n", block.TimeStamp)
fmt.Printf("难度值(随便写的): %d\n", block.Difficulty)
fmt.Printf("随机数 : %d\n", block.Nonce)
fmt.Printf("当前区块哈希值: %x\n", block.Hash)
fmt.Printf("区块数据 :%s\n", block.Transactions[0].TXInputs[0].Sig)
return nil
})
return nil
})
}
//找到指定地址的所有的utxo
func (bc *BlockChain) FindUTXOs(address string) []TXOutput {
var UTXO []TXOutput
txs := bc.FindUTXOTransactions(address)
for _, tx := range txs {
for _, output := range tx.TXOutputs {
if address == output.PubKeyHash {
UTXO = append(UTXO, output)
}
}
}
return UTXO
}
//根据需求找到合理的utxo
func (bc *BlockChain) FindNeedUTXOs(from string, amount float64) (map[string][]uint64, float64) {
//找到的合理的utxos集合
utxos := make(map[string][]uint64)
var calc float64
txs := bc.FindUTXOTransactions(from)
for _, tx := range txs {
for i, output := range tx.TXOutputs {
if from == output.PubKeyHash {
//fmt.Printf("222222")
//UTXO = append(UTXO, output)
//fmt.Printf("333333 : %f\n", UTXO[0].Value)
//找到自己需要的最少的utxo
//3. 比较一下是否满足转账需求
// a. 满足的话,直接返回 utxos, calc
// b. 不满足继续统计
if calc < amount {
//1. 把utxo加进来,
//utxos := make(map[string][]uint64)
//array := utxos[string(tx.TXID)] //确认一下是否可行
//array = append(array, uint64(i))
utxos[string(tx.TXID)] = append(utxos[string(tx.TXID)], uint64(i))
//2. 统计一下当前utxo的总额
//第一次进来: calc =3, map[3333] = []uint64{0}
//第二次进来: calc =3 + 2, map[3333] = []uint64{0, 1}
//第三次进来:calc = 3 + 2 + 10, map[222] = []uint64{0}
calc += output.Value
//加完之后满足条件了,
if calc >= amount {
//break
fmt.Printf("找到了满足的金额:%f\n", calc)
return utxos, calc
}
} else {
fmt.Printf("不满足转账金额,当前总额:%f, 目标金额: %f\n", calc, amount)
}
}
}
}
return utxos, calc
}
func (bc *BlockChain) FindUTXOTransactions(address string) []*Transaction {
var txs []*Transaction //存储所有包含utxo交易集合
//定义一个map来保存消费过的output,key是这个output的交易id,value是这个交易中索引的数组
//map[交易id][]int64
spentOutputs := make(map[string][]int64)
//创建迭代器
it := bc.NewIterator()
for {
//1.遍历区块
block := it.Next()
//2. 遍历交易
for _, tx := range block.Transactions {
//fmt.Printf("current txid : %x\n", tx.TXID)
OUTPUT:
//3. 遍历output,找到和自己相关的utxo(在添加output之前检查一下是否已经消耗过)
// i : 0, 1, 2, 3
for i, output := range tx.TXOutputs {
//fmt.Printf("current index : %d\n", i)
//在这里做一个过滤,将所有消耗过的outputs和当前的所即将添加output对比一下
//如果相同,则跳过,否则添加
//如果当前的交易id存在于我们已经表示的map,那么说明这个交易里面有消耗过的output
//map[2222] = []int64{0}
//map[3333] = []int64{0, 1}
//这个交易里面有我们消耗过得output,我们要定位它,然后过滤掉
if spentOutputs[string(tx.TXID)] != nil {
for _, j := range spentOutputs[string(tx.TXID)] {
//[]int64{0, 1} , j : 0, 1
if int64(i) == j {
//fmt.Printf("111111")
//当前准备添加output已经消耗过了,不要再加了
continue OUTPUT
}
}
}
//这个output和我们目标的地址相同,满足条件,加到返回UTXO数组中
if output.PubKeyHash == address {
//重点
//返回所有包含我的outx的交易的集合
txs = append(txs, tx)
//fmt.Printf("333333 : %f\n", UTXO[0].Value)
} else {
//fmt.Printf("333333")
}
}
//如果当前交易是挖矿交易的话,那么不做遍历,直接跳过
if !tx.IsCoinbase() {
//4. 遍历input,找到自己花费过的utxo的集合(把自己消耗过的标示出来)
for _, input := range tx.TXInputs {
//判断一下当前这个input和目标(李四)是否一致,如果相同,说明这个是李四消耗过的output,就加进来
if input.Sig == address {
//spentOutputs := make(map[string][]int64)
//indexArray := spentOutputs[string(input.TXid)]
//indexArray = append(indexArray, input.Index)
spentOutputs[string(input.TXid)] = append(spentOutputs[string(input.TXid)], input.Index)
//map[2222] = []int64{0}
//map[3333] = []int64{0, 1}
}
}
} else {
//fmt.Printf("这是coinbase,不做input遍历!")
}
}
if len(block.PrevHash) == 0 {
break
fmt.Printf("区块遍历完成退出!")
}
}
return txs
}
03.blockchainiterator.go
package main
import (
"./lib/bolt"
"log"
)
type BlockChainIterator struct {
db *bolt.DB
//游标,用于不断索引
currentHashPointer []byte
}
func (bc *BlockChain) NewIterator() *BlockChainIterator {
return &BlockChainIterator{
bc.db,
//最初指向区块链的最后一个区块,随着Next的调用,不断变化
bc.tail,
}
}
//迭代器是属于区块链的
//Next方式是属于迭代器的
//1. 返回当前的区块
//2. 指针前移
func (it *BlockChainIterator) Next() *Block {
var block Block
it.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(blockBucket))
if bucket == nil {
log.Panic("迭代器遍历时bucket不应该为空,请检查!")
}
blockTmp := bucket.Get(it.currentHashPointer)
//解码动作
block = Deserialize(blockTmp)
//游标哈希左移
it.currentHashPointer = block.PrevHash
return nil
})
return &block
}
04.cli.go
package main
import (
"os"
"fmt"
"strconv"
)
//这是一个用来接收命令行参数并且控制区块链操作的文件
type CLI struct {
bc *BlockChain
}
const Usage = `
printChain "正向打印区块链"
printChainR "反向打印区块链"
getBalance --address ADDRESS "获取指定地址的余额"
send FROM TO AMOUNT MINER DATA "由FROM转AMOUNT给TO,由MINER挖矿,同时写入DATA"
newWallet "创建一个新的钱包(私钥公钥对)"
listAddresses "列举所有的钱包地址"
`
//接受参数的动作,我们放到一个函数中
func (cli *CLI) Run() {
//./block printChain
//./block addBlock --data "HelloWorld"
//1. 得到所有的命令
args := os.Args
if len(args) < 2 {
fmt.Printf(Usage)
return
}
//2. 分析命令
cmd := args[1]
switch cmd {
case "printChain":
fmt.Printf("正向打印区块\n")
cli.PrinBlockChain()
case "printChainR":
fmt.Printf("反向打印区块\n")
cli.PrinBlockChainReverse()
case "getBalance":
fmt.Printf("获取余额\n")
if len(args) == 4 && args[2] == "--address" {
address := args[3]
cli.GetBalance(address)
}
case "send":
fmt.Printf("转账开始...\n")
if len(args) != 7 {
fmt.Printf("参数个数错误,请检查!\n")
fmt.Printf(Usage)
return
}
//./block send FROM TO AMOUNT MINER DATA "由FROM转AMOUNT给TO,由MINER挖矿,同时写入DATA"
from := args[2]
to := args[3]
amount, _ := strconv.ParseFloat(args[4], 64)
miner := args[5]
data := args[6]
cli.Send(from, to, amount, miner, data)
case "newWallet":
fmt.Printf("创建新的钱包...\n")
cli.NewWallet()
case "listAddresses":
fmt.Printf("列举所有地址...\n")
cli.ListAddresses()
default:
fmt.Printf("无效的命令,请检查!\n")
fmt.Printf(Usage)
}
}
05.commandLine.go
package main
import (
"fmt"
"time"
)
//正向打印
func (cli *CLI) PrinBlockChain() {
cli.bc.Printchain()
fmt.Printf("打印区块链完成\n")
}
//反向打印
func (cli *CLI) PrinBlockChainReverse() {
bc := cli.bc
//创建迭代器
it := bc.NewIterator()
//调用迭代器,返回我们的每一个区块数据
for {
//返回区块,左移
block := it.Next()
fmt.Printf("===========================\n\n")
fmt.Printf("版本号: %d\n", block.Version)
fmt.Printf("前区块哈希值: %x\n", block.PrevHash)
fmt.Printf("梅克尔根: %x\n", block.MerkelRoot)
timeFormat := time.Unix(int64(block.TimeStamp), 0).Format("2006-01-02 15:04:05")
fmt.Printf("时间戳: %s\n", timeFormat)
fmt.Printf("难度值(随便写的): %d\n", block.Difficulty)
fmt.Printf("随机数 : %d\n", block.Nonce)
fmt.Printf("当前区块哈希值: %x\n", block.Hash)
fmt.Printf("区块数据 :%s\n", block.Transactions[0].TXInputs[0].Sig)
if len(block.PrevHash) == 0 {
fmt.Printf("区块链遍历结束!")
break
}
}
}
func (cli *CLI) GetBalance(address string) {
utxos := cli.bc.FindUTXOs(address)
total := 0.0
for _, utxo := range utxos {
total += utxo.Value
}
fmt.Printf("\"%s\"的余额为:%f\n", address, total)
}
func (cli *CLI) Send(from, to string, amount float64, miner, data string) {
//fmt.Printf("from : %s\n", from)
//fmt.Printf("to : %s\n", to)
//fmt.Printf("amount : %f\n", amount)
//fmt.Printf("miner : %s\n", miner)
//fmt.Printf("data : %s\n", data)
//1. 创建挖矿交易
coinbase := NewCoinbaseTX(miner, data)
//2. 创建一个普通交易
tx := NewTransaction(from, to, amount, cli.bc)
if tx == nil {
//fmt.Printf("无效的交易")
return
}
//3. 添加到区块
cli.bc.AddBlock([]*Transaction{coinbase, tx})
fmt.Printf("转账成功!")
}
func (cli *CLI) NewWallet() {
ws := NewWallets()
address := ws.CreateWallet()
fmt.Printf("地址:%s\n", address)
}
func (cli *CLI) ListAddresses() {
ws := NewWallets()
addresses := ws.ListAllAddresses()
for _, address := range addresses {
fmt.Printf("地址:%s\n", address)
}
}
06.main.go
package main
//import "fmt"
func main() {
bc := NewBlockChain("张三")
cli := CLI{bc}
cli.Run()
}
07proofofwork.go
package main
import (
"math/big"
"bytes"
"crypto/sha256"
"fmt"
)
//定义一个工作量证明的结构ProofOfWork
//
type ProofOfWork struct {
//a. block
block *Block
//b. 目标值
//一个非常大数,它有很丰富的方法:比较,赋值方法
target *big.Int
}
//2. 提供创建POW的函数
//
//- NewProofOfWork(参数)
func NewProofOfWork(block *Block) *ProofOfWork {
pow := ProofOfWork{
block: block,
}
//我们指定的难度值,现在是一个string类型,需要进行转换
targetStr := "0000100000000000000000000000000000000000000000000000000000000000"
//
//引入的辅助变量,目的是将上面的难度值转成big.int
tmpInt := big.Int{}
//将难度值赋值给big.int,指定16进制的格式
tmpInt.SetString(targetStr, 16)
pow.target = &tmpInt
return &pow
}
//
//3. 提供计算不断计算hash的哈数
//
//- Run()
func (pow *ProofOfWork) Run() ([]byte, uint64) {
//1. 拼装数据(区块的数据,还有不断变化的随机数)
//2. 做哈希运算
//3. 与pow中的target进行比较
//a. 找到了,退出返回
//b. 没找到,继续找,随机数加1
var nonce uint64
block := pow.block
var hash [32]byte
fmt.Println("开始挖矿...")
for {
//1. 拼装数据(区块的数据,还有不断变化的随机数)
tmp := [][]byte{
Uint64ToByte(block.Version),
block.PrevHash,
block.MerkelRoot,
Uint64ToByte(block.TimeStamp),
Uint64ToByte(block.Difficulty),
Uint64ToByte(nonce),
//只对区块头做哈希值,区块体通过MerkelRoot产生影响
//block.Data,
}
//将二维的切片数组链接起来,返回一个一维的切片
blockInfo := bytes.Join(tmp, []byte{})
//2. 做哈希运算
//func Sum256(data []byte) [Size]byte {
hash = sha256.Sum256(blockInfo)
//3. 与pow中的target进行比较
tmpInt := big.Int{}
//将得到hash数组转换成一个big.int
tmpInt.SetBytes(hash[:])
//比较当前的哈希与目标哈希值,如果当前的哈希值小于目标的哈希值,就说明找到了,否则继续找
// -1 if x < y
// 0 if x == y
// +1 if x > y
//
//func (x *Int) Cmp(y *Int) (r int) {
if tmpInt.Cmp(pow.target) == -1 {
//a. 找到了,退出返回
fmt.Printf("挖矿成功!hash : %x, nonce : %d\n", hash, nonce)
//break
return hash[:], nonce
} else {
//b. 没找到,继续找,随机数加1
nonce++
}
}
//return []byte("HelloWorld"), 10
}
08.transaction.go
package main
import (
"bytes"
"encoding/gob"
"log"
"crypto/sha256"
"fmt"
"go一期/lib/base58"
)
const reward = 50
//1. 定义交易结构
type Transaction struct {
TXID []byte //交易ID
TXInputs []TXInput //交易输入数组
TXOutputs []TXOutput //交易输出的数组
}
//定义交易输入
type TXInput struct {
//引用的交易ID
TXid []byte
//引用的output的索引值
Index int64
//解锁脚本,我们用地址来模拟
//Sig string
//真正的数字签名,由r,s拼成的[]byte
Signature []byte
//约定,这里的PubKey不存储原始的公钥,而是存储X和Y拼接的字符串,在校验端重新拆分(参考r,s传递)
//注意,是公钥,不是哈希,也不是地址
PubKey []byte
}
//定义交易输出
type TXOutput struct {
//转账金额
Value float64
//锁定脚本,我们用地址模拟
//PubKeyHash string
//收款方的公钥的哈希,注意,是哈希而不是公钥,也不是地址
PubKeyHash []byte
}
//由于现在存储的字段是地址的公钥哈希,所以无法直接创建TXOutput,
//为了能够得到公钥哈希,我们需要处理一下,写一个Lock函数
func (output *TXOutput) Lock(address string) {
//1. 解码
//2. 截取出公钥哈希:去除version(1字节),去除校验码(4字节)
addressByte := base58.Decode(address) //25字节
len := len(addressByte)
pubKeyHash := addressByte[1:len-4]
//真正的锁定动作!!!!!
output.PubKeyHash = pubKeyHash
}
//给TXOutput提供一个创建的方法,否则无法调用Lock
func NewTXOutput(value float64, address string) *TXOutput {
output := TXOutput{
Value: value,
}
output.Lock(address)
return &output
}
//设置交易ID
func (tx *Transaction) SetHash() {
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(tx)
if err != nil {
log.Panic(err)
}
data := buffer.Bytes()
hash := sha256.Sum256(data)
tx.TXID = hash[:]
}
//实现一个函数,判断当前的交易是否为挖矿交易
func (tx *Transaction) IsCoinbase() bool {
//1. 交易input只有一个
//if len(tx.TXInputs) == 1 {
// input := tx.TXInputs[0]
// //2. 交易id为空
// //3. 交易的index 为 -1
// if !bytes.Equal(input.TXid, []byte{}) || input.Index != -1 {
// return false
// }
//}
//return true
if len(tx.TXInputs) == 1 && len(tx.TXInputs[0].TXid) == 0 && tx.TXInputs[0].Index == -1 {
return true
}
return false
}
//2. 提供创建交易方法(挖矿交易)
func NewCoinbaseTX(address string, data string) *Transaction {
//挖矿交易的特点:
//1. 只有一个input
//2. 无需引用交易id
//3. 无需引用index
//矿工由于挖矿时无需指定签名,所以这个sig字段可以由矿工自由填写数据,一般是填写矿池的名字
input := TXInput{[]byte{}, -1, data}
output := TXOutput{reward, address}
//对于挖矿交易来说,只有一个input和一output
tx := Transaction{[]byte{}, []TXInput{input}, []TXOutput{output}}
tx.SetHash()
return &tx
}
//创建普通的转账交易
//3. 创建outputs
//4. 如果有零钱,要找零
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
//1. 找到最合理UTXO集合 map[string][]uint64
utxos, resValue := bc.FindNeedUTXOs(from, amount)
if resValue < amount {
fmt.Printf("余额不足,交易失败!")
return nil
}
var inputs []TXInput
var outputs []TXOutput
//2. 创建交易输入, 将这些UTXO逐一转成inputs
//map[2222] = []int64{0}
//map[3333] = []int64{0, 1}
for id, indexArray := range utxos {
for _, i := range indexArray {
input := TXInput{[]byte(id), int64(i), from}
inputs = append(inputs, input)
}
}
//创建交易输出
output := TXOutput{amount, to}
outputs = append(outputs, output)
//找零
if resValue > amount {
outputs = append(outputs, TXOutput{resValue - amount, from})
}
tx := Transaction{[]byte{}, inputs, outputs}
tx.SetHash()
return &tx
}
09.wallet.go
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"log"
"crypto/sha256"
//"golang.org/x/crypto/ripemd160"
"./lib/ripemd160"
//"github.com/btcsuite/btcutil/base58"
"./lib/base58"
)
//这里的钱包时一结构,每一个钱包保存了公钥,私钥对
type Wallet struct {
//私钥
Private *ecdsa.PrivateKey
//PubKey *ecdsa.PublicKey
// 这里的PubKey不存储原始的公钥,而是存储X和Y拼接的字符串,在校验端重新拆分(参考r,s传递)
PubKey []byte //
}
//创建钱包
func NewWallet() *Wallet {
//创建曲线
curve := elliptic.P256()
//生成私钥
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
log.Panic()
}
//生成公钥
pubKeyOrig := privateKey.PublicKey
//拼接X, Y
pubKey := append(pubKeyOrig.X.Bytes(), pubKeyOrig.Y.Bytes()...)
return &Wallet{Private: privateKey, PubKey: pubKey}
}
//生成地址
func (w *Wallet) NewAddress() string {
pubKey := w.PubKey
rip160HashValue := HashPubKey(pubKey)
version := byte(00)
//拼接version
payload := append([]byte{version}, rip160HashValue...)
//checksum
checkCode := CheckSum(payload)
//25字节数据
payload = append(payload, checkCode...)
//go语言有一个库,叫做btcd,这个是go语言实现的比特币全节点源码
address := base58.Encode(payload)
return address
}
func HashPubKey(data []byte) []byte {
hash := sha256.Sum256(data)
//理解为编码器
rip160hasher := ripemd160.New()
_, err := rip160hasher.Write(hash[:])
if err != nil {
log.Panic(err)
}
//返回rip160的哈希结果
rip160HashValue := rip160hasher.Sum(nil)
return rip160HashValue
}
func CheckSum(data []byte) []byte {
//两次sha256
hash1 := sha256.Sum256(data)
hash2 := sha256.Sum256(hash1[:])
//前4字节校验码
checkCode := hash2[:4]
return checkCode
}
10.wallets.go
package main
import (
"io/ioutil"
"bytes"
"encoding/gob"
"log"
"crypto/elliptic"
"os"
)
const walletFile = "wallet.dat"
//定一个 Wallets结构,它保存所有的wallet以及它的地址
type Wallets struct {
//map[地址]钱包
WalletsMap map[string]*Wallet
}
//创建方法,返回当前所有钱包的实例
func NewWallets() *Wallets {
var ws Wallets
ws.WalletsMap = make(map[string]*Wallet)
ws.loadFile()
return &ws
}
func (ws *Wallets) CreateWallet() string {
wallet := NewWallet()
address := wallet.NewAddress()
ws.WalletsMap[address] = wallet
ws.saveToFile()
return address
}
//保存方法,把新建的wallet添加进去
func (ws *Wallets) saveToFile() {
var buffer bytes.Buffer
//panic: gob: type not registered for interface: elliptic.p256Curve
gob.Register(elliptic.P256())
encoder := gob.NewEncoder(&buffer)
err := encoder.Encode(ws)
//一定要注意校验!!!
if err != nil {
log.Panic(err)
}
ioutil.WriteFile(walletFile, buffer.Bytes(), 0600)
}
//读取文件方法,把所有的wallet读出来
func (ws *Wallets) loadFile() {
//在读取之前,要先确认文件是否在,如果不存在,直接退出
_, err := os.Stat(walletFile)
if os.IsNotExist(err) {
//ws.WalletsMap = make(map[string]*Wallet)
return
}
//读取内容
content, err := ioutil.ReadFile(walletFile)
if err != nil {
log.Panic(err)
}
//解码
//panic: gob: type not registered for interface: elliptic.p256Curve
gob.Register(elliptic.P256())
decoder := gob.NewDecoder(bytes.NewReader(content))
var wsLocal Wallets
err = decoder.Decode(&wsLocal)
if err != nil {
log.Panic(err)
}
//ws = &wsLocal
//对于结构来说,里面有map的,要指定赋值,不要再最外层直接赋值
ws.WalletsMap = wsLocal.WalletsMap
}
func (ws *Wallets) ListAllAddresses() []string {
var addresses []string
//遍历钱包,将所有的key取出来返回
for address := range ws.WalletsMap {
addresses = append(addresses, address)
}
return addresses
}