go语言实现简易比特系统(八):总结

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在这里插入图片描述

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

最后

欢迎大家加入到区块链交流学习群,群里有很多大佬,并且有很多学习资源可以自行下载。祝愿大家都能在区块链领域有所成就。
在这里插入图片描述

最后推荐一位大佬的公众号,欢迎关注哦:区块链技术栈

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值