diff --git a/src/AntShares/Consensus/ConsensusService.cs b/src/AntShares/Consensus/ConsensusService.cs
index cc51aa297b..32cc412b58 100644
--- a/src/AntShares/Consensus/ConsensusService.cs
+++ b/src/AntShares/Consensus/ConsensusService.cs
@@ -104,7 +104,7 @@ private void CheckSignatures()
for (int i = 0, j = 0; i < context.Miners.Length && j < context.M; i++)
if (context.Signatures[i] != null)
{
- sc.Add(contract, context.Miners[i], context.Signatures[i]);
+ sc.AddSignature(contract, context.Miners[i], context.Signatures[i]);
j++;
}
sc.Signable.Scripts = sc.GetScripts();
diff --git a/src/AntShares/Core/SignatureContext.cs b/src/AntShares/Core/SignatureContext.cs
deleted file mode 100644
index c4f7263d78..0000000000
--- a/src/AntShares/Core/SignatureContext.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-using AntShares.Cryptography.ECC;
-using AntShares.IO.Json;
-using AntShares.VM;
-using AntShares.Wallets;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-
-namespace AntShares.Core
-{
- ///
- /// 签名上下文
- ///
- public class SignatureContext
- {
- ///
- /// 要签名的数据
- ///
- public readonly ISignable Signable;
- ///
- /// 要验证的脚本散列值
- ///
- public readonly UInt160[] ScriptHashes;
- private readonly byte[][] redeemScripts;
- private readonly Dictionary[] signatures;
- private readonly bool[] completed;
-
- ///
- /// 判断签名是否完成
- ///
- public bool Completed
- {
- get
- {
- return completed.All(p => p);
- }
- }
-
- ///
- /// 对指定的数据构造签名上下文
- ///
- /// 要签名的数据
- public SignatureContext(ISignable signable)
- {
- this.Signable = signable;
- this.ScriptHashes = signable.GetScriptHashesForVerifying();
- this.redeemScripts = new byte[ScriptHashes.Length][];
- this.signatures = new Dictionary[ScriptHashes.Length];
- this.completed = new bool[ScriptHashes.Length];
- }
-
- ///
- /// 添加一个签名
- ///
- /// 该签名所对应的合约
- /// 该签名所对应的公钥
- /// 签名
- /// 返回签名是否已成功添加
- public bool Add(Contract contract, ECPoint pubkey, byte[] signature)
- {
- for (int i = 0; i < ScriptHashes.Length; i++)
- {
- if (ScriptHashes[i] == contract.ScriptHash)
- {
- if (redeemScripts[i] == null)
- redeemScripts[i] = contract.RedeemScript;
- if (signatures[i] == null)
- signatures[i] = new Dictionary();
- if (signatures[i].ContainsKey(pubkey))
- signatures[i][pubkey] = signature;
- else
- signatures[i].Add(pubkey, signature);
- completed[i] |= contract.ParameterList.Length == signatures[i].Count && contract.ParameterList.All(p => p == ContractParameterType.Signature);
- return true;
- }
- }
- return false;
- }
-
- ///
- /// 从指定的json对象中解析出签名上下文
- ///
- /// json对象
- /// 返回上下文
- public static SignatureContext FromJson(JObject json)
- {
- ISignable signable = typeof(SignatureContext).GetTypeInfo().Assembly.CreateInstance(json["type"].AsString()) as ISignable;
- using (MemoryStream ms = new MemoryStream(json["hex"].AsString().HexToBytes(), false))
- using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8))
- {
- signable.DeserializeUnsigned(reader);
- }
- SignatureContext context = new SignatureContext(signable);
- JArray scripts = (JArray)json["scripts"];
- for (int i = 0; i < scripts.Count; i++)
- {
- if (scripts[i] != null)
- {
- context.redeemScripts[i] = scripts[i]["redeem_script"].AsString().HexToBytes();
- context.signatures[i] = new Dictionary();
- JArray sigs = (JArray)scripts[i]["signatures"];
- for (int j = 0; j < sigs.Count; j++)
- {
- ECPoint pubkey = ECPoint.DecodePoint(sigs[j]["pubkey"].AsString().HexToBytes(), ECCurve.Secp256r1);
- byte[] signature = sigs[j]["signature"].AsString().HexToBytes();
- context.signatures[i].Add(pubkey, signature);
- }
- context.completed[i] = scripts[i]["completed"].AsBoolean();
- }
- }
- return context;
- }
-
- ///
- /// 从签名上下文中获得完整签名的合约脚本
- ///
- /// 返回合约脚本
- public Script[] GetScripts()
- {
- if (!Completed) throw new InvalidOperationException();
- Script[] scripts = new Script[signatures.Length];
- for (int i = 0; i < scripts.Length; i++)
- {
- using (ScriptBuilder sb = new ScriptBuilder())
- {
- foreach (byte[] signature in signatures[i].OrderBy(p => p.Key).Select(p => p.Value))
- {
- sb.Push(signature);
- }
- scripts[i] = new Script
- {
- StackScript = sb.ToArray(),
- RedeemScript = redeemScripts[i]
- };
- }
- }
- return scripts;
- }
-
- public static SignatureContext Parse(string value)
- {
- return FromJson(JObject.Parse(value));
- }
-
- ///
- /// 把签名上下文转为json对象
- ///
- /// 返回json对象
- public JObject ToJson()
- {
- JObject json = new JObject();
- json["type"] = Signable.GetType().FullName;
- using (MemoryStream ms = new MemoryStream())
- using (BinaryWriter writer = new BinaryWriter(ms, Encoding.UTF8))
- {
- Signable.SerializeUnsigned(writer);
- writer.Flush();
- json["hex"] = ms.ToArray().ToHexString();
- }
- JArray scripts = new JArray();
- for (int i = 0; i < signatures.Length; i++)
- {
- if (signatures[i] == null)
- {
- scripts.Add(null);
- }
- else
- {
- scripts.Add(new JObject());
- scripts[i]["redeem_script"] = redeemScripts[i].ToHexString();
- JArray sigs = new JArray();
- foreach (var pair in signatures[i])
- {
- JObject signature = new JObject();
- signature["pubkey"] = pair.Key.EncodePoint(true).ToHexString();
- signature["signature"] = pair.Value.ToHexString();
- sigs.Add(signature);
- }
- scripts[i]["signatures"] = sigs;
- scripts[i]["completed"] = completed[i];
- }
- }
- json["scripts"] = scripts;
- return json;
- }
-
- public override string ToString()
- {
- return ToJson().ToString();
- }
- }
-}
diff --git a/src/AntShares/IO/Json/JObject.cs b/src/AntShares/IO/Json/JObject.cs
index 92113d1bb1..e536b74f2d 100644
--- a/src/AntShares/IO/Json/JObject.cs
+++ b/src/AntShares/IO/Json/JObject.cs
@@ -216,7 +216,7 @@ public static implicit operator JObject(double value)
public static implicit operator JObject(string value)
{
- return new JString(value);
+ return value == null ? null : new JString(value);
}
}
}
diff --git a/src/AntShares/Wallets/Contract.cs b/src/AntShares/Wallets/Contract.cs
index f1ea659d80..522bdc55d6 100644
--- a/src/AntShares/Wallets/Contract.cs
+++ b/src/AntShares/Wallets/Contract.cs
@@ -70,6 +70,16 @@ public UInt160 ScriptHash
public int Size => PublicKeyHash.Size + ParameterList.Length.GetVarSize() + ParameterList.Length + RedeemScript.Length.GetVarSize() + RedeemScript.Length;
+ public ContractType Type
+ {
+ get
+ {
+ if (IsStandard) return ContractType.SignatureContract;
+ if (IsMultiSigContract()) return ContractType.MultiSigContract;
+ return ContractType.CustomContract;
+ }
+ }
+
public static Contract Create(UInt160 publicKeyHash, ContractParameterType[] parameterList, byte[] redeemScript)
{
return new Contract
@@ -169,6 +179,54 @@ public override int GetHashCode()
return ScriptHash.GetHashCode();
}
+ private bool IsMultiSigContract()
+ {
+ int m, n = 0;
+ int i = 0;
+ if (RedeemScript.Length < 37) return false;
+ if (RedeemScript[i] > (byte)ScriptOp.OP_16) return false;
+ if (RedeemScript[i] < (byte)ScriptOp.OP_1 && RedeemScript[i] != 1 && RedeemScript[i] != 2) return false;
+ switch (RedeemScript[i])
+ {
+ case 1:
+ m = RedeemScript[++i];
+ ++i;
+ break;
+ case 2:
+ m = BitConverter.ToUInt16(RedeemScript, ++i);
+ i += 2;
+ break;
+ default:
+ m = RedeemScript[i++] - 80;
+ break;
+ }
+ if (m < 1 || m > 1024) return false;
+ while (RedeemScript[i] == 33)
+ {
+ i += 34;
+ if (RedeemScript.Length <= i) return false;
+ ++n;
+ }
+ if (n < m || n > 1024) return false;
+ switch (RedeemScript[i])
+ {
+ case 1:
+ if (n != RedeemScript[++i]) return false;
+ ++i;
+ break;
+ case 2:
+ if (n != BitConverter.ToUInt16(RedeemScript, ++i)) return false;
+ i += 2;
+ break;
+ default:
+ if (n != RedeemScript[i++] - 80) return false;
+ break;
+ }
+ if (RedeemScript[i++] != (byte)ScriptOp.OP_CHECKMULTISIG) return false;
+ if (RedeemScript.Length != i) return false;
+ return true;
+ }
+
///
/// 序列化
///
diff --git a/src/AntShares/Wallets/ContractType.cs b/src/AntShares/Wallets/ContractType.cs
new file mode 100644
index 0000000000..8aa6f5fd60
--- /dev/null
+++ b/src/AntShares/Wallets/ContractType.cs
@@ -0,0 +1,9 @@
+namespace AntShares.Wallets
+{
+ public enum ContractType : byte
+ {
+ SignatureContract,
+ MultiSigContract,
+ CustomContract
+ }
+}
diff --git a/src/AntShares/Wallets/SignatureContext.cs b/src/AntShares/Wallets/SignatureContext.cs
new file mode 100644
index 0000000000..3f70fd56ee
--- /dev/null
+++ b/src/AntShares/Wallets/SignatureContext.cs
@@ -0,0 +1,252 @@
+using AntShares.Core;
+using AntShares.Cryptography.ECC;
+using AntShares.IO.Json;
+using AntShares.VM;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using System.Reflection;
+using System.Text;
+
+namespace AntShares.Wallets
+{
+ ///
+ /// 签名上下文
+ ///
+ public class SignatureContext
+ {
+ ///
+ /// 要签名的数据
+ ///
+ public readonly ISignable Signable;
+ ///
+ /// 要验证的脚本散列值
+ ///
+ public readonly UInt160[] ScriptHashes;
+ private readonly byte[][] redeemScripts;
+ private readonly byte[][][] parameters;
+ private readonly JObject[] temp;
+
+ ///
+ /// 判断签名是否完成
+ ///
+ public bool Completed
+ {
+ get
+ {
+ return parameters.All(p => p != null && p.All(q => q != null));
+ }
+ }
+
+ ///
+ /// 对指定的数据构造签名上下文
+ ///
+ /// 要签名的数据
+ public SignatureContext(ISignable signable)
+ {
+ this.Signable = signable;
+ this.ScriptHashes = signable.GetScriptHashesForVerifying();
+ this.redeemScripts = new byte[ScriptHashes.Length][];
+ this.parameters = new byte[ScriptHashes.Length][][];
+ this.temp = new JObject[ScriptHashes.Length];
+ }
+
+ public bool Add(Contract contract, int index, byte[] parameter)
+ {
+ int i = GetIndex(contract);
+ if (i < 0) return false;
+ if (redeemScripts[i] == null)
+ redeemScripts[i] = contract.RedeemScript;
+ if (parameters[i] == null)
+ parameters[i] = new byte[contract.ParameterList.Length][];
+ parameters[i][index] = parameter;
+ return true;
+ }
+
+ public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature)
+ {
+ if (contract.Type == ContractType.MultiSigContract)
+ {
+ int index = GetIndex(contract);
+ if (index < 0) return false;
+ if (redeemScripts[index] == null)
+ redeemScripts[index] = contract.RedeemScript;
+ if (parameters[index] == null)
+ parameters[index] = new byte[contract.ParameterList.Length][];
+ if (temp[index] == null) temp[index] = new JArray();
+ JArray array = (JArray)temp[index];
+ JObject obj = new JObject();
+ obj["pubkey"] = pubkey.EncodePoint(true).ToHexString();
+ obj["signature"] = signature.ToHexString();
+ array.Add(obj);
+ if (array.Count == contract.ParameterList.Length)
+ {
+ List points = new List();
+ {
+ int i = 0;
+ switch (contract.RedeemScript[i++])
+ {
+ case 1:
+ ++i;
+ break;
+ case 2:
+ i += 2;
+ break;
+ }
+ while (contract.RedeemScript[i++] == 33)
+ {
+ points.Add(ECPoint.DecodePoint(contract.RedeemScript.Skip(i).Take(33).ToArray(), ECCurve.Secp256r1));
+ i += 33;
+ }
+ }
+ Dictionary dic = points.Select((p, i) => new
+ {
+ PublicKey = p,
+ Index = i
+ }).ToDictionary(p => p.PublicKey, p => p.Index);
+ byte[][] sigs = array.Select(p => new
+ {
+ Signature = p["signature"].AsString().HexToBytes(),
+ Index = dic[ECPoint.DecodePoint(p["pubkey"].AsString().HexToBytes(), ECCurve.Secp256r1)]
+ }).OrderBy(p => p.Index).Select(p => p.Signature).ToArray();
+ for (int i = 0; i < sigs.Length; i++)
+ if (!Add(contract, i, sigs[i]))
+ throw new InvalidOperationException();
+ temp[index] = null;
+ }
+ return true;
+ }
+ else
+ {
+ int index = -1;
+ for (int i = 0; i < contract.ParameterList.Length; i++)
+ if (contract.ParameterList[i] == ContractParameterType.Signature)
+ if (index >= 0)
+ throw new NotSupportedException();
+ else
+ index = i;
+ return Add(contract, index, signature);
+ }
+ }
+
+ ///
+ /// 从指定的json对象中解析出签名上下文
+ ///
+ /// json对象
+ /// 返回上下文
+ public static SignatureContext FromJson(JObject json)
+ {
+ ISignable signable = typeof(SignatureContext).GetTypeInfo().Assembly.CreateInstance(json["type"].AsString()) as ISignable;
+ using (MemoryStream ms = new MemoryStream(json["hex"].AsString().HexToBytes(), false))
+ using (BinaryReader reader = new BinaryReader(ms, Encoding.UTF8))
+ {
+ signable.DeserializeUnsigned(reader);
+ }
+ SignatureContext context = new SignatureContext(signable);
+ JArray scripts = (JArray)json["scripts"];
+ for (int i = 0; i < scripts.Count; i++)
+ {
+ if (scripts[i] != null)
+ {
+ context.redeemScripts[i] = scripts[i]["redeem_script"].AsString().HexToBytes();
+ JArray ps = (JArray)scripts[i]["parameters"];
+ context.parameters[i] = new byte[ps.Count][];
+ for (int j = 0; j < ps.Count; j++)
+ {
+ context.parameters[i][j] = ps[j]?.AsString().HexToBytes();
+ }
+ context.temp[i] = scripts[i]["temp"];
+ }
+ }
+ return context;
+ }
+
+ private int GetIndex(Contract contract)
+ {
+ for (int i = 0; i < ScriptHashes.Length; i++)
+ if (ScriptHashes[i].Equals(contract.ScriptHash))
+ return i;
+ return -1;
+ }
+
+ ///
+ /// 从签名上下文中获得完整签名的合约脚本
+ ///
+ /// 返回合约脚本
+ public Script[] GetScripts()
+ {
+ if (!Completed) throw new InvalidOperationException();
+ Script[] scripts = new Script[parameters.Length];
+ for (int i = 0; i < scripts.Length; i++)
+ {
+ using (ScriptBuilder sb = new ScriptBuilder())
+ {
+ foreach (byte[] parameter in parameters[i])
+ {
+ if (parameter.Length <= 2)
+ sb.Push(new BigInteger(parameter));
+ else
+ sb.Push(parameter);
+ }
+ scripts[i] = new Script
+ {
+ StackScript = sb.ToArray(),
+ RedeemScript = redeemScripts[i]
+ };
+ }
+ }
+ return scripts;
+ }
+
+ public static SignatureContext Parse(string value)
+ {
+ return FromJson(JObject.Parse(value));
+ }
+
+ ///
+ /// 把签名上下文转为json对象
+ ///
+ /// 返回json对象
+ public JObject ToJson()
+ {
+ JObject json = new JObject();
+ json["type"] = Signable.GetType().FullName;
+ using (MemoryStream ms = new MemoryStream())
+ using (BinaryWriter writer = new BinaryWriter(ms, Encoding.UTF8))
+ {
+ Signable.SerializeUnsigned(writer);
+ writer.Flush();
+ json["hex"] = ms.ToArray().ToHexString();
+ }
+ JArray scripts = new JArray();
+ for (int i = 0; i < redeemScripts.Length; i++)
+ {
+ if (redeemScripts[i] == null)
+ {
+ scripts.Add(null);
+ }
+ else
+ {
+ scripts.Add(new JObject());
+ scripts[i]["redeem_script"] = redeemScripts[i].ToHexString();
+ JArray ps = new JArray();
+ foreach (byte[] parameter in parameters[i])
+ {
+ ps.Add(parameter?.ToHexString());
+ }
+ scripts[i]["parameters"] = ps;
+ scripts[i]["temp"] = temp[i];
+ }
+ }
+ json["scripts"] = scripts;
+ return json;
+ }
+
+ public override string ToString()
+ {
+ return ToJson().ToString();
+ }
+ }
+}
diff --git a/src/AntShares/Wallets/Wallet.cs b/src/AntShares/Wallets/Wallet.cs
index e1a5624ca0..51c8ea907c 100644
--- a/src/AntShares/Wallets/Wallet.cs
+++ b/src/AntShares/Wallets/Wallet.cs
@@ -724,7 +724,7 @@ public bool Sign(SignatureContext context)
Account account = GetAccountByScriptHash(scriptHash);
if (account == null) continue;
byte[] signature = context.Signable.Sign(account);
- fSuccess |= context.Add(contract, account.PublicKey, signature);
+ fSuccess |= context.AddSignature(contract, account.PublicKey, signature);
}
return fSuccess;
}