NEO多签工具

0x00 前言

目前已写好的NEO多签分为两种,一种是基于NEL-GUI的GUI多签工具,仅支持对鉴权合约的多签工作;另一种是基于NEL-ThinSDK-cs的请钱包多签工具,终端操作,支持鉴权合约和应用合约的多签工作。本文将分为两大部分,第一部分介绍两个工具的使用方法,第二部分介绍技术实现细节。

0x01 NEL-GUI多签

这是第一部分的第一小结,介绍通过NEL-GUI进行鉴权合约的多签,这个工具的目的主要是方便对多签合约账户的转账操作。

首先下载NEL-GUI,也就是 God of NEO 李总基于NEO-GUI写的GUI工具。地址:

git clone https://github.com/NewEconoLab/neo-gui-nel.git

下载完之用VS打开,项目选择neo-gui:

项目选择

然后启动项目,项目启动成功后会出现与熟悉的NEO-GUI很像的界面,不同的是NEL-GUI在选项卡里多了Plugin选项:

Plugin选项卡

这个插件选项里面就是NEL开发的各种小工具了,我写的多签工具也放在这里面。

在Plugin下拉列表种选择多签工具,就可以打开多签工具的界面:

多签界面

如果要对多签合约操作,只需要在合约地址列表种选择多签合约:

选择多签合约

然后在下面的WIF框里添加需要进行签名的私钥就可以了:

私钥导入

最后填好目标账户地址和金额就可以了。

0x02 ThinSDK 多签

这里是第一部分的第二小节,主要介绍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

可以看到这里是执行成功的。为了确定这里是不是真的有效,我们使用错误的私钥再调用一下:

错误的私钥如下:

  • L4ZntdDCocMJi4ozpTw4uTtxtAFNNCP2mX6m3P9CMJN66Dt2YJqP
  • KwEre52ubrRLr1zcTSnEbxM1eTzwqbUn7SVowknJsT9ypu8oTEku

重新调用多签合约:

调用失败

交易id 0x096997ca95011f5a4856b8947fe4a57780aeb8643b0a2515ed0568190962cfc5

经以上测试,多签工具功能正常。

0x03 多签分析

首先分析多签合约账户。

多签合约账户的构造代码如下:

         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);
            }

猜你喜欢

转载自my.oschina.net/u/2276921/blog/1816175