在上一章节中,我们把简单的用户定义的字节当做地址来使用,比如在上一 章我测试用到的zyj和dxn。在这一章节中我们要正真的去实现区块链中的地址。
大家应该还记得上一章中提到过区块链中的交易是地址与地址之间的,地址的背后才是我们人来操作,因此我们会发现一个问题,就是在上一章中的这些我们自定义的地址并没有什么意义,因为随便谁都可以使用,转移该地址中的getbalnace,但是现实中我们并不想这样。所以这就涉及到了公钥、私钥与数字签名了。
在比特币中,你的身份是通过一对公钥和私钥来证明的,公钥是可以公开的,私钥就不能公开了,私钥相当于密码用来解锁你地址上的币。私钥和公钥只不过是随机的字节序列,人类通过肉眼去读取很困难。比特币使用了Base58转换算法把公钥转换成我们方便识别的字符串
数字签名:当数据从发送方传送到接收方时,数据不会被修改;数据由某一确定的发送方创建;发送方无法否认发送过数据这一事实。通过在数据上应用签名算法(也就是对数据进行签名),你就可以得到一个签名,这个签名晚些时候会被验证。生成数字签名需要一个私钥,而验证签名需要一个公钥。签名有点类似于印章。
签名的具体过程为:交易发送方对发送的交易进行签名,此时需要用到发送的交易和发送方的私钥。交易接收方进行验证签名,此时需要用到的是接收到的 被签名的交易、接收到的签名、发送方的公钥。简单来说,验证过程可以被描述为:检查签名是由被签名数据加上私钥得来,并且公钥恰好是由该私钥生成。
现在来回顾一个交易完整的生命周期:
-
起初,创世块里面包含了一个 coinbase 交易。在 coinbase 交易中,没有输入,所以也就不需要签名。coinbase 交易的输出包含了一个哈希过的公钥(使用的是
RIPEMD16(SHA256(PubKey)) 算法) -
当一个人发送币时,就会创建一笔交易。这笔交易的输入会引用之前交易的输出。每个输入会存储一个公钥(没有被哈希)和整个交易的一个签名。
-
比特币网络中接收到交易的其他节点会对该交易进行验证。除了一些其他事情,他们还会检查:在一个输入中,公钥哈希与所引用的输出哈希相匹配(这保证了发送方只能花费属于自己的币);签名是正确的(这保证了交易是由币的实际拥有者所创建)。
-
当一个矿工准备挖一个新块时,他会将交易放到块中,然后开始挖矿。
-
当新块被挖出来以后,网络中的所有其他节点会接收到一条消息,告诉其他人这个块已经被挖出并被加入到区块链。
-
当一个块被加入到区块链以后,交易就算完成,它的输出就可以在新的交易中被引用
比特币使用椭圆曲线来产生私钥。椭圆曲线是一个复杂的数学概念,比特币使用的是 ECDSA(Elliptic Curve Digital Signature Algorithm)算法来对交易进行签名,我们也会使用该算法。
在开始写代码之前我们需要清除这几个关系:首先私钥是随机从底层机器码中取出的256位的2进制数,然后通过椭圆曲线算法生成我们的私钥。公钥是由私钥经经过复杂的哈希运算得到的,当然是不可逆的。然后就是地址,地址又是公钥经过哈希运算和base58得到我们能容易识别的地址。
这里我们通过画图来直观的认识通过公钥生成地址的过程:
下面就用代码实现钱包地址:
package wallet
import (
"bytes"
"crypto/sha256"
"crypto/elliptic"
"crypto/ecdsa"
"crypto/rand"
"log"
"os"
"fmt"
"io/ioutil"
"encoding/gob"
"golang.org/x/crypto/ripemd160"
"go_code/A_golang_blockchain/base58"
)
const version = byte(0x00)
const walletFile = "wallet.dat"
const addressChecksumLen = 4 //对校验位一般取4位
//创建一个钱包结构体,钱包里面只装公钥和私钥
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
//实例化一个钱包
func NewWallet() *Wallet {
//生成秘钥对
private , public := newKeyPair()
wallet := &Wallet{private,public}
return wallet
}
//生成**对函数
func newKeyPair() (ecdsa.PrivateKey,[]byte) {
//返回一个实现了P-256的曲线
curve := elliptic.P256()
//通过椭圆曲线 随机生成一个私钥
private ,err := ecdsa.GenerateKey(curve,rand.Reader)
if err != nil {
log.Panic(err)
}
pubKey := append(private.PublicKey.X.Bytes(),private.PublicKey.Y.Bytes()...)
return *private,pubKey
}
//生成一个地址
func (w Wallet) GetAddress() []byte {
//调用公钥哈希函数,实现RIPEMD160(SHA256(Public Key))
pubKeyHash := HashPubKey(w.PublicKey)
//存储version和公钥哈希的切片
versionedPayload := append([]byte{version},pubKeyHash...)
//调用checksum函数,对上面的切片进行双重哈希后,取出哈希后的切片的前面部分作为检验位的值
checksum := checksum(versionedPayload)
//把校验位加到上面切片后面
fullPayload := append(versionedPayload,checksum...)
//通过base58编码上述切片得到地址
address := base58.Base58Encode(fullPayload)
return address
}
//公钥哈希函数,实现RIPEMD160(SHA256(Public Key))
func HashPubKey(pubKey []byte) []byte {
//先hash公钥
publicSHA256 := sha256.Sum256(pubKey)
//对公钥哈希值做 ripemd160运算
RIPEMD160Hasher := ripemd160.New()
_,err := RIPEMD160Hasher.Write(publicSHA256[:])
if err != nil {
log.Panic(err)
}
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
//校验位checksum,双重哈希运算
func checksum(payload []byte) []byte {
//下面双重哈希payload,在调用中,所引用的payload为(version + Pub Key Hash)
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
//addressChecksumLen代表保留校验位长度
return secondSHA[:addressChecksumLen]
}
//判断输入的地址是否有效,主要是检查后面的校验位是否正确
func ValidateAddress(address string) bool {
//解码base58编码过的地址
pubKeyHash := base58.Base58Decode([]byte(address))
//拆分pubKeyHash,pubKeyHash组成形式为:(一个字节的version) + (Public key hash) + (Checksum)
actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:]
version := pubKeyHash[0]
pubKeyHash = pubKeyHash[1:len(pubKeyHash)-addressChecksumLen]
targetChecksum := checksum(append([]byte{version},pubKeyHash...))
//比较拆分出的校验位与计算出的目标校验位是否相等
return bytes.Compare(actualChecksum,targetChecksum) == 0
}
//创建一个钱包集合的结构体
type Wallets struct {
Wallets map[string]*Wallet
}
// 实例化一个钱包集合,
func NewWallets() (*Wallets, error) {
wallets := Wallets{}
wallets.Wallets = make(map[string]*Wallet)
err := wallets.LoadFromFile()
return &wallets, err
}
// 将 Wallet 添加进 Wallets
func (ws *Wallets) CreateWallet() string {
wallet := NewWallet()
address := fmt.Sprintf("%s", wallet.GetAddress())
ws.Wallets[address] = wallet
return address
}
// 得到存储在wallets里的地址
func (ws *Wallets) GetAddresses() []string {
var addresses []string
for address := range ws.Wallets {
addresses = append(addresses, address)
}
return addresses
}
// 通过地址返回出钱包
func (ws Wallets) GetWallet(address string) Wallet {
return *ws.Wallets[address]
}
// 从文件中加载钱包s
func (ws *Wallets) LoadFromFile() error {
if _, err := os.Stat(walletFile); os.IsNotExist(err) {
return err
}
fileContent, err := ioutil.ReadFile(walletFile)
if err != nil {
log.Panic(err)
}
var wallets Wallets
gob.Register(elliptic.P256())
decoder := gob.NewDecoder(bytes.NewReader(fileContent))
err = decoder.Decode(&wallets)
if err != nil {
log.Panic(err)
}
ws.Wallets = wallets.Wallets
return nil
}
// 将钱包s保存到文件
func (ws Wallets) SaveToFile() {
var content bytes.Buffer
gob.Register(elliptic.P256())
encoder := gob.NewEncoder(&content)
err := encoder.Encode(ws)
if err != nil {
log.Panic(err)
}
err = ioutil.WriteFile(walletFile, content.Bytes(), 0644)
if err != nil {
log.Panic(err)
}
}
说明:代码中引用了包"golang.org/x/crypto/ripemd160",这个包我们在命令行直接 go get golang.org/x/crypto/ripemd160 命令一般=是连不通的,除非翻墙。但是我们可以直接在github上面下载整个crypto包下来,操作如下:
- 退回到你的电脑的go文件的src目录下,创建两个目录为golang.org/x/,
- 在x目录下面输入命令git clone https://github.com/golang/crypto.git
- 此时crypto包已经克隆成功,你的编辑器目录下已经有了golang.org目录
- 然后直接引用"golang.org/x/crypto/ripemd160"包
至此,我们已经得到了一个钱包地址。下面我们会实现签名,由于代码比较多,我把签名的几段核心代码附在上面,里面注解也比较详细,相信之前几章看完后看这章的代码就比较容易了。
签名的核心代码:
//对交易签名
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey,prevTXs map[string]Transaction) {
if tx.IsCoinbase() {
return
}
for _,vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
txCopy := tx.TrimmedCopy()
for inID,vin := range txCopy.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubkeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
r,s,err := ecdsa.Sign(rand.Reader,&privKey,txCopy.ID)
if err != nil {
log.Panic(err)
}
signature := append(r.Bytes(),s.Bytes()...)
tx.Vin[inID].Signature = signature
}
}
验证签名的代码:
//验证 交易输入的签名
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
if tx.IsCoinbase() {
return true
}
for _,vin := range tx.Vin {
//遍历输入交易,如果发现输入交易引用的上一交易的ID不存在,则Panic
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
txCopy := tx.TrimmedCopy() //修剪后的副本
curve := elliptic.P256() //椭圆曲线实例
for inID,vin := range tx.Vin {
prevTX := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil //双重验证
txCopy.Vin[inID].PubKey = prevTX.Vout[vin.Vout].PubkeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
r := big.Int{}
s := big.Int{}
sigLen := len(vin.Signature)
r.SetBytes(vin.Signature[:(sigLen / 2)])
s.SetBytes(vin.Signature[(sigLen / 2):])
x := big.Int{}
y := big.Int{}
keyLen := len(vin.PubKey)
x.SetBytes(vin.PubKey[:(keyLen / 2)])
y.SetBytes(vin.PubKey[(keyLen / 2):])
rawPubKey := ecdsa.PublicKey{curve,&x,&y}
if ecdsa.Verify(&rawPubKey,txCopy.ID,&r,&s) == false {
return false
}
}
return true
}
在签名和验证签名中都会用到的修剪交易副本的方法,这个方法很重要,我觉得也是理解签名的一个关键所在。
//创建在签名中修剪后的交易副本,之所以要这个副本是因为简化了输入交易本身的签名和公钥
func (tx *Transaction) TrimmedCopy() Transaction {
var inputs []TXInput
var outputs []TXOutput
for _,vin := range tx.Vin {
inputs = append(inputs,TXInput{vin.Txid,vin.Vout,nil,nil})
}
for _,vout := range tx.Vout {
outputs = append(outputs,TXOutput{vout.Value,vout.PubkeyHash})
}
txCopy := Transaction{tx.ID,inputs,outputs}
return txCopy
}
上面实现了签名方法后,我们要转移到区块链上了,我们签名的是区块链上区块中的交易,所以我们要想到实现签名方法的地方是我们创建交易的时候,下面这个方法实现对区块中交易的签名。
//对交易输入进行签名
func (bc *Blockchain) SignTransaction(tx *transaction.Transaction,privKey ecdsa.PrivateKey) {
prevTXs := make(map[string]transaction.Transaction)
for _,vin :=range tx.Vin {
prevTX,err := bc.FindTransaction(vin.Txid) //找到输入引用的输出所在的交易
if err != nil {
log.Panic(err)
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
tx.Sign(privKey,prevTXs)
}
签名最终作用的是创建一笔交易的函数中,因为我们创建一笔交易就要对其进行签名。
/发送币操作,相当于创建一笔未花费输出交易
func NewUTXOTransaction(from,to string,amount int,bc *Blockchain) *transaction.Transaction {
var inputs []transaction.TXInput
var outputs []transaction.TXOutput
//validOutputs是一个存放要用到的未花费输出的交易/输出的map
//acc,validOutputs := bc.FindSpendableOutputs(from,amount)
wallets,err := wallet.NewWallets()
if err != nil {
log.Panic(err)
}
_wallet := wallets.GetWallet(from)
pubKeyHash := wallet.HashPubKey(_wallet.PublicKey)
acc, validOutputs := bc.FindSpendableOutputs(pubKeyHash, amount)
if acc < amount {
log.Panic("ERROR:Not enough tokens...")
}
//通过validOutputs里面的数据来放入建立一个输入列表
for txid,outs := range validOutputs {
//反序列化得到txID
txID,err := hex.DecodeString(txid)
if err != nil {
log.Panic(err)
}
//遍历输出outs切片,得到TXInput里的Vout字段值
for _,out := range outs {
//input := transaction.TXInput{txID,out,from}
input := transaction.TXInput{txID,out,nil,_wallet.PublicKey}
inputs = append(inputs,input)
}
}
//建立一个输出列表
//outputs = append(outputs,transaction.TXOutput{amount,to})
outputs = append(outputs,*transaction.NewTXOutput(amount,to))
if acc > amount {
//outputs = append(outputs,transaction.TXOutput{acc - amount,from}) //相当于找零
outputs = append(outputs,*transaction.NewTXOutput(acc - amount,from)) //相当于找零
}
tx := transaction.Transaction{nil,inputs,outputs}
//tx.SetID()
tx.ID = tx.Hash()
bc.SignTransaction(&tx, _wallet.PrivateKey)
return &tx
}
然后就是矿工把交易打包进区块的时候会验证交易签名。
//验证交易
func (bc *Blockchain) VerifyTransaction(tx *transaction.Transaction) bool {
prevTXs := make(map[string]transaction.Transaction)
for _, vin := range tx.Vin {
prevTX,err := bc.FindTransaction(vin.Txid)
if err != nil {
log.Panic(err)
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
return tx.Verify(prevTXs) //验证签名
}
为此,签名的核心代码基本完成。现在我们的区块链有了钱包地址的概念了,也会自动对我们发起的交易进行签名了。下面我们来运行一把看看。
测试的时候还是最好把之前的blockchain.db数据库删除从头开始;
下面我们依次实现:生成两个地址,用其中一个地址来创建一条链,把矿工地址的币发送给另一个地址,查询地址余额,打印钱包中的这两个地址,打印出链:
1、生成两个地址
2、用其中一个地址来创建一条链。
3、把矿工地址的币发送给另一个地址。
4、查询地址余额,打印钱包中的这两个地址。
5、打印出区块链信息:
以上就是这章节的测试结果,下面我想还是附上全部代码吧
到本章结束后编写的代码如下:
(说明:代码结构有点乱,因为编写的时候我是尽量写在一个文件下面的,后面代码全部完成后会把一些实现单独功能的函数和方法进行单独封装到独立的文件下面,我现在代码的文件结构就是一个包下面就一个文件,没有封装其他文件在同一个包中,这是不对的,这只是个人习惯)
base58包,因为要用到base58这种编码方式来生成地址,所以编写了这个方法在单独的包里:
package base58
import (
"bytes"
"math/big"
)
var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
//将字节数组编码为Base58
func Base58Encode(input []byte) []byte {
var result []byte
x := big.NewInt(0).SetBytes(input)
base := big.NewInt(int64(len(b58Alphabet)))
zero := big.NewInt(0)
mod := &big.Int{}
for x.Cmp(zero) != 0 {
x.DivMod(x, base, mod)
result = append(result, b58Alphabet[mod.Int64()])
}
ReverseBytes(result)
for b := range input {
if b == 0x00 {
result = append([]byte{b58Alphabet[0]}, result...)
} else {
break
}
}
return result
}
//解码Base58编码的数据
func Base58Decode(input []byte) []byte {
result := big.NewInt(0)
zeroBytes := 0
for b := range input {
if b == 0x00 {
zeroBytes++
}
}
payload := input[zeroBytes:]
for _, b := range payload {
charIndex := bytes.IndexByte(b58Alphabet, b)
result.Mul(result, big.NewInt(58))
result.Add(result, big.NewInt(int64(charIndex)))
}
decoded := result.Bytes()
decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...)
return decoded
}
// 反转字节数组
func ReverseBytes(data []byte) {
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
data[i], data[j] = data[j], data[i]
}
}
block包:
package block
import (
"crypto/sha256"
"encoding/gob"
"bytes"
"log"
"go_code/A_golang_blockchain/transaction"
)
//区块的结构体
type Block struct {
Timestamp int64
Transactions []*transaction.Transaction
PrevBlockHash []byte
Hash []byte
Nonce int
}
//区块交易字段的哈希,因为每个交易ID是序列化并哈希后的交易数据结构,所以我们只需把所以交易的ID进行哈希就可以了
func (b *Block) HashTransactions() []byte {
var txHash [32]byte
var txHashes [][]byte
for _,tx := range b.Transactions {
txHashes = append(txHashes,tx.Hash())
}
txHash = sha256.Sum256(bytes.Join(txHashes,[]byte{}))
return txHash[:]
}
//0.3 实现Block的序列化
func (b *Block) Serialize() []byte {
//首先定义一个buffer存储序列化后的数据
var result bytes.Buffer
//实例化一个序列化实例,结果保存到result中
encoder := gob.NewEncoder(&result)
//对区块进行实例化
err := encoder.Encode(b)
if err != nil {
log.Panic(err)
}
return result.Bytes()
}
//0.3 实现反序列化函数
func DeserializeBlock(d []byte) *Block {
var block Block
decoder := gob.NewDecoder(bytes.NewReader(d))
err := decoder.Decode(&block)
if err != nil {
log.Panic(err)
}
return &block
}
blockchain包:
package blockchain
import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"github.com/boltdb/bolt"
"go_code/A_golang_blockchain/block"
"go_code/A_golang_blockchain/pow"
"go_code/A_golang_blockchain/transaction"
"go_code/A_golang_blockchain/wallet"
"log"
"errors"
)
/*
区块链实现
*/
const dbFile = "blockchain.db"
const blocksBucket = "blocks"
const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
//区块链
type Blockchain struct {
tip []byte
db *bolt.DB
}
//工厂模式db
func(bc *Blockchain) Db() *bolt.DB {
return bc.db
}
//把区块添加进区块链,挖矿
func (bc *Blockchain) MineBlock(transactions []*transaction.Transaction) {
var lastHash []byte
//在一笔交易被放入一个块之前进行验证
for _, tx := range transactions {
if bc.VerifyTransaction(tx) != true {
log.Panic("ERROR: 无效 transaction")
}
}
//只读的方式浏览数据库,获取当前区块链顶端区块的哈希,为加入下一区块做准备
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("l")) //通过键"l"拿到区块链顶端区块哈希
return nil
})
if err != nil {
log.Panic(err)
}
//prevBlock := bc.Blocks[len(bc.Blocks)-1]
//求出新区块
newBlock := pow.NewBlock(transactions,lastHash)
// bc.Blocks = append(bc.Blocks,newBlock)
//把新区块加入到数据库区块链中
err = bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash,newBlock.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"),newBlock.Hash)
bc.tip = newBlock.Hash
return nil
})
}
//创建创世区块 /修改/
func NewGenesisBlock(coinbase *transaction.Transaction) *block.Block {
return pow.NewBlock([]*transaction.Transaction{coinbase},[]byte{})
}
//实例化一个区块链,默认存储了创世区块 ,接收一个地址为挖矿奖励地址 /修改/
func NewBlockchain(address string) *Blockchain {
//return &Blockchain{[]*block.Block{NewGenesisBlock()}}
var tip []byte
//打开一个数据库文件,如果文件不存在则创建该名字的文件
db,err := bolt.Open(dbFile,0600,nil)
if err != nil {
log.Panic(err)
}
//读写操作数据库
err = db.Update(func(tx *bolt.Tx) error{
b := tx.Bucket([]byte(blocksBucket))
//查看名字为blocksBucket的Bucket是否存在
if b == nil {
//不存在则从头 创建
fmt.Println("不存在区块链,需要重新创建一个区块链...")
//genesis := NewGenesisBlock() //创建创世区块
//此时的创世区块就要包含交易coinbaseTx
cbtx := transaction.NewCoinbaseTX(address,genesisCoinbaseData)
genesis := NewGenesisBlock(cbtx)
b,err := tx.CreateBucket([]byte(blocksBucket)) //创建名为blocksBucket的桶
if err != nil {
log.Panic(err)
}
err = b.Put(genesis.Hash,genesis.Serialize()) //写入键值对,区块哈希对应序列化后的区块
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"),genesis.Hash) //"l"键对应区块链顶端区块的哈希
if err != nil {
log.Panic(err)
}
tip = genesis.Hash //指向最后一个区块,这里也就是创世区块
} else {
//如果存在blocksBucket桶,也就是存在区块链
//通过键"l"映射出顶端区块的Hash值
tip = b.Get([]byte("l"))
}
return nil
})
bc := Blockchain{tip,db} //此时Blockchain结构体字段已经变成这样了
return &bc
}
//分割线——————迭代器——————
type BlockchainIterator struct {
currentHash []byte
db *bolt.DB
}
//当需要遍历当前区块链时,创建一个此区块链的迭代器
func (bc *Blockchain) Iterator() *BlockchainIterator {
bci := &BlockchainIterator{bc.tip,bc.db}
return bci
}
//迭代器的任务就是返回链中的下一个区块
func (i *BlockchainIterator) Next() *block.Block {
var Block *block.Block
//只读方式打开区块链数据库
err := i.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
//获取数据库中当前区块哈希对应的被序列化后的区块
encodeBlock := b.Get(i.currentHash)
//反序列化,获得区块
Block = block.DeserializeBlock(encodeBlock)
return nil
})
if err != nil {
log.Panic(err)
}
//把迭代器中的当前区块哈希设置为上一区块的哈希,实现迭代的作用
i.currentHash =Block.PrevBlockHash
return Block
}
//在区块链上找到每一个区块中属于address用户的未花费交易输出,返回未花费输出的交易切片
func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []transaction.Transaction {
var unspentTXs []transaction.Transaction
//创建一个map,存储已经花费了的交易输出
spentTXOs := make(map[string][]int)
//因为要在链上遍历区块,所以要使用到迭代器
bci := bc.Iterator()
for {
block := bci.Next() //迭代
//遍历当前区块上的交易
for _,tx := range block.Transactions {
txID := hex.EncodeToString(tx.ID) //把交易ID转换成string类型,方便存入map中
//标签
Outputs:
//遍历当前交易中的输出切片,取出交易输出
for outIdx,out := range tx.Vout {
//在已经花费了的交易输出map中,如果没有找到对应的交易输出,则表示当前交易的输出未花费
//反之如下
if spentTXOs[txID] != nil {
//存在当前交易的输出中有已经花费的交易输出,
//则我们遍历map中保存的该交易ID对应的输出的index
//提示:(这里的已经花费的交易输出index其实就是输入TXInput结构体中的Vout字段)
for _,spentOutIdx := range spentTXOs[txID] {
//首先要清楚当前交易输出是一个切片,里面有很多输出,
//如果map里存储的引用的输出和我们当前遍历到的输出index重合,则表示该输出被引用了
if spentOutIdx == outIdx {
continue Outputs //我们就继续遍历下一轮,找到未被引用的输出
}
}
}
//到这里是得到此交易输出切片中未被引用的输出
//这里就要从这些未被引用的输出中筛选出属于该用户address地址的输出
if out.IsLockedWithKey(pubKeyHash) {
unspentTXs = append(unspentTXs,*tx)
}
}
//判断是否为coinbase交易
if tx.IsCoinbase() == false {
//如果不是,则遍历当前交易的输入
for _,in := range tx.Vin {
//如果当前交易的输入是被该地址address所花费的,就会有对应的该地址的引用输出
//则在map上记录该输入引用的该地址对应的交易输出
if in.UsesKey(pubKeyHash) {
inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID],in.Vout)
}
}
}
}
//退出for循环的条件就是遍历到的创世区块后
if len(block.PrevBlockHash) == 0 {
break
}
}
return unspentTXs
}
//通过找到未花费输出交易的集合,我们返回集合中的所有未花费的交易输出
func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []transaction.TXOutput {
var UTXOs []transaction.TXOutput
//找到address地址下的未花费交易输出的交易的集合
unspentTransactions := bc.FindUnspentTransactions(pubKeyHash)
//遍历交易集合得到交易,从交易中提取出输出字段Vout,从输出字段中提取出属于address的输出
for _,tx := range unspentTransactions {
for _, out := range tx.Vout {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs,out)
}
}
}
//返回未花费交易输出
return UTXOs
}
//发送币操作,相当于创建一笔未花费输出交易
func NewUTXOTransaction(from,to string,amount int,bc *Blockchain) *transaction.Transaction {
var inputs []transaction.TXInput
var outputs []transaction.TXOutput
//validOutputs是一个存放要用到的未花费输出的交易/输出的map
//acc,validOutputs := bc.FindSpendableOutputs(from,amount)
wallets,err := wallet.NewWallets()
if err != nil {
log.Panic(err)
}
_wallet := wallets.GetWallet(from)
pubKeyHash := wallet.HashPubKey(_wallet.PublicKey)
acc, validOutputs := bc.FindSpendableOutputs(pubKeyHash, amount)
if acc < amount {
log.Panic("ERROR:Not enough tokens...")
}
//通过validOutputs里面的数据来放入建立一个输入列表
for txid,outs := range validOutputs {
//反序列化得到txID
txID,err := hex.DecodeString(txid)
if err != nil {
log.Panic(err)
}
//遍历输出outs切片,得到TXInput里的Vout字段值
for _,out := range outs {
//input := transaction.TXInput{txID,out,from}
input := transaction.TXInput{txID,out,nil,_wallet.PublicKey}
inputs = append(inputs,input)
}
}
//建立一个输出列表
//outputs = append(outputs,transaction.TXOutput{amount,to})
outputs = append(outputs,*transaction.NewTXOutput(amount,to))
if acc > amount {
//outputs = append(outputs,transaction.TXOutput{acc - amount,from}) //相当于找零
outputs = append(outputs,*transaction.NewTXOutput(acc - amount,from)) //相当于找零
}
tx := transaction.Transaction{nil,inputs,outputs}
//tx.SetID()
tx.ID = tx.Hash()
bc.SignTransaction(&tx, _wallet.PrivateKey)
return &tx
}
//找到可以花费的交易输出,这是基于上面的FindUnspentTransactions 方法
func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte,amount int) (int,map[string][]int) {
//未花费交易输出map集合
unspentOutputs := make(map[string][]int)
//未花费交易
unspentTXs := bc.FindUnspentTransactions(pubKeyHash)
accumulated := 0 //累加未花费交易输出中的Value值
Work:
for _,tx := range unspentTXs {
txID := hex.EncodeToString(tx.ID)
for outIdx,out := range tx.Vout {
if out.IsLockedWithKey(pubKeyHash) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txID] = append(unspentOutputs[txID],outIdx)
if accumulated >= amount {
break Work
}
}
}
}
return accumulated,unspentOutputs
}
//通过交易ID找到一个交易
func (bc *Blockchain) FindTransaction(ID []byte) (transaction.Transaction,error) {
bci := bc.Iterator()
for {
block := bci.Next()
for _,tx := range block.Transactions {
if bytes.Compare(tx.ID,ID) == 0 {
return *tx,nil
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return transaction.Transaction{},errors.New("Transaction is not found")
}
//对交易输入进行签名
func (bc *Blockchain) SignTransaction(tx *transaction.Transaction,privKey ecdsa.PrivateKey) {
prevTXs := make(map[string]transaction.Transaction)
for _,vin :=range tx.Vin {
prevTX,err := bc.FindTransaction(vin.Txid) //找到输入引用的输出所在的交易
if err != nil {
log.Panic(err)
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
tx.Sign(privKey,prevTXs)
}
//验证交易
func (bc *Blockchain) VerifyTransaction(tx *transaction.Transaction) bool {
prevTXs := make(map[string]transaction.Transaction)
for _, vin := range tx.Vin {
prevTX,err := bc.FindTransaction(vin.Txid)
if err != nil {
log.Panic(err)
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
return tx.Verify(prevTXs) //验证签名
}
pow包:(基本没变化)
package pow
import (
"fmt"
"crypto/sha256"
"strconv"
"bytes"
"math/big"
"go_code/A_golang_blockchain/block"
"go_code/A_golang_blockchain/transaction"
"math"
"time"
)
//在实际的比特币区块链中,加入一个区块是非常困难的事情,其中运用得到的就是工作量证明
//创建一个工作量证明的结构体
type ProofOfWork struct {
block *block.Block //要证明的区块
target *big.Int //难度值
}
//声明一个挖矿难度
const targetBits = 10
//实例化一个工作量证明
func NewProofOfWork(b *block.Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target,uint(256 - targetBits))
pow := &ProofOfWork{b,target}
return pow
}
//准备需要进行哈希的数据
func (pow *ProofOfWork) prepareData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.HashTransactions(), //这里被修改,把之前的Data字段修改成交易字段的哈希
[]byte(strconv.FormatInt(pow.block.Timestamp,10)),
[]byte(strconv.FormatInt(targetBits,10)),
[]byte(strconv.FormatInt(int64(nonce),10)),
},
[]byte{},
)
return data
}
//进行工作量证明,证明成功会返回随机数和区块哈希
func (pow *ProofOfWork) Run() (int,[]byte) {
nonce := 0
var hash [32]byte
var hashInt big.Int
for nonce < math.MaxInt64 {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
hashInt.SetBytes(hash[:])
//把哈希后的数据与难度值进行比较
if hashInt.Cmp(pow.target) == -1 {
fmt.Printf("工作量证明成功 hash= %x nonce = %v\n",hash,nonce)
break
}else{
nonce ++
}
}
fmt.Println()
return nonce,hash[:]
}
//实例化一个区块 /更改data为transaction/
func NewBlock(transactions []*transaction.Transaction,prevBlockHash []byte) *block.Block {
block := &block.Block{time.Now().Unix(),transactions,prevBlockHash,[]byte{},0}
// block.SetHash()
pow := NewProofOfWork(block)
nonce,hash := pow.Run()
block.Hash = hash
block.Nonce = nonce
return block
}
//其他节点验证nonce是否正确
func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int
data := pow.prepareData(pow.block.Nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
isValid := hashInt.Cmp(pow.target) == -1
return isValid
}
transaction包:
package transaction
import (
"strings"
"math/big"
"crypto/elliptic"
"encoding/hex"
"crypto/ecdsa"
"crypto/sha256"
"crypto/rand"
"encoding/gob"
"bytes"
"fmt"
"log"
"go_code/A_golang_blockchain/wallet"
"go_code/A_golang_blockchain/base58"
)
var subsidy int = 50 //挖矿奖励
/*创建一个交易的数据结构,交易是由交易ID、交易输入、交易输出组成的,
一个交易有多个输入和多个输出,所以这里的交易输入和输出应该是切片类型的
*/
type Transaction struct {
ID []byte
Vin []TXInput
Vout []TXOutput
}
/*
1、每一笔交易的输入都会引用之前交易的一笔或多笔交易输出
2、交易输出保存了输出的值和锁定该输出的信息
3、交易输入保存了引用之前交易输出的交易ID、具体到引用
该交易的第几个输出、能正确解锁引用输出的签名信息
*/
//交易输出
type TXOutput struct {
Value int //输出的值(可以理解为金额)
//ScriptPubKey string // 锁定该输出的脚本(目前还没实现地址,所以还不能锁定该输出为具体哪个地址所有)
PubkeyHash []byte //存储“哈希”后的公钥,这里的哈希不是单纯的sha256
}
//交易输入
type TXInput struct {
Txid []byte //引用的之前交易的ID
Vout int //引用之前交易输出的具体是哪个输出(一个交易中输出一般有很多)
//ScriptSig string // 能解锁引用输出交易的签名脚本(目前还没实现地址,所以本章不能实现此功能)
Signature []byte //签名脚本
PubKey []byte // 公钥,这里的公钥是正真的公钥
}
/*
区块链上存储的交易都是由这些输入输出交易所组成的,
一个输入交易必须引用之前的输出交易,一个输出交易会被之后的输入所引用。
问题来了,在最开始的区块链上是先有输入还是先有输出喃?
答案是先有输出,因为是区块链的创世区块产生了第一个输出,
这个输出也就是我们常说的挖矿奖励-狗头金,每一个区块都会有一个这样的输出,
这是奖励给矿工的交易输出,这个输出是凭空产生的。
*/
//现在我们来创建一个这样的coinbase挖矿输出
//to 代表此输出奖励给谁,一般都是矿工地址,data是交易附带的信息
func NewCoinbaseTX(to,data string) *Transaction {
if data == "" {
data = fmt.Sprintf("奖励给 '%s'",to)
}
//此交易中的交易输入,没有交易输入信息
//txin := TXInput{[]byte{},-1,[]byte{},}
txin := TXInput{[]byte{},-1,nil,[]byte(data)}
//交易输出,subsidy为奖励矿工的币的数量
txout := NewTXOutput(subsidy,to)
//组成交易
//tx := Transaction{nil,[]TXInput{txin},[]TXOutput{txout}}
tx := Transaction{nil,[]TXInput{txin},[]TXOutput{*txout}}
//设置该交易的ID
//tx.SetID()
tx.ID = tx.Hash()
return &tx
}
////设置交易ID,交易ID是序列化tx后再哈希
//func (tx *Transaction) SetID() {
//返回一个序列化后的交易
func (tx Transaction) Serialize() []byte {
//var hash [32]byte
var encoder bytes.Buffer
enc := gob.NewEncoder(&encoder)
err := enc.Encode(tx)
if err != nil {
log.Panic(err)
}
//hash = sha256.Sum256(encoder.Bytes())
//tx.ID = hash[:]
return encoder.Bytes()
}
//返回交易的哈希值
func (tx *Transaction) Hash() []byte {
var hash [32]byte
txCopy := *tx
txCopy.ID = []byte{}
hash = sha256.Sum256(txCopy.Serialize())
return hash[:]
}
/*
1、每一个区块至少存储一笔coinbase交易,所以我们在区块的字段中把Data字段换成交易。
2、把所有涉及之前Data字段都要换了,比如NewBlock()、GenesisBlock()、pow里的函数
*/
//定义在输入和输出上的锁定和解锁方法,目的是让用户只能花自己所用于地址上的币
//输入上锁定的秘钥,表示能引用的输出是unlockingData
// func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
// return in.ScriptSig == unlockingData
// }
//方法检查输入是否使用了指定**来解锁一个输出
func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
lockingHash := wallet.HashPubKey(in.PubKey)
return bytes.Compare(lockingHash,pubKeyHash) == 0
}
//输出上的解锁秘钥,表示能被引用的输入是unlockingData
// func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
// return out.ScriptPubKey == unlockingData
// }
//锁定交易输出到固定的地址,代表该输出只能由指定的地址引用
func (out *TXOutput) Lock(address []byte) {
pubKeyHash := base58.Base58Decode(address)
pubKeyHash = pubKeyHash[1:len(pubKeyHash)-4]
out.PubkeyHash = pubKeyHash
}
//判断输入的公钥"哈希"能否解锁该交易输出
func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
return bytes.Compare(out.PubkeyHash,pubKeyHash) == 0
}
//创建一个新的交易输出
func NewTXOutput(value int,address string) *TXOutput {
txo := &TXOutput{value,nil}
txo.Lock([]byte(address))
return txo
}
//判断是否为coinbase交易
func (tx *Transaction) IsCoinbase() bool {
return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
}
//对交易签名
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey,prevTXs map[string]Transaction) {
if tx.IsCoinbase() {
return
}
for _,vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
txCopy := tx.TrimmedCopy()
for inID,vin := range txCopy.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubkeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
r,s,err := ecdsa.Sign(rand.Reader,&privKey,txCopy.ID)
if err != nil {
log.Panic(err)
}
signature := append(r.Bytes(),s.Bytes()...)
tx.Vin[inID].Signature = signature
}
}
//验证 交易输入的签名
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
if tx.IsCoinbase() {
return true
}
for _,vin := range tx.Vin {
//遍历输入交易,如果发现输入交易引用的上一交易的ID不存在,则Panic
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
txCopy := tx.TrimmedCopy() //修剪后的副本
curve := elliptic.P256() //椭圆曲线实例
for inID,vin := range tx.Vin {
prevTX := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil //双重验证
txCopy.Vin[inID].PubKey = prevTX.Vout[vin.Vout].PubkeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
r := big.Int{}
s := big.Int{}
sigLen := len(vin.Signature)
r.SetBytes(vin.Signature[:(sigLen / 2)])
s.SetBytes(vin.Signature[(sigLen / 2):])
x := big.Int{}
y := big.Int{}
keyLen := len(vin.PubKey)
x.SetBytes(vin.PubKey[:(keyLen / 2)])
y.SetBytes(vin.PubKey[(keyLen / 2):])
rawPubKey := ecdsa.PublicKey{curve,&x,&y}
if ecdsa.Verify(&rawPubKey,txCopy.ID,&r,&s) == false {
return false
}
}
return true
}
//创建在签名中修剪后的交易副本,之所以要这个副本是因为简化了输入交易本身的签名和公钥
func (tx *Transaction) TrimmedCopy() Transaction {
var inputs []TXInput
var outputs []TXOutput
for _,vin := range tx.Vin {
inputs = append(inputs,TXInput{vin.Txid,vin.Vout,nil,nil})
}
for _,vout := range tx.Vout {
outputs = append(outputs,TXOutput{vout.Value,vout.PubkeyHash})
}
txCopy := Transaction{tx.ID,inputs,outputs}
return txCopy
}
//把交易转换成我们能正常读的形式
func (tx Transaction) String() string {
var lines []string
lines = append(lines, fmt.Sprintf("--Transaction %x:", tx.ID))
for i, input := range tx.Vin {
lines = append(lines, fmt.Sprintf(" -Input %d:", i))
lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid))
lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout))
lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature))
lines = append(lines, fmt.Sprintf(" PubKey:%x", input.PubKey))
}
for i, output := range tx.Vout {
lines = append(lines, fmt.Sprintf(" -Output %d:", i))
lines = append(lines, fmt.Sprintf(" Value: %d", output.Value))
lines = append(lines, fmt.Sprintf(" Script: %x", output.PubkeyHash))
}
return strings.Join(lines,"\n")
}
wallet包:
package wallet
import (
"bytes"
"crypto/sha256"
"crypto/elliptic"
"crypto/ecdsa"
"crypto/rand"
"log"
"os"
"fmt"
"io/ioutil"
"encoding/gob"
"golang.org/x/crypto/ripemd160"
"go_code/A_golang_blockchain/base58"
)
const version = byte(0x00)
const walletFile = "wallet.dat"
const addressChecksumLen = 4 //对校验位一般取4位
//创建一个钱包结构体,钱包里面只装公钥和私钥
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
//实例化一个钱包
func NewWallet() *Wallet {
//生成秘钥对
private , public := newKeyPair()
wallet := &Wallet{private,public}
return wallet
}
//生成**对函数
func newKeyPair() (ecdsa.PrivateKey,[]byte) {
//返回一个实现了P-256的曲线
curve := elliptic.P256()
//通过椭圆曲线 随机生成一个私钥
private ,err := ecdsa.GenerateKey(curve,rand.Reader)
if err != nil {
log.Panic(err)
}
pubKey := append(private.PublicKey.X.Bytes(),private.PublicKey.Y.Bytes()...)
return *private,pubKey
}
//生成一个地址
func (w Wallet) GetAddress() []byte {
//调用公钥哈希函数,实现RIPEMD160(SHA256(Public Key))
pubKeyHash := HashPubKey(w.PublicKey)
//存储version和公钥哈希的切片
versionedPayload := append([]byte{version},pubKeyHash...)
//调用checksum函数,对上面的切片进行双重哈希后,取出哈希后的切片的前面部分作为检验位的值
checksum := checksum(versionedPayload)
//把校验位加到上面切片后面
fullPayload := append(versionedPayload,checksum...)
//通过base58编码上述切片得到地址
address := base58.Base58Encode(fullPayload)
return address
}
//公钥哈希函数,实现RIPEMD160(SHA256(Public Key))
func HashPubKey(pubKey []byte) []byte {
//先hash公钥
publicSHA256 := sha256.Sum256(pubKey)
//对公钥哈希值做 ripemd160运算
RIPEMD160Hasher := ripemd160.New()
_,err := RIPEMD160Hasher.Write(publicSHA256[:])
if err != nil {
log.Panic(err)
}
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
//校验位checksum,双重哈希运算
func checksum(payload []byte) []byte {
//下面双重哈希payload,在调用中,所引用的payload为(version + Pub Key Hash)
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
//addressChecksumLen代表保留校验位长度
return secondSHA[:addressChecksumLen]
}
//判断输入的地址是否有效,主要是检查后面的校验位是否正确
func ValidateAddress(address string) bool {
//解码base58编码过的地址
pubKeyHash := base58.Base58Decode([]byte(address))
//拆分pubKeyHash,pubKeyHash组成形式为:(一个字节的version) + (Public key hash) + (Checksum)
actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:]
version := pubKeyHash[0]
pubKeyHash = pubKeyHash[1:len(pubKeyHash)-addressChecksumLen]
targetChecksum := checksum(append([]byte{version},pubKeyHash...))
//比较拆分出的校验位与计算出的目标校验位是否相等
return bytes.Compare(actualChecksum,targetChecksum) == 0
}
//创建一个钱包集合的结构体
type Wallets struct {
Wallets map[string]*Wallet
}
// 实例化一个钱包集合,
func NewWallets() (*Wallets, error) {
wallets := Wallets{}
wallets.Wallets = make(map[string]*Wallet)
err := wallets.LoadFromFile()
return &wallets, err
}
// 将 Wallet 添加进 Wallets
func (ws *Wallets) CreateWallet() string {
wallet := NewWallet()
address := fmt.Sprintf("%s", wallet.GetAddress())
ws.Wallets[address] = wallet
return address
}
// 得到存储在wallets里的地址
func (ws *Wallets) GetAddresses() []string {
var addresses []string
for address := range ws.Wallets {
addresses = append(addresses, address)
}
return addresses
}
// 通过地址返回出钱包
func (ws Wallets) GetWallet(address string) Wallet {
return *ws.Wallets[address]
}
// 从文件中加载钱包s
func (ws *Wallets) LoadFromFile() error {
if _, err := os.Stat(walletFile); os.IsNotExist(err) {
return err
}
fileContent, err := ioutil.ReadFile(walletFile)
if err != nil {
log.Panic(err)
}
var wallets Wallets
gob.Register(elliptic.P256())
decoder := gob.NewDecoder(bytes.NewReader(fileContent))
err = decoder.Decode(&wallets)
if err != nil {
log.Panic(err)
}
ws.Wallets = wallets.Wallets
return nil
}
// 将钱包s保存到文件
func (ws Wallets) SaveToFile() {
var content bytes.Buffer
gob.Register(elliptic.P256())
encoder := gob.NewEncoder(&content)
err := encoder.Encode(ws)
if err != nil {
log.Panic(err)
}
err = ioutil.WriteFile(walletFile, content.Bytes(), 0644)
if err != nil {
log.Panic(err)
}
}
CLI包:
package CLI
import (
"go_code/A_golang_blockchain/base58"
"go_code/A_golang_blockchain/transaction"
"fmt"
"os"
"flag"
"go_code/A_golang_blockchain/blockchain"
"go_code/A_golang_blockchain/pow"
"go_code/A_golang_blockchain/wallet"
"strconv"
"log"
)
//首先我们想要拥有这些命令 1.加入区块命令 2.打印区块链命令
//创建一个CLI结构体
type CLI struct {
//BC *blockchain.Blockchain
}
//加入输入格式错误信息提示
func (cli *CLI) printUsage() {
fmt.Println("Usage:")
// fmt.Println(" getbalance -address ADDRESS 得到该地址的余额")
fmt.Println(" createblockchain -address ADDRESS 创建一条链并且该地址会得到狗头金")
fmt.Println(" createwallet - 创建一个钱包,里面放着一对秘钥")
fmt.Println(" getbalance -address ADDRESS 得到该地址的余额")
fmt.Println(" listaddresses - Lists all addresses from the wallet file")
fmt.Println(" printchain - 打印链")
fmt.Println(" send -from FROM -to TO -amount AMOUNT 地址from发送amount的币给地址to")
}
//判断命令行参数,如果没有输入参数则显示提示信息
func (cli *CLI) validateArgs() {
if len(os.Args) < 2 {
cli.printUsage()
os.Exit(1)
}
}
// //加入区块函数调用
// func (cli *CLI) addBlock(data string) {
// cli.BC.MineBlock(data)
// fmt.Println("成功加入区块...")
// }
//创建一条链
func (cli *CLI) createBlockchain(address string) {
if !wallet.ValidateAddress(address) {
log.Panic("ERROR: Address is not valid")
}
bc := blockchain.NewBlockchain(address)
bc.Db().Close()
fmt.Println("Done!")
}
//创建钱包函数
func (cli *CLI) createWallet() {
wallets, _ := wallet.NewWallets()
address := wallets.CreateWallet()
wallets.SaveToFile()
fmt.Printf("Your new address: %s\n", address)
}
//求账户余额
func (cli *CLI) getBalance(address string) {
if !wallet.ValidateAddress(address) {
log.Panic("ERROR: Address is not valid")
}
bc := blockchain.NewBlockchain(address)
defer bc.Db().Close()
balance := 0
pubKeyHash := base58.Base58Decode([]byte(address))
pubKeyHash = pubKeyHash[1:len(pubKeyHash)-4] //这里的4是校验位字节数,这里就不在其他包调过来了
UTXOs := bc.FindUTXO(pubKeyHash)
//遍历UTXOs中的交易输出out,得到输出字段out.Value,求出余额
for _,out := range UTXOs {
balance += out.Value
}
fmt.Printf("Balance of '%s':%d\n",address,balance)
}
//列出地址名单,钱包集合中的地址有哪些
func (cli *CLI) listAddresses() {
wallets, err := wallet.NewWallets()
if err != nil {
log.Panic(err)
}
addresses := wallets.GetAddresses()
for _, address := range addresses {
fmt.Println(address)
}
}
//打印区块链函数调用
func (cli *CLI) printChain() {
//实例化一条链
bc := blockchain.NewBlockchain("") //因为已经有了链,不会重新创建链,所以接收的address设置为空
defer bc.Db().Close()
//这里需要用到迭代区块链的思想
//创建一个迭代器
bci := bc.Iterator()
for {
block := bci.Next() //从顶端区块向前面的区块迭代
fmt.Printf("------======= 区块 %x ============\n", block.Hash)
fmt.Printf("时间戳:%v\n",block.Timestamp)
fmt.Printf("PrevHash:%x\n",block.PrevBlockHash)
//fmt.Printf("Data:%s\n",block.Data)
//fmt.Printf("Hash:%x\n",block.Hash)
//验证当前区块的pow
pow := pow.NewProofOfWork(block)
boolen := pow.Validate()
fmt.Printf("POW is %s\n",strconv.FormatBool(boolen))
for _,tx := range block.Transactions {
transaction := (*tx).String()
fmt.Printf("%s\n",transaction)
}
fmt.Printf("\n\n")
if len(block.PrevBlockHash) == 0 {
break
}
}
}
//send方法
func (cli *CLI) send(from,to string,amount int) {
if !wallet.ValidateAddress(from) {
log.Panic("ERROR: Address is not valid")
}
if !wallet.ValidateAddress(to) {
log.Panic("ERROR: Address is not valid")
}
bc := blockchain.NewBlockchain(from)
defer bc.Db().Close()
tx := blockchain.NewUTXOTransaction(from,to,amount,bc)
//挖出一个包含该交易的区块,此时区块只有这一个交易
bc.MineBlock([]*transaction.Transaction{tx})
fmt.Println("发送成功...")
}
//入口函数
func (cli *CLI) Run() {
//判断命令行输入参数的个数,如果没有输入任何参数则打印提示输入参数信息
cli.validateArgs()
//实例化flag集合
getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError)
createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError)
listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError)
sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
//注册flag标志符
getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for")
createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to")
sendFrom := sendCmd.String("from", "", "Source wallet address")
sendTo := sendCmd.String("to", "", "Destination wallet address")
sendAmount := sendCmd.Int("amount", 0, "Amount to send")
switch os.Args[1] { //os.Args为一个保存输入命令的切片
case "getbalance":
err := getBalanceCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "createblockchain":
err := createBlockchainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "createwallet":
err := createWalletCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "listaddresses":
err := listAddressesCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "printchain":
err := printChainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "send":
err := sendCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default:
cli.printUsage()
os.Exit(1)
}
//进入被解析出的命令,进一步操作
if getBalanceCmd.Parsed() {
if *getBalanceAddress == "" {
getBalanceCmd.Usage()
os.Exit(1)
}
cli.getBalance(*getBalanceAddress)
}
if createBlockchainCmd.Parsed() {
if *createBlockchainAddress == "" {
createBlockchainCmd.Usage()
os.Exit(1)
}
cli.createBlockchain(*createBlockchainAddress)
}
if createWalletCmd.Parsed() {
cli.createWallet()
}
if listAddressesCmd.Parsed() {
cli.listAddresses()
}
if printChainCmd.Parsed() {
cli.printChain()
}
if sendCmd.Parsed() {
if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
sendCmd.Usage()
os.Exit(1)
}
cli.send(*sendFrom, *sendTo, *sendAmount)
}
}
main包:(不变)
package main
import (
"go_code/A_golang_blockchain/CLI"
)
func main() {
// bc := Blockchain.NewBlockchain()
// defer bc.Db().Close()
cli := CLI.CLI{}
cli.Run()
}
最后没有什么想说的了,敲了一天代码,又调试了一晚上bug,就这样吧。