目前已寫好的NEO多籤分爲兩種,一種是基於NEL-GUI的GUI多籤工具,僅支持對鑑權合約的多籤工做;另外一種是基於NEL-ThinSDK-cs的請錢包多籤工具,終端操做,支持鑑權合約和應用合約的多籤工做。本文將分爲兩大部分,第一部分介紹兩個工具的使用方法,第二部分介紹技術實現細節。git
這是第一部分的第一小結,介紹經過NEL-GUI進行鑑權合約的多籤,這個工具的目的主要是方便對多籤合約帳戶的轉帳操做。github
首先下載NEL-GUI,也就是 God of NEO 李總基於NEO-GUI寫的GUI工具。地址:api
git clone https://github.com/NewEconoLab/neo-gui-nel.git
下載完之用VS打開,項目選擇neo-gui:數據結構
而後啓動項目,項目啓動成功後會出現與熟悉的NEO-GUI很像的界面,不一樣的是NEL-GUI在選項卡里多了Plugin選項:工具
這個插件選項裏面就是NEL開發的各類小工具了,我寫的多籤工具也放在這裏面。測試
在Plugin下拉列表種選擇多籤工具,就能夠打開多籤工具的界面:ui
若是要對多籤合約操做,只須要在合約地址列表種選擇多籤合約:插件
而後在下面的WIF框裏添加須要進行簽名的私鑰就能夠了:debug
最後填好目標帳戶地址和金額就能夠了。3d
這裏是第一部分的第二小節,主要介紹ThinSDK 多籤的終端操做。
首先下載 ThinSDK-cs:
git clone https://github.com/NewEconoLab/neo-thinsdk-cs.git
而後用vs打開,選擇smartContractDemo項目,編譯運行。
輸入multisign進入多籤功能。
多籤功能裏的 <2> 是進行應用合約多籤的。爲了方便測試,我在測試網發佈了一個多籤的應用合約,合約代碼:
public class Multi : SmartContract { public readonly static byte[] pubkey0 = { 2, 201, 40, 35, 13, 133, 217, 231, 75, 94, 76, 243, 237, 8, 84, 124, 118, 197, 253, 56, 208, 101, 194, 157, 78, 192, 203, 94, 102, 154, 16, 143, 55 }; public readonly static byte[] pubkey1 = { 3, 177, 14, 33, 182, 106, 2, 10, 89, 150, 79, 237, 3, 70, 190, 12, 174, 176, 227, 235, 111, 113, 254, 3, 207, 183, 188, 189, 16, 191, 31, 225, 223, }; public static bool Main(string method, object[] args) { if(Runtime.CheckWitness(Multi.pubkey0) && Runtime.CheckWitness(Multi.pubkey1)) { return true; } return false; } }
其中pubkey0的私鑰對應:L2ME3NL8XgWLa6XVVzCJyccPw3C7bnqHzWhtfdPaeZzzdX8MJSkj pubkey1的私鑰對應:KwuezVnxhfUGiex7HM4ttrKBF4pTQRREkVmL1PW91gBZTRtsrLm9
合約地址爲:0x4c0f57b61d997297560190b1e397fe6d58fce94a
合約信息:
在調用合約時,首先輸入須要驗證的私鑰的數量,我這裏輸入2,而後分別輸出私鑰:
等15秒左右以後經過neodebug工具來查看合於執行結果:
交易id:0x500f037992dadcfb16fd55882109bd4c1629be8a19665f02e33d7dee9cc77632
能夠看到這裏是執行成功的。爲了肯定這裏是否是真的有效,咱們使用錯誤的私鑰再調用一下:
錯誤的私鑰以下:
從新調用多籤合約:
交易id 0x096997ca95011f5a4856b8947fe4a57780aeb8643b0a2515ed0568190962cfc5
經以上測試,多籤工具功能正常。
首先分析多籤合約帳戶。
多籤合約帳戶的構造代碼以下:
using (ScriptBuilder sb = new ScriptBuilder()) { sb.EmitPush(m); foreach (ECPoint publicKey in publicKeys.OrderBy(p => p)) { sb.EmitPush(publicKey.EncodePoint(true)); } sb.EmitPush(publicKeys.Length); sb.Emit(OpCode.CHECKMULTISIG); return sb.ToArray(); }
能夠分析出多籤合約腳本結構:
腳本執行的時候經過多籤碼進入多籤合約驗證邏輯,而後讀取公鑰數量,從腳本中加載公鑰。
在對交易進行多籤的時候,數據結構以下:
{ "type": "Neo.Core.ContractTransaction", "hex": "8000000165046db244a897aeb4b04abdc154847083204f3eb64bbe4edb8736e3a45b6e50000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6000e1f505000000007264db854ba951aa2244150df4345585c310444a", "items": { "0xc627d59f76070a2239d3ccd1fa86e44916be580e": { "script": "52210387419467127d60bef1e11126cb1311609319e7a50c04781950498355cb8e1ad62102976eca673c5ccb15ca293f219734189b2738875b0e799856101e3347e2d7114a52ae", "parameters": [{ "type": "Signature" }, { "type": "Signature" }], "signatures": { "0387419467127d60bef1e11126cb1311609319e7a50c04781950498355cb8e1ad6": "f02796e55c82657e3f363ac9a3bf1a74e64a45f2051e9437a856d279a6febde38efaad0e038f37cccd7796acce9441852916cf056945ff0fd640497d7d3baf24" } } } }
能夠看到根據最小簽名的多少,會有相應數量的Signature,咱們對這個signature進行分析,發現這個signatures相互之間沒有關聯,徹底是私鑰對hex字段的內容進行簽名獲得的。
因而遍歷輸入的Wif私鑰,輪流對交易添加簽名:
for (int j = 0; j < wifsList.Items.Count; j++) { var prikey = Wallet.GetPrivateKeyFromWIF(wifsList.Items[j].ToString()); KeyPair key = new KeyPair(prikey); WalletAccount account = plugin_multisign.api.CurrentWallet.GetAccount(context.ScriptHashes[0]); byte[] signature = context.Verifiable.Sign(key); context.AddSignature(account.Contract, key.PublicKey, signature); }
因而就能夠實現多簽了。
針對應用合約的多籤於此有點不一樣,在調用合約的時候,首先會對合約中input的來源數量與簽名數量進行驗證:
if (hashes.Length != verifiable.Scripts.Length) return false;
這樣代碼位置在neo/Core/Helper.cs中,用在合約驗證的時候。
hashes是經過input獲取的地址哈希列表:
/// <summary> /// 獲取須要校驗的腳本散列值 /// </summary> /// <returns>返回須要校驗的腳本散列值</returns> public virtual UInt160[] GetScriptHashesForVerifying() { if (References == null) throw new InvalidOperationException(); HashSet<UInt160> hashes = new HashSet<UInt160>(Inputs.Select(p => References[p].ScriptHash)); hashes.UnionWith(Attributes.Where(p => p.Usage == TransactionAttributeUsage.Script).Select(p => new UInt160(p.Data))); foreach (var group in Outputs.GroupBy(p => p.AssetId)) { AssetState asset = Blockchain.Default.GetAssetState(group.Key); if (asset == null) throw new InvalidOperationException(); if (asset.AssetType.HasFlag(AssetType.DutyFlag)) { hashes.UnionWith(group.Select(p => p.ScriptHash)); } } return hashes.OrderBy(p => p).ToArray(); }
verifiable.Scripts則是添加的簽名數量,也就是說若是想參與多籤,那麼就須要添加相應帳戶的input,這也就意味着,若是這個帳戶裏沒有資產,或者是一個新的帳戶,那麼就沒法參與多籤。 所以在構造合約多籤的時候,我經過遍歷wif,對每一個wif帳戶都添加了input:
List<TransactionInput> list_inputs = new List<TransactionInput>(); List<TransactionOutput> list_outputs = new List<TransactionOutput>(); foreach (var prikey in prikeys) { byte[] pubkey = ThinNeo.Helper.GetPublicKeyFromPrivateKey(prikey); string address = ThinNeo.Helper.GetAddressFromPublicKey(pubkey); Dictionary<string, List<Utxo>> dir = await Helper.GetBalanceByAddress(Config.api, address); if (dir.ContainsKey(Nep55_1.id_GAS) == false) { Console.WriteLine("no gas"); return null; } TransactionInput input = new TransactionInput(); input.hash = dir[Nep55_1.id_GAS][0].txid; input.index = (ushort)dir[Nep55_1.id_GAS][0].n; list_inputs.Add(input); TransactionOutput outputchange = new TransactionOutput(); outputchange.toAddress = ThinNeo.Helper.GetPublicKeyHashFromAddress(address); outputchange.value = dir[Nep55_1.id_GAS][0].value; outputchange.assetId = new Hash256(Nep55_1.id_GAS); list_outputs.Add(outputchange); }