Skip to content

Commit

Permalink
Add standard cryptography primitives (neo-project#1419)
Browse files Browse the repository at this point in the history
  • Loading branch information
shargon committed Apr 26, 2020
1 parent 735bd07 commit 1af9369
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 78 deletions.
5 changes: 2 additions & 3 deletions src/neo/Consensus/ConsensusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,7 @@ private void OnCommitReceived(ConsensusPayload payload, Commit commit)
{
existingCommitPayload = payload;
}
else if (Crypto.VerifySignature(hashData, commit.Signature,
context.Validators[payload.ValidatorIndex].EncodePoint(false)))
else if (Crypto.VerifySignature(hashData, commit.Signature, context.Validators[payload.ValidatorIndex]))
{
existingCommitPayload = payload;
CheckCommits();
Expand Down Expand Up @@ -433,7 +432,7 @@ private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest m
byte[] hashData = context.EnsureHeader().GetHashData();
for (int i = 0; i < context.CommitPayloads.Length; i++)
if (context.CommitPayloads[i]?.ConsensusMessage.ViewNumber == context.ViewNumber)
if (!Crypto.VerifySignature(hashData, context.CommitPayloads[i].GetDeserializedMessage<Commit>().Signature, context.Validators[i].EncodePoint(false)))
if (!Crypto.VerifySignature(hashData, context.CommitPayloads[i].GetDeserializedMessage<Commit>().Signature, context.Validators[i]))
context.CommitPayloads[i] = null;

if (context.TransactionHashes.Length == 0)
Expand Down
78 changes: 58 additions & 20 deletions src/neo/Cryptography/Crypto.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Numerics;
using System.Security.Cryptography;

namespace Neo.Cryptography
Expand Down Expand Up @@ -32,38 +33,75 @@ public static byte[] Sign(byte[] message, byte[] prikey, byte[] pubkey)
}
}

public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ReadOnlySpan<byte> pubkey)
public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ECC.ECPoint pubkey)
{
if (pubkey.Length == 33 && (pubkey[0] == 0x02 || pubkey[0] == 0x03))
if (pubkey.Curve == ECC.ECCurve.Secp256r1)
{
try
byte[] buffer = pubkey.EncodePoint(false);
using (var ecdsa = ECDsa.Create(new ECParameters
{
pubkey = ECC.ECPoint.DecodePoint(pubkey, ECC.ECCurve.Secp256r1).EncodePoint(false).AsSpan(1);
}
catch
Curve = ECCurve.NamedCurves.nistP256,
Q = new ECPoint
{
X = buffer[1..33],
Y = buffer[33..]
}
}))
{
return false;
return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256);
}
}
else if (pubkey.Length == 65 && pubkey[0] == 0x04)
{
pubkey = pubkey[1..];
}
else if (pubkey.Length != 64)
else
{
throw new ArgumentException();
var ecdsa = new ECC.ECDsa(pubkey);
var r = new BigInteger(signature[..32], true, true);
var s = new BigInteger(signature[32..], true, true);
return ecdsa.VerifySignature(message.Sha256(), r, s);
}
using (var ecdsa = ECDsa.Create(new ECParameters
}

public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ReadOnlySpan<byte> pubkey, ECC.ECCurve curve)
{
if (curve == ECC.ECCurve.Secp256r1)
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new ECPoint
if (pubkey.Length == 33 && (pubkey[0] == 0x02 || pubkey[0] == 0x03))
{
X = pubkey[..32].ToArray(),
Y = pubkey[32..].ToArray()
try
{
pubkey = ECC.ECPoint.DecodePoint(pubkey, curve).EncodePoint(false).AsSpan(1);
}
catch
{
return false;
}
}
}))
else if (pubkey.Length == 65 && pubkey[0] == 0x04)
{
pubkey = pubkey[1..];
}
else
{
throw new ArgumentException();
}
using (var ecdsa = ECDsa.Create(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256,
Q = new ECPoint
{
X = pubkey[..32].ToArray(),
Y = pubkey[32..].ToArray()
}
}))
{
return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256);
}
}
else
{
return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256);
var ecdsa = new ECC.ECDsa(ECC.ECPoint.DecodePoint(pubkey, curve));
var r = new BigInteger(signature[..32], true, true);
var s = new BigInteger(signature[32..], true, true);
return ecdsa.VerifySignature(message.Sha256(), r, s);
}
}
}
Expand Down
107 changes: 107 additions & 0 deletions src/neo/Cryptography/ECC/ECDsa.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Numerics;
using System.Security.Cryptography;

namespace Neo.Cryptography.ECC
{
public class ECDsa
{
private readonly byte[] privateKey;
private readonly ECPoint publicKey;
private readonly ECCurve curve;

public ECDsa(byte[] privateKey, ECCurve curve)
: this(curve.G * privateKey)
{
this.privateKey = privateKey;
}

public ECDsa(ECPoint publicKey)
{
this.publicKey = publicKey;
this.curve = publicKey.Curve;
}

private BigInteger CalculateE(BigInteger n, ReadOnlySpan<byte> message)
{
int messageBitLength = message.Length * 8;
BigInteger trunc = new BigInteger(message, isUnsigned: true, isBigEndian: true);
if (n.GetBitLength() < messageBitLength)
{
trunc >>= messageBitLength - n.GetBitLength();
}
return trunc;
}

public BigInteger[] GenerateSignature(ReadOnlySpan<byte> message)
{
if (privateKey == null) throw new InvalidOperationException();
BigInteger e = CalculateE(curve.N, message);
BigInteger d = new BigInteger(privateKey, isUnsigned: true, isBigEndian: true);
BigInteger r, s;
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
do
{
BigInteger k;
do
{
do
{
k = rng.NextBigInteger(curve.N.GetBitLength());
}
while (k.Sign == 0 || k.CompareTo(curve.N) >= 0);
ECPoint p = ECPoint.Multiply(curve.G, k);
BigInteger x = p.X.Value;
r = x.Mod(curve.N);
}
while (r.Sign == 0);
s = (k.ModInverse(curve.N) * (e + d * r)).Mod(curve.N);
if (s > curve.N / 2)
{
s = curve.N - s;
}
}
while (s.Sign == 0);
}
return new BigInteger[] { r, s };
}

private static ECPoint SumOfTwoMultiplies(ECPoint P, BigInteger k, ECPoint Q, BigInteger l)
{
int m = Math.Max(k.GetBitLength(), l.GetBitLength());
ECPoint Z = P + Q;
ECPoint R = P.Curve.Infinity;
for (int i = m - 1; i >= 0; --i)
{
R = R.Twice();
if (k.TestBit(i))
{
if (l.TestBit(i))
R = R + Z;
else
R = R + P;
}
else
{
if (l.TestBit(i))
R = R + Q;
}
}
return R;
}

public bool VerifySignature(ReadOnlySpan<byte> message, BigInteger r, BigInteger s)
{
if (r.Sign < 1 || s.Sign < 1 || r.CompareTo(curve.N) >= 0 || s.CompareTo(curve.N) >= 0)
return false;
BigInteger e = CalculateE(curve.N, message);
BigInteger c = s.ModInverse(curve.N);
BigInteger u1 = (e * c).Mod(curve.N);
BigInteger u2 = (r * c).Mod(curve.N);
ECPoint point = SumOfTwoMultiplies(curve.G, u1, publicKey, u2);
BigInteger v = point.X.Value.Mod(curve.N);
return v.Equals(r);
}
}
}
4 changes: 2 additions & 2 deletions src/neo/SmartContract/Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public static byte[] CreateMultiSigRedeemScript(int m, params ECPoint[] publicKe
}
sb.EmitPush(publicKeys.Length);
sb.Emit(OpCode.PUSHNULL);
sb.EmitSysCall(InteropService.Crypto.ECDsaCheckMultiSig);
sb.EmitSysCall(InteropService.Crypto.CheckMultisigWithECDsaSecp256r1);
return sb.ToArray();
}
}
Expand All @@ -102,7 +102,7 @@ public static byte[] CreateSignatureRedeemScript(ECPoint publicKey)
{
sb.EmitPush(publicKey.EncodePoint(true));
sb.Emit(OpCode.PUSHNULL);
sb.EmitSysCall(InteropService.Crypto.ECDsaVerify);
sb.EmitSysCall(InteropService.Crypto.VerifyWithECDsaSecp256r1);
return sb.ToArray();
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/neo/SmartContract/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private static bool IsMultiSigContract(byte[] script, out int m, out int n, List
if (script[i++] != (byte)OpCode.PUSHNULL) return false;
if (script[i++] != (byte)OpCode.SYSCALL) return false;
if (script.Length != i + 4) return false;
if (BitConverter.ToUInt32(script, i) != InteropService.Crypto.ECDsaCheckMultiSig)
if (BitConverter.ToUInt32(script, i) != InteropService.Crypto.CheckMultisigWithECDsaSecp256r1)
return false;
return true;
}
Expand All @@ -104,7 +104,7 @@ public static bool IsSignatureContract(this byte[] script)
|| script[1] != 33
|| script[35] != (byte)OpCode.PUSHNULL
|| script[36] != (byte)OpCode.SYSCALL
|| BitConverter.ToUInt32(script, 37) != InteropService.Crypto.ECDsaVerify)
|| BitConverter.ToUInt32(script, 37) != InteropService.Crypto.VerifyWithECDsaSecp256r1)
return false;
return true;
}
Expand Down
52 changes: 45 additions & 7 deletions src/neo/SmartContract/InteropService.Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ partial class InteropService
{
public static class Crypto
{
public static readonly InteropDescriptor ECDsaVerify = Register("Neo.Crypto.ECDsaVerify", Crypto_ECDsaVerify, 0_01000000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor ECDsaCheckMultiSig = Register("Neo.Crypto.ECDsaCheckMultiSig", Crypto_ECDsaCheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor SHA256 = Register("Neo.Crypto.SHA256", Crypto_SHA256, 0_01000000, TriggerType.All, CallFlags.None);

public static readonly InteropDescriptor VerifyWithECDsaSecp256r1 = Register("Neo.Crypto.ECDsa.Secp256r1.Verify", Crypto_ECDsaSecp256r1Verify, 0_01000000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor VerifyWithECDsaSecp256k1 = Register("Neo.Crypto.ECDsa.Secp256k1.Verify", Crypto_ECDsaSecp256k1Verify, 0_01000000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor CheckMultisigWithECDsaSecp256r1 = Register("Neo.Crypto.ECDsa.Secp256r1.CheckMultiSig", Crypto_ECDsaSecp256r1CheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor CheckMultisigWithECDsaSecp256k1 = Register("Neo.Crypto.ECDsa.Secp256k1.CheckMultiSig", Crypto_ECDsaSecp256k1CheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None);

private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack, StoreView snapshot)
{
Expand All @@ -25,10 +29,34 @@ private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack, StoreView
if (item is Array array) n = array.Count;
else n = (int)item.GetBigInteger();
if (n < 1) return 0;
return ECDsaVerify.Price * n;
return VerifyWithECDsaSecp256r1.Price * n;
}

private static bool Crypto_SHA256(ApplicationEngine engine)
{
StackItem item0 = engine.CurrentContext.EvaluationStack.Pop();
ReadOnlySpan<byte> value = item0 switch
{
InteropInterface _interface => _interface.GetInterface<IVerifiable>().GetHashData(),
Null _ => engine.ScriptContainer.GetHashData(),
_ => item0.GetSpan()
};

engine.CurrentContext.EvaluationStack.Push(value.ToArray().Sha256());
return true;
}

private static bool Crypto_ECDsaVerify(ApplicationEngine engine)
private static bool Crypto_ECDsaSecp256r1Verify(ApplicationEngine engine)
{
return Crypto_ECDsaVerify(engine, Cryptography.ECC.ECCurve.Secp256r1);
}

private static bool Crypto_ECDsaSecp256k1Verify(ApplicationEngine engine)
{
return Crypto_ECDsaVerify(engine, Cryptography.ECC.ECCurve.Secp256k1);
}

private static bool Crypto_ECDsaVerify(ApplicationEngine engine, Cryptography.ECC.ECCurve curve)
{
StackItem item0 = engine.CurrentContext.EvaluationStack.Pop();
ReadOnlySpan<byte> message = item0 switch
Expand All @@ -41,7 +69,7 @@ private static bool Crypto_ECDsaVerify(ApplicationEngine engine)
ReadOnlySpan<byte> signature = engine.CurrentContext.EvaluationStack.Pop().GetSpan();
try
{
engine.CurrentContext.EvaluationStack.Push(Cryptography.Crypto.VerifySignature(message, signature, pubkey));
engine.CurrentContext.EvaluationStack.Push(Cryptography.Crypto.VerifySignature(message, signature, pubkey, curve));
}
catch (ArgumentException)
{
Expand All @@ -50,7 +78,17 @@ private static bool Crypto_ECDsaVerify(ApplicationEngine engine)
return true;
}

private static bool Crypto_ECDsaCheckMultiSig(ApplicationEngine engine)
private static bool Crypto_ECDsaSecp256r1CheckMultiSig(ApplicationEngine engine)
{
return Crypto_ECDsaCheckMultiSig(engine, Cryptography.ECC.ECCurve.Secp256r1);
}

private static bool Crypto_ECDsaSecp256k1CheckMultiSig(ApplicationEngine engine)
{
return Crypto_ECDsaCheckMultiSig(engine, Cryptography.ECC.ECCurve.Secp256k1);
}

private static bool Crypto_ECDsaCheckMultiSig(ApplicationEngine engine, Cryptography.ECC.ECCurve curve)
{
StackItem item0 = engine.CurrentContext.EvaluationStack.Pop();
ReadOnlySpan<byte> message = item0 switch
Expand Down Expand Up @@ -98,7 +136,7 @@ private static bool Crypto_ECDsaCheckMultiSig(ApplicationEngine engine)
{
for (int i = 0, j = 0; fSuccess && i < m && j < n;)
{
if (Cryptography.Crypto.VerifySignature(message, signatures[i], pubkeys[j]))
if (Cryptography.Crypto.VerifySignature(message, signatures[i], pubkeys[j], curve))
i++;
j++;
if (m - i > n - j)
Expand Down
2 changes: 1 addition & 1 deletion src/neo/SmartContract/Manifest/ContractGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static ContractGroup FromJson(JObject json)
/// <returns>Return true or false</returns>
public bool IsValid(UInt160 hash)
{
return Crypto.VerifySignature(hash.ToArray(), Signature, PubKey.EncodePoint(false));
return Crypto.VerifySignature(hash.ToArray(), Signature, PubKey);
}

public virtual JObject ToJson()
Expand Down
4 changes: 2 additions & 2 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size)
if (witness_script.IsSignatureContract())
{
size += 67 + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null, null);
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.VerifyWithECDsaSecp256r1, null, null);
}
else if (witness_script.IsMultiSigContract(out int m, out int n))
{
Expand All @@ -358,7 +358,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size)
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n;
using (ScriptBuilder sb = new ScriptBuilder())
networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.ECDsaVerify, null, null) * n;
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.VerifyWithECDsaSecp256r1, null, null) * n;
}
else
{
Expand Down
Loading

0 comments on commit 1af9369

Please sign in to comment.