開發區塊鏈數字貨幣交易系統需要什麼方法
區塊鏈技術從發展到現在已經到3.0了,區塊鏈3.0可以用的實體行業很多地方,本文介紹一下開發區塊鏈數字貨幣交易系統需要什麼方法,包括一些源碼分享和一些開發方式等等,大家有不懂的可以及時問我,數字貨幣,現指代英文Cryptocurrency,區塊鏈技術最主要幾大特性是去中心化、加密解密技術、 匿名性 可追溯 不可複製 不可篡改 區塊鏈技術、共識機制,這些技術相互支撐,通過共識機製成為一個整體,實現了加密貨幣特有的安全、公開、可追溯的支付和交易。
寫一個自己的區塊鏈,不是讓程序員憑空想像,而是使用已經的開源平台,通過學習理念、工具、通過編寫簡答的函數即可以實現自己的區塊鏈,下面我給大家詳細說一下 開始製作我們自己第一個的數字貨幣了,首先準備好對應的編譯環境(C++的建議在Linux)和安裝好對應開發環境和工具(每個體系網上都有詳細的安裝教程文檔)。不過系統和開發環境的搭建、程序編譯等過程都比較繁瑣和有比較多坑(很多跟環境、依賴庫和版本有關,請留意),不建議普通用戶自己製作,就開發數字貨幣交易系統來說有主鏈和側鏈,主鏈和側鏈是什麼我就不用細說了,我想大家都明白,開發主鏈和側鏈需要的技術是不一樣的,目前國內而言能夠開發主鏈的公司很少。
開發交易所一整套需要什麼:首先要辦理牌照 但是國內的牌照現在已經停止註冊幾千元的區塊鏈牌照現在已經幾十萬,而且是線下交易如果沒有熟人別人也不會轉給你牌照,日本美國的牌照很難拿,這不是錢的事情日本拿個牌照要花幾千萬元人民幣,而且至少要排隊半年甚至幾年時間時間成本太長,還要會弄錢包.區塊鏈瀏覽器.交易平台系統.場內場外.上幣落地app.項目白皮書.對接主流交易所.跨境支付.貨幣發行系統.主鏈開發.基金會發起.等等這一整套都需要技術。
交易所開發的技術原理 除了充幣、提幣的功能需要和區塊鏈打交道之外,交易所的絕大多數功能,都是運行在自己伺服器上的,交易所產品的主要功能如下:1.用戶系統:註冊 登錄 KYC認證2.安全系統:密碼修改 簡訊綁定 Google二次認證 郵箱認證3.資金系統:充幣 提幣 餘額查詢4.交易系統:買入 賣出 撮合功能。等等還有很多我後面的文章再說,這裡最複雜的是交易系統,需要考慮多人在線的實時性能,以及撮合的正確性。最繁瑣的是充幣、提幣,如果一個交易所支持多個幣種,則需要部署每個幣種的節點,並能為每一個註冊用戶自動創建錢包地址,用以區分不同用戶的充值,並通過消息隊列實時檢查用戶充值的節點確認狀態。
如何添加數據呢 我們添加的每一份數據都需要按照同樣的方式添加到每個人保存的副本中。我們能否具有計算能力呢?為什麼需要計算能力?我們從此可以得到一個通用的計算機,也把數據的修改模型更加地簡化和通用化。我們如何定義計算能力呢?要回答這個問題,我們首先要想的是這個分散式的計算機的各個部分是如何構成的。誰來構成整個存儲空間?每一個具體的地址。每一個地址保存了什麼?數據。如何才能對地址計算呢?我們可以把對數據的處理邏輯也放入這個地址。那麼一個地址到底需要什麼呢?地址信息、財富信息、數據信息、代碼。於是,所謂的狀態就是指系統中每一個地址和地址對應的狀態的集合。我們通過一個一個的交易來進入新的狀態。接著, 我們可以把狀態轉移的過程也記錄下來,這個就是記錄transaction的block。這些block連接在一起,形成blockchain。
如何應對同時寫入的混亂如何防止很多人一起寫造成的混亂呢?大家同時解決一個難題,誰先解出來,誰就能夠寫入。如何防止有人同時解出來?這個有可能,但是連續多次都是同時有人解出來的概率較低,於是選擇鏈最長的那一個。具備以上基本概念,讓我們在這裡通過實際的代碼進一步實戰,實現一個支持Token的BlockChain。參考:Naivecoin: a tutorial for building a cryptocurrency 一般所說的「挖掘一個新區塊」其實包括兩部分,第一階段組裝出新區塊的所有數據成員,包括交易列表txs、叔區塊uncles等,並且所有交易都已經執行完畢,各帳號狀態更新完畢;第二階段對該區塊進行授勛/封印(Seal),沒有成功Seal的區塊不能被廣播給其他節點。第二階段所消耗的運算資源,遠超第一階段。
· Seal過程由共識演算法(consensus algorithm)族完成,包括Ethash演算法和Clique演算法兩種實現。前者是產品環境下真實採用的,後者是針對測試網路(testnet)使用的。Seal()函數並不會增加或修改區塊中任何跟有效數據有關的部分,它的目的是通過一系列複雜的步驟,或計算或公認,選拔出能夠出產新區塊的個體。
· Ethash演算法(PoW)基於運算能力來篩選出挖掘區塊的獲勝者,運算過程中使用了大量、多次、多種的哈希函數,通過極高的計算資源消耗,來限制某些節點通過超常規的計算能力輕易形成「中心化」傾向。
· Clique演算法(PoA)利用數字簽名演算法完成Seal操作,不過簽名所用公鑰,同時也是common.Address類型的地址必須是已認證的。所有認證地址基於特殊的投票地址進行動態管理,記名投票由不記名投票和投票方地址隨機組合而成,杜絕重複的不記名投票,嚴格限制外部代碼惡意操縱投票數據
· 在實踐「去中心化」方面,以太坊還在區塊結構中增加了叔區塊(uncles)成員以加大計算資源的消耗,並通過在交易執行環節對叔區塊作者(挖掘者)的獎勵,以收益機制來調動網路中各節點運算資源分布更加均勻。
如何保存數據 區塊鏈的基本概念是:保存持續增長的有序數據的分散式資料庫。要實現自己的區塊鏈,需要滿足以下功能:
定義數據塊 定義數據塊之間的關係 添加數據塊的功能 節點之間的通訊 節點之間同步數據 對節點的控制能力
區塊的結構 因為區塊鏈中的數據是相互連接的數據塊,因此我們需要創建LinkedList來實現這樣的場景。
如上圖所示,我們可以看到以下核心元素:index:區塊的遞增編號 data:該區塊所保存的數據 timestamp:時間戳 hash:通過SHA256演算法對該區塊進行的簽名previousHash:前一個數據塊的hash值,從而將區塊串聯了起來
我們可以得到對應的代碼:
class Block {
public index: number;
public hash: string;
public previousHash: string;
public timestamp: number;
public data: string;
constructor(index: number, hash: string, previousHash: string, timestamp: number, data: string) {
this.index = index;
this.previousHash = previousHash;
this.timestamp = timestamp;
this.data = data;
this.hash = hash; }
}
如何保證數據不被篡改
在計算機的世界中,一切都是用數學來解釋,用數學來證明。對於一個數據塊,我們計算出它的摘要。只要保證數據有變化,必然會引發摘要變化即可。在這裡我們使用的是SHA256的Hash演算法。從這個角度來說,Hash也可以被看成這塊數據的DNA。
具體的Hash的過程如下,
const calculateHash = (index: number, previousHash: string, timestamp: number, data: string): string => CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
聰明的讀者可能會發現,這個簡單的方法並不能防範更加複雜的黑客攻擊。因此,我們會不斷的改進我們的代碼,以抵擋這個世界的某些惡意。
但是現在的我們至少能夠通過Hash來唯一的驗證一個區塊鏈的結構了。
為什麼?因為我們在做Hash的時候,也把這個區塊對應的上一個區塊的Hash放了進來,因此如果有人想要篡改整個區塊鏈上的任何一個區塊,都會產生蝴蝶效應,後續的區塊都會為止失效。
如上圖所示,如果我們把區塊44的數據從TREE改為STREET,那麼它自身的Hash結果會改變,接著區塊45中的previousHash也會發生改變,於是區塊45的Hash也會改變,以此類推。因此,越早的區塊發生異常,那麼帶來的影響就會越大。
如何創建第一個區塊
第一個數據塊的難點在哪裡?它沒有previousHash!因此,我們直接硬編碼即可。
const genesisBlock: Block = new Block(
null, 1465154705, "my genesis block!!");
如何創建新的區塊 創建區塊如同炒菜,需要備好所有的原料。如同以下的代碼所示,我們需要找到最後一個有效的區塊,推理出下一個區塊的index,得到當前的時間戳,再結合上一個區塊的hash和當前的數據,也就能知道當前區塊的hash,從而創建出新的區塊。
const generateNextBlock = (blockData: string) => {
const previousBlock: Block = getLatestBlock();
const nextIndex: number = previousBlock.index + 1;
const nextTimestamp: number = new Date().getTime() / 1000;
const nextHash: string = calculateHash(nextIndex, previousBlock.hash,
nextTimestamp, blockData);
const newBlock: Block = new Block(nextIndex, nextHash,
previousBlock.hash, nextTimestamp, blockData);
return newBlock;};
區塊保存在哪裡在當前的版本中,我們只是保存在JavaScript所使用的內存中,因此很容易丟失,但是我們可以逐漸完善,讓數據的保存越來越持久。
const blockchain: Block[] = [genesisBlock];
如何驗證數據的有效性 在任何一個時刻,如果其他人給了我們一個新的區塊,我們如何驗證這個區塊是正確的呢?這需要符合以下的基本要求:
區塊之間的索引是+1遞增的. 當前區塊的previousHash需要和之前區塊的Hash相同 .區塊自身的Hash需要正確
於是,我們實現的代碼如下:
const isValidNewBlock = (newBlock: Block, previousBlock: Block) => {
if (previousBlock.index + 1 !== newBlock.index) {
console.log("invalid index");
return false; } else if (previousBlock.hash !== newBlock.previousHash) {
console.log("invalid previoushash");
return false; } else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
console.log(typeof (newBlock.hash) + " " + typeof calculateHashForBlock(newBlock)); console.log("invalid hash: " + calculateHashForBlock(newBlock) + " " + newBlock.hash); return false; } return true;};
這裡還有一個細節,如果區塊內數據的結構不正確,也可能是一個問題。因此我們還要額外進行判斷。
const isValidBlockStructure = (block: Block): boolean => {
return typeof block.index === "number" && typeof block.hash === "string" && typeof block.previousHash === "string" && typeof block.timestamp === "number" && typeof block.data === "string";};
我們現在可以完整的驗證一條區塊鏈了嗎?可以。我們首先要單獨處理第一個區塊,然後依次驗證之後的每一個區塊。
const isValidChain = (blockchainToValidate: Block[]): boolean => {
const isValidGenesis = (block: Block): boolean => {
return JSON.stringify(block) === JSON.stringify(genesisBlock); }; if (!isValidGenesis(blockchainToValidate[0])) {
return false; } for (let i = 1; i
if (!isValidNewBlock(blockchainToValidate[i],
blockchainToValidate[i - 1])) {
return false; } } return true;};
區塊鏈分叉了怎麼辦
在一個分散式的環境中,有可能不同的人在同一個區塊後添加了新的不同的區塊,那我們要聽誰的呢?聽大多數人的話(儘管現實中大多數人的話也許……)!那誰才能代表大多數的人民呢?實力更強大,更長的那一條鏈。
因此在遇到多條鏈的時候,我們可以直接選擇更長的一條。具體代碼如下,
const replaceChain = (newBlocks: Block[]) => {
if (isValidChain(newBlocks) && newBlocks.length > getBlockchain().length) {
console.log("Received blockchain is valid.
Replacing current blockchain with received blockchain");
blockchain = newBlocks;
broadcastLatest(); } else {
console.log("Received blockchain invalid"); }};
節點之間要如何通訊
因為在整個網路中有很多節點,大家都有可能去創建區塊,這就需要大家通過協商通訊的方式達成共識,這需要以下三個基本能力:
當個節點創建了一個區塊,需要通知整個網路,當一個節點連接上了一個新的節點,需要主動詢問對方最新的區塊,當一個節點遇到一個新的區塊,它會根據判斷的結果向網路請求更多的區塊。
上圖給出了節點通訊的具體流程。需要注意的是,在我們的代碼中,所有的連接都被爆存在了 WebSocket[]。我們並沒有實現節點發現的功能,因此節點的位置需要手動的添加。
如何控制節點 們需要一個對外的介面來控制一個節點,從而能夠查看節點的區塊、添加區塊、查看連通的節點、添加節點。於是我們通過以下代碼實現了HTTP對外的服務介面。
const initHttpServer = ( myHttpPort: number ) => {
const app = express(); app.use(bodyParser.json());
app.get("/blocks", (req, res) => {
res.send(getBlockchain()); }); app.post("/mineBlock", (req, res) => {
res.send(newBlock); }); app.get("/peers", (req, res) => {
res.send(getSockets().map(( s: any ) =>
s._socket.remoteAddress + ":" + s._socket.remotePort)); }); app.post("/addPeer", (req, res) => {
connectToPeers(req.body.peer); res.send(); }); app.listen(myHttpPort, () => {
console.log("Listening http on port: " + myHttpPort); });};
於是,我們可以直接訪問介面進行控制。例如,獲得全部區塊的列表。
#get all blocks from the node> curl http://localhost:3001/blocks
系統的架構是什麼
如上圖所示,我們每個節點都會向外提供兩個服務:讓外部用戶能夠控制這個節點的HTTP server服務,支持節點之間通訊的Websocket HTTP server服務
小結:綜上所述,我們已經構建了能夠保存區塊的區塊鏈的服務結構,實現了創建區塊和控制節點的基本能力。讓我們繼續添加更多的功能吧。
如何應對攻擊 在我們已經實現的版本中,每個人都能在其中添加區塊,這樣不僅可能造成混亂,而且如果有人拚命的添加區塊也會阻塞整個網路。
如何應對呢?那我們就限制每個人添加區塊的能力吧。如何限制呢?記得你在網站每次註冊新賬號的時候都會出現的驗證碼嗎?我們只要讓大家在每次添加區塊的時候都要做一道「難題」即可。這就是Proof-of-Work的基本原理,而這個解題過程就被稱之為挖礦。
因此,這個難題的設置會影響到節點添加區塊的難度。越難的題會讓我們越難添加區塊,相對來說安全性會上升,但是延遲很可能增加。
如何設置不同難度的題目
一個好的題目要讓計算機便於理解,運算規則相對簡單,運算方式相對公平。於是結合Hash演算法的題目被設計了出來:找到一個特定區塊,這個區塊的Hash需要有特殊的前綴。
這個前綴越特殊,難度就越大。於是我們可以定義出題目的難度difficulty為你所定義的特殊的前綴是由幾個0組成。例如,如果你只要求找到的區塊的Hash有一個0(difficulty=0),那麼可能相對簡單;但是如果你要求你找到的區塊的Hash的前綴有10個0(difficulty=10),那麼就有點難了。下圖給出了更細節的展示。
我們可以相應的實現檢查Hash是否滿足difficulty的代碼。
const hashMatchesDifficulty = (hash: string, difficulty: number): boolean => {
const hashInBinary: string = hexToBinary(hash);
const requiredPrefix: string = "0".repeat(difficulty);
return hashInBinary.startsWith(requiredPrefix);};
為了找到滿足difficulty條件的Hash,我們需要對同一個區塊計算出不同的Hash。但是這個Hash演算法的一致性相矛盾。可是我們可以通過在區塊中加入新的參數來實現Hash結果的變化,因為SHA256會因為數據的任何微小變化為完全變化。於是我們添加了一個叫做Nonce的參數,並且不斷的改變這個參數直到挖到我們想要的Hash結果。於是一個區塊的數據結構更新如下:
class Block {
public index: number;
public hash: string;
public previousHash: string;
public timestamp: number;
public data: string;
public difficulty: number;
public nonce: number;
constructor(index: number, hash: string, previousHash: string,
timestamp: number, data: string, difficulty: number, nonce: number) {
this.index = index;
this.previousHash = previousHash;
this.timestamp = timestamp;
this.data = data;
this.hash = hash;
this.difficulty = difficulty;
this.nonce = nonce; }}
如何解一個難題
基於以上的分析,我們不斷的增加Nonce的值,直到找到一個有效的Hash,具體代碼如下:
const findBlock = (index: number, previousHash: string, timestamp: number,
data: string, difficulty: number): Block => {
let nonce = 0; while (true) {
const hash: string =
calculateHash(index, previousHash, timestamp, data, difficulty, nonce);
if (hashMatchesDifficulty(hash, difficulty)) {
return new Block(index, hash, previousHash, timestamp,
data, difficulty, nonce); } nonce++; }};
當我們找到了一個有效的Hash,就把這個區塊廣播給整個網路。
如何確定難度
雖然我們能夠指定問題的難度,但是我們要如何設置難度呢?而且如何才能讓網路的節點都認同這個難度呢?
讓我們回歸到區塊鏈的本身。區塊鏈無非是一個區塊的鏈表,並且每隔一段時間會加入一個新的區塊。而我們的題目就是在控制加入區塊的難度,也就是加入的時間間隔,於是我們引入一個全局參數:
·
BLOCK_GENERATION_INTERVAL:定義了多久產生一個區塊(Bitcoin是10分鐘,Ethereum大概10-20秒)
·
但是隨著環境的變化,例如有更多的節點加入網路,我們並不能一致維持這個時間,因此我們每隔一段時間需要調整一下難度,於是我們引入第二個全局參數:
·
DIFFICULTY_ADJUSTMENT_INTERVAL:定義了每隔多久調整一次難度(Bitcoin是2016個區塊,Ethereum是更加動態的調整)
·
在我們的代碼中,我們會設置間隔為10秒。
// in seconds
const BLOCK_GENERATION_INTERVAL: number = 10;
// in blocks
const DIFFICULTY_ADJUSTMENT_INTERVAL: number = 10;
於是在我們的區塊鏈中每產生10個區塊就會查看區塊的生成頻率是否滿足我們的預期
BLOCK_GENERATION_INTERVAL * DIFFICULTY_ADJUSTMENT_INTERVAL。我們會根據預期和現實之間的差異決定如何調整難度。具體來說,我們判斷差異是否到了2倍的範圍,我們會對difficulty進行+1或者-1的操作。具體代碼如下:
const getDifficulty = (aBlockchain: Block[]): number => {
const latestBlock: Block = aBlockchain[blockchain.length - 1];
if (latestBlock.index % DIFFICULTY_ADJUSTMENT_INTERVAL === 0 && latestBlock.index !== 0) { return getAdjustedDifficulty(latestBlock, aBlockchain); } else { return latestBlock.difficulty; }};const getAdjustedDifficulty = (latestBlock: Block, aBlockchain: Block[]) => {
const prevAdjustmentBlock: Block =
aBlockchain[blockchain.length - DIFFICULTY_ADJUSTMENT_INTERVAL];
const timeExpected: number = BLOCK_GENERATION_INTERVAL * DIFFICULTY_ADJUSTMENT_INTERVAL;
const timeTaken: number = latestBlock.timestamp - prevAdjustmentBlock.timestamp; if (timeTaken timeExpected * 2) {
return prevAdjustmentBlock.difficulty - 1; } else { return prevAdjustmentBlock.difficulty; }};
時間戳被篡改了怎麼辦
我們題目的難度的調整需要用到區塊中保存的時間戳,但是這個時間戳可以由一個節點寫入任何值,我們如何應對這樣的攻擊呢?這裡的難點還在於不同節點上的時間本來就會有一定的差異。但是我們知道如果區塊的時間戳和我們自己的時間相差越遠則越可能有問題,因此我們把這個差異限制上一個區塊的創建時間到當前時間範圍的1分鐘以內。
const isValidTimestamp = (newBlock: Block, previousBlock: Block): boolean => {
return ( previousBlock.timestamp - 60
有人通過低難度產生超長鏈怎麼辦?
我們在上一節討論過當遇到分叉的時候,選擇更長的一個。但是一個惡意節點可以產生一個很長的但是每個區塊的難度都是最簡單的分叉。這樣怎麼辦?那就把選擇標準從最長調整為最難的。也就是說,我們會選擇累計解題難度最大的分叉,因為這背後所代表的人民的力量更加強大。如何計算累計的難度呢?因為每加一個0,我們的計算難度的期望會乘以2,所以我們計算每個區塊的2^difficulty來求得累計的難度,以此做為選擇標準。
如上圖所示,再A和B兩個鏈條中,雖然B更短,但是因為B的難度更大,所以我們會選擇B。
有一個需要注意的是,這裡的關鍵是難度,而並非Hash的前置的0,因為我們有可能碰巧得到一個更多的0的情況。這個思路被稱之為「中本聰共識」,是中本聰發明Bitcoin時的一個重要貢獻。因為每個節點只有相對較小的Hash計算能力,因此他們會傾向於選擇累計難度更長的鏈條來貢獻自己的力量,從而讓自己的貢獻得到認可。
小結:如何應對攻擊
Proof-of-work的特點在於難於計算,易於驗證。因此尋找特定前綴的SHA256成為一個很好的難題。
我們已經在代碼中加入了難度,並且節點可以通過挖礦來把區塊添加到區塊鏈中。
如何交易
我們在前兩節實現的區塊鏈只是對數據的基本保存,如何能夠在這個基礎上構建金融體系?但是一個金融體系的基本需求是什麼呢?所有權:一個人能夠安全的擁有token,交易權:一個人能夠把自己的token和他人交易,但是我們的區塊鏈是一個沒有「信任」的分散式的網路,如何才能構建出「確定性」呢?這需要我們找到一個不可抵賴的證明體系。
如何證明你是你
其實證明自己往往是最難的,這需要我們落地到一個我們可以相信的事情。想一想古代碎玉為半,之後團圓相認的場景。在計算機的世界也是一樣,我們把一塊美玉的一半告訴全世界,然後把另一半藏在自己身上,這樣之後你自己能夠拼接處這塊美玉。
但這背後的難點在於,別人有了你公布出來的一半的玉,是可以偽造另一半的。但是在計算機的世界裡,公鑰加密體系卻沒有這個缺陷。你有兩個鑰匙:公鑰和私鑰。公鑰是由私鑰推演出來的,並且會公布給所有人。對於你自己發出去的信息,你都可以用你的私鑰簽名。其他人會收到你的信息和你的簽名,然後他會用你的公鑰來驗證這個信息是否是通過你的私鑰進行的簽名。具體流程如下圖所示。
具體來說,我們會選擇橢圓曲線加密演算法(ECDSA)。到目前為止,我們引入了密碼學中的兩個核心工具:SHA256來支撐區塊數據一致性驗證 ECDSA來支撐用戶賬號的驗證
·
公鑰和私鑰長什麼樣
一個有效的私鑰是一個32位元組的字元串,示例如下:
一個有效的公鑰是由『04』開頭,緊接著64個位元組的自負換,示例如下:
公鑰是由私鑰演繹得到的,我們可以直接把它做為區塊鏈中一個用戶的賬號地址。
如何記錄一次交易
我們已經能夠讓用戶證明自己是誰了,現在就要記錄他們之間的交易了。我們需要三個信息 從哪裡來:發送者地址. 到哪裡去:接收者地址 . 交易多少:數量 . 即便如此,我們依然有個疑問?發送者如何證明自己有這個token呢?那麼他就需要提供之前他獲得這個token的證據。於是我們還需要第四個信息:指向自己的證據的指針。一個例子如下圖所示。
於是我們需要兩個結構分別表示交易的發起者和交易的接收者。
接收者長什麼樣
對於接受者,我們需要知道他的地址和交易的數量。如上一節所述,地址是ECDSA 的公鑰。這意味著,我們還需要保證只有對應私鑰的擁有者才能進一步操作這些token。這個結構體的代碼如下:
class TxOut {
public address: string;
public amount: number;
constructor(address: string, amount: number) {
this.address = address;
this.amount = amount; }}
發起者長什麼樣
交易的發起者需要提供自己token來源的證據,也就是指向之前的交易。但是他要證明自己對這個交易的擁有權,因此需要提供通過自己私鑰加密的簽名。這個結構體的代碼如下:
class TxIn {
public txOutId: string;
public txOutIndex: number;
public signature: string;}
需要注意的是這裡保存的只是通過私鑰進行的簽名,而不是私鑰本身。在區塊鏈的整個系統中,僅僅存在他的公鑰和簽名,而不會出現他的私鑰。
如上圖所示,整個過程就是發起者解鎖了txIns中的tokens,然後把它們轉給了TxOut中的接收者。
完整的交易長什麼樣
結合之前的討論,我們可以構建出最終的交易:
class Transaction {
public id: string;
public txIns: TxIn[];
public txOuts: TxOut[];}
如何唯一表示一次交易
我們依然可以使用SHA256來進行Hash,並且使用這個Hash來做為交易的id。這裡要注意的是我們並沒有包含發起者的簽名,這個會在之後添加。
const getTransactionId = (transaction: Transaction): string => {
const txInContent: string = transaction.txIns .map((txIn: TxIn) => txIn.txOutId + txIn.txOutIndex) .reduce((a, b) => a + b, "");
const txOutContent: string = transaction.txOuts .map((txOut: TxOut) => txOut.address + txOut.amount) .reduce((a, b) => a + b, "");
return CryptoJS.SHA256(txInContent + txOutContent).toString();};
如何對交易進行簽名
因為在區塊鏈中所有的交易都是公開的,因此要保證沒有人能夠利用這些交易進行攻擊。於是我們需要對所有敏感的信息都進行簽名。具體代碼如下:
const signTxIn = (transaction: Transaction, txInIndex: number,
privateKey: string, aUnspentTxOuts: UnspentTxOut[]): string => {
const txIn: TxIn = transaction.txIns[txInIndex];
const dataToSign = transaction.id;
const referencedUnspentTxOut: UnspentTxOut =
findUnspentTxOut(txIn.txOutId, txIn.txOutIndex, aUnspentTxOuts);
const referencedAddress = referencedUnspentTxOut.address;
const key = ec.keyFromPrivate(privateKey, "hex");
const signature: string = toHexString(key.sign(dataToSign).toDER());
return signature;};
但是我們會發現這裡只是對交易id進行了簽名,這樣足夠了嗎?
一種潛在的攻擊方式如下:當攻擊者CCC收到一個交易:從地址AAA向地址BBB發送10個token,交易id為0x555...。他會嘗試把接受者修改為自己,然後把這個交易發送到網路中。於是這個消息變成了:從地址AAA向地址CCC發送10個token。但是,當另一個節點DDD接收到這個交易信息之後,會進行驗證,他首先計算交易id。但是這時候因為接受者被改變了,因此交易id也會改變,例如成為了0x567...。於是發現問題。
及時攻擊者也修改了id為0x567...,但是AAA只是對0x555...進行了簽名,因此簽名的數據會不匹配。因此,攻擊者也會被識破。
到目前為止,整個協議看似是安全的。
如何找到用戶擁有的token
在一起交易中,發起者需要提供自己所擁有的沒有使用的token。因此,我們需要從當前的區塊鏈中找到這些信息,於是我們需要維持整個系統中沒有花費掉token的情況。這樣的數據結構如以下代碼所示:
class UnspentTxOut {
public readonly txOutId: string;
public readonly txOutIndex: number;
public readonly address: string;
public readonly amount: number;
constructor(txOutId: string, txOutIndex: number, address: string, amount: number) {
this.txOutId = txOutId;
this.txOutIndex = txOutIndex;
this.address = address;
this.amount = amount; }}
我們進一步可以把系統中所有未花費的token記錄在一個數組中:
let unspentTxOuts: UnspentTxOut[] = [];
如何更新未花費的數據信息
我們什麼時候更新呢?當新的區塊產生的時候。因為這個區塊里會包含新的交易信息。因此,我們需要從新的區塊中找到所有未花費的token的信息,並且記錄在newUnspentTxOuts之中,代碼如下:
const newUnspentTxOuts: UnspentTxOut[] = newTransactions .map((t) => { return t.txOuts.map((txOut, index) => new UnspentTxOut(t.id, index, txOut.address, txOut.amount)); }) .reduce((a, b) => a.concat(b), []);
我們同時也要知道哪些未被花費的token被花費掉了,這個被記錄在consumedTxOuts,代碼如下:
const consumedTxOuts: UnspentTxOut[] = newTransactions .map((t) => t.txIns) .reduce((a, b) => a.concat(b), []) .map((txIn) => new UnspentTxOut(txIn.txOutId, txIn.txOutIndex, "", 0));
最終我們通過刪除已經花費的並且加上新的未話費的,從而產生了新的未話費?數組resultingUnspentTxOuts,具體代碼如下:
const resultingUnspentTxOuts = aUnspentTxOuts .filter(((uTxO) => !findUnspentTxOut(uTxO.txOutId, uTxO.txOutIndex, consumedTxOuts))) .concat(newUnspentTxOuts);
以上邏輯通過updateUnspentTxOuts的方法來實現。需要注意的是這個方法要在驗證了區塊正確性的基礎上再來執行,否則會產生各種風險。
如何驗證交易的有效性
剛才提到了,我們需要驗證交易的有效性,要如何做呢?這背後要思考的是有什麼情況會產生異常。
交易結構異常怎麼辦?我們需要判斷交易的結構如何符合我們的標準。
const isValidTransactionStructure = (transaction: Transaction) => { if (typeof transaction.id !== "string") { console.log("transactionId missing"); return false; } ... //check also the other members of class }
交易id異常怎麼辦?我們需要進行判斷。
if (getTransactionId(transaction) !== transaction.id) { console.log("invalid tx id: " + transaction.id); return false; }
發起者信息異常怎麼辦?我們可以對簽名進行判斷,同時也要判斷token尚未被花費。
const validateTxIn = (txIn: TxIn, transaction: Transaction, aUnspentTxOuts: UnspentTxOut[]): boolean => { const referencedUTxOut: UnspentTxOut = aUnspentTxOuts.find((uTxO) => uTxO.txOutId === txIn.txOutId && uTxO.txOutId === txIn.txOutId); if (referencedUTxOut == null) { console.log("referenced txOut not found: " + JSON.stringify(txIn)); return false; } const address = referencedUTxOut.address; const key = ec.keyFromPublic(address, "hex"); return key.verify(transaction.id, txIn.signature);};
交易數量異常怎麼辦?我們需要對發起者標註的未花費的數量和交易的實際數量進行對比,查看兩者是否相等。
const totalTxInValues: number = transaction.txIns .map((txIn) => getTxInAmount(txIn, aUnspentTxOuts)) .reduce((a, b) => (a + b), 0); const totalTxOutValues: number = transaction.txOuts .map((txOut) => txOut.amount) .reduce((a, b) => (a + b), 0); if (totalTxOutValues !== totalTxInValues) { console.log("totalTxOutValues !== totalTxInValues in tx: " + transaction.id); return false; }
區塊鏈的token最初從哪裡來
我們可以不斷的回溯每一個交易,但是最初的交易的token從哪裡來呢?這需要我們定義無中生有的基礎交易。
在基礎交易中,它只有接收者,而沒有發起者。這如同國家銀行印刷了新的鈔票。在我們的區塊鏈中,將其定義為50。
const COINBASE_AMOUNT: number = 50;
這個沒有起點的交易從哪裡來呢?來自於我們對支撐系統的「礦工」的獎勵。每當你挖出一個區塊,系統會獎勵你50個token。
我們要如何保存這些初始的獎勵呢?可以添加一個額外的標誌符。因為這個獎勵是連同區塊一起產生的,所以我們可以使用區塊的id。
但是我們需要一些特殊的方法來驗證這類初始獎勵的有效性:
const validateCoinbaseTx = (transaction: Transaction, blockIndex: number): boolean => { if (getTransactionId(transaction) !== transaction.id) { console.log("invalid coinbase tx id: " + transaction.id); return false; } if (transaction.txIns.length !== 1) { console.log("one txIn must be specified in the coinbase transaction"); return; } if (transaction.txIns[0].txOutIndex !== blockIndex) { console.log("the txIn index in coinbase tx must be the block height"); return false; } if (transaction.txOuts.length !== 1) { console.log("invalid number of txOuts in coinbase transaction"); return false; } if (transaction.txOuts[0].amount != COINBASE_AMOUNT) { console.log("invalid coinbase amount in coinbase transaction"); return false; } return true;};
小結:如何交易
我們在本節討論了如何在區塊鏈中支持交易。核心概念是每個交易把一些未花費的token轉換了新的主人。我們是通過一個人的私鑰來定義歸屬權的。
但是我們依然需要手動的創建交易,因此我們會在下一章介紹如何實現錢包。
我就先講這麼多工信部已經發布(中國區塊鏈技術和應用發展白皮書) 360區塊貓 百度萊茨狗 茅台 京東 萬達 星巴克 騰訊多已經布局區塊鏈,我們目前做區塊鏈搭建主鏈側鏈都做也有海內外貨幣牌照等等(國內大部分公司只會做側鏈) 搭建貨幣電子錢包.區塊鏈瀏覽器.交易平台系統.數字貨幣交易所.場內場外.上幣落地app.項目白皮書.對接主流交易所.跨境支付.貨幣發行系統.主鏈開發.基金會發起.海內外貨幣牌照等一整套技術解決方案 (國內大部分只會做側鏈)還有第一區塊鏈認購溯源系統,可用在醫療 物流 食品肉類。第二養寵物系統 就像百度萊茨狗 小米加密兔 360區塊貓 第三物聯網和門禁系統可用在房產房租 第四金融行業方面跨境支付博彩銀行等等都可以做,後續很有很多我後面的文章再講
怎麼開發區塊鏈數字貨幣交易所需要什麼代碼和技術談談區塊鏈交易所搭建所需要的代碼和方法從官方企業白皮書看區塊鏈搭建主鏈到工業應用區塊鏈搭建開發與特點在工業方面的使用


TAG:彭利 |