主页 > imtoken在线官网 > 1、比特币交易和传统银行交易有什么区别?
1、比特币交易和传统银行交易有什么区别?
一、比特币交易介绍 1、比特币交易与传统银行交易有什么区别?
传统银行的每个账户都有一个数据库来保存用户信息,比如姓名、卡号、余额等信息。 每产生一笔交易,银行系统都会更新用户账户的余额字段。
比特币的数据库只有交易,没有用户账户信息。 那么比特币系统是如何维护用户的比特币的呢?
与银行系统不同,比特币系统中的每笔交易都基于之前的交易。 用户的比特币分散在不同的交易中。 如果想知道某个钱包地址的余额,需要遍历整个账本,统计属于该地址的交易。
2.变更机制
比如张三钱包里有100个比特币,给李四转了80个比特币。 相当于张三钱包里有一张100元的钞票,给李四转了80元。 那么剩下的20块钱怎么办呢?
答案是比特币系统会扣掉李四的钱,然后把剩下的转给自己。 这就是比特币的变化机制。
三、交接单
比特币系统中没有付款人和收款人,只有输入和输出。 每一个输入对应一个之前别人给你转账时产生的输出。
(1) 多对一转账(众筹)
(2) 一对多转账(红包)
(3) 多对多传输
4. 费用
大多数交易都包含交易费(矿工费),这是为保护网络安全而给予比特币矿工的一种补偿形式。 费用本身也充当一种安全机制,使攻击者在经济上不利地用交易淹没网络。
交易费用是根据交易数据的大小计算的,而不是比特币交易的价值。
交易数据结构没有交易费用字段。 相反比特币的数据库在哪里比特币的数据库在哪里,交易费用是指输入和输出之间的差额。 从所有输入中扣除所有输出后的多余部分由矿工收取作为汽油费。
计算公式:交易手续费=所有输入-所有输出
5. 比特币交易结构
一笔比特币交易由两部分组成:输入和输出。
(1) 交易输出
比如张三给李四转账,比特币系统会产生一个输出,里面包含两个东西:“转账金额”和“锁定脚本”。
转账金额:比特币数量
Locking script:李四的公钥哈希(可以理解为用李四的公钥加密,只有李四的私钥才能解密)
(2) 交易输入
与输出对应的是输入,每个输入都来自一个输出。 比如李四给王五转账,系统会创建一个输入,包括:
引用交易的ID:在哪笔交易中,需要从张三->李四转账的交易ID
引用事务的 OUTPUT 的索引:
解锁脚本:张三的签名和公钥。 签名用于验证钱是我的,公钥用于查找引用的输出。
注:挖矿奖励不需要参考任何产出,相当于有一个特殊的投入。
完整的验证脚本如下图所示:
6. 未消费输出(UTXO)
这就涉及到输出消耗的问题。 对于未使用的输出,我们有一个特殊的名称,称为未花费的交易输出(UTXO)。 UTXO是比特币交易中最小的支付单位,不可分割。 每一个 UTXO 都必须被一次性消耗掉,然后产生一个新的 UTXO 并存储在比特币网络的 UTXO 池中。
“用户的比特币余额”的概念是通过比特币钱包应用程序创建的衍生产品。 比特币钱包通过扫描区块链并汇总属于该用户的所有 UTXO 来计算用户的余额。
二、继续完善功能 1、增加交易
(1) 定义交易结构
type TXInput struct {
TXID []byte // 引用输出的交易ID
OutputIndex int64 // 引用输出的索引
//ScriptSig string // 解锁脚本(实际上这里应该是签名和公钥)
Signature []byte // 签名
PubKey []byte // 公钥
}
type TXOutput struct {
Value float64 // 金额
//ScriptPubKey string // 锁定脚本(公钥哈希)
PubKeyHash []byte // 公钥哈希
}
type Transaction struct {
TXID []byte // 交易ID
Inputs []TXInput // 交易输入
Outputs []TXOutput // 交易输出
}
(2) 设置交易ID
// 设置交易ID
func (tx *Transaction) SetHash() {
// 对tx进行加密处理,生成字节流
var buffer bytes.Buffer
encoder := gob.NewEncoder(&buffer)
encoder.Encode(&tx)
// 使用sha256对字节流再次假面,生成一个hash值
data := buffer.Bytes()
hash := sha256.Sum256(data)
// 把hash作为交易的ID
tx.TXID = hash[:]
}
(3) 创建CoinBase挖矿交易
const reward = 50 // 挖矿奖励
// 创建挖矿交易
// 参数一:矿工地址
// 参数二:矿工附加信息
func NewCoinBaseTx(address string, data string) *Transaction {
input := TXInput{nil, -1, data}
output := TXOutput{reward, address}
tx := Transaction{nil, []TXInput{input}, []TXOutput{output}}
tx.SetHash()
return &tx
}
(4)重写Block结构,使用交易数组代替Data。
// 定义区块
type Block struct {
// 版本号
Version uint64
// 前区块哈希值
PrevHash []byte
// 梅克尔根
MerKleRoot []byte
// 时间戳
TimeStamp uint64
// 难度值
Difficulty uint64
//随机数
Nonce uint64
// 当前区块哈希值
Hash []byte
// 区块数据
//Data []byte
Data []*Transaction // 区块的交易数据
}
(5)重写NewBlock函数
// 创建方法
func NewBlock(data []*Transaction, prevHash []byte) *Block {
block := Block{
Version: 00,
PrevHash: prevHash,
MerKleRoot: []byte{},
TimeStamp: uint64(time.Now().Unix()),
Difficulty: 1,
Nonce: 1,
//Data: []byte(data),
Data: data,
}
//提供一个设置哈希的方法
//block.SetHash()
pow := NewProofOfWork(block)
hash, nonce := pow.Run()
block.Hash = hash
block.Nonce = nonce
return &block
}
(6)重写NewBlockChain函数,添加address参数,调用NewCoinBase创建和添加挖矿交易。
// 创建方法
//func NewBlockChain() *BlockChain {
func NewBlockChain(address string) *BlockChain {
/*// 创建创世块
genericBlock := NewBlock([]byte(genesisInfo), []byte{})
// 创建BlockChain
bc := BlockChain{[]*Block{genericBlock}}
return &bc*/
var db *bolt.DB
var lastHash []byte
// 1. 打开数据库
db, err := bolt.Open(dbName, 0600, nil)
if err != nil {
panic("bolt.Open err!")
}
db.Update(func(tx *bolt.Tx) error {
// 2. 打开抽屉Bucket
bucket := tx.Bucket([]byte(bucketName))
// 3. 如果Bucket是否为nil,则创建一个Bucket
if bucket == nil {
bucket, err = tx.CreateBucket([]byte(bucketName))
if err != nil {
panic("tx.CreateBucket err!")
}
// 创建创世块
//genericBlock := NewBlock([]byte(genesisInfo), []byte{})
//创建coinbase交易 <---这里修改
coinbaseTx := NewCoinBaseTx(address, genesisInfo)
//创建创世块 <---这里修改
genericBlock := NewBlock([]*Transaction{coinbaseTx}, []byte{})
// 把创世块保存在bucket中
bucket.Put(genericBlock.Hash, genericBlock.Serialize())
// 把创世块的hash保存在last中
bucket.Put([]byte(last), genericBlock.Hash)
// 记录lastHash
lastHash = genericBlock.Hash
} else {
// 4. 如果Bucket不为nil,记录lastHash
lastHash = bucket.Get([]byte(last))
}
return nil
})
return &BlockChain{db, lastHash}
}
(7)重写AddBlock函数,将参数data改为txs。
// 添加区块
//func (bc *BlockChain) AddBlock(data string) {
func (bc *BlockChain) AddBlock(txs []*Transaction) {
/*// 获取最后区块
lastBlock := bc.Blocks[len(bc.Blocks)-1]
// 创建一个新区块
block := NewBlock([]byte(data), lastBlock.Hash)
// 添加新区块
bc.Blocks = append(bc.Blocks, block)*/
// 获取最后区块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)修改PrepareData函数,注释掉block.Data。
// 根据nonce生成区块哈希,该方法与SetHash方法类似
func (pow *ProofOfWork) PrepareData(nonce uint64) []byte {
block := pow.Block
block.MerKleRoot = block.HashTransactions()
tmp := [][]byte{
block.PrevHash,
//block.Data,
block.MerKleRoot,
uint64ToByte(block.Version),
uint64ToByte(block.TimeStamp),
uint64ToByte(block.Difficulty),
uint64ToByte(nonce),
}
blockInfo := bytes.Join(tmp, []byte{})
// 2.使用sha256加密
hash := sha256.Sum256(blockInfo)
return hash[:]
}
(9) 添加HashTransaction函数
该函数是生成默克尔树根哈希值。 正常的生成过程是利用所有交易的哈希值生成一棵平衡二叉树。 这里,为了简化代码,我们目前直接拼接区块中交易的哈希值 然后进行哈希运算。
// 对区块中所有交易ID进行哈希运算,作为梅克尔根
func (block *Block)HashTransactions() []byte {
var tmp [][]byte
for _, tx := range block.Data {
//交易的ID就是交易的哈希值,还记得吗?我们在Transaction里面提供了方法。
tmp = append(tmp, tx.TXID)
}
data := bytes.Join(tmp, []byte{})
hash := sha256.Sum256(data)
return hash[:]
}
(10)修改命令行
注释掉 addBlock 函数。
/*// 添加区块
func (cli *Cli) addBlock(data string) {
cli.bc.AddBlock(data)
}*/
将“block addBlock --data DATA”的内容放在常量Usage中。
const Usage = `
block printChain "打印区块链"
block getBalance --address ADDRESS "查询账户余额"
block send FROM TO AMOUNT MINER DATA "由FROM给TO转AMOUNT个比特币,并指定MINER为矿工"
block newWallet "创建一个钱包"
block listAddress "列出所有钱包地址"
`
修改run函数,注释掉“addblock”情况下的代码。
/*case "addBlock":
if len(os.Args) == 4 && os.Args[2] == "--data" {
data := os.Args[3]
if data == "" {
fmt.Println("data should not be empty!")
return
}
cli.addBlock(data)
}*/
修改main函数,调用NewBlockChain函数时传入矿工地址。
func main() {
bc := NewBlockChain("张三")
cli := Cli{bc}
cli.Run()
}
2.查询余额
比特币通过转账在系统中流通,付款人的钱包会使用付款人的解锁脚本解锁可控的UTXO,完成消费。 同时,通过收款人地址锁定支付金额,完成收款,从而实现金额的转移。
(1)解锁脚本
解锁脚本是检查输入是否可以使用某个地址锁定的utxo。 所以对于解锁脚本,外部提供了锁定信息,我去看看能不能解锁。
func (input *TXInput) CanUnlockUTXOWith(unlockData string /*收款人的地址*/) bool {
//ScriptSig是签名,v4版本中使用付款人的地址填充
return input.ScriptSig == unlockData
}
(2) 锁定脚本
锁定脚本用于指定比特币的新所有者,并在创建输出时创建。 对于这个输出,它一直在等待签名的到来,以检查签名是否可以解锁锁定的比特币。
func (output *TXOutput) CanBeUnlockedWith(unlockData string/*付款人的地址(签名)*/) bool {
//ScriptPubKey是锁定信息,v4版本中使用收款人的地址填充
return output.ScriptPubKey == unlockData
}
(3) 找出UTXO所在的交易集合
所有未花费的比特币都在 UTXO 中,而 UTXO 又包含在交易中。 因此,要想获得一次性的UTXO,就必须找到这些UTXO所在的交易。 如果每个人都有N个UTXO,那么我们需要找到所有的交易,也就是要找到包含UTXO的交易集合。
// 查询所有UTXO交易
// 参数:账户地址
func (bc *BlockChain) FindUTXOTransaction(address string) []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 {
txs = append(txs, *tx)
}
}
// 遍历input
if !tx.isCoinBase() {
for _, input := range tx.Inputs {
// 如果input的ScriptSig等于address,就代表该input是属于指定address的账户
if input.ScriptSig == address {
spentUTXOs[string(input.TXID)] = append(spentUTXOs[string(input.TXID)], int64(input.OutputIndex))
}
}
}
}
if (len(block.PrevHash) == 0) {
return txs
}
}
}
(4)定义isCoinBase函数
// 判断当前交易是否是挖矿交易
func (tx *Transaction) isCoinBase() bool {
return len(tx.Inputs) == 1 && tx.Inputs[0].TXID == nil && tx.Inputs[0].OutputIndex == -1
}
(5) 获取指定地址的UTXO集合
// 查找所有的utxo
func (bc *BlockChain) FindUTXOs(address string) []TXOutput {
txs := bc.FindUTXOTransaction(address)
var outputs []TXOutput
// 遍历所有utxos交易
for _, tx := range txs {
// 遍历utxo交易的所有output
for _, output := range tx.Outputs {
// 如果output的ScriptPubKey等于address,就代表该output是我们要找到output
if output.ScriptPubKey == address {
outputs = append(outputs, output)
}
}
}
return outputs
}
(6) 添加GetBalance函数获取余额。
// 查询余额
func (cli *Cli) GetBalance(address string) {
// 查询所有未消费的output
utxos := cli.bc.FindUTXOs(address)
var total float64
for _, output := range utxos {
total += output.Value
}
fmt.Printf("%s的余额: %f\n", address, total)
}
(7)修改Run函数,增加getBalance的case判断。
func (cli *Cli) Run() {
args := os.Args
if len(args) < 2 {
fmt.Println(Usage)
return
}
// 获取命令名称
command := args[1]
switch command {
case "printChain":
cli.printChain()
case "getBalance":
if len(os.Args) == 4 && os.Args[2] == "--address" {
address := os.Args[3]
if len(address) == 0 {
fmt.Println("地址不能为空!")
return
}
cli.GetBalance(address)
} else {
fmt.Println(Usage)
}
default:
fmt.Println(Usage)
}
}
3. 交易转账
(1) 创建交易
// 创建交易
func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
// 1.找到最优的utxos
utxos, total := bc.FindNeedUTXOs(from, 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, from}
inputs = append(inputs, input)
}
}
output := TXOutput{amount, to}
outputs = append(outputs, output)
// 找零
if total > amount {
output = TXOutput{total - amount, from}
outputs = append(outputs, output)
}
// 4.创建Transaction
tx := Transaction{nil, inputs, outputs}
tx.SetHash()
return &tx
}
(2)定义FindNeedUTXOs函数
// 查找最合理的utxo
func (bc *BlockChain) FindNeedUTXOs(address string, amount float64) (map[string][]int64, float64) {
txs := bc.FindUTXOTransaction(address)
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 total < amount {
total += output.Value
needUTXOs[string(tx.TXID)] = append(needUTXOs[string(tx.TXID)], int64(i))
} else {
break OUTPUT_TAG
}
}
}
}
return needUTXOs, total
}
(3) 添加发送命令
// 转账方法
func (cli *Cli) Send(from, to string, amount float64, miner string, data string) {
// 创建交易
tx := NewTransaction(from, to, amount, cli.bc)
// 创建挖矿交易
coinbase := NewCoinBaseTx(miner, data)
// 添加区块
cli.bc.AddBlock([]*Transaction{coinbase, tx})
fmt.Println("转账成功...")
// 打印余额
cli.GetBalance(from)
}
(4)修改Run方法,增加case发送条件判断。
func (cli *Cli) Run() {
args := os.Args
if len(args) < 2 {
fmt.Println(Usage)
return
}
// 获取命令名称
command := args[1]
switch command {
...
...
case "send":
args := os.Args
if len(args) < 7 {
fmt.Println("参数不正确!")
fmt.Println(Usage)
return
}
//block send FROM TO AMOUNT MINER DATA "由FROM给TO转AMOUNT个比特币,并指定MINER为矿工"
from := args[2]
to := args[3]
num, _ := strconv.ParseFloat(args[4], 16)
miner := args[5]
data := args[6]
cli.Send(from, to , num, miner, data)
default:
fmt.Println(Usage)
}
}