摘要:交易我們都知道得到比特幣需要挖礦,其實挖礦也屬于一種交易,不過是一種沒有確定交易輸入的一種交易,它也被稱作交易。這筆交易由轉賬發發起,因此需要提供轉賬方的私鑰進行解鎖腳本。驗證通過則證明這是屬于轉賬方的,可以用于交易。
BTC中的utxo模型
BTC中引入了許多創新的概念與技術,區塊鏈、PoW共識、RSA加密、萌芽階段的智能合約等名詞是經常被圈內人所提及,誠然這些創新的實現使得BTC變成了一種有可靠性和安全性保證的封閉生態系統,但是在這個BTC生態中如果沒有搭配區塊鏈模式的轉賬模塊,那么貨幣的流通屬性也就無從談起了。若要實現轉賬交易模塊, “是否采用傳統的賬戶模型實現交易;如何在區塊鏈上存儲交易信息,如何實現信息壓縮;如何驗證交易信息;系統的最大交易并發量”等問題確實值得思考。
BTC一一解決了這些,它放棄了傳統的基于賬戶的交易模型,而是采用基于區塊鏈存儲的utxo(unspent transaction output)模型。筆者嘗試分析了為什么不使用傳統的賬戶模型:
BTC的存儲單元為區塊鏈,區塊鏈的數據結構本質上是單向鏈表,它并不是傳統的關系型數據庫,無法新建賬戶表
存儲壓力。如果采用傳統的方式,則賬戶表會隨著時間的推移不停地增大,為后續的表的分片與備份造成很大困難
易造成隱私泄露。賬戶表的信息會直觀的暴露余額等敏感信息
utxo模型則很有技巧的避免了這些,在utxo模型下實現的每一筆交易,都不需要顯式的提供轉賬地址和接收地址(utxo中沒有賬戶,也不需要提供地址),只需提供這比交易的 交易輸入 和 交易輸出 即可,而交易輸入與交易輸出又是什么?
交易輸入指向一筆交易輸出,而且 “這筆交易輸出是可以供轉賬者消費的,因此這筆交易輸出也被稱作utxo(未花費交易輸出)”,它包括“某一筆交易、指向這筆交易的某個可用交易輸出的索引值和一個解鎖腳本”。這個解鎖腳本用來驗證某筆可用的消費輸出是否可以被提供解鎖腳本的人所使用。
交易輸出則是存儲BTC“余額”的一個數據結構,它廣義上包括兩部分:BTC的數量和一個鎖定腳本。 BTC的數量可以理解為余額,表示這筆交易產生的結果;而鎖定腳本則是用某種算法鎖定這個BTC余額,直到某人可以提供解鎖該腳本的數據鑰匙,這比數額BTC才會被這個人所消費。
從這個角度看,一筆交易會包含若干個交易輸入,同時產生若干個交易輸出。這些交易輸入都會指向之前某筆交易的未被消費輸出(utxo),并提供各自的解鎖腳本以證明這些utxo里的BTC是屬于轉賬方;同時將轉賬產生的所有交易輸出用對應方的公鑰進行加密(此處是為了更好的理解才解釋為公鑰加密,實質上是公鑰哈希,即btc地址進行逆向base58編碼的一段字符串),鎖定這幾筆交易輸出,等待交易輸入中的解鎖腳本解鎖。
所以,BTC沒有賬戶的概念,所有的“余額”都在區塊鏈上,不過這些余額都已經被加密了,只有提供私鑰和簽名的人才可以使用對應的utxo的余額,因此這就是為什么BTC持有者必須保存好自己的私鑰的原因。
UTXO的node.js實現 交易輸入export class Input { private txId: string; private outputIndex: number; private unlockScript: string; public get $txId(): string { return this.txId; } public set $txId(value: string) { this.txId = value; } public get $outputIndex(): number { return this.outputIndex; } public set $outputIndex(value: number) { this.outputIndex = value; } public get $unlockScript(): string { return this.unlockScript; } public set $unlockScript(value: string) { this.unlockScript = value; } constructor(txId: string, index: number, unlockScript: string){ this.txId = txId; this.outputIndex = index; this.unlockScript = unlockScript; } // 反序列化,進行類型轉換 public static createInputsFromUnserialize(objs: Array){ let ins = []; objs.forEach((obj)=>{ ins.push(new Input(obj.txId,obj.outputIndex,obj.unlockScript)); }); return ins; } canUnlock (privateKey: string): boolean{ if(privateKey == this.unlockScript){ return true; }else{ return false; } } }
私有屬性txId標識 “某個可用的utxo所屬的交易”,是一串sha256編碼的字符串;
outputIndex表示 “這個可用的utxo在對應交易的序號值”;
unlockScript則是解鎖腳本,此處并未完全按照BTC的原型去實現,而是簡單的驗證使用者的私鑰來實現鑒權,原理上仍遵從BTC的思想。
import * as rsaConfig from "../../rsa.json"; export class Output { private value: number; // 鎖定腳本,需要使用UTXO歸屬者用私鑰進行簽名通過 // 當解鎖UTXO成功后,此UTXO變為下一個交易的交易輸入,同時使用接收方的地址(公鑰)鎖定本次交易的交易輸出, // 等待接收方使用私鑰簽名使用該UTXO // 因此,btc沒有賬戶的概念,所有的“錢”由自己的公鑰所加密保存,只有用自己的私鑰才能使用這些錢(即解鎖了UTXO的解鎖腳本) private lockScript: string; // 該屬性僅僅在交易時使用,設置屬性 private txId: string; // 該屬性僅僅在交易時使用,設置屬性 private index: number; public get $index(): number { return this.index; } public set $index(value: number) { this.index = value; } public get $txId(): string { return this.txId; } public set $txId(value: string) { this.txId = value; } public get $value(): number { return this.value; } public set $value(value: number) { this.value = value; } /* public get $lockScript(): string { return this.lockScript; } public set $lockScript(value: string) { this.lockScript = value; } */ constructor(value: number,publicKey: string){ this.value = value; this.lockScript = publicKey; } // 反序列化,進行類型轉換 public static createOnputsFromUnserialize(objs: Array
交易輸出中的value屬性標識當前utxo的余額,即BTC個數;
lockScript屬性為鎖定腳本,在我們的簡易實現中就為接收方的公鑰,并不是BTC中的逆波蘭式,但大體原理相同,都需要提供私鑰來進行解密。
一筆交易,包含了若干個交易輸入和交易輸出,同時也提供了一個txId唯一的標識這比交易。從結構上看是這樣的:
export class Transaction { private txId: string; private inputTxs: Array; private outputTxs: Array
其中 txId的計算這里并沒有嚴格按照BTC實現的那樣進行計算,而是簡單的進行對象序列化進行一次sha256。
coinbase交易我們都知道得到比特幣需要挖礦,其實挖礦也屬于一種交易,不過是一種沒有確定交易輸入的一種交易,它也被稱作coinbase交易。coinbase交易在每一個區塊中都會存在,它的總額包括了系統針對礦工打包交易過程的獎勵以及其他轉賬方提供的手續費,如下圖:
因此,創建一個coinbase交易也很容易
// coinbase交易用于給礦工獎勵,input為空,output為礦工報酬 public static createCoinbaseTx(pubKey: string, info: string){ let input = new Input("",-1,info); let output = new Output(AWARD, pubKey); let tx = new Transaction("",[input],[output]) tx.setTxId(); return tx; }
在我們的實現中,只需提供鎖定utxo的公鑰以及一串描述字符串即可,最后設置交易的txId,完成coinbase交易的創建。
也提供了識別coinbase交易的方法:
public static isCoinbaseTx(tx: Transaction){ if(tx.$inputTxs.length == 1 && tx.$inputTxs[0].$outputIndex == -1 && tx.$inputTxs[0].$txId == ""){ return true; }else{ return false; } }
至此,coinbase交易就完成了,這是最簡單的一種交易,并沒有涉及到轉賬方,也就是交易輸入。
轉賬交易使用BTC就避免不了轉賬,轉賬事務在utxo模型的實現就是添加了一筆Transaction到某個區塊而已。每一筆交易都需要交易輸入和交易輸出,因此在BTC中,轉賬的核心就是找到轉賬方的utxo進行消費,同時將指定數量的BTC劃到指定的消費輸出上,如果仍有剩余,則找零至自己的消費輸出。
// 創建轉賬交易 public static createTransaction(from: string, fromPubkey: string, fromKey: string, to: string, toPubkey: string, coin: number){ let outputs = this.findUTXOToTransfer(fromKey, coin); console.log(`UTXOToTransfer: ${JSON.stringify(outputs)}, from: ${from} to ${to} transfer ${coin}`) let inputTx = [], sum = 0, outputTx = []; outputs.forEach((o)=>{ sum += o.$value; inputTx.push(new Input(o.$txId,o.$index,fromKey)); }); if(sum < coin){ throw Error(`余額不足,轉賬失敗! from ${from} to ${to} transfer ${coin}btc, but only have ${sum}btc`); } // 公鑰鎖住腳本 outputTx.push(new Output(coin,toPubkey)); if(sum > coin){ outputTx.push(new Output(sum-coin,fromPubkey)); } let tx = new Transaction("",inputTx,outputTx); tx.setTxId(); return tx; }
創建一個交易,需要提供轉賬方的地址(公鑰哈希)、轉賬方的公鑰和私鑰、接收方的地址、接收方的公鑰以及轉賬的BTC數量。這筆交易由轉賬發發起,因此需要提供轉賬方的私鑰進行解鎖腳本。
首先,通過 findUTXOToTransfer 找到滿足轉賬數量的可用的utxo,它需要提供轉賬方的私鑰以及轉賬數量;
接下來根據獲得的可用utxo,進行創建對應的交易輸入;
然后用接收方的公鑰加密交易輸出,同時如果有余額的化找零給自己,用自己的公鑰加密;
最后根據得到的交易輸入與交易輸出,創建一筆交易,計算txId,加入到區塊中(我們的demo是在單機下進行模擬,并未實現多播),等待挖礦。
轉賬的核心在于 findUTXOToTransfer,在findUTXOToTransfer中,通過調用 getAllUnspentOutputTx拿到所有的可用的utxo,并篩選出滿足給定數量BTC的utxo。
public static getAllUnspentOutputTx(secreteKey: string): Array{ let outputIndexHash: Object = this.getAllSpentOutput(secreteKey); let unspentOutputsTx = []; let keys = Object.keys(outputIndexHash); let block = BlockDao.getSingletonInstance().getBlock(chain.$lastACKHash); while(block && block instanceof Block){ block.$txs && block.$txs.forEach((tx)=>{ if(keys.includes(tx.$txId)){ tx.$outputTxs.forEach((output,i)=>{ // 過濾已消費的output if(i == outputIndexHash[tx.$txId]) return; if(output.canUnlock(secreteKey)){ unspentOutputsTx.push(tx); } }); }else{ for(let i=0,len=tx.$outputTxs.length;i 在getAllUnspentOutputTx中,通過 getAllSpentOutput 遍歷本地持久化的區塊鏈,拿到所有的可供消費utxo,這些utxo并不僅僅屬于轉賬方,因此需要在針對每個utxo嘗試進行驗證邏輯,即output.canUnlock(secreteKey)。驗證通過則證明這是屬于轉賬方的BTC,可以用于交易。
在getAllSpentOutput中,通過遍歷每一個交易輸入獲取它指向前面交易的某個utxo來得到所有的utxo,當然對于coinbase交易我們無法找到他的交易輸入,因此會進行過濾。
至此,utxo的轉賬流程已經完成,下面需要做的就是把這比交易加入到區塊中了,這已不是本文的核心。
尾聲本文所講的utxo示例是基于作者對BTC實現的基礎上的簡單實現,有不當之處還請讀者指出。另外,本文的代碼開源在 https://github.com/royalrover... 的 feature/utxo分支 上,希望大家一起提建議!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93840.html
摘要:完整的步驟如下檢查比特幣或的余額,錢包地址。比特幣的到帳時間是個區塊的高度,約分鐘。 通過 Nodejs 買賣Bitcoin showImg(https://segmentfault.com/img/remote/1460000018771566?w=1200&h=659); Github Repo 方案一: 通過ExinCore API進行幣幣交易 Exincore 提供了基于Mix...
摘要:下面的代碼,可以讀取比特幣錢包余額網內免手續費的,并且即時確認任何幣在內部的交易,都是無手續費的,并且立刻到賬。 基于Mixin Network的 Nodejs 比特幣開發教程: 創建比特幣錢包 showImg(https://segmentfault.com/img/remote/1460000018771566?w=1200&h=659);我們已經創建過一個回復消息的機器人和一個能...
摘要:分享一些以太坊比特幣等區塊鏈相關的交互式在線編程實戰教程以太坊,主要是針對工程師使用進行區塊鏈以太坊開發的詳解。這里是原文如何用為以太坊和比特幣生成虛擬地址 今天,我們將編寫一個非常簡單的python腳本來生成虛榮地址,這些地址是以某個短語或字母序列開頭的加密貨幣地址。該過程涉及生成私鑰并檢查目標短語的地址,直到找到滿意的地址。 安裝包 首先,我們需要安裝一些可以執行計算的軟件包,以便...
摘要:比特幣區塊鏈無疑是當今業界的最熱門的。目前,每個成功的礦工獲得可能每年更換一次或通過比特幣社區決策作為成功向區塊鏈添加一塊交易的獎勵。填寫其他詳細信息,例如比特幣金額和可選說明。 比特幣區塊鏈無疑是當今業界的最熱門的。通過這篇博客,我將盡力向大家介紹加密貨幣比特幣的概念,以及它如何創造我們稱之為區塊鏈的革命性技術。 這個問題經常引起混淆。這篇文章可以快速解釋和清理這方面的混亂! 什么是...
閱讀 887·2023-04-25 19:17
閱讀 2184·2021-09-10 11:26
閱讀 1902·2019-08-30 15:54
閱讀 3416·2019-08-30 15:53
閱讀 2683·2019-08-30 11:20
閱讀 3397·2019-08-29 15:12
閱讀 1235·2019-08-29 13:16
閱讀 2390·2019-08-26 12:19