一、添加钱包

(1)定义钱包结构体

type Wallet struct {
	PrivateKey ecdsa.PrivateKey // 私钥
	PublicKey  []byte // 保存了公钥的X和Y
}

由于不想在交易中传递公钥本身,提供公钥在网络上的传输效率,所以我们将公钥拆分成两个[]byte变量,然后将他们拼接成一个[]byte后存放在公钥字段中。

在verify之前一直把这个拼接的byte数组当成公钥,在verifty时将它再拆成X, Y 两个big.Int 类型的数据,然后拼装成真实的公钥。

(2)创建钱包

// 创建方法
func NewWallet() *Wallet {
	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic("ecdsa.GenerateKey err!")
	}
	publicKeyOrigin := privateKey.PublicKey
	publicKey := append(publicKeyOrigin.X.Bytes(), publicKeyOrigin.Y.Bytes()...)
	return &Wallet{*privateKey, publicKey}
}

(3)修改Run方法,添加case newWallet条件判断。

func (cli *Cli) Run() {
	args := os.Args
	if len(args) < 2 {
		fmt.Println(Usage)
		return
	}
	// 获取命令名称
	command := args[1]
	switch command {
		...
        ...
		case "newWallet":
			cli.CreateWallet()
		default:
			fmt.Println(Usage)
	}
}

(4)添加CreateWallet函数

// 创建钱包
func (cli *Cli) CreateWallet() {
	wallet := NewWallet()
	fmt.Printf("private key : %x\n", wallet.PrivateKey)
	fmt.Printf("public  key : %x\n", wallet.PublicKey)
}

 

二、生成地址

(1)定义GetAddress函数

// 生成钱包地址
func (wallet Wallet) GetAddress() string {
	// 创建公钥哈希
	publicKeyBytes := sha256.Sum256(wallet.PublicKey)
	ripemd := ripemd160.New()
	_, err := ripemd.Write(publicKeyBytes[:])
	if err != nil {
		panic("ripemd.Write err!")
	}
	publicKeyHash := ripemd.Sum(nil)
	// 创建payload
	payload := append([]byte{00}, publicKeyHash...)
	// 创建checkSum
	hash1 := sha256.Sum256(payload)
	checkSum := sha256.Sum256(hash1[:])
	// 创建checkCode
	checkCode := append(payload, checkSum[:4]...)
	// 生成address
	address := base58.Encode(checkCode)
	return address
}

(2)修改CreateWallet函数,打印地址。

// 创建钱包
func (cli *Cli) CreateWallet() {
	wallet := NewWallet()
	fmt.Printf("private key : %x\n", wallet.PrivateKey)
	fmt.Printf("public  key : %x\n", wallet.PublicKey)
	fmt.Printf("address : %s\n", wallet.GetAddress())
}

 

三、创建钱包容器

比特币客户端可以生成无数多个秘钥对,将它们持久化,保存在一个wallets.bat文件中。

(1)定义钱包容器

type Wallets struct {
	Wallets map[string]*Wallet
}

(2)提供创建方法

// 创建方法
func NewWallets() *Wallets {
	var ws Wallets
	ws.Wallets = make(map[string]*Wallet)
	return &ws
}

(3)创建钱包

// 创建钱包
func (ws *Wallets) CreateWallet() string {
	wallet := NewWallet()
	address := wallet.GetAddress()
	ws.Wallets[address] = wallet
	return address
}

(4)修改Cli对象的CreateWallet方法

// 创建钱包
func (cli *Cli) CreateWallet() {
	/*wallet := NewWallet()
	fmt.Printf("private key : %x\n", wallet.PrivateKey)
	fmt.Printf("public  key : %x\n", wallet.PublicKey)
	fmt.Printf("address : %s\n", wallet.GetAddress())*/

	ws := NewWallets()
	address := ws.CreateWallet()
	fmt.Printf("address  : %s\n", address)
}

(5)保存钱包到本地

// 保存钱包数据到本地
func (ws *Wallets) SaveWallets() {
	// 1.对Wallets加密
	var buffer bytes.Buffer
	// 注册接口类型, 否则会出现“gob: type not registered for interface: elliptic.p256Curve”
	gob.Register(elliptic.P256())
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(&ws)
	if err != nil {
		panic(err)
	}
	// 2.把加密后数据写入文件中
	err = ioutil.WriteFile(walletFile, buffer.Bytes(), 0644)
	if err != nil {
		panic("ioutil.WriteFile err!")
	}
}

注意:如果Encode/Decode类型是interface或者struct中某些字段是interface{}的时候,需要在gob中注册interface。

(6)修改CreateWallet函数,调用SaveWallets方法。

// 创建钱包
func (ws *Wallets) CreateWallet() string {
	wallet := NewWallet()
	address := wallet.GetAddress()
	ws.Wallets[address] = wallet
	// 保存钱包到本地
	ws.SaveWallets()
	return address
}

(7)从本地加载钱包

// 从本地读取钱包数据
func (ws *Wallets) LoadWallets() {
	// 1.校验钱包文件是否存在
	_, err := os.Stat(walletFile)
	if os.IsNotExist(err) {
		return
	}
	// 2.读取文件数据
	data, err := ioutil.ReadFile(walletFile)
	if err != nil {
		panic("ioutil.ReadFile err!")
	}
	// 3.解密
	gob.Register(elliptic.P256())
	decoder := gob.NewDecoder(bytes.NewReader(data))
	var wallets Wallets
	err = decoder.Decode(&wallets)
	if err != nil {
		panic(err)
	}
	// 4.把数据设置到wallets的Wallets字段
	ws.Wallets = wallets.Wallets
}

(8)修改NewWallets函数,调用LoadWallets方法。

// 创建方法
func NewWallets() *Wallets {
	var ws Wallets
	ws.Wallets = make(map[string]*Wallet)
	ws.LoadWallets()
	return &ws
}

(9)获取所有地址

// 获取所有地址
func (ws *Wallets) GetAllAddress() []string {
	var addresses []string
	for address := range ws.Wallets {
		addresses = append(addresses, address)
	}
	return addresses
}

(10)添加命令

// 列出所有钱包地址
func (cli *Cli) ListAddress() {
	ws := NewWallets()
	addresses := ws.GetAllAddress()
	for _, addr := range addresses {
		fmt.Printf("地址:%s\n", addr)
	}
}

(11)修改Run函数,添加case listAddress条件判断。

func (cli *Cli) Run() {
	args := os.Args
	if len(args) < 2 {
		fmt.Println(Usage)
		return
	}
	// 获取命令名称
	command := args[1]
	switch command {
		...
        ...
		case "listAddresses":
			cli.ListAddress()
		default:
			fmt.Println(Usage)
	}
}

 

四、修改交易结构

(1)修改TXInput和TXOutput字段。

type TXInput struct {
	TXID        []byte // 引用输出的交易ID
	OutputIndex int64  // 引用输出的索引
	//ScriptSig   string // 解锁脚本(实际上这里应该是签名和公钥)
	Signature []byte // 签名
	PubKey    []byte // 公钥
}

type TXOutput struct {
	Value float64 // 金额
	//ScriptPubKey string  // 锁定脚本(公钥哈希)
	PubKeyHash []byte // 公钥哈希
}

(2)创建锁定脚本

// 锁定脚本(设置output的公钥哈希)
func (output *TXOutput) Lock(address string) {
	// 1.使用base58加密,得到checkCode
	checkCode := base58.Decode(address)
	// 2.截取字节数组,去掉左边1位,右边4位
	publicKeyHash := checkCode[1 : len(checkCode)-4]
	// 3.设置公钥哈希
	output.PubKeyHash = publicKeyHash
}

(3)提供NewTXOuptput函数

// 创建Output
// 参数一:接收金额
// 参数二:接收帐号地址
func NewTXOutput(value float64, address string) *TXOutput {
	output := TXOutput{
		Value: value,
	}
	output.Lock(address)
	return &output
}

(4)修改NewCoinBase函数,使用NewTXOutput方法创建output对象。

// 创建挖矿交易
// 参数一:矿工地址
// 参数二:矿工附加信息
func NewCoinBaseTx(address string, data string) *Transaction {
	input := TXInput{nil, -1, nil, []byte(data)}
	//output := TXOutput{reward, address}
	output := NewTXOutput(reward, address)
	tx := Transaction{nil, []TXInput{input}, []TXOutput{*output}}
	tx.SetHash()
	return &tx
}

(5)修改NewTransaction函数。

创建新的交易一定是要使用钱包里面的公钥私钥,具体步骤:

1)打开钱包,根据创建人的address找到对应的钱包;

2)查找可用的utxo,注意此时传递的不再是地址,而是地址的公钥哈希;

3)创建输入和输出;

4)使用私钥对交易进行签名;

// 创建交易
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
	// 创建Wallets
	ws := NewWallets()
	// 获取转账人的钱包
	wallet := ws.Wallets[from]
	if wallet == nil {
		fmt.Printf("本地没有 %s 的钱包,无法创建交易\n", from)
		return nil
	}
	pubKey := wallet.PublicKey
	//priKey := wallet.PrivateKey // 签名时候用到

	// 解析公钥,得到公钥哈希
	pubKeyHash := HashPubKey(pubKey)

	// 1.找到最优的utxos
	utxos, total := bc.FindNeedUTXOs(pubKeyHash, amount)
	// 2.检查余额是否足够
	if total < amount {
		fmt.Println("余额不足!")
		return nil
	}
	// 3.如果余额足够,那么创建新的区块
	var inputs []TXInput
	var outputs []TXOutput

	for txId, outputIndexs := range utxos {
		for _, outputIndex := range outputIndexs {
			input := TXInput{[]byte(txId), outputIndex, nil, pubKey}
			inputs = append(inputs, input)
		}
	}

	//output := TXOutput{amount, to}
	output := NewTXOutput(amount, to)
	outputs = append(outputs, *output)
	// 找零
	if total > amount {
		//output = TXOutput{total - amount, from}
		output = NewTXOutput(total-amount, from)
		outputs = append(outputs, *output)
	}
	// 4.创建Transaction
	tx := Transaction{nil, inputs, outputs}
	tx.SetHash()

	return &tx
}

(6)提供HashPubKey函数

// 从公钥生成公钥哈希
func HashPubKey(pubKey []byte) []byte {
	hash := sha256.Sum256(pubKey)
	ripemd160 := ripemd160.New()
	_, err := ripemd160.Write(hash[:])
	if err != nil {
		panic("ripemd160.Write err!")
	}
	publicKeyHash := ripemd160.Sum(nil)
	return publicKeyHash
}

(7)修改FindNeedUTXOs函数,把参数address替换成pubKeyHash。

// 查找最合理的utxo
func (bc *BlockChain) FindNeedUTXOs(pubKeyHash []byte, amount float64) (map[string][]int64, float64) {
	txs := bc.FindUTXOTransaction(pubKeyHash)
	needUTXOs := make(map[string][]int64) // 保存最合理的utxo
	var total float64                     // 最合理utxo的总金额
OUTPUT_TAG:
	for _, tx := range txs {
		for i, output := range tx.Outputs {
			//if output.ScriptPubKey == address {
			if bytes.Equal(output.PubKeyHash, pubKeyHash) {
				if total < amount {
					total += output.Value
					needUTXOs[string(tx.TXID)] = append(needUTXOs[string(tx.TXID)], int64(i))
				} else {
					break OUTPUT_TAG
				}
			}
		}
	}
	return needUTXOs, total
}

(8)修改FindUTXOTransaction函数,把参数address替换成pubKeyHash。

// 查询所有UTXO交易
// 参数:账户地址
func (bc *BlockChain) FindUTXOTransaction(pubKeyHash []byte) []Transaction {
	var txs []Transaction
	spentUTXOs := make(map[string][]int64) // 已消费的UTXO, Key代表交易地址,Value为引用output的索引
	// 遍历所有区块
	itr := bc.NewIterator()
	for {
		block := itr.Prev()
		// 遍历交易
		for _, tx := range block.Data {
			// 遍历output
			OUTPUT_TAG:
			for i, output := range tx.Outputs {
				if spentUTXOs[string(tx.TXID)] != nil {
					// 获取消费过的utxo的索引
					indexs := spentUTXOs[string(tx.TXID)]
					// 循环比较当前output的索引是否在indexs中存在,如果存在就代表该output已经被消费
					for _, index := range indexs {
						if index == int64(i) {
							continue OUTPUT_TAG
						}
					}
				}
				// 如果output的ScriptPubKey等于address,代表该output是属于adddres指定的账户
				//if output.ScriptPubKey == address {
				if bytes.Equal(output.PubKeyHash, pubKeyHash) {
					txs = append(txs, *tx)
				}
			}

			// 遍历input
			if !tx.isCoinBase() {
				for _, input := range tx.Inputs {
					// 如果input的ScriptSig等于address,就代表该input是属于指定address的账户
					//if input.ScriptSig == address {
					if bytes.Equal(HashPubKey(input.PubKey), pubKeyHash) {
						spentUTXOs[string(input.TXID)] = append(spentUTXOs[string(input.TXID)], int64(input.OutputIndex))
					}
				}
			}
		}
		if (len(block.PrevHash) == 0) {
			return txs
		}
	}
}

(9)修改FindUTXOs函数,把参数address替换成pubKeyHash。

// 查找所有的utxo
func (bc *BlockChain) FindUTXOs(pubKeyHash []byte) []TXOutput {
	txs := bc.FindUTXOTransaction(pubKeyHash)
	var outputs []TXOutput
	// 遍历所有utxos交易
	for _, tx := range txs {
		// 遍历utxo交易的所有output
		for _, output := range tx.Outputs {
			// 如果output的ScriptPubKey等于address,就代表该output是我们要找到output
			//if output.ScriptPubKey == address {
			if bytes.Equal(output.PubKeyHash, pubKeyHash) {
				outputs = append(outputs, output)
			}
		}
	}
	return outputs
}

(10)修改GetBalance函数,根据address反推出公钥哈希。

// 查询余额
func (cli *Cli) GetBalance(address string) {
    isOk := checkAddress(address)
	if !isOk {
		fmt.Println("地址无效")
		return
	}
	// 由地址反推出公钥哈希
	checkCode := base58.Decode(address)
	pubKeyHash := checkCode[1: len(checkCode)-4]
	// 查询所有未消费的output
	utxos := cli.bc.FindUTXOs(pubKeyHash)
	var total float64
	for _, output := range utxos {
		total += output.Value
	}
	fmt.Printf("%s的余额: %f\n", address, total)
}

(11)定义checkAddress函数,校验地址有效性。

// 校验地址
func checkAddress(address string) bool {
	checkCode := base58.Decode(address)
	// 获取payload
	payload := checkCode[:len(checkCode)-4]
	// 获取checkSum
	checkSum := checkCode[len(checkCode)-4:]
	// 对payload执行两次sha256运算,得到新的checkSum
	hash := sha256.Sum256(payload)
	targetCheckSum := sha256.Sum256(hash[:])
	// 比较两个checkSum是否相等,如果相等代表地址有效
	return bytes.Equal(checkSum, targetCheckSum[:4])
}

 

五、数字签名和认证

1. 功能描述

签名需要什么?1)想要签名的数据;2)私钥;

验证需要什么?2)想要签名的数据;2)签名;3)公钥;

签名数据应该包含:

   1)欲使用utxo中的pubKeyHash(付款人);

   2)新生成utxo中的pubKeyHash(收款人);

   3)转账金额

最后,把签好的数据放在每一个input的sig中。

由于每一笔交易都可能引用多个utxo(存在于多条交易中),所以我们要遍历所有的引用交易,并对它们逐个签名。

什么时候签名?

交易创建完成之后,在写入区块之前,我们要对其进行签名。

什么时候验证?矿工挖矿前先进行验证。

2. 功能实现

(1)添加Sign函数

为了便于我们遍历账本,签名函数由Transaction提供,但是签名动作由blockChain来实现,最后在SignTransaction内部调用Sign函数。

// 签名
// 参数一:私钥
// 参数二:要签名的交易
func (tx *Transaction) Sign(priKey *ecdsa.PrivateKey, prevTXs map[string]Transaction) {
	if tx.isCoinBase() {
		return
	}
	// 1.把tx的副本,然后把input中的Signature和PublicKey字段置空
	// 2.遍历txCopy中的所有inputs,然后得到引用output的公钥哈希,并保存在input的PublicKey中
	// 3.为txCopy生成Id值(当前input所引用output的公钥哈希+outputs),该txId就是我们要签名的最终数据
	// 4.执行签名动作,得到r和s
	// 5.把r和s进行拼接,然后放入到我们所签名的input的Signature字段中
	txCopy := tx.TrimmedCopy()
	for i, input := range txCopy.Inputs {
		prevTx := prevTXs[string(input.TXID)]
		if len(prevTx.TXID) == 0 {
			panic("引用交易无效")
		}
		output := prevTx.Outputs[input.OutputIndex]
		txCopy.Inputs[i].PubKey = output.PubKeyHash
		txCopy.SetHash()
		txCopy.Inputs[i].PubKey = nil
		// 得到签名数据
		signData := txCopy.TXID
		r, s, err := ecdsa.Sign(rand.Reader, priKey, signData)
		if err != nil {
			panic(err)
		}
		signature := append(r.Bytes(), s.Bytes()...)
		tx.Inputs[i].Signature = signature
	}
}

(2)定义TrimmedCopy函数,该函数用于创建交易的副本,但是input的签名和公钥先置空。

func (tx *Transaction) TrimmedCopy() Transaction {
	var inputs []TXInput
	var outputs []TXOutput
	// 赋值inputs
	for _, input := range tx.Inputs {
		// 1.构建一个新的input,该input与遍历出来的input相同,但是Signature和PublicKey字段留空
		// 2.把新的input添加到inputs集合中
		inputs = append(inputs, TXInput{input.TXID, input.OutputIndex, nil, nil})
	}
	// 复制outputs
	for _, output := range tx.Outputs {
		outputs = append(outputs, output)
	}
	return Transaction{tx.TXID, inputs, outputs}
}

(3)定义SignTransaction函数

// 对交易添加签名
// 注意:需要对每一个input都要添加签名
// 签名所需:私钥 + 数据
func (bc *BlockChain) SignTransaction(tx Transaction, priKey ecdsa.PrivateKey) {
	// Key为交易Id, Value为对应的交易
	prevTXs := make(map[string]Transaction)
	// 遍历所有input,找出所有input对应的交易
	for _, input := range tx.Inputs {
		// 根据交易ID查找Transaction
		prevTX, err := bc.FindTransaction(input.TXID)
		if err != nil {
			panic(err)
		}
		prevTXs[string(input.TXID)] = prevTX
	}
	// 签名
	tx.Sign(&priKey, prevTXs)
}

(4)定义FindTransaction函数

// 根据交易ID查找
func (bc *BlockChain) FindTransaction(txId []byte) (Transaction, error) {
	itr := bc.NewIterator()
	for {
		block := itr.Prev()
		for _, tx := range block.Data {
			if bytes.Equal(tx.TXID, txId) {
				return *tx, nil
			}
		}
		if len(block.PrevHash) == 0 {
			break
		}
	}
	return Transaction{}, errors.New("Transactioin not found")
}

(5)修改NewTransaction函数,调用SignTransaction方法进行数字签名。

// 创建交易
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
	// 创建Wallets
	ws := NewWallets()
	// 获取转账人的钱包
	wallet := ws.Wallets[from]
	if wallet == nil {
		fmt.Printf("本地没有 %s 的钱包,无法创建交易\n", from)
		return nil
	}
	pubKey := wallet.PublicKey
	priKey := wallet.PrivateKey // 签名时候用到

	.....


	// 5. 签名
	bc.SignTransaction(tx, priKey)

	return &tx
}

(6)添加认证函数

// 校验
func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool {
	if tx.isCoinBase() {
		return true
	}
	prevTXs := make(map[string]Transaction)
	for _, input := range tx.Inputs {
		prevTX, err := bc.FindTransaction(input.TXID)
		if err != nil {
			log.Panic(err)
		}
		prevTXs[string(prevTX.TXID)] = prevTX
	}
	return tx.Verify(prevTXs)
}

(7)定义Verify函数

// 校验
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
	if tx.isCoinBase() {
		return true
	}
	// 1.准备校验数据
	txCopy := tx.TrimmedCopy()
	for i, input := range tx.Inputs {
		// 根据input的txid找到前交易
		prevTx := prevTXs[string(input.TXID)]
		if len(prevTx.TXID) == 0 {
			panic("引用交易无效!")
		}
		// 得到input所引用output的公钥哈希,并设置到txCopy的input的PublicKey字段中
		txCopy.Inputs[i].PubKey = prevTx.Outputs[input.OutputIndex].PubKeyHash
		// 生成ID
		txCopy.SetHash()
		// 校验数据
		data := txCopy.TXID
		// 2.得到Signature,然后拆分出r和s
		signature := input.Signature
		r := big.Int{}
		s := big.Int{}
		r.SetBytes(signature[:len(signature)/2])
		s.SetBytes(signature[len(signature)/2:])
		// 3.得到公钥,然后拆分出x和y
		pubKey := input.PubKey
		x := big.Int{}
		y := big.Int{}
		x.SetBytes(pubKey[:len(pubKey)/2])
		y.SetBytes(pubKey[len(pubKey)/2:])
		// 构建一个原生的publicKey
		publicKeyOrigin := ecdsa.PublicKey{elliptic.P256(), &x, &y}
		// 4.Verify
		isOk := ecdsa.Verify(&publicKeyOrigin, data, &r, &s)
		if !isOk {
			return false
		}
	}
	return true
}

(7)修改AddBlock函数

当一笔交易发送到对端时,接收方在打包到自己的区块前,需要先对交易进行校验,从而保证

1.持有者花费的确实是自己的钱

2.交易确实是由私钥的持有者发起的

func (bc *BlockChain) AddBlock(txs []*Transaction) {
	// 校验交易(每个交易都需要校验)
	for _, tx := range txs {
		isOk := bc.VerifyTransaction(tx)
		if !isOk {
			fmt.Println("矿工发现无效交易!")
			return
		}
	}

	// 获取最后区块hash
	lastHash := bc.Tail
	// 创建区块
	//block := NewBlock([]byte(data), lastHash)
	block := NewBlock(txs, lastHash)
	// 更新操作
	bc.Db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(bucketName))
		if bucket == nil {
			panic("bucket should not be nil!")
		}
		// 向bolt数据库添加新区块
		bucket.Put(block.Hash, block.Serialize())
		// 更新数据库的last
		bucket.Put([]byte(last), block.Hash)
		// 更新bc.Tail
		bc.Tail = block.Hash
		return nil
	})
}

(8)添加打印命令

func (tx Transaction) String() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TXID))

	for i, input := range tx.Inputs {

		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.OutputIndex))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
		lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.PubKey))
	}

	for i, output := range tx.Outputs {
		lines = append(lines, fmt.Sprintf("     Output %d:", i))
		lines = append(lines, fmt.Sprintf("       Value:  %f", output.Value))
		lines = append(lines, fmt.Sprintf("       Script: %x", output.PubKeyHash))
	}

	return strings.Join(lines, "\n")
}