1. 系统流程图
这是代码要实现的功能
2. 主函数
func main () {
//创建一个区块链, 指定输出地址
bc := NewBlockChain("1KzwEHm9adpgyT3DhDQPX7m99wQ4juXtiw")
//调用命令行命令
cli := CLI{bc}
//处理相应请求
cli.Run()
}
3. 命令行函数
type CLI struct {
bc *BlockChain
}
const Usage = `
printChain 正向打印区块链
printChainR 反向打印区块链
getBalance --address ADDRESS 获取指定地址的余额"
send FROM TO AMONUNT MINER DATA 由FROM转AMOUNT给TO,由MINER挖矿,同时写入DATA
newWallet 创建一个新的钱包(私钥公钥对)
listAddresses 列举所有的钱包地址
`
//根据命令行参数处理对应请求
func (cli *CLI) Run() {
cmd := args[1]
switch cmd {
case "printChain":
//打印区块
fmt.Printf("正向打印区块\n")
cli.PrintBlockChain()
case "printChainR":
//反向打印区块
fmt.Printf("反向打印区块\n")
cli.PrintBlockChainReverse()
case "getBalance":
//获取余额
fmt.Printf("获取余额\n")
if len(args) == 4 && args[2] == "--address" {
address := args[3]
cli.GetBalance(address)
}
case "send":
from := args[2]
to := args[3]
amount, _ := strconv.ParseFloat(os.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("无效命令,请检查!")
fmt.Printf(Usage)
}
}
4. 区块链打印功能
//正向打印
func (cli *CLI)PrintBlockChain(){
cli.bc.Printchain()
fmt.Printf("打印区块完成!\n")
}
//反向打印
func (cli *CLI) PrintBlockChainReverse(){
bc := cli.bc
//创建迭代器
it := bc.NewIterator()
for{
//返回区块并左移
block := it.Next()
for _, tx := range block.Transactions{
fmt.Println(tx)
}
if len(block.PrevHash) == 0 {
fmt.Printf("区块遍历完成!\n")
break
}
}
}
5. 获取账户余额
func (cli *CLI)GetBalance(address string){
//1. 校验地址
if !IsValidAddress(address){
fmt.Printf("地址无效:%s\n", address)
return
}
//2. 生成公钥哈希
pubKeyHash := GetPubKeyFromAddress(address)
//3. 通过公钥哈希找到所有的UTXO
utxos := cli.bc.FindUTXOs(pubKeyHash)
total := 0.0
//4. 遍历UTXO,得到余额
for _, utxo := range utxos{
total += utxo.Value
}
fmt.Printf("\"%s\"的余额为:%f\n", address, total)
}
6. 创建钱包
func(cli *CLI)NewWallet(){
//1.加载本地钱包
ws := NewWallets()
//2.创建新的钱包
address := ws.CreateWallet()
fmt.Printf("地址:%s\n", address)
}
func (ws *Wallets) CreateWallet() string{
wallet := NewWalet()
//3.为新钱包生成地址
address := wallet.NewAdress()
ws.WalletsMap[address] = wallet
//4.保存新钱包到钱包集和本地
ws.savaToFile()
return address
}
7. 获取所有钱包地址
func (cli *CLI) ListAddresses(){
//1.加载本地钱包
ws := NewWallets()
//2.遍历所有地址
addresses := ws.ListAllAddress()
for _, address := range addresses{
fmt.Printf("地址:%s\n", address)
}
}
8. 转账
func (cli *CLI) Send(from, to string, amount float64, miner, data string){
//1. 校验地址
if !IsValidAddress(from){
fmt.Printf("地址无效 from:%s\n", from)
return
}
//2. 创建挖矿交易
coinbase := NewCoinbaseTX(miner, data)
//3. 创建一个普通交易
tx := NewTransaction(from, to, amount, cli.bc)
if tx == nil {
fmt.Printf("无效交易!\n")
return
}
//4. 添加到区块
cli.bc.AddBlock([]*Transaction{coinbase, tx})
fmt.Printf("转账成功!\n")
}
8.1 创建普通交易
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction{
//通过钱包得到公私钥,签名时使用
ws := NewWallets()
wallet := ws.WalletsMap[from]
if wallet == nil {
fmt.Printf("没有找到该地址的钱包,交易创建失败!\n")
return nil
}
pubKey := wallet.PubKey
privateKey := wallet.Private
//得到公钥哈希
pubKeyHash := HashPubKey(pubKey)
//1.找到最合理的UTXO集合
utxos, resValue := bc.FindNeedUTXOs(pubKeyHash, amount)
if resValue < amount{
fmt.Printf("余额不足,交易失败!\n")
return nil
}
var inputs []TXInput
var outputs []TXOutput
//2.创建交易输入,将这些UTXO逐一转成inputs
for id, indexArray := range utxos{
for _, i := range indexArray{
input := TXInput{
Txid: []byte(id),
Index: int64(i),
Signature: nil,
PubKey: pubKey,
}
inputs = append(inputs, input)
}
}
//3.创建交易输出
output := NewTXOutput(amount, to)
outputs= append(outputs, *output)
//4.找零
if resValue > amount{
output = NewTXOutput(resValue - amount, from)
outputs = append(outputs, *output)
}
tx := Transaction{
TXID: []byte{},
TXInputs: inputs,
TXOutputs: outputs,
}
//5.设置交易哈希
tx.SetHash()
//6.对交易签名
bc.SignTransaction(&tx, privateKey)
return &tx
}
8.2 找到合理的UTXO![在这里插入图片描述](https://img-blog.csdnimg.cn/20210410100208610.PNG#pic_center)
func (bc *BlockChain) FindNeedUTXOs(senderPubKeyHash []byte, amount float64) (map[string][]uint64, float64) {
//找到的合理uutxos集合
utxos := make(map[string][]uint64)
//找到的utxos里面包含的总数
var calc float64
//1.获取转账者的所有交易
txs := bc.FindUTXOTransactions(senderPubKeyHash)
for _, tx := range txs {
for i, output := range tx.TXOutputs {
if bytes.Equal(senderPubKeyHash, output.PubKeyHash){
//比较
if calc < amount {
//2.把utxo加进来
utxos[string(tx.TXID)] = append(utxos[string(tx.TXID)], uint64(i))
//统计一下当前utxo的总额
calc += output.Value
if calc >= amount {
fmt.Printf("找到了满足的金额: %f\n", calc)
return utxos, calc
}
} else {
fmt.Printf("不满足转账金额,当前金额:%f, 目标金额:%f\n", calc, amount)
}
}
}
}
return utxos, calc
}
8.3 签名
func (tx *Transaction) Sign(privateKey *ecdsa.PrivateKey, prevTXs map[string]Transaction){
//对每一个input都签名一次,签名的数据是由当前input引用的output的哈希+当前的outputs
if tx.IsCoinbase(){
return
}
//1.创建一个当前交易的副本:txCopy,使用函数:TrimmedCopy:要把Signature和PubKey字段设置为null
txCopy := tx.TrimmedCopy()
//2.循环遍历txCopy的inputs,得到这个input索引的output的公钥哈希
for i, input := range txCopy.TXInputs{
prevTX := prevTXs[string(input.Txid)]
if len(prevTX.TXID) == 0{
log.Panic("引用的交易无效\n")
}
//设置副本输入的公钥
txCopy.TXInputs[i].PubKey = prevTX.TXOutputs[input.Index].PubKeyHash
//3,对拼好的txCopy进行哈希处理,SetHash得到TXID,这个TXID就是要签名的最终数据
txCopy.SetHash()
//还原,以免影响后面的input签名
txCopy.TXInputs[i].PubKey = nil
signDataHash := txCopy.TXID
//4.执行签名动作,得到r,s字节流
r, s, err := ecdsa.Sign(rand.Reader, privateKey, signDataHash)
if err != nil{
log.Panic(err)
}
//5.放到签名的inputs的Signature中
signature := append(r.Bytes(), s.Bytes()...)
tx.TXInputs[i].Signature = signature
}
}
8.4 校验
func (tx *Transaction) Verify (prevTXs map[string]Transaction) bool{
if tx.IsCoinbase(){
return true
}
//1.得到签名的数据
txCopy := tx.TrimmedCopy()
for i, input := range tx.TXInputs{
prevTX := prevTXs[string(input.Txid)]
if len(prevTX.TXID) == 0{
log.Panic("引用的交易无效\n")
}
txCopy.TXInputs[i].PubKey = prevTX.TXOutputs[input.Index].PubKeyHash
txCopy.SetHash()
dataHash := txCopy.TXID
//2.得到signature,反退回r,s
signature := input.Signature
//3拆解PubKey, X,Y得到原生公钥
pubKey := input.PubKey
r := big.Int{}
s := big.Int{}
r.SetBytes(signature[:len(signature)/2])
s.SetBytes(signature[len(signature)/2:])
X := big.Int{}
Y := big.Int{}
//pubKey平均分,前半部分给X,后半部分给Y
X.SetBytes(pubKey[:len(pubKey)/2])
Y.SetBytes(pubKey[len(pubKey)/2:])
pubKeyOrigin := ecdsa.PublicKey{elliptic.P256(), &X, &Y}
//4.Verify
if !ecdsa.Verify(&pubKeyOrigin, dataHash, &r, &s){
return false
}
}
return true
}
说明
源码来自于黑马程序员的区块链教程,本篇文章只是简单的对代码逻辑进行梳理,具体细节可以查看源码。另外,系统中还有很多可以完善的地方,以后有机会的话会继续完整这个系统。
源码地址:https://github.com/intrepidzoro/learn_blockchain/tree/master/go_bitcoin
最后
欢迎大家加入到区块链交流学习群,群里有很多大佬,并且有很多学习资源可以自行下载。祝愿大家都能在区块链领域有所成就。
最后推荐一位大佬的公众号,欢迎关注哦:区块链技术栈