社區大佬:「交易是操做區塊鏈的惟一方式。」git
在NEO中,幾乎除了共識以外的全部的對區塊鏈的操做都是一種「交易」,甚至在「交易」面前,合約都只是一個小弟。交易類型的定義在Core中的TransactionType中:github
源碼位置: neo/Core/TransactionType小程序
/// <summary> /// 用於分配字節費的特殊交易 /// </summary> [ReflectionCache(typeof(MinerTransaction))] MinerTransaction = 0x00, /// <summary> /// 用於分發資產的特殊交易 /// </summary> [ReflectionCache(typeof(IssueTransaction))] IssueTransaction = 0x01, [ReflectionCache(typeof(ClaimTransaction))] ClaimTransaction = 0x02, /// <summary> /// 用於報名成爲記帳候選人的特殊交易 /// </summary> [ReflectionCache(typeof(EnrollmentTransaction))] EnrollmentTransaction = 0x20, /// <summary> /// 用於資產登記的特殊交易 /// </summary> [ReflectionCache(typeof(RegisterTransaction))] RegisterTransaction = 0x40, /// <summary> /// 合約交易,這是最經常使用的一種交易 /// </summary> [ReflectionCache(typeof(ContractTransaction))] ContractTransaction = 0x80, /// <summary> /// 投票合約 //votingDialog /// </summary> [ReflectionCache(typeof(StateTransaction))] StateTransaction = 0x90, /// <summary> /// Publish scripts to the blockchain for being invoked later. /// </summary> [ReflectionCache(typeof(PublishTransaction))] PublishTransaction = 0xd0, /// <summary> /// 調用合約 GUI invocatransactiondialog /// </summary> [ReflectionCache(typeof(InvocationTransaction))] InvocationTransaction = 0xd1
這些交易不只名目繁多,並且實際功能和我「覺得」的還有些不一樣,爲了分別搞清楚每種交易是作什麼的,我幾乎又把NEO和GUI的源碼翻了個遍。數組
以上就是NEO中的9種交易類型,也基本上就是除了 議長 建立新區塊以外全部的能夠影響到區塊鏈生成的操做手段。雖然我將每種交易類型都大概分析了一下,可是仍是有些問題沒有搞清楚,好比爲何建立新資產和部署合約的時候是進行系統調用而不是發送相應的交易,我從源碼裏沒找到答案。網絡
從個人題目就能夠看出,我並不許備對每一種交易類型都詳細介紹,畢竟究其本質,都只是腳本不一樣的合約而已。我這裏要詳細分析的是UTXO交易,就是合約交易,也是咱們平常在NEO網絡上進行的轉帳操做。 在社區裏常常聽到有人在問:「NEO帳戶到底是什麼?「NEO和GAS餘額是怎麼來的?」「交易究竟轉的是什麼?」。我感受這些問題我以前都有過,就是首先對 代幣 這種概念不是很清晰,其次對虛擬的_帳戶_更是沒法理解。其實在最初開始看源碼,也是但願能在這個過程當中解決本身對於區塊鏈和智能合約認知上的不足。幸運的是,隨着一個個模塊看下來,對於NEO總體的系統架構和運行原理已經基本能夠說是瞭然於胸。可見Linux之父那句:」Talk is cheap,show me your code.「(別逼逼,看代碼),仍是頗有道理的。 對不起,我逼逼的有點多。架構
要理解餘額的計算原理,首先仍是要理解UTXO交易模型,這個東西社區大佬李總在羣裏發佈了一系列的講解視頻,感興趣的能夠去膜一下。《Mastering BitCoin》的第六章 Transaction 也對此進行了詳細的講解,想看的同窗能夠在文末找到下載鏈接。 計算餘額的代碼在wallet類中。dom
源碼位置:neo/Wallets/Wallet源碼分析
public Fixed8 GetBalance(UInt256 asset_id) { return GetCoins(GetAccounts().Select(p => p.ScriptHash)) .Where(p => !p.State.HasFlag(CoinState.Spent) //未花費狀態 && p.Output.AssetId.Equals(asset_id)) //資產類型 .Sum(p => p.Output.Value); }
其實從這裏就能夠很清晰的看出來這個餘額的計算過程了,就是將與地址對應的Output進行遍歷,將與指定資產類型相同且狀態不是spent(Spent表明已轉出)的值相加。更加直白通俗的解釋就是,加入每個指向你帳戶的Output至關於給你的一筆錢,全部未被花費的Output加起來,就是你如今的餘額。區塊鏈
在NEO中,轉帳的時候須要構造一個Transaction對象,這個對象中須要指定資產類型、轉帳數額、資產源(Output),目標地址,見證人(Witness)。因爲GUI中的轉帳這塊是能夠同時從當前錢包的多個地址中構造一個交易的,比較複雜,我這裏用我小程序的代碼來作講解,基本原理是同樣的。ui
輕錢包關於交易處理的這塊的代碼是改自NEL的TS輕錢包,不過去除了TS本來代碼中關於界面的代碼,而後從新封裝爲js模塊,至關於GUI轉帳功能魔鬼瘦身版本以後又進行的地獄級瘦身,代碼量極大減小。 小程序轉帳的入口在send.wpy界面中:
源碼位置:neowalletforwechat/src/pages/send.wpy/OnSend
//交易金額 var count = NEL.neo.Fixed8.parse(that.amount + ''); //資產種類 let coin = that.checked === 'GAS' ? CoinTool.id_GAS : CoinTool.id_NEO; wepy.showLoading({ title: '交易生成中' }); //構造交易對象 var tran = CoinTool.makeTran(UTXO.assets, that.targetAddr, coin, count); // console.log(tran); //生成交易id 沒錯是隨機數 let randomStr = await Random.getSecureRandom(256); //添加資產源、資產輸出、見證人、簽名 const txid = await TransactionTool.setTran( tran, prikey, pubkey, randomStr );
在構造交易對象的時候調用CoinTool的makeTran方法,須要傳入四個參數,一個是OutPuts,一個是目標帳戶,第三個是資產類型,最後一個是資產數量。這個方法對應於neo core中的neo/Wallets/Wallet.cs/MakeTransaction<T>。二者功能基本是一致的。makeTran中的對交易對象的初始化代碼以下:
//新建交易對象 var tran = new NEL.thinneo.TransAction.Transaction(); //交易類型爲合約交易 tran.type = NEL.thinneo.TransAction.TransactionType.ContractTransaction; tran.version = 0;//0 or 1 tran.extdata = null; tran.attributes = []; tran.inputs = [];
在UTXO交易模型中,每筆交易會有一個或者多個資金來源,也就是那些指向當前地址的Output,將這些OutPut做爲新交易的輸入:
//交易輸入 for (var i = 0; i < us.length; i++) { //構造新的input var input = new NEL.thinneo.TransAction.TransactionInput(); //新交易的prehash指向output input.hash = NEL.helper.UintHelper.hexToBytes(us[i].txid).reverse(); input.index = us[i].n; input["_addr"] = us[i].addr; //向交易資產來源中添加新的input tran.inputs.push(input); //計算已添加的資產總量 count = count.add(us[i].count); scraddr = us[i].addr; //若是已添加的資產數量大於或等於須要的數量,則再也不添加新的 if (count.compareTo(sendcount) > 0) { break; } }
在一筆交易中,本身帳戶的output變成新交易的input,而後新交易會指定新的output。一般,一筆交易中除了有一個指向目的帳戶的output以外,還會有一個用於找零的Output。這裏爲了方便,我仍是講一個小故事。
UTXO交易其實就是這樣的,output是不能分割的,只要被轉出,就一塊兒轉出,而後再轉入一個新的output做爲找零。output構造的代碼以下:
//輸出 if (sendcount.compareTo(NEL.neo.Fixed8.Zero) > 0) { var output = new NEL.thinneo.TransAction.TransactionOutput(); //資產類型 output.assetId = NEL.helper.UintHelper.hexToBytes(assetid).reverse(); //交易金額 output.value = sendcount; //目的帳戶 output.toAddress = NEL.helper.Helper.GetPublicKeyScriptHash_FromAddress(targetaddr); //添加轉帳交易 tran.outputs.push(output); } //找零 var change = count.subtract(sendcount); //計算找零的額度 if (change.compareTo(NEL.neo.Fixed8.Zero) > 0) { var outputchange = new NEL.thinneo.TransAction.TransactionOutput(); //找零地址設置爲本身 outputchange.toAddress = NEL.helper.Helper.GetPublicKeyScriptHash_FromAddress(scraddr); //設置找零額度 outputchange.value = change; //找零資產類型 outputchange.assetId = NEL.helper.UintHelper.hexToBytes(assetid).reverse(); //添加找零交易 tran.outputs.push(outputchange); }
以上就是構造一筆新交易的過程。基本上一個交易的結構都有了,從哪裏來,到哪裏去,轉多少,都已經構造完成,接下來就須要對這筆交易進行簽名。
與傳統的面對面交易不一樣,在網絡中發佈的交易如何對用戶身份進行確認呢?只要知道對方的地址,就能夠獲取到對方的Output,若是僅僅靠一個轉帳對象就可以成功轉出對方帳戶的資金,那麼這不全亂套了麼。因此對於一筆交易,除了須要構造交易必須的元素以外,還須要對交易進行簽名,向區塊鏈證實,是帳戶的全部者在進轉出交易。 在NEO Core中的簽名方法在neo/Cryptography/ECC/ECDsa.cs文件中定義,因爲這部分屬於密碼學範疇,不屬於我要分析的部分,這裏就大概提一下:
源碼位置:thinsdk-ts/thinneo/Helper.cs/Sign
//計算公鑰 var PublicKey = ECPoint.multiply(ECCurve.secp256r1.G, privateKey); var pubkey = PublicKey.encodePoint(false).subarray(1, 64); //獲取CryptoKey var key = new CryptoKey.ECDsaCryptoKey(PublicKey, privateKey); var ecdsa = new ECDsa(key); { //簽名 return new Uint8Array(ecdsa.sign(message,randomStr)); }
真正簽名的部分其實就是標準的ECDsa數字簽名,返回值是一個長度爲64的Uint8數組,前32字節是R,後32字節是S:
let arr = new Uint8Array(64); Arrayhelper.copy(r.toUint8Array(false, 32), 0, arr, 0, 32); Arrayhelper.copy(s.toUint8Array(false, 32), 0, arr, 32, 32); return arr.buffer;
參數S和R都是ECDsa數字簽名驗證時很是重要的參數。
僅僅計算出簽名是不夠的,咱們還須要將簽名添加到交易中,這個過程就是添加見證人:
tran.AddWitness(signdata, pubkey, WalletHelper.wallet.address);
添加見證人的過程其實就是將上一步的簽名信息和經過公鑰獲取到的驗證信息push到見證人腳本中,刪除了複雜驗證過程的見證人添加過程以下:
源碼位置:thinsdk-ts/thinneo/Transaction.cs
//增長我的帳戶見證人(就是用這我的的私鑰對交易籤個名,signdata傳進來) public AddWitness(signdata: Uint8Array, pubkey: Uint8Array, addrs: string): void { var vscript = Helper.GetAddressCheckScriptFromPublicKey(pubkey); //iscript 對我的帳戶見證人他是一條pushbytes 指令 var sb = new ScriptBuilder(); sb.EmitPushBytes(signdata); var iscript = sb.ToArray(); this.AddWitnessScript(vscript, iscript); } //增長智能合約見證人 public AddWitnessScript(vscript: Uint8Array, iscript: Uint8Array): void { var newwit = new Witness(); newwit.VerificationScript = vscript; newwit.InvocationScript = iscript; //添加新見證人 this.witnesses.push(newwit); }
這裏我仍是有問題的,就是這個交易加入涉及到一個錢包下的多個帳戶,是否是應該有多個簽名呢?理論上確定是的,畢竟每一個帳戶都擁有本身獨立的私鑰,因此我又去看了下GUI的轉帳代碼. GUI獲取交易並簽名的入口是位於MainForm文件中的轉帳方法,調用的是Helper中的SignAndShowInformation,在這個SignAndShowInformation中,調用的是Wallet文件中的Sign方法,傳入的是交易的上下文:
源碼位置:neo/Wallets/Wallet.cs
public bool Sign(ContractParametersContext context) { bool fSuccess = false; foreach(UInt160 scriptHash in context.ScriptHashes) { WalletAccount account = GetAccount(scriptHash); if (account ?.HasKey != true) continue; KeyPair key = account.GetKey(); byte[] signature = context.Verifiable.Sign(key); fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature); } return fSuccess; }
從源碼中能夠看出,NEO在對交易進行簽名的時候會分別使用每個參與交易的帳戶的私鑰來進行簽名。在簽名完成後,會調用GetScripts方法來獲取腳本,就是在這個方法中,交易添加了多個見證人:
public Witness[] GetScripts() { if (!Completed) throw new InvalidOperationException(); // 腳本哈希數量 == 見證人數量 Witness[] scripts = new Witness[ScriptHashes.Count]; for (int i = 0; i < ScriptHashes.Count; i++) { ContextItem item = ContextItems[ScriptHashes[i]]; using(ScriptBuilder sb = new ScriptBuilder()) { foreach(ContractParameter parameter in item.Parameters.Reverse()) { sb.EmitPush(parameter); } //添加新的見證人 scripts[i] = new Witness { InvocationScript = sb.ToArray(), VerificationScript = item.Script ?? new byte[0] }; } } return scripts; }
因此若是你的GUI錢包往外轉帳金額大於單獨一個帳戶的金額時,實際上是多個帳戶共同完成一筆交易的。
下載鏈接:《Mastering BitCoin》:https://github.com/Liaojinghui/BlockChainBooks