Skip to content

Commit

Permalink
Merge branch 'master' into fix/removes-xunit-for-good
Browse files Browse the repository at this point in the history
  • Loading branch information
cschuchardt88 authored Feb 19, 2025
2 parents 3830751 + fdd852e commit cec1d60
Show file tree
Hide file tree
Showing 24 changed files with 852 additions and 140 deletions.
180 changes: 145 additions & 35 deletions src/Neo/Cryptography/Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Utilities.Encoders;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using ECPoint = Neo.Cryptography.ECC.ECPoint;
Expand All @@ -27,22 +29,15 @@ namespace Neo.Cryptography
/// </summary>
public static class Crypto
{
private static readonly BigInteger s_prime = new(1,
Hex.Decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"));

private static readonly ECDsaCache CacheECDsa = new();
private static readonly bool IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private static readonly ECCurve secP256k1 = ECCurve.CreateFromFriendlyName("secP256k1");
private static readonly X9ECParameters bouncySecp256k1 = SecNamedCurves.GetByName("secp256k1");
private static readonly X9ECParameters bouncySecp256r1 = SecNamedCurves.GetByName("secp256r1");

/// <summary>
/// Holds domain parameters for Secp256r1 elliptic curve.
/// </summary>
private static readonly ECDomainParameters secp256r1DomainParams = new ECDomainParameters(bouncySecp256r1.Curve, bouncySecp256r1.G, bouncySecp256r1.N, bouncySecp256r1.H);

/// <summary>
/// Holds domain parameters for Secp256k1 elliptic curve.
/// </summary>
private static readonly ECDomainParameters secp256k1DomainParams = new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H);

/// <summary>
/// Calculates the 160-bit hash value of the specified message.
/// </summary>
Expand Down Expand Up @@ -89,18 +84,11 @@ public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = n
{
if (hashAlgorithm == HashAlgorithm.Keccak256 || (IsOSX && ecCurve == ECC.ECCurve.Secp256k1))
{
var domain =
ecCurve == null || ecCurve == ECC.ECCurve.Secp256r1 ? secp256r1DomainParams :
ecCurve == ECC.ECCurve.Secp256k1 ? secp256k1DomainParams :
throw new NotSupportedException(nameof(ecCurve));
var signer = new ECDsaSigner();
var privateKey = new BigInteger(1, priKey);
var priKeyParameters = new ECPrivateKeyParameters(privateKey, domain);
var priKeyParameters = new ECPrivateKeyParameters(privateKey, ecCurve.BouncyCastleDomainParams);
signer.Init(true, priKeyParameters);
var messageHash =
hashAlgorithm == HashAlgorithm.SHA256 ? message.Sha256() :
hashAlgorithm == HashAlgorithm.Keccak256 ? message.Keccak256() :
throw new NotSupportedException(nameof(hashAlgorithm));
var messageHash = GetMessageHash(message, hashAlgorithm);
var signature = signer.GenerateSignature(messageHash);

var signatureBytes = new byte[64];
Expand Down Expand Up @@ -157,30 +145,17 @@ public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte

if (hashAlgorithm == HashAlgorithm.Keccak256 || (IsOSX && pubkey.Curve == ECC.ECCurve.Secp256k1))
{
var domain =
pubkey.Curve == ECC.ECCurve.Secp256r1 ? secp256r1DomainParams :
pubkey.Curve == ECC.ECCurve.Secp256k1 ? secp256k1DomainParams :
throw new NotSupportedException(nameof(pubkey.Curve));
var curve =
pubkey.Curve == ECC.ECCurve.Secp256r1 ? bouncySecp256r1.Curve :
bouncySecp256k1.Curve;

var point = curve.CreatePoint(
var point = pubkey.Curve.BouncyCastleCurve.Curve.CreatePoint(
new BigInteger(pubkey.X.Value.ToString()),
new BigInteger(pubkey.Y.Value.ToString()));
var pubKey = new ECPublicKeyParameters("ECDSA", point, domain);
var pubKey = new ECPublicKeyParameters("ECDSA", point, pubkey.Curve.BouncyCastleDomainParams);
var signer = new ECDsaSigner();
signer.Init(false, pubKey);

var sig = signature.ToArray();
var r = new BigInteger(1, sig, 0, 32);
var s = new BigInteger(1, sig, 32, 32);

var messageHash =
hashAlgorithm == HashAlgorithm.SHA256 ? message.Sha256() :
hashAlgorithm == HashAlgorithm.Keccak256 ? message.Keccak256() :
throw new NotSupportedException(nameof(hashAlgorithm));

var messageHash = GetMessageHash(message, hashAlgorithm);
return signer.VerifySignature(messageHash, r, s);
}

Expand Down Expand Up @@ -249,5 +224,140 @@ public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte
{
return VerifySignature(message, signature, ECPoint.DecodePoint(pubkey, curve), hashAlgorithm);
}

/// <summary>
/// Get hash from message.
/// </summary>
/// <param name="message">Original message</param>
/// <param name="hashAlgorithm">The hash algorithm to be used hash the message, the default is SHA256.</param>
/// <returns>Hashed message</returns>
public static byte[] GetMessageHash(byte[] message, HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256)
{
return hashAlgorithm switch
{
HashAlgorithm.SHA256 => message.Sha256(),
HashAlgorithm.Keccak256 => message.Keccak256(),
_ => throw new NotSupportedException(nameof(hashAlgorithm))
};
}

/// <summary>
/// Get hash from message.
/// </summary>
/// <param name="message">Original message</param>
/// <param name="hashAlgorithm">The hash algorithm to be used hash the message, the default is SHA256.</param>
/// <returns>Hashed message</returns>
public static byte[] GetMessageHash(ReadOnlySpan<byte> message, HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256)
{
return hashAlgorithm switch
{
HashAlgorithm.SHA256 => message.Sha256(),
HashAlgorithm.Keccak256 => message.Keccak256(),
_ => throw new NotSupportedException(nameof(hashAlgorithm))
};
}

/// <summary>
/// Recovers the public key from a signature and message hash.
/// </summary>
/// <param name="signature">Signature, either 65 bytes (r[32] || s[32] || v[1]) or
/// 64 bytes in “compact” form (r[32] || yParityAndS[32]).</param>
/// <param name="hash">32-byte message hash</param>
/// <returns>The recovered public key</returns>
/// <exception cref="ArgumentException">Thrown if signature or hash is invalid</exception>
public static ECC.ECPoint ECRecover(byte[] signature, byte[] hash)
{
if (signature.Length != 65 && signature.Length != 64)
throw new ArgumentException("Signature must be 65 or 64 bytes", nameof(signature));
if (hash.Length != 32)
throw new ArgumentException("Message hash must be 32 bytes", nameof(hash));

try
{
// Extract (r, s) and compute integer recId
BigInteger r, s;
int recId;

if (signature.Length == 65)
{
// Format: r[32] || s[32] || v[1]
r = new BigInteger(1, [.. signature.Take(32)]);
s = new BigInteger(1, [.. signature.Skip(32).Take(32)]);

// v could be 0..3 or 27..30 (Ethereum style).
var v = signature[64];
recId = v >= 27 ? v - 27 : v; // normalize
if (recId < 0 || recId > 3)
throw new ArgumentException("Recovery value must be in [0..3] after normalization.", nameof(signature));
}
else
{
// 64 bytes “compact” format: r[32] || yParityAndS[32]
// yParity is fused into the top bit of s.

r = new BigInteger(1, [.. signature.Take(32)]);
var yParityAndS = new BigInteger(1, signature.Skip(32).ToArray());

// Mask out top bit to get s
var mask = BigInteger.One.ShiftLeft(255).Subtract(BigInteger.One);
s = yParityAndS.And(mask);

// Extract yParity (0 or 1)
var yParity = yParityAndS.TestBit(255);

// For “compact,” map parity to recId in [0..1].
// For typical usage, recId in {0,1} is enough:
recId = yParity ? 1 : 0;
}

// Decompose recId into i = recId >> 1 and yBit = recId & 1
var iPart = recId >> 1; // usually 0..1
var yBit = (recId & 1) == 1;

// BouncyCastle curve constants
var n = ECC.ECCurve.Secp256k1.BouncyCastleCurve.N;
var e = new BigInteger(1, hash);

// eInv = -e mod n
var eInv = BigInteger.Zero.Subtract(e).Mod(n);
// rInv = (r^-1) mod n
var rInv = r.ModInverse(n);
// srInv = (s * r^-1) mod n
var srInv = rInv.Multiply(s).Mod(n);
// eInvrInv = (eInv * r^-1) mod n
var eInvrInv = rInv.Multiply(eInv).Mod(n);

// x = r + iPart * n
var x = r.Add(BigInteger.ValueOf(iPart).Multiply(n));
// Verify x is within the curve prime
if (x.CompareTo(s_prime) >= 0)
throw new ArgumentException("x is out of range of the secp256k1 prime.", nameof(signature));

// Decompress to get R
var decompressedRKey = DecompressKey(ECC.ECCurve.Secp256k1.BouncyCastleCurve.Curve, x, yBit);
// Check that R is on curve
if (!decompressedRKey.Multiply(n).IsInfinity)
throw new ArgumentException("R point is not valid on this curve.", nameof(signature));

// Q = (eInv * G) + (srInv * R)
var q = Org.BouncyCastle.Math.EC.ECAlgorithms.SumOfTwoMultiplies(
ECC.ECCurve.Secp256k1.BouncyCastleCurve.G, eInvrInv,
decompressedRKey, srInv);

return ECPoint.FromBytes(q.Normalize().GetEncoded(false), ECC.ECCurve.Secp256k1);
}
catch (Exception ex)
{
throw new ArgumentException("Invalid signature parameters", nameof(signature), ex);
}
}

private static Org.BouncyCastle.Math.EC.ECPoint DecompressKey(
Org.BouncyCastle.Math.EC.ECCurve curve, BigInteger xBN, bool yBit)
{
var compEnc = X9IntegerConverter.IntegerToBytes(xBN, 1 + X9IntegerConverter.GetByteLength(curve));
compEnc[0] = (byte)(yBit ? 0x03 : 0x02);
return curve.DecodePoint(compEnc);
}
}
}
16 changes: 13 additions & 3 deletions src/Neo/Cryptography/ECC/ECCurve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// modifications are permitted.

using Neo.Extensions;
using Org.BouncyCastle.Crypto.Parameters;
using System.Globalization;
using System.Numerics;

Expand All @@ -33,9 +34,14 @@ public class ECCurve
/// </summary>
public readonly ECPoint G;

public readonly Org.BouncyCastle.Asn1.X9.X9ECParameters BouncyCastleCurve;
/// <summary>
/// Holds domain parameters for Secp256r1 elliptic curve.
/// </summary>
public readonly ECDomainParameters BouncyCastleDomainParams;
internal readonly int ExpectedECPointLength;

private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G)
private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G, string curveName)
{
this.Q = Q;
ExpectedECPointLength = ((int)VM.Utility.GetBitLength(Q) + 7) / 8;
Expand All @@ -44,6 +50,8 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
this.N = N;
Infinity = new ECPoint(null, null, this);
this.G = ECPoint.DecodePoint(G, this);
BouncyCastleCurve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName(curveName);
BouncyCastleDomainParams = new ECDomainParameters(BouncyCastleCurve.Curve, BouncyCastleCurve.G, BouncyCastleCurve.N, BouncyCastleCurve.H);
}

/// <summary>
Expand All @@ -55,7 +63,8 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
BigInteger.Zero,
7,
BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", NumberStyles.AllowHexSpecifier),
("04" + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8").HexToBytes()
("04" + "79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" + "483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8").HexToBytes(),
"secp256k1"
);

/// <summary>
Expand All @@ -67,7 +76,8 @@ private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G
BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", NumberStyles.AllowHexSpecifier),
BigInteger.Parse("005AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", NumberStyles.AllowHexSpecifier),
BigInteger.Parse("00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", NumberStyles.AllowHexSpecifier),
("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes()
("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes(),
"secp256r1"
);
}
}
2 changes: 1 addition & 1 deletion src/Neo/Cryptography/HashAlgorithm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ public enum HashAlgorithm : byte
/// <summary>
/// The Keccak256 hash algorithm.
/// </summary>
Keccak256 = 0x01,
Keccak256 = 0x01
}
}
4 changes: 1 addition & 3 deletions src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ public override int GetHashCode()

protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expressions = DeserializeConditions(ref reader, maxNestDepth - 1);
Expressions = DeserializeConditions(ref reader, maxNestDepth);
if (Expressions.Length == 0) throw new FormatException();
}

Expand All @@ -78,7 +77,6 @@ protected override void SerializeWithoutType(BinaryWriter writer)

private protected override void ParseJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
JArray expressions = (JArray)json["expressions"];
if (expressions.Count > MaxSubitems) throw new FormatException();
Expressions = expressions.Select(p => FromJson((JObject)p, maxNestDepth - 1)).ToArray();
Expand Down
2 changes: 0 additions & 2 deletions src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public override int GetHashCode()

protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expression = DeserializeFrom(ref reader, maxNestDepth - 1);
}

Expand All @@ -76,7 +75,6 @@ protected override void SerializeWithoutType(BinaryWriter writer)

private protected override void ParseJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expression = FromJson((JObject)json["expression"], maxNestDepth - 1);
}

Expand Down
4 changes: 1 addition & 3 deletions src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ public override int GetHashCode()

protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
Expressions = DeserializeConditions(ref reader, maxNestDepth - 1);
Expressions = DeserializeConditions(ref reader, maxNestDepth);
if (Expressions.Length == 0) throw new FormatException();
}

Expand All @@ -78,7 +77,6 @@ protected override void SerializeWithoutType(BinaryWriter writer)

private protected override void ParseJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
JArray expressions = (JArray)json["expressions"];
if (expressions.Count > MaxSubitems) throw new FormatException();
Expressions = expressions.Select(p => FromJson((JObject)p, maxNestDepth - 1)).ToArray();
Expand Down
6 changes: 4 additions & 2 deletions src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace Neo.Network.P2P.Payloads.Conditions
public abstract class WitnessCondition : IInteroperable, ISerializable
{
internal const int MaxSubitems = 16;
internal const int MaxNestingDepth = 2;
internal const int MaxNestingDepth = 3;

/// <summary>
/// The type of the <see cref="WitnessCondition"/>.
Expand Down Expand Up @@ -54,7 +54,7 @@ protected static WitnessCondition[] DeserializeConditions(ref MemoryReader reade
{
WitnessCondition[] conditions = new WitnessCondition[reader.ReadVarInt(MaxSubitems)];
for (int i = 0; i < conditions.Length; i++)
conditions[i] = DeserializeFrom(ref reader, maxNestDepth);
conditions[i] = DeserializeFrom(ref reader, maxNestDepth - 1);
return conditions;
}

Expand All @@ -66,6 +66,7 @@ protected static WitnessCondition[] DeserializeConditions(ref MemoryReader reade
/// <returns>The deserialized <see cref="WitnessCondition"/>.</returns>
public static WitnessCondition DeserializeFrom(ref MemoryReader reader, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
WitnessConditionType type = (WitnessConditionType)reader.ReadByte();
if (ReflectionCache<WitnessConditionType>.CreateInstance(type) is not WitnessCondition condition)
throw new FormatException();
Expand Down Expand Up @@ -109,6 +110,7 @@ void ISerializable.Serialize(BinaryWriter writer)
/// <returns>The converted <see cref="WitnessCondition"/>.</returns>
public static WitnessCondition FromJson(JObject json, int maxNestDepth)
{
if (maxNestDepth <= 0) throw new FormatException();
WitnessConditionType type = Enum.Parse<WitnessConditionType>(json["type"].GetString());
if (ReflectionCache<WitnessConditionType>.CreateInstance(type) is not WitnessCondition condition)
throw new FormatException("Invalid WitnessConditionType.");
Expand Down
Loading

0 comments on commit cec1d60

Please sign in to comment.