(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")
}