主页 > imtoken国际版下载 > 如何使用golang实现一个比特币交易功能
如何使用golang实现一个比特币交易功能
如何使用golang实现一个比特币交易功能? 针对这个问题,本文详细介绍了相应的分析和解决方法,希望能帮助更多想要解决这个问题的朋友找到更简单更容易的方法。
比特币交易
交易是比特币的核心,区块链的唯一目的就是安全可靠地存储交易。 在区块链中,交易一旦创建,任何人都无法修改或删除。
对于每一笔新的交易,它的输入都会参考上一笔交易的输出(这里有个例外,coinbase交易),参考就是花费的意思。 所谓引用之前的输出,就是将之前的输出包含在另一笔交易的输入中,也就是花费之前的交易输出。 交易的输出是硬币实际存储的地方。 下图说明了事务之间的相互关系:
注意:
有些输出与输入无关
一笔交易的输入可以参考之前多笔交易的输出
输入必须引用输出
在整篇文章中,我们将使用“金钱”、“硬币”、“花费”、“发送”、“帐户”等词。 但是在比特币中,没有这样的概念。 一个交易只是一个脚本(script)来锁定(lock)一些值(value),而这些值只能被锁定它们的人解锁(unlock)。
每笔比特币交易都会产生一个输出,并记录在区块链上。 将比特币发送给某人实际上意味着创建一个新的 UTXO 并将其注册到该人的地址,该地址可供他使用。
交易的主要功能:
func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) { if !ValidateAddress(from) { log.Panic("ERROR: Sender address is not valid") } if !ValidateAddress(to) { log.Panic("ERROR: Recipient address is not valid") } bc := NewBlockchain(nodeID) //获取区块链实例 UTXOSet := UTXOSet{bc} //创建UTXO集 defer bc.Db.Close() wallets, err := NewWallets(nodeID) if err != nil { log.Panic(err) } wallet := wallets.GetWallet(from) tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet) if mineNow { cbTx := NewCoinbaseTX(from, "") txs := []*Transaction{cbTx, tx} newBlock := bc.MineBlock(txs) UTXOSet.Update(newBlock) } else { sendTx(knownNodes[0], tx) } fmt.Println("Success!") }
我们从头开始分析整个交易过程,首先使用ValidateAddress()方法判断输入地址是否为合法的比特币地址,然后从我们的blotDB数据库中获取区块链实例(我们使用一个数据库来存储区块链数据,读者这里可以忽略),其中读取数据库的代码如下
func NewBlockchain(nodeID string) *Blockchain { dbFile := fmt.Sprintf(dbFile, nodeID) if dbExists(dbFile) == false { fmt.Println("No existing blockchain found. Create one first.") os.Exit(1) } var tip []byte db, err := bolt.Open(dbFile, 0600, nil) //打开数据库 if err != nil { log.Panic(err) } err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) tip = b.Get([]byte("l")) //读取最新的区块链 return nil }) if err != nil { log.Panic(err) } bc := Blockchain{tip, db} return &bc }
我们区块链的基本原型是
type Blockchain struct { tip []byte Db *bolt.DB } type Block struct { Timestamp int64 Transactions []*Transaction PrevBlockHash []byte Hash []byte Nonce int Height int }
获取区块链实例后,我们创建一个utxo集合,其数据结构为
type UTXOSet struct { Blockchain *Blockchain }
然后我们从钱包文件中获取我们的钱包集合(wallets),然后调用我们的转账函数。
func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction { var inputs []TXInput var outputs []TXOutput pubKeyHash := HashPubKey(wallet.PublicKey) acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount) //找到能够使用的输出 if acc < amount { //如果能够使用的输出小于目标值,则返回错误 log.Panic("ERROR: Not enough funds") } // Build a list of inputs for txid, outs := range validOutputs { txID, err := hex.DecodeString(txid) if err != nil { log.Panic(err) } for _, out := range outs { input := TXInput{txID, out, nil, wallet.PublicKey} inputs = append(inputs, input) } } // Build a list of outputs from := fmt.Sprintf("%s", wallet.GetAddress()) outputs = append(outputs, *NewTXOutput(amount, to)) //创建新的交易输出 if acc > amount { outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change //找零输出 } tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() //创建一笔交易 UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey) //对交易签名 return &tx }
对于一个事务,它的数据结构是
type Transaction struct { ID []byte Vin []TXInput Vout []TXOutput } type TXInput struct { Txid []byte Vout int Signature []byte PubKey []byte } type TXOutput struct { Value int PubKeyHash []byte } type UTXOSet struct { Blockchain *Blockchain }
对于一笔交易,其输出主要包括两部分:一定数量的比特币(Value)和锁定的脚本(ScriptPubKey)。 要花这笔钱,必须解锁剧本。 一个input是指上一笔交易的output:Txid存储了上一笔交易的ID比特币如何应用交易,Vout存储了该笔交易output的所有output的index(因为一个transaction可能有多个output,所以需要信息指明哪个是output)具体哪一个)Signature是签名,Pubkey是公钥,两者都保证用户不能花属于别人的币。
func HashPubKey(pubKey []byte) []byte { // RIPEMD160(SHA256(PubKey)) publicSHA256 := sha256.Sum256(pubKey) RIPEMD160Hasher := ripemd160.New() _, err := RIPEMD160Hasher.Write(publicSHA256[:]) if err != nil { log.Panic(err) } publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) return publicRIPEMD160 } func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) //为输出开辟一块内存空间 accumulated := 0 db := u.Blockchain.db //获取存取区块链的数据库 err := db.View(func(tx *bolt.Tx) error { //读取数据库 b := tx.Bucket([]byte(utxoBucket)) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { //遍历数据库 txID := hex.EncodeToString(k) outs := DeserializeOutputs(v) for outIdx, out := range outs.Outputs { if out.IsLockedWithKey(pubkeyHash) && accumulated < amount { //如果能够解锁输出,代表utxo集中的输出是的所有者是该公钥所对应的人 accumulated += out.Value //累加值 unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) //加到数组中 } } } return nil }) if err != nil { log.Panic(err) } return accumulated, unspentOutputs } func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { //判断输出是否能够被某个公钥解锁 return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0 } func NewTXOutput(value int, address string) *TXOutput { txo := &TXOutput{value, nil} //注册一个输出 txo.Lock([]byte(address)) //设置输出的pubhashkey return txo } func (out *TXOutput) Lock(address []byte) { pubKeyHash := Base58Decode(address) pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] out.PubKeyHash = pubKeyHash }
在创建新的输出时,我们必须找到所有不可花费的输出,并确保它们具有足够的价值(value),这就是 FindSpendableOutputs 所做的。 然后,对于找到的每个输出,输出对输入的引用。 接下来,我们创建两个输出:
一个被收件人地址锁定。 这是将硬币实际转移到其他地址。
一个被发件人地址锁定。 这是一个变化。 只有当未花费的输出超过新交易所需时才会产生。 记住:输出是不可分割的。
func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) if err != nil { log.Panic(err) } prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } tx.Sign(privKey, prevTXs) } func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {//方法接受一个私钥和之前一个交易的map if tx.IsCoinbase() { return }//判断是是否为发币交易,因为发币交易没有输入,故不用进行签名 for _, vin := range tx.Vin { if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { log.Panic("ERROR: Previous transaction is not correct") } } txCopy := tx.TrimmedCopy() //将会被签名的是修剪后的交易副本,而不是一个完整的交易 for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash //迭代副本中的每一个输入,在每个输入中,Pubkey 被设置为所引用输出的PubKeyHash / dataToSign := fmt.Sprintf("%x\n", txCopy) r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign))//我们通过private对txCopy进行签名将这串数字连接起来储存在signature中 if err != nil { log.Panic(err) } signature := append(r.Bytes(), s.Bytes()...) tx.Vin[inID].Signature = signature txCopy.Vin[inID].PubKey = nil } } func (tx *Transaction) TrimmedCopy() Transaction { var inputs []TXInput var outputs []TXOutput for _, vin := range tx.Vin {//将输入的TXInput.Signature 和TXIput.PubKey设置为空 inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil}) } for _, vout := range tx.Vout { outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash}) } txCopy := Transaction{tx.ID, inputs, outputs} return txCopy }
交易必须签名,因为只有这样才能保证发送方不会花费别人的币,如果签名无效,那么这笔交易也将被视为无效比特币如何应用交易,因为这笔交易无法添加到区块中的区域chain.考虑到交易解锁之前的输出,然后重新分配里面的值,并锁定新的输出,那么必须要签名的数据
因此,在比特币中,签名的不是交易,而是存储引用输出的 ScriptPubKey 的部分签名输入的副本
如果现在挖矿
cbTx := NewCoinbaseTX(from, "") txs := []*Transaction{cbTx, tx} newBlock := bc.MineBlock(txs) UTXOSet.Update(newBlock) func NewCoinbaseTX(to, data string) *Transaction { if data == "" { //如果数据为空生成一个随机数据 randData := make([]byte, 20) _, err := rand.Read(randData) if err != nil { log.Panic(err) } data = fmt.Sprintf("%x", randData) }//生成一笔挖矿交易 txin := TXInput{[]byte{}, -1, nil, []byte(data)} txout := NewTXOutput(subsidy, to) tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}} tx.ID = tx.Hash() return &tx } func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block { //开始挖矿 var lastHash []byte var lastHeight int for _, tx := range transactions { // TODO: ignore transaction if it's not valid if bc.VerifyTransaction(tx) != true { log.Panic("ERROR: Invalid transaction") //对打包在区块中的交易进行认证 } } err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) //获取最新的一个块的hash值 blockData := b.Get(lastHash) block := DeserializeBlock(blockData) //将最新的一个块解序列 lastHeight = block.Height return nil }) if err != nil { log.Panic(err) } newBlock := NewBlock(transactions, lastHash, lastHeight+1) err = bc.db.Update(func(tx *bolt.Tx) error { //更新区块链数据库 b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { log.Panic(err) } err = b.Put([]byte("l"), newBlock.Hash) if err != nil { log.Panic(err) } bc.tip = newBlock.Hash return nil }) if err != nil { log.Panic(err) } return newBlock } func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { if tx.IsCoinbase() { return true } prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) if err != nil { log.Panic(err) } prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } return tx.Verify(prevTXs) } func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { if tx.IsCoinbase() { //判断是否为大笔交易 return true } for _, vin := range tx.Vin { if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil { log.Panic("ERROR: Previous transaction is not correct") //判断输入地址的有效性 } } txCopy := tx.TrimmedCopy() //创建一个裁剪版本的交易副本 curve := elliptic.P256() //我们需要相同区块用于生成密钥对 for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash r := big.Int{} s := big.Int{} sigLen := len(vin.Signature) r.SetBytes(vin.Signature[:(sigLen / 2)]) s.SetBytes(vin.Signature[(sigLen / 2):]) x := big.Int{} y := big.Int{} keyLen := len(vin.PubKey) x.SetBytes(vin.PubKey[:(keyLen / 2)]) y.SetBytes(vin.PubKey[(keyLen / 2):]) //这里我们解包存储在 TXInput.Signature 和 TXInput.PubKey 中的值,因为一个签名就是一对数字,一个公钥就是一对坐标。我们之前为了存储将它们连接在一起,现在我们需要对它们进行解包在 crypto/ecdsa 函数中使用 dataToVerify := fmt.Sprintf("%x\n", txCopy) rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false { //验证 return false } txCopy.Vin[inID].PubKey = nil } return true } func NewBlock(transactions []*Transaction, prevBlockHash []byte, height int) *Block {//产生一个新的块 block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height}//定义数据结构 pow := NewProofOfWork(block) //定义工作量证明的数据结构 nonce, hash := pow.Run() //挖矿 block.Hash = hash[:] block.Nonce = nonce return block } func (pow *ProofOfWork) Run() (int, []byte) { var hashInt big.Int var hash [32]byte nonce := 0 fmt.Printf("Mining a new block") for nonce < maxNonce { data := pow.prepareData(nonce) hash = sha256.Sum256(data) fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ } } fmt.Print("\n\n") return nonce, hash[:] } func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, pow.block.HashTransactions(), IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), }, []byte{}, ) return data } func (u UTXOSet) Update(block *Block) { db := u.Blockchain.db err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) for _, tx := range block.Transactions { if tx.IsCoinbase() == false { for _, vin := range tx.Vin { updatedOuts := TXOutputs{} outsBytes := b.Get(vin.Txid) outs := DeserializeOutputs(outsBytes) for outIdx, out := range outs.Outputs { if outIdx != vin.Vout { updatedOuts.Outputs = append(updatedOuts.Outputs, out) } } if len(updatedOuts.Outputs) == 0 { err := b.Delete(vin.Txid) if err != nil { log.Panic(err) } } else { err := b.Put(vin.Txid, updatedOuts.Serialize()) if err != nil { log.Panic(err) } } } } newOutputs := TXOutputs{} for _, out := range tx.Vout { newOutputs.Outputs = append(newOutputs.Outputs, out) } err := b.Put(tx.ID, newOutputs.Serialize()) if err != nil { log.Panic(err) } } return nil }) if err != nil { log.Panic(err) } }
golang适合做什么
golang可以做服务端开发,但是golang非常适合做日志处理、数据打包、虚拟机处理、数据库代理等工作。 在网络编程方面,它也广泛应用于Web应用、API应用等领域。
关于如何使用golang实现比特币交易功能的问题的答案分享到这里。 希望以上内容能够对大家有所帮助。 如果您还有很多疑惑没有解决,可以关注易速云行业资讯频道,了解更多相关知识。