Skip to content

Commit

Permalink
Native: extend CryptoLib's verifyWithECDsa with hasher parameter
Browse files Browse the repository at this point in the history
A port of
nspcc-dev/neo-go@1e2b438.

This commit contains minor protocol extension needed for custom
Koblitz-based verification scripts (an alternative to
#3205).

Replace native CryptoLib's verifyWithECDsa `curve` parameter by
`curveHash` parameter which is a enum over supported pairs of named
curves and hash functions.

Even though this change is a compatible extension of the protocol, it
changes the genesis state due to parameter renaming (CryptoLib's
manifest is changed). But we're going to resync chain in 3.7 release
anyway, so it's not a big deal.

Also, we need to check mainnet and testnet compatibility in case if
anyone has ever called verifyWithECDsa with 24 or 25 `curve` value.

Signed-off-by: Anna Shaleva <[email protected]>
  • Loading branch information
AnnaShaleva committed May 3, 2024
1 parent 429a081 commit 0470483
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 40 deletions.
61 changes: 45 additions & 16 deletions src/Neo/Cryptography/Crypto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static class Crypto
private static readonly bool IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
private static readonly ECCurve secP256k1 = ECCurve.CreateFromFriendlyName("secP256k1");
private static readonly X9ECParameters bouncySecp256k1 = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
private static readonly X9ECParameters bouncySecp256r1 = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1");

/// <summary>
/// Calculates the 160-bit hash value of the specified message.
Expand All @@ -50,22 +51,30 @@ public static byte[] Hash256(ReadOnlySpan<byte> message)
}

/// <summary>
/// Signs the specified message using the ECDSA algorithm.
/// Signs the specified message using the ECDSA algorithm and specified hash algorithm.
/// </summary>
/// <param name="message">The message to be signed.</param>
/// <param name="priKey">The private key to be used.</param>
/// <param name="ecCurve">The <see cref="ECC.ECCurve"/> curve of the signature, default is <see cref="ECC.ECCurve.Secp256r1"/>.</param>
/// <param name="hasher">The hash algorithm to hash the message, default is SHA256.</param>
/// <returns>The ECDSA signature for the specified message.</returns>
public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = null)
public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = null, Hasher hasher = Hasher.SHA256)
{
if (IsOSX && ecCurve == ECC.ECCurve.Secp256k1)
if (hasher == Hasher.Keccak256 || (IsOSX && ecCurve == ECC.ECCurve.Secp256k1))
{
var domain = new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H);
var domain =
ecCurve == null || ecCurve == ECC.ECCurve.Secp256r1 ? new ECDomainParameters(bouncySecp256r1.Curve, bouncySecp256r1.G, bouncySecp256r1.N, bouncySecp256r1.H) :
ecCurve == ECC.ECCurve.Secp256k1 ? new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H) :
throw new NotSupportedException(nameof(ecCurve));
var signer = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner();
var privateKey = new BigInteger(1, priKey);
var priKeyParameters = new ECPrivateKeyParameters(privateKey, domain);
signer.Init(true, priKeyParameters);
var signature = signer.GenerateSignature(message.Sha256());
var messageH =
hasher == Hasher.SHA256 ? message.Sha256() :
hasher == Hasher.Keccak256 ? message.Keccak256() :
throw new NotSupportedException(nameof(hasher));
var signature = signer.GenerateSignature(messageH);

var signatureBytes = new byte[64];
var rBytes = signature[0].ToByteArrayUnsigned();
Expand All @@ -87,24 +96,35 @@ public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = n
Curve = curve,
D = priKey,
});
return ecdsa.SignData(message, HashAlgorithmName.SHA256);
var hashAlg =
hasher == Hasher.SHA256 ? HashAlgorithmName.SHA256 :
throw new NotSupportedException(nameof(hasher));
return ecdsa.SignData(message, hashAlg);
}

/// <summary>
/// Verifies that a digital signature is appropriate for the provided key and message.
/// Verifies that a digital signature is appropriate for the provided key, message and hash algorithm.
/// </summary>
/// <param name="message">The signed message.</param>
/// <param name="signature">The signature to be verified.</param>
/// <param name="pubkey">The public key to be used.</param>
/// <param name="hasher">The hash algorithm to be used to hash the message, the default is SHA256.</param>
/// <returns><see langword="true"/> if the signature is valid; otherwise, <see langword="false"/>.</returns>
public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ECC.ECPoint pubkey)
public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ECC.ECPoint pubkey, Hasher hasher = Hasher.SHA256)
{
if (signature.Length != 64) return false;

if (IsOSX && pubkey.Curve == ECC.ECCurve.Secp256k1)
if (hasher == Hasher.Keccak256 || (IsOSX && pubkey.Curve == ECC.ECCurve.Secp256k1))
{
var domain = new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H);
var point = bouncySecp256k1.Curve.CreatePoint(
var domain =
pubkey.Curve == ECC.ECCurve.Secp256r1 ? new ECDomainParameters(bouncySecp256r1.Curve, bouncySecp256r1.G, bouncySecp256r1.N, bouncySecp256r1.H) :
pubkey.Curve == ECC.ECCurve.Secp256k1 ? new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H) :
throw new NotSupportedException(nameof(pubkey.Curve));
var curve =
pubkey.Curve == ECC.ECCurve.Secp256r1 ? bouncySecp256r1.Curve :
bouncySecp256k1.Curve;

var point = curve.CreatePoint(
new BigInteger(pubkey.X.Value.ToString()),
new BigInteger(pubkey.Y.Value.ToString()));
var pubKey = new ECPublicKeyParameters("ECDSA", point, domain);
Expand All @@ -115,11 +135,19 @@ public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte
var r = new BigInteger(1, sig, 0, 32);
var s = new BigInteger(1, sig, 32, 32);

return signer.VerifySignature(message.Sha256(), r, s);
var messageH =
hasher == Hasher.SHA256 ? message.Sha256() :
hasher == Hasher.Keccak256 ? message.Keccak256() :
throw new NotSupportedException(nameof(hasher));

return signer.VerifySignature(messageH, r, s);
}

var ecdsa = CreateECDsa(pubkey);
return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256);
var hashAlg =
hasher == Hasher.SHA256 ? HashAlgorithmName.SHA256 :
throw new NotSupportedException(nameof(hasher));
return ecdsa.VerifyData(message, signature, hashAlg);
}

/// <summary>
Expand Down Expand Up @@ -153,16 +181,17 @@ public static ECDsa CreateECDsa(ECC.ECPoint pubkey)
}

/// <summary>
/// Verifies that a digital signature is appropriate for the provided key and message.
/// Verifies that a digital signature is appropriate for the provided key, curve, message and hasher.
/// </summary>
/// <param name="message">The signed message.</param>
/// <param name="signature">The signature to be verified.</param>
/// <param name="pubkey">The public key to be used.</param>
/// <param name="curve">The curve to be used by the ECDSA algorithm.</param>
/// <param name="hasher">The hash algorithm to be used hash the message, the default is SHA256.</param>
/// <returns><see langword="true"/> if the signature is valid; otherwise, <see langword="false"/>.</returns>
public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ReadOnlySpan<byte> pubkey, ECC.ECCurve curve)
public static bool VerifySignature(ReadOnlySpan<byte> message, ReadOnlySpan<byte> signature, ReadOnlySpan<byte> pubkey, ECC.ECCurve curve, Hasher hasher = Hasher.SHA256)
{
return VerifySignature(message, signature, ECC.ECPoint.DecodePoint(pubkey, curve));
return VerifySignature(message, signature, ECC.ECPoint.DecodePoint(pubkey, curve), hasher);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// NamedCurve.cs file belongs to the neo project and is free
// Hasher.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
Expand All @@ -9,24 +9,21 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.SmartContract.Native
namespace Neo.Cryptography
{
/// <summary>
/// Represents the named curve used in ECDSA.
/// Represents hash function identifiers supported by ECDSA message signature and verification.
/// </summary>
/// <remarks>
/// https://tools.ietf.org/html/rfc4492#section-5.1.1
/// </remarks>
public enum NamedCurve : byte
public enum Hasher : byte
{
/// <summary>
/// The secp256k1 curve.
/// The SHA256 hash algorithm.
/// </summary>
secp256k1 = 22,
SHA256 = 0x00,

/// <summary>
/// The secp256r1 curve, which known as prime256v1 or nistP-256.
/// The Keccak256 hash algorithm.
/// </summary>
secp256r1 = 23
Keccak256 = 0x01,
}
}
35 changes: 35 additions & 0 deletions src/Neo/Cryptography/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Neo.IO;
using Neo.Network.P2P.Payloads;
using Neo.Wallets;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
Expand Down Expand Up @@ -153,6 +154,40 @@ public static byte[] Sha256(this Span<byte> value)
return Sha256((ReadOnlySpan<byte>)value);
}

/// <summary>
/// Computes the hash value for the specified byte array using the keccak256 algorithm.
/// </summary>
/// <param name="value">The input to compute the hash code for.</param>
/// <returns>The computed hash code.</returns>
public static byte[] Keccak256(this byte[] value)
{
KeccakDigest keccak = new(256);
keccak.BlockUpdate(value, 0, value.Length);
byte[] result = new byte[keccak.GetDigestSize()];
keccak.DoFinal(result, 0);
return result;
}

/// <summary>
/// Computes the hash value for the specified byte array using the keccak256 algorithm.
/// </summary>
/// <param name="value">The input to compute the hash code for.</param>
/// <returns>The computed hash code.</returns>
public static byte[] Keccak256(this ReadOnlySpan<byte> value)
{
return Keccak256(value.ToArray());
}

/// <summary>
/// Computes the hash value for the specified byte array using the keccak256 algorithm.
/// </summary>
/// <param name="value">The input to compute the hash code for.</param>
/// <returns>The computed hash code.</returns>
public static byte[] Keccak256(this Span<byte> value)
{
return Keccak256(value.ToArray());
}

public static byte[] AES256Encrypt(this byte[] plainData, byte[] key, byte[] nonce, byte[] associatedData = null)
{
if (nonce.Length != 12) throw new ArgumentOutOfRangeException(nameof(nonce));
Expand Down
22 changes: 10 additions & 12 deletions src/Neo/SmartContract/Native/CryptoLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

using Neo.Cryptography;
using Neo.Cryptography.ECC;
using Org.BouncyCastle.Crypto.Digests;
using System;
using System.Collections.Generic;

Expand All @@ -22,10 +21,12 @@ namespace Neo.SmartContract.Native
/// </summary>
public sealed partial class CryptoLib : NativeContract
{
private static readonly Dictionary<NamedCurve, ECCurve> curves = new()
private static readonly Dictionary<NamedCurveHash, (ECCurve, Hasher)> curves = new()
{
[NamedCurve.secp256k1] = ECCurve.Secp256k1,
[NamedCurve.secp256r1] = ECCurve.Secp256r1
[NamedCurveHash.secp256k1SHA256] = (ECCurve.Secp256k1, Hasher.SHA256),
[NamedCurveHash.secp256r1SHA256] = (ECCurve.Secp256r1, Hasher.SHA256),
[NamedCurveHash.secp256k1Keccak256] = (ECCurve.Secp256k1, Hasher.Keccak256),
[NamedCurveHash.secp256r1Keccak256] = (ECCurve.Secp256r1, Hasher.Keccak256),
};

internal CryptoLib() : base() { }
Expand Down Expand Up @@ -73,11 +74,7 @@ public static byte[] Murmur32(byte[] data, uint seed)
[ContractMethod(Hardfork.HF_Cockatrice, CpuFee = 1 << 15)]
public static byte[] Keccak256(byte[] data)
{
KeccakDigest keccak = new(256);
keccak.BlockUpdate(data, 0, data.Length);
byte[] result = new byte[keccak.GetDigestSize()];
keccak.DoFinal(result, 0);
return result;
return data.Keccak256();
}

/// <summary>
Expand All @@ -86,14 +83,15 @@ public static byte[] Keccak256(byte[] data)
/// <param name="message">The signed message.</param>
/// <param name="pubkey">The public key to be used.</param>
/// <param name="signature">The signature to be verified.</param>
/// <param name="curve">The curve to be used by the ECDSA algorithm.</param>
/// <param name="curveHash">A pair of the curve to be used by the ECDSA algorithm and the hasher function to be used to hash message.</param>
/// <returns><see langword="true"/> if the signature is valid; otherwise, <see langword="false"/>.</returns>
[ContractMethod(CpuFee = 1 << 15)]
public static bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, NamedCurve curve)
public static bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, NamedCurveHash curveHash)
{
try
{
return Crypto.VerifySignature(message, signature, pubkey, curves[curve]);
var ch = curves[curveHash];
return Crypto.VerifySignature(message, signature, pubkey, ch.Item1, ch.Item2);
}
catch (ArgumentException)
{
Expand Down
42 changes: 42 additions & 0 deletions src/Neo/SmartContract/Native/NamedCurveHash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// NamedCurveHash.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.SmartContract.Native
{
/// <summary>
/// Represents a pair of the named curve used in ECDSA and a hash algorithm used to hash message.
/// </summary>
/// <remarks>
/// https://tools.ietf.org/html/rfc4492#section-5.1.1
/// </remarks>
public enum NamedCurveHash : byte
{
/// <summary>
/// The secp256k1 curve and SHA256 hash algorithm.
/// </summary>
secp256k1SHA256 = 22,

/// <summary>
/// The secp256r1 curve, which known as prime256v1 or nistP-256, and SHA256 hash algorithm.
/// </summary>
secp256r1SHA256 = 23,

/// <summary>
/// The secp256k1 curve and Keccak256 hash algorithm.
/// </summary>
secp256k1Keccak256 = 24,

/// <summary>
/// The secp256r1 curve, which known as prime256v1 or nistP-256, and Keccak256 hash algorithm.
/// </summary>
secp256r1Keccak256 = 25
}
}
Loading

0 comments on commit 0470483

Please sign in to comment.