Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add secp256k1 #1419

Merged
merged 18 commits into from
Apr 26, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 40 additions & 12 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,13 +33,22 @@ 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, ReadOnlySpan<byte> pubkey, ECC.ECCurve.Curve curve = ECC.ECCurve.Curve.Secp256r1)
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
if (pubkey.Length == 33 && (pubkey[0] == 0x02 || pubkey[0] == 0x03))
{
try
{
pubkey = ECC.ECPoint.DecodePoint(pubkey, ECC.ECCurve.Secp256r1).EncodePoint(false).AsSpan(1);
switch (curve)
{
case ECC.ECCurve.Curve.Secp256r1:
pubkey = ECC.ECPoint.DecodePoint(pubkey, ECC.ECCurve.Secp256r1).EncodePoint(false).AsSpan(1);
break;
case ECC.ECCurve.Curve.Secp256k1:
pubkey = ECC.ECPoint.DecodePoint(pubkey, ECC.ECCurve.Secp256k1).EncodePoint(false).AsSpan(1);
break;
default: return false;
}
}
catch
{
Expand All @@ -53,17 +63,35 @@ public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte
{
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()
}
}))

switch (curve)
{
return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256);
case ECC.ECCurve.Curve.Secp256r1:
{
var ncurve = ECCurve.NamedCurves.nistP256;

using (var ecdsa = ECDsa.Create(new ECParameters
{
Curve = ncurve,
Q = new ECPoint
{
X = pubkey[..32].ToArray(),
Y = pubkey[32..].ToArray()
}
}))
{
return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256);
}
}
case ECC.ECCurve.Curve.Secp256k1:
{
var publicKey = ECC.ECPoint.FromBytes(pubkey.ToArray(), ECC.ECCurve.Secp256k1);
var r = new BigInteger(signature[..32].ToArray(), true, true);
var s = new BigInteger(signature[32..].ToArray(), true, true);

return ECC.ECCurve.Secp256k1.VerifySignature(message.Sha256(), publicKey, r, s);
}
default: return false;
}
}
}
Expand Down
55 changes: 55 additions & 0 deletions src/neo/Cryptography/ECC/ECCurve.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Globalization;
using System.Numerics;

Expand Down Expand Up @@ -25,6 +26,54 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
this.G = ECPoint.DecodePoint(G, this);
}

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 bool VerifySignature(ReadOnlySpan<byte> message, ECPoint publicKey, BigInteger r, BigInteger s)
{
if (r.Sign < 1 || s.Sign < 1 || r.CompareTo(N) >= 0 || s.CompareTo(N) >= 0)
return false;
BigInteger e = CalculateE(N, message);
BigInteger c = s.ModInverse(N);
BigInteger u1 = (e * c).Mod(N);
BigInteger u2 = (r * c).Mod(N);
ECPoint point = SumOfTwoMultiplies(G, u1, publicKey, u2);
BigInteger v = point.X.Value.Mod(N);
return v.Equals(r);
}

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 static readonly ECCurve Secp256k1 = new ECCurve
(
BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", NumberStyles.AllowHexSpecifier),
Expand All @@ -42,5 +91,11 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
BigInteger.Parse("00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", NumberStyles.AllowHexSpecifier),
("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes()
);

public enum Curve
{
Secp256r1 = 0x00,
Secp256k1 = 0x01
}
}
}
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.ECDsaSecp256r1CheckMultiSig);
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.ECDsaSecp256r1Verify);
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.ECDsaSecp256r1CheckMultiSig)
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.ECDsaSecp256r1Verify)
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);
erikzhang marked this conversation as resolved.
Show resolved Hide resolved

public static readonly InteropDescriptor ECDsaSecp256r1Verify = Register("Neo.Crypto.ECDsa.Secp256r1.Verify", Crypto_ECDsaSecp256r1Verify, 0_01000000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor ECDsaSecp256k1Verify = Register("Neo.Crypto.ECDsa.Secp256k1.Verify", Crypto_ECDsaSecp256k1Verify, 0_01000000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor ECDsaSecp256r1CheckMultiSig = Register("Neo.Crypto.ECDsa.Secp256r1.CheckMultiSig", Crypto_ECDsaSecp256r1CheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor ECDsaSecp256k1CheckMultiSig = Register("Neo.Crypto.ECDsa.Secp256k1.CheckMultiSig", Crypto_ECDsaSecp256k1CheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None);
erikzhang marked this conversation as resolved.
Show resolved Hide resolved

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 ECDsaSecp256r1Verify.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.Curve.Secp256r1);
}

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

private static bool Crypto_ECDsaVerify(ApplicationEngine engine, Cryptography.ECC.ECCurve.Curve 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.Curve.Secp256r1);
}

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

private static bool Crypto_ECDsaCheckMultiSig(ApplicationEngine engine, Cryptography.ECC.ECCurve.Curve 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
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.ECDsaSecp256r1Verify, 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.ECDsaSecp256r1Verify, null, null) * n;
}
else
{
Expand Down
6 changes: 3 additions & 3 deletions tests/neo.UnitTests/Consensus/UT_Consensus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm
Contract originalContract = Contract.CreateMultiSigContract(mockContext.Object.M, mockContext.Object.Validators);
Console.WriteLine($"\nORIGINAL Contract is: {originalContract.ScriptHash}");
Console.WriteLine($"ORIGINAL NextConsensus: {mockContext.Object.Block.NextConsensus}\nENSURING values...");
originalContract.ScriptHash.Should().Be(UInt160.Parse("0x9412c3107a59fa732ccd94866976f7bbb3d9c372"));
mockContext.Object.Block.NextConsensus.Should().Be(UInt160.Parse("0x9412c3107a59fa732ccd94866976f7bbb3d9c372"));
originalContract.ScriptHash.Should().Be(UInt160.Parse("0x7ab841144dcdbf228ff57f7068f795e2afd1a3c1"));
mockContext.Object.Block.NextConsensus.Should().Be(UInt160.Parse("0x7ab841144dcdbf228ff57f7068f795e2afd1a3c1"));

Console.WriteLine("\n==========================");
Console.WriteLine("will trigger OnPersistCompleted again with OnStart flag!");
Expand All @@ -175,7 +175,7 @@ public void ConsensusService_SingleNodeActors_OnStart_PrepReq_PrepResponses_Comm

Console.WriteLine("will create template MakePrepareRequest...");
mockContext.Object.PrevHeader.Timestamp = defaultTimestamp;
mockContext.Object.PrevHeader.NextConsensus.Should().Be(UInt160.Parse("0x9412c3107a59fa732ccd94866976f7bbb3d9c372"));
mockContext.Object.PrevHeader.NextConsensus.Should().Be(UInt160.Parse("0x7ab841144dcdbf228ff57f7068f795e2afd1a3c1"));
var prepReq = mockContext.Object.MakePrepareRequest();
var ppToSend = (PrepareRequest)prepReq.ConsensusMessage;
// Forcing hashes to 0 because mempool is currently shared
Expand Down
25 changes: 25 additions & 0 deletions tests/neo.UnitTests/Cryptography/UT_Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,30 @@ public void TestVerifySignature()
Action action = () => Crypto.VerifySignature(message, signature, wrongKey).Should().BeFalse();
action.Should().Throw<ArgumentException>();
}

[TestMethod]
public void TestSecp256k1()
{
byte[] message = System.Text.Encoding.Default.GetBytes("hello");
byte[] signature = "5331be791532d157df5b5620620d938bcb622ad02c81cfc184c460efdad18e695480d77440c511e9ad02ea30d773cb54e88f8cbb069644aefa283957085f38b5".HexToBytes();
byte[] pubKey = "03ea01cb94bdaf0cd1c01b159d474f9604f4af35a3e2196f6bdfdb33b2aa4961fa".HexToBytes();

Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Curve.Secp256k1)
.Should().BeTrue();

message = System.Text.Encoding.Default.GetBytes("world");
signature = "b1e6ff4f40536fb7ed706b0f7567903cc227a5241a079fb86f3de51b8321c1e690f37ad0c788848605c1653567935845f0d35a8a1a37174dcbbd235caac8e969".HexToBytes();
pubKey = "03661b86d54eb3a8e7ea2399e0db36ab65753f95fff661da53ae0121278b881ad0".HexToBytes();

Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Curve.Secp256k1)
.Should().BeTrue();

message = System.Text.Encoding.Default.GetBytes("中文");
signature = "b8cba1ff42304d74d083e87706058f59cdd4f755b995926d2cd80a734c5a3c37e4583bfd4339ac762c1c91eee3782660a6baf62cd29e407eccd3da3e9de55a02".HexToBytes();
pubKey = "03661b86d54eb3a8e7ea2399e0db36ab65753f95fff661da53ae0121278b881ad0".HexToBytes();

Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Curve.Secp256k1)
.Should().BeTrue();
}
}
}
6 changes: 3 additions & 3 deletions tests/neo.UnitTests/Ledger/UT_Blockchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ public void TestContainsTransaction()
[TestMethod]
public void TestGetCurrentBlockHash()
{
Blockchain.Singleton.CurrentBlockHash.Should().Be(UInt256.Parse("0xdba446947a90b2862ef050703b44828ad8b02d11978f8ef59bd3e1c97aabf6e5"));
Blockchain.Singleton.CurrentBlockHash.Should().Be(UInt256.Parse("0xc9387b803c8b4c6c1f69f6c876ed7848482c414b0225eb2a3a5395af39425455"));
}

[TestMethod]
public void TestGetCurrentHeaderHash()
{
Blockchain.Singleton.CurrentHeaderHash.Should().Be(UInt256.Parse("0xdba446947a90b2862ef050703b44828ad8b02d11978f8ef59bd3e1c97aabf6e5"));
Blockchain.Singleton.CurrentHeaderHash.Should().Be(UInt256.Parse("0xc9387b803c8b4c6c1f69f6c876ed7848482c414b0225eb2a3a5395af39425455"));
}

[TestMethod]
Expand All @@ -88,7 +88,7 @@ public void TestGetBlock()
[TestMethod]
public void TestGetBlockHash()
{
Blockchain.Singleton.GetBlockHash(0).Should().Be(UInt256.Parse("0xdba446947a90b2862ef050703b44828ad8b02d11978f8ef59bd3e1c97aabf6e5"));
Blockchain.Singleton.GetBlockHash(0).Should().Be(UInt256.Parse("0xc9387b803c8b4c6c1f69f6c876ed7848482c414b0225eb2a3a5395af39425455"));
Blockchain.Singleton.GetBlockHash(10).Should().BeNull();
}

Expand Down
Loading