本章节重点部分为 INput output的类定义与使用 交易传输改写 在中间部分

ecdsa签名demo

package main

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

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

	//选择一个椭圆曲线(在elliptic包)
	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))

	//func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) {

	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("r:%x,len(s):%d\n",s.Bytes(),len(s.Bytes()))
	signature := append(r.Bytes(),s.Bytes()...)

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

	//传输中....: 数据, 签名signature,公钥

	//在接收端从中把r和s切出来
	var r1 big.Int
	var s1 big.Int
	r1Data := signature[:len(signature)/2]
	s1Data := signature[len(signature)/2:]

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

	fmt.Printf("r1 : %x, len(r1): %d\n", r1.Bytes(), len(r1.Bytes()))
	fmt.Printf("s1 : %x, len(s1): %d\n", s1.Bytes(), len(s1.Bytes()))

	//func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool {
	res := ecdsa.Verify(&pubKey,dataHash[:],&r1,&s1)
	fmt.Printf("res:%v\n",res)
}

生成新的比特币地址

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

地址生成规则如下:

定义钱包结构

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

type WalletKeyPair struct {
	PrivateKey *ecdsa.PrivateKey

	//	type PublicKey struct {
	//	elliptic.Curve
	//	X, Y *big.Int
	//}

	//我们可以将公钥的X,Y进行字节流拼接后传输,这样在对端再进行切割还原,好处是可以方便后面的编码
	PublicKey []byte
}

func NewWalletKeyPair() *WalletKeyPair {

	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

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

	publicKeyRaw := privateKey.PublicKey

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

根据公钥获取地址

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

	//创建一个hash160对象
	//向hash160中write数据
	//做哈希运算

	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[:])

	//4字节校验码
	checksum := second[0:4]

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

	address := base58.Encode(payload)

	return address
}

获取钱包实例

//创建Wallets, 返回Wallets的实例
func NewWallets() *Wallets {
	var ws Wallets

	ws.WalletsMap = make(map[string]*WalletKeyPair)
	//1. 把所有的钱包从本地加载出来
	//TODO

	//2. 把实例返回
	return &ws
}

创建新的钱包

//这个Wallets是对外的,WalletKeyPair是对内的
//Wallets调用WalletKeypPair

func (ws *Wallets) CreateWallet() string {
	//滴啊用NewWalletKeyPair
	wallet := NewWalletKeyPair()
	//将返回的walletKeypair添加到WalletMap中
	address := wallet.GetAddress()

	ws.WalletsMap[address] = wallet
	//
	//保存到本地文件
	//TODO

	return address
}

调用

func (cli *CLI) CreateWallet() {
	ws := NewWallets()
	address := ws.CreateWallet()

	fmt.Printf("新的钱包地址为: %s\n", address)
}

保存钱包到文件

注意两个知识点:

1. gob.Register:对于结构中的interface类型,需要明确类型 在gob进行注册 编码解码时都需要
2. iotutil.WriteFile实现文件写入

//保存钱包到文件
func (ws *Wallets) SaveToFile() bool {

	var buffer bytes.Buffer

	//将接口类型明确注册一下,否则gob编码失败!
	gob.Register(elliptic.P256())

	encoder := gob.NewEncoder(&buffer)

	err := encoder.Encode(ws)

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

	content := buffer.Bytes()

	//func WriteFile(filename string, data []byte, perm os.FileMode) error {
	err = ioutil.WriteFile(WalletName, content, 0600)
	if err != nil {
		fmt.Printf("钱包创建失败!\n")
		return false
	}

	return true
}

加载钱包
1. ioutil.ReadFile
2. 别忘了校验解码结果

func (ws *Wallets) LoadFromFile() bool {
	//判断文件是否存在
	if !IsFileExist(WalletName) {
		fmt.Printf("钱包文件不存在,准备创建!\n")
		return true
	}

	//读取文件
	//func ReadFile(filename string) ([]byte, error) {
	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 {
		fmt.Printf("err : %v\n", err)
		return false
	}

	//赋值给ws
	ws.WalletsMap = wallets.WalletsMap

	return true
}

遍历钱包,打印所有地址

func (ws *Wallets) ListAddress() []string {
	//遍历ws.WalletsMap结构返回key即可

	var addresses []string

	for address, _ := range ws.WalletsMap {
		addresses = append(addresses, address)
	}

	return addresses
}

在commands.go中调用

func (cli *CLI) ListAddresses() {
	ws := NewWallets()

	addresses := ws.ListAddress()
	for _, address := range addresses {
		fmt.Printf("address : %s\n", address)
	}
}

重点部分

进行改写

改写TXInput和TXOutput

type TXInput struct {
	TXID    []byte //交易id

	Index   int64  //output的索引
	//Address string //解锁脚本,先使用地址来模拟

	Signature []byte //交易签名

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

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

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

地址可以反推出公钥哈希 并且附给 TXOutput 中的 PubKeyHash []byte 在转账给地址时需要先反推出公钥哈希

TXOutput提供Lock方法
给定地址,得到地址的公钥哈希,锁定这个output

//给定转账地址,得到这个地址的公钥哈希,完成对output的锁定
func (output *TXOutput) Lock(address string) {

	//address -> public key hash
	//25字节
	decodeInfo := base58.Decode(address)

	pubKeyHash := decodeInfo[1:len(decodeInfo)-4]

	output.PubKeyHash = pubKeyHash
}

创建TXOutput
由于创建过程需要调用Lock方法,所以我们自顶一个NewTXOutput方法

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

	output.Lock(address)

	return output
}

重点部分

多参照下面的图,只是把Address地址改变了 逻辑并没用太大的改变 在如下改写代码时候也应该对照看这两个图对应,以免混淆

input中存储公钥 做哈希处理后 和output种的公钥哈希进行比较

在获取余额这个过程,不要打开钱包,因为有可能查看余额的人不是地址本人 [通过地址反推出公钥哈希即可]


改写NewCoinbaseTx

将奖励值定义为常量 const reward = 12.5
func NewCoinbaseTx(miner string, data string) *Transaction {

	//我们在后面的程序中,需要识别一个交易是否为coinbase,所以我们需要设置一些特殊的值,用于判断
	inputs := []TXInput{TXInput{nil, -1, nil, []byte(data)}}
	//outputs := []TXOutput{TXOutput{12.5, miner}}

	output := NewTXOutput(reward, miner)
	outputs := []TXOutput{output}

	tx := Transaction{nil, inputs, outputs}
	tx.SetTXID()

	return &tx
}

改写NewTransaction

func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
	//1. 打开钱包
	ws := NewWallets()

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

	if wallet == nil {
		fmt.Printf("%s 的私钥不存在,交易创建失败!\n", from)
		return nil
	}

	//2. 获取公钥,私钥
	//privateKey := wallet.PrivateKey //目前使用不到,步骤三签名时使用
	publickKey := wallet.PublicKey

	pubKeyHash := hashPubkHash(from)

	utxos := make(map[string][]int64) //标识能用的utxo
	var resValue float64              //这些utxo存储的金额
	//假如李四转赵六4,返回的信息为:
	//utxos[0x333] = int64{0, 1}
	//resValue : 5

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

	//2. 如果找到钱不足以转账,创建交易失败。
	if resValue < amount { fmt.Printf("余额不足,交易失败!\n") return nil } var inputs []TXInput var outputs []TXOutput //3. 将outputs转成inputs for txid /*0x333*/ , indexes := range utxos { for _, i /*0, 1*/ := range indexes { input := TXInput{[]byte(txid), i, nil, publickKey} inputs = append(inputs, input) } } //4. 创建输出,创建一个属于收款人的output //output := TXOutput{amount, to} output := NewTXOutput(amount, to) outputs = append(outputs, output) //5. 如果有找零,创建属于付款人output if resValue > amount {
		//output1 := TXOutput{resValue - amount, from}
		output1 := NewTXOutput(resValue-amount, from)
		outputs = append(outputs, output1)
	}

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

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

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

改写GetAddress函数

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中write数据
	//做哈希运算

	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[:])

	//4字节校验码
	checksum := second[0:4]

	return checksum
}

改写GetBalance

func (bc *BlockChain) GetBalance(address string) {

	//这个过程,不要打开钱包,因为有可能查看余额的人不是地址本人 [通过地址反推出公钥哈希即可]
	decodeInfo := base58.Decode(address)

	pubKeyHash := decodeInfo[1:len(decodeInfo)-4]

	utxoinfos := bc.FindMyUtoxs(pubKeyHash)

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

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

【重要】地址有效性校验
如果不进行地址有效性校验 在查询余额转账 如果把结尾的字母改了 不能准确识别 解码后取前面字符 这也是checksum校验码的作用

在转账发送方地址 接收方地址、查询余额使用到地址的地方都应该进行地址有效性校验

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:]

	return bytes.Equal(checksum1, checksum2)
}