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