目前已写好的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); }