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 hardfork HF_Echidna #3454

Draft
wants to merge 40 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
bcc5fd0
add hardofork HF_Echidna
Jim8y Aug 7, 2024
e63f10b
Add entries to `Designation` event (#3397)
shargon Aug 8, 2024
9d16c53
[Neo Core StdLib] Add Base64url (#3453)
Jim8y Aug 8, 2024
f746f8d
add hardofork HF_Echidna
Jim8y Aug 7, 2024
8d7f9e8
Add entries to `Designation` event (#3397)
shargon Aug 8, 2024
3fc8077
[Neo Core StdLib] Add Base64url (#3453)
Jim8y Aug 8, 2024
620d938
Merge branch 'HF_Echidna' of github.com:neo-project/neo into HF_Echidna
Jim8y Oct 2, 2024
7e31d0c
add hardofork HF_Echidna
Jim8y Aug 7, 2024
993e3fe
Add entries to `Designation` event (#3397)
shargon Aug 8, 2024
37bf0cb
[Neo Core StdLib] Add Base64url (#3453)
Jim8y Aug 8, 2024
7dba130
format
Jim8y Nov 6, 2024
0457ccd
Merge branch 'HF_Echidna' of github.com:neo-project/neo into HF_Echidna
Jim8y Nov 6, 2024
d7a291e
Merge Master
cschuchardt88 Nov 17, 2024
6e780c0
Fixed typo
cschuchardt88 Nov 17, 2024
02d6ab9
Added back #3397
cschuchardt88 Nov 17, 2024
8fb30df
Fixed tests
cschuchardt88 Nov 17, 2024
f9244eb
fixed global.json
cschuchardt88 Nov 17, 2024
650c9e9
Merge branch 'master' into HF_Echidna
Jim8y Nov 18, 2024
331541a
Merge branch 'master' into HF_Echidna
Jim8y Nov 20, 2024
eb96d14
Merge branch 'master' into HF_Echidna
shargon Nov 20, 2024
74498e5
Merge branch 'master' into HF_Echidna
Jim8y Nov 23, 2024
577c431
Merge branch 'master' into HF_Echidna
cschuchardt88 Dec 4, 2024
1c82ed9
Update src/Neo/Neo.csproj
cschuchardt88 Dec 4, 2024
b81b127
Update src/Neo/Neo.csproj
cschuchardt88 Dec 4, 2024
a3a6fd5
Merge branch 'master' into HF_Echidna
Jim8y Dec 11, 2024
c086661
Merge branch 'master' into HF_Echidna
Jim8y Dec 19, 2024
0f1507f
Merge branch 'master' into HF_Echidna
Jim8y Dec 19, 2024
f91b680
[`Fix`]: integer overflow in `JumpTable.SubStr ` (#3496)
nan01ab Dec 19, 2024
705f4bb
Fix NEO callstates (#3599)
shargon Dec 19, 2024
498ed91
Merge branch 'master' into HF_Echidna
Jim8y Dec 19, 2024
08c3be6
Merge branch 'master' into HF_Echidna
Jim8y Dec 19, 2024
59fe8b5
fix tests error (#3636)
Jim8y Dec 19, 2024
a784c41
NeoToken: accept candidate registration via onNEP17Payment (#3597)
roman-khimov Dec 20, 2024
e2acc64
specify the argument exception information.
Jim8y Dec 20, 2024
216c39e
Fix Ut (#3635)
shargon Dec 20, 2024
548bd05
NeoToken: add NEP-27 to supported standards list starting from Echidn…
AnnaShaleva Dec 27, 2024
8470de5
Merge branch 'master' into HF_Echidna
cschuchardt88 Dec 29, 2024
93fc37b
ut: fix HF_Echidna unit tests (#3646)
shargon Dec 31, 2024
b1a3ea3
[Core Add] Add support to Ed25519 (#3507)
Jim8y Jan 9, 2025
f4f1980
Merge branch 'master' into HF_Echidna
Jim8y Jan 11, 2025
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
3 changes: 2 additions & 1 deletion src/Neo.CLI/config.fs.mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"HF_Aspidochelone": 3000000,
"HF_Basilisk": 4500000,
"HF_Cockatrice": 5800000,
"HF_Domovoi": 5800000
"HF_Domovoi": 5800000,
"HF_Echidna": 5800001
},
"StandbyCommittee": [
"026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086",
Expand Down
3 changes: 2 additions & 1 deletion src/Neo.CLI/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"HF_Aspidochelone": 1730000,
"HF_Basilisk": 4120000,
"HF_Cockatrice": 5450000,
"HF_Domovoi": 5570000
"HF_Domovoi": 5570000,
"HF_Echidna": 5570001
},
"InitialGasDistribution": 5200000000000000,
"ValidatorsCount": 7,
Expand Down
3 changes: 2 additions & 1 deletion src/Neo.CLI/config.mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"HF_Aspidochelone": 1730000,
"HF_Basilisk": 4120000,
"HF_Cockatrice": 5450000,
"HF_Domovoi": 5570000
"HF_Domovoi": 5570000,
"HF_Echidna": 5570001
},
"InitialGasDistribution": 5200000000000000,
"ValidatorsCount": 7,
Expand Down
3 changes: 2 additions & 1 deletion src/Neo.CLI/config.testnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"HF_Aspidochelone": 210000,
"HF_Basilisk": 2680000,
"HF_Cockatrice": 3967000,
"HF_Domovoi": 4144000
"HF_Domovoi": 4144000,
"HF_Echidna": 4144001
},
"InitialGasDistribution": 5200000000000000,
"ValidatorsCount": 7,
Expand Down
2 changes: 1 addition & 1 deletion src/Neo.VM/JumpTable/JumpTable.Splice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public virtual void SubStr(ExecutionEngine engine, Instruction instruction)
if (index < 0)
throw new InvalidOperationException($"The index can not be negative for {nameof(OpCode.SUBSTR)}, index: {index}.");
var x = engine.Pop().GetSpan();
if (index + count > x.Length)
if (checked(index + count) > x.Length)
throw new InvalidOperationException($"The index + count is out of range for {nameof(OpCode.SUBSTR)}, index: {index}, count: {count}, {index + count}/[0, {x.Length}].");
Types.Buffer result = new(count, false);
x.Slice(index, count).CopyTo(result.InnerBuffer.Span);
Expand Down
99 changes: 99 additions & 0 deletions src/Neo/Cryptography/Ed25519.cs
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;

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);
}
}
}
3 changes: 2 additions & 1 deletion src/Neo/Hardfork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum Hardfork : byte
HF_Aspidochelone,
HF_Basilisk,
HF_Cockatrice,
HF_Domovoi
HF_Domovoi,
HF_Echidna
}
}
2 changes: 2 additions & 0 deletions src/Neo/Neo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.1" />
<PackageReference Include="System.IO.Hashing" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
67 changes: 60 additions & 7 deletions src/Neo/ProtocolSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;

namespace Neo
Expand Down Expand Up @@ -123,19 +124,70 @@ public record ProtocolSettings

public static ProtocolSettings Custom { get; set; }

/// <summary>
/// Searches for a file in the given path. If not found, checks in the executable directory.
/// </summary>
/// <param name="fileName">The name of the file to search for.</param>
/// <param name="path">The primary path to search in.</param>
/// <returns>Full path of the file if found, null otherwise.</returns>
public static string FindFile(string fileName, string path)
{

// Check if the given path is relative
if (!Path.IsPathRooted(path))
{
// Combine with the executable directory if relative
var executablePath = AppDomain.CurrentDomain.BaseDirectory;
Copy link
Member

@cschuchardt88 cschuchardt88 Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be set to whatever. Better to use AppContext.BaseDirectory

path = Path.Combine(executablePath, path);
}

// Check if file exists in the specified (resolved) path
var fullPath = Path.Combine(path, fileName);
if (File.Exists(fullPath))
{
return fullPath;
}

// Check if file exists in the executable directory
var executableDir = AppContext.BaseDirectory;
fullPath = Path.Combine(executableDir, fileName);
if (File.Exists(fullPath))
{
return fullPath;
}

// File not found in either location
return null;
}

/// <summary>
/// Loads the <see cref="ProtocolSettings"/> from the specified stream.
/// </summary>
/// <param name="stream">The stream of the settings.</param>
/// <returns>The loaded <see cref="ProtocolSettings"/>.</returns>
public static ProtocolSettings Load(Stream stream)
{
var config = new ConfigurationBuilder().AddJsonStream(stream).Build();
var section = config.GetSection("ProtocolConfiguration");
return Load(section);
}

/// <summary>
/// Loads the <see cref="ProtocolSettings"/> at the specified path.
/// </summary>
/// <param name="path">The path of the settings file.</param>
/// <param name="optional">Indicates whether the file is optional.</param>
/// <returns>The loaded <see cref="ProtocolSettings"/>.</returns>
public static ProtocolSettings Load(string path, bool optional = true)
public static ProtocolSettings Load(string path)
{
IConfigurationRoot config = new ConfigurationBuilder().AddJsonFile(path, optional).Build();
IConfigurationSection section = config.GetSection("ProtocolConfiguration");
var settings = Load(section);
CheckingHardfork(settings);
return settings;
path = FindFile(path, Environment.CurrentDirectory);

if (path is null)
{
return Default;
}

using var stream = File.OpenRead(path);
return Load(stream);
}

/// <summary>
Expand Down Expand Up @@ -165,6 +217,7 @@ public static ProtocolSettings Load(IConfigurationSection section)
? EnsureOmmitedHardforks(section.GetSection("Hardforks").GetChildren().ToDictionary(p => Enum.Parse<Hardfork>(p.Key, true), p => uint.Parse(p.Value))).ToImmutableDictionary()
: Default.Hardforks
};
CheckingHardfork(Custom);
return Custom;
}

Expand Down
38 changes: 37 additions & 1 deletion src/Neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace Neo.SmartContract
public partial class ApplicationEngine : ExecutionEngine
{
protected static readonly JumpTable DefaultJumpTable = ComposeDefaultJumpTable();
protected static readonly JumpTable NotEchidnaJumpTable = ComposeNotEchidnaJumpTable();

/// <summary>
/// The maximum cost that can be spent when a contract is executed in test mode.
Expand Down Expand Up @@ -215,6 +216,13 @@ private static JumpTable ComposeDefaultJumpTable()
return table;
}

public static JumpTable ComposeNotEchidnaJumpTable()
{
var jumpTable = ComposeDefaultJumpTable();
jumpTable[OpCode.SUBSTR] = VulnerableSubStr;
return jumpTable;
}

protected static void OnCallT(ExecutionEngine engine, Instruction instruction)
{
if (engine is ApplicationEngine app)
Expand Down Expand Up @@ -400,13 +408,41 @@ internal override void UnloadContext(ExecutionContext context)
/// <returns>The engine instance created.</returns>
public static ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock = null, ProtocolSettings settings = null, long gas = TestModeGas, IDiagnostic diagnostic = null)
{
var index = persistingBlock?.Index ?? (snapshot == null ? 0 : NativeContract.Ledger.CurrentIndex(snapshot));

// Adjust jump table according persistingBlock
var jumpTable = ApplicationEngine.DefaultJumpTable;

var jumpTable = settings == null || settings.IsHardforkEnabled(Hardfork.HF_Echidna, index) ? DefaultJumpTable : NotEchidnaJumpTable;
return Provider?.Create(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable)
?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable);
}

/// <summary>
/// Extracts a substring from the specified buffer and pushes it onto the evaluation stack.
/// <see cref="OpCode.SUBSTR"/>
/// </summary>
/// <param name="engine">The execution engine.</param>
/// <param name="instruction">The instruction being executed.</param>
/// <remarks>Pop 3, Push 1</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void VulnerableSubStr(ExecutionEngine engine, Instruction instruction)
{
var count = (int)engine.Pop().GetInteger();
if (count < 0)
throw new InvalidOperationException($"The count can not be negative for {nameof(OpCode.SUBSTR)}, count: {count}.");
var index = (int)engine.Pop().GetInteger();
if (index < 0)
throw new InvalidOperationException($"The index can not be negative for {nameof(OpCode.SUBSTR)}, index: {index}.");
var x = engine.Pop().GetSpan();
// Note: here it's the main change
if (index + count > x.Length)
throw new InvalidOperationException($"The index + count is out of range for {nameof(OpCode.SUBSTR)}, index: {index}, count: {count}, {index + count}/[0, {x.Length}].");

VM.Types.Buffer result = new(count, false);
x.Slice(index, count).CopyTo(result.InnerBuffer.Span);
engine.Push(result);
}

public override void LoadContext(ExecutionContext context)
{
// Set default execution context state
Expand Down
2 changes: 1 addition & 1 deletion src/Neo/SmartContract/Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static Contract CreateMultiSigContract(int m, IReadOnlyCollection<ECPoint
public static byte[] CreateMultiSigRedeemScript(int m, IReadOnlyCollection<ECPoint> publicKeys)
{
if (!(1 <= m && m <= publicKeys.Count && publicKeys.Count <= 1024))
throw new ArgumentException();
throw new ArgumentException($"Invalid multisig parameters: m={m}, publicKeys.Count={publicKeys.Count}");
using ScriptBuilder sb = new();
sb.EmitPush(m);
foreach (ECPoint publicKey in publicKeys.OrderBy(p => p))
Expand Down
3 changes: 2 additions & 1 deletion src/Neo/SmartContract/Native/ContractMethodAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
namespace Neo.SmartContract.Native
{
[DebuggerDisplay("{Name}")]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)]
// We allow multiple attributes because the fees or requiredCallFlags may change between hard forks.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
internal class ContractMethodAttribute : Attribute, IHardforkActivable
{
public string Name { get; init; }
Expand Down
3 changes: 2 additions & 1 deletion src/Neo/SmartContract/Native/ContractMethodMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ internal class ContractMethodMetadata : IHardforkActivable

public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribute)
{
Name = attribute.Name ?? member.Name.ToLower()[0] + member.Name[1..];
Name = attribute.Name ?? member.Name;
Name = Name.ToLowerInvariant()[0] + Name[1..];
Handler = member switch
{
MethodInfo m => m,
Expand Down
Loading
Loading