-
Notifications
You must be signed in to change notification settings - Fork 1k
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
[Core Add] Add support to Ed25519 #3507
Open
Jim8y
wants to merge
24
commits into
neo-project:HF_Echidna
Choose a base branch
from
Jim8y:ed25519
base: HF_Echidna
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
47cbf41
ed25519
Jim8y 72837f6
foamt
Jim8y 778c228
Merge branch 'HF_Echidna' into ed25519
Jim8y d1b3a5c
update format
Jim8y d319995
Update src/Neo/SmartContract/Native/RoleManagement.cs
Jim8y f2859fc
Update src/Neo/SmartContract/Native/CryptoLib.cs
Jim8y 4217717
Clean changes
shargon f1ad696
ed25519
Jim8y a289d54
foamt
Jim8y e9cb3ef
update format
Jim8y 57c19c5
add comment, update param order, CerifyWithEd25519 without exception,…
Jim8y bba43ad
Update src/Neo/SmartContract/Native/RoleManagement.cs
Jim8y 05d849f
Update src/Neo/SmartContract/Native/CryptoLib.cs
Jim8y d23f57d
Clean changes
shargon e67ca52
Merge branch 'ed25519' of github.com:Jim8y/neo into ed25519
Jim8y b915fe1
format
Jim8y 31a9030
fix ut param order
Jim8y 8dbbb5d
update native state
Jim8y e0328e3
`[Move]` Part-10 Classes into Different Library - `Neo.Extensions` (#…
cschuchardt88 c531917
[Add] Get Accounts and Balances for GasToken (#3614)
cschuchardt88 5e879df
Merge branch 'master' into ed25519
Jim8y 775bf10
Merge branch 'HF_Echidna' into ed25519
Jim8y f99177c
Merge branch 'master' into ed25519
Jim8y e96c16c
revert unnecessary change
Jim8y File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright (C) 2015-2024 The Neo Project. | ||
// | ||
// Ed25519.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. | ||
|
||
using Org.BouncyCastle.Crypto.Generators; | ||
using Org.BouncyCastle.Crypto.Parameters; | ||
using Org.BouncyCastle.Crypto.Signers; | ||
using Org.BouncyCastle.Security; | ||
using System; | ||
using System.Linq; | ||
|
||
namespace Neo.Cryptography; | ||
|
||
public class Ed25519 | ||
{ | ||
internal const int PublicKeySize = 32; | ||
private const int PrivateKeySize = 32; | ||
internal const int SignatureSize = 64; | ||
|
||
/// <summary> | ||
/// Generates a new Ed25519 key pair. | ||
/// </summary> | ||
/// <returns>A byte array containing the private key.</returns> | ||
public static byte[] GenerateKeyPair() | ||
{ | ||
var keyPairGenerator = new Ed25519KeyPairGenerator(); | ||
keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom())); | ||
var keyPair = keyPairGenerator.GenerateKeyPair(); | ||
return ((Ed25519PrivateKeyParameters)keyPair.Private).GetEncoded(); | ||
} | ||
|
||
/// <summary> | ||
/// Derives the public key from a given private key. | ||
/// </summary> | ||
/// <param name="privateKey">The private key as a byte array.</param> | ||
/// <returns>The corresponding public key as a byte array.</returns> | ||
/// <exception cref="ArgumentException">Thrown when the private key size is invalid.</exception> | ||
public static byte[] GetPublicKey(byte[] privateKey) | ||
{ | ||
if (privateKey.Length != PrivateKeySize) | ||
throw new ArgumentException("Invalid private key size", nameof(privateKey)); | ||
|
||
var privateKeyParams = new Ed25519PrivateKeyParameters(privateKey, 0); | ||
return privateKeyParams.GeneratePublicKey().GetEncoded(); | ||
} | ||
|
||
/// <summary> | ||
/// Signs a message using the provided private key. | ||
/// Parameters are in the same order as the sample in the Ed25519 specification | ||
/// Ed25519.sign(privkey, pubkey, msg) with pubkey omitted | ||
/// ref. https://datatracker.ietf.org/doc/html/rfc8032. | ||
/// </summary> | ||
/// <param name="privateKey">The private key used for signing.</param> | ||
/// <param name="message">The message to be signed.</param> | ||
/// <returns>The signature as a byte array.</returns> | ||
/// <exception cref="ArgumentException">Thrown when the private key size is invalid.</exception> | ||
public static byte[] Sign(byte[] privateKey, byte[] message) | ||
{ | ||
if (privateKey.Length != PrivateKeySize) | ||
throw new ArgumentException("Invalid private key size", nameof(privateKey)); | ||
|
||
var signer = new Ed25519Signer(); | ||
signer.Init(true, new Ed25519PrivateKeyParameters(privateKey, 0)); | ||
signer.BlockUpdate(message, 0, message.Length); | ||
return signer.GenerateSignature(); | ||
} | ||
|
||
/// <summary> | ||
/// Verifies an Ed25519 signature for a given message using the provided public key. | ||
/// Parameters are in the same order as the sample in the Ed25519 specification | ||
/// Ed25519.verify(public, msg, signature) | ||
/// ref. https://datatracker.ietf.org/doc/html/rfc8032. | ||
/// </summary> | ||
/// <param name="publicKey">The 32-byte public key used for verification.</param> | ||
/// <param name="message">The message that was signed.</param> | ||
/// <param name="signature">The 64-byte signature to verify.</param> | ||
/// <returns>True if the signature is valid for the given message and public key; otherwise, false.</returns> | ||
/// <exception cref="ArgumentException">Thrown when the signature or public key size is invalid.</exception> | ||
public static bool Verify(byte[] publicKey, byte[] message, byte[] signature) | ||
{ | ||
if (signature.Length != SignatureSize) | ||
throw new ArgumentException("Invalid signature size", nameof(signature)); | ||
|
||
if (publicKey.Length != PublicKeySize) | ||
throw new ArgumentException("Invalid public key size", nameof(publicKey)); | ||
|
||
var verifier = new Ed25519Signer(); | ||
verifier.Init(false, new Ed25519PublicKeyParameters(publicKey, 0)); | ||
verifier.BlockUpdate(message, 0, message.Length); | ||
return verifier.VerifySignature(signature); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// Copyright (C) 2015-2024 The Neo Project. | ||
// | ||
// UT_Ed25519.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. | ||
|
||
using FluentAssertions; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Neo.Cryptography; | ||
using Neo.Extensions; | ||
using Neo.IO; | ||
using Neo.Network.P2P.Payloads; | ||
using Neo.SmartContract; | ||
using Neo.Wallets; | ||
using Neo.Wallets.NEP6; | ||
using System; | ||
using System.Linq; | ||
using System.Text; | ||
|
||
namespace Neo.UnitTests.Cryptography | ||
{ | ||
[TestClass] | ||
public class UT_Ed25519 | ||
{ | ||
[TestMethod] | ||
public void TestGenerateKeyPair() | ||
{ | ||
byte[] keyPair = Ed25519.GenerateKeyPair(); | ||
keyPair.Should().NotBeNull(); | ||
keyPair.Length.Should().Be(32); | ||
} | ||
|
||
[TestMethod] | ||
public void TestGetPublicKey() | ||
{ | ||
byte[] privateKey = Ed25519.GenerateKeyPair(); | ||
byte[] publicKey = Ed25519.GetPublicKey(privateKey); | ||
publicKey.Should().NotBeNull(); | ||
publicKey.Length.Should().Be(Ed25519.PublicKeySize); | ||
} | ||
|
||
[TestMethod] | ||
public void TestSignAndVerify() | ||
{ | ||
byte[] privateKey = Ed25519.GenerateKeyPair(); | ||
byte[] publicKey = Ed25519.GetPublicKey(privateKey); | ||
byte[] message = Encoding.UTF8.GetBytes("Hello, Neo!"); | ||
|
||
byte[] signature = Ed25519.Sign(privateKey, message); | ||
signature.Should().NotBeNull(); | ||
signature.Length.Should().Be(Ed25519.SignatureSize); | ||
|
||
bool isValid = Ed25519.Verify(publicKey, message, signature); | ||
isValid.Should().BeTrue(); | ||
} | ||
|
||
[TestMethod] | ||
public void TestFailedVerify() | ||
{ | ||
byte[] privateKey = Ed25519.GenerateKeyPair(); | ||
byte[] publicKey = Ed25519.GetPublicKey(privateKey); | ||
byte[] message = Encoding.UTF8.GetBytes("Hello, Neo!"); | ||
|
||
byte[] signature = Ed25519.Sign(privateKey, message); | ||
|
||
// Tamper with the message | ||
byte[] tamperedMessage = Encoding.UTF8.GetBytes("Hello, Neo?"); | ||
|
||
bool isValid = Ed25519.Verify(publicKey, tamperedMessage, signature); | ||
isValid.Should().BeFalse(); | ||
|
||
// Tamper with the signature | ||
byte[] tamperedSignature = new byte[signature.Length]; | ||
Array.Copy(signature, tamperedSignature, signature.Length); | ||
tamperedSignature[0] ^= 0x01; // Flip one bit | ||
|
||
isValid = Ed25519.Verify(publicKey, message, tamperedSignature); | ||
isValid.Should().BeFalse(); | ||
|
||
// Use wrong public key | ||
byte[] wrongPrivateKey = Ed25519.GenerateKeyPair(); | ||
byte[] wrongPublicKey = Ed25519.GetPublicKey(wrongPrivateKey); | ||
|
||
isValid = Ed25519.Verify(wrongPublicKey, message, signature); | ||
isValid.Should().BeFalse(); | ||
} | ||
|
||
[TestMethod] | ||
public void TestInvalidPrivateKeySize() | ||
{ | ||
byte[] invalidPrivateKey = new byte[31]; // Invalid size | ||
Action act = () => Ed25519.GetPublicKey(invalidPrivateKey); | ||
act.Should().Throw<ArgumentException>().WithMessage("Invalid private key size*"); | ||
} | ||
|
||
[TestMethod] | ||
public void TestInvalidSignatureSize() | ||
{ | ||
byte[] message = Encoding.UTF8.GetBytes("Test message"); | ||
byte[] invalidSignature = new byte[63]; // Invalid size | ||
byte[] publicKey = new byte[Ed25519.PublicKeySize]; | ||
Action act = () => Ed25519.Verify(publicKey, message, invalidSignature); | ||
act.Should().Throw<ArgumentException>().WithMessage("Invalid signature size*"); | ||
} | ||
|
||
[TestMethod] | ||
public void TestInvalidPublicKeySize() | ||
{ | ||
byte[] message = Encoding.UTF8.GetBytes("Test message"); | ||
byte[] signature = new byte[Ed25519.SignatureSize]; | ||
byte[] invalidPublicKey = new byte[31]; // Invalid size | ||
Action act = () => Ed25519.Verify(invalidPublicKey, message, signature); | ||
act.Should().Throw<ArgumentException>().WithMessage("Invalid public key size*"); | ||
} | ||
|
||
// Test vectors from RFC 8032 (https://datatracker.ietf.org/doc/html/rfc8032) | ||
// Section 7.1. Test Vectors for Ed25519 | ||
|
||
[TestMethod] | ||
public void TestVectorCase1() | ||
{ | ||
byte[] privateKey = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60".HexToBytes(); | ||
byte[] publicKey = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a".HexToBytes(); | ||
byte[] message = Array.Empty<byte>(); | ||
byte[] signature = ("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155" + | ||
"5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b").HexToBytes(); | ||
|
||
Ed25519.GetPublicKey(privateKey).Should().Equal(publicKey); | ||
Ed25519.Sign(privateKey, message).Should().Equal(signature); | ||
} | ||
|
||
[TestMethod] | ||
public void TestVectorCase2() | ||
{ | ||
byte[] privateKey = "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb".HexToBytes(); | ||
byte[] publicKey = "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c".HexToBytes(); | ||
byte[] message = Encoding.UTF8.GetBytes("r"); | ||
byte[] signature = ("92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da" + | ||
"085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00").HexToBytes(); | ||
|
||
Ed25519.GetPublicKey(privateKey).Should().Equal(publicKey); | ||
Ed25519.Sign(privateKey, message).Should().Equal(signature); | ||
} | ||
|
||
[TestMethod] | ||
public void TestVectorCase3() | ||
{ | ||
byte[] privateKey = "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7".HexToBytes(); | ||
byte[] publicKey = "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025".HexToBytes(); | ||
byte[] signature = ("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac" + | ||
"18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a").HexToBytes(); | ||
byte[] message = "af82".HexToBytes(); | ||
Ed25519.GetPublicKey(privateKey).Should().Equal(publicKey); | ||
Ed25519.Sign(privateKey, message).Should().Equal(signature); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I was talking only about ArgumentException, e.g. only about input data of invalid length and some other ArgumentException thrown by this part of code. However, catching any exception is a valid solution as far.
@roman-khimov, what do you think about cases when we should return true/false or FAULT VM?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be both ways, but we need to know all of the exceptions that can happen in this block. Some are likely OK to be converted to
false
result (invalid signature), some may not (verifier
constructor failure?). I'd expect some symmetry to ECDSA verification function. Like what happens if the key is wrong?