Skip to content

Conversation

@adrian-fjellberg
Copy link

Adds the ability to sign arbitrary messages using wallet accounts. The user can specify the elliptic curve to use for signing (secp256r1 or secp256k1). It generates a payload using salt and the provided message, then signs the payload with each available wallet account, displaying the address, public key, salt and signature.

Description

This PR introduces a new sign_message command to the Neo CLI, enabling message signing outside of transactions.
Previously, the CLI only supported signing serialized transaction data, which made it impossible to produce verifiable message signatures for use in external applications, authentication workflows, or dApp integrations.

With this change, users can now easily sign and verify arbitrary messages directly from their local wallet.

Change Log

  • Added the "sign_message" command to the Neo CLI.
  • Changed Neo.CLI.MainService to implement the "sign_message" command.
  • Added optional curve parameter (supports secp256r1 and secp256k1)
  • Implemented payload creation using salt + message for improved uniqueness

Fixes #4285

Type of change

  • Optimization (the change is only an optimization)
  • Style (the change is only a code style for better maintenance or standard purpose)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • Unit Testing
  • Run Application: manually tested sign_message on local CLI with both curves
  • Local Computer Tests
  • No Testing

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Adds the ability to sign arbitrary messages using wallet accounts. The user can specify the elliptic curve to use for signing (secp256r1 or secp256k1). It generates a payload using salt and the provided message, then signs the payload with each available wallet account, displaying the address, public key, salt and signature.
var signature = Crypto.Sign(payload, key.PrivateKey, selectedCurve);

ConsoleHelper.Info("Address: ", account.Address);
ConsoleHelper.Info(" PublicKey: ", key.PublicKey.EncodePoint(true).ToHexString());
Copy link
Contributor

Choose a reason for hiding this comment

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

we dont really support secp256k1, the key you get is still p-256 pubkey.

Copy link
Author

Choose a reason for hiding this comment

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

Ok. I think I saw in the code for Crypto.Sign that both were supported so that is why I added it.

Do you want me tor remove the option to select curve and only use secp256r1?

Copy link
Member

Choose a reason for hiding this comment

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

We need a structured format that specifies the address, public key, and algorithm for signing. This also facilitates verification.

Copy link
Author

Choose a reason for hiding this comment

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

Some questions:

  1. Should we require the user specifiy either the public key or address for signing?

  2. If yes to question 1: If there is only one address in the open wallet, could we default to using this one? I think this will make signing more user friendly.

  3. For specifing the algorithim, do you mean the curve or padding, salt, etc.? Or both?

  4. Can we provide defaults for each option so that the command become user friendly?

Copy link
Member

Choose a reason for hiding this comment

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

I mean we need to define an output format including address, public key, and algorithm of the signature.

Copy link
Author

Choose a reason for hiding this comment

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

Latest commit includes the algorithm and curve used + some helpful text and link to documentation on how to verify the generated signatures.

@erikzhang, is this what you wanted?

Or did you want a stuctured format in terms of JSON or YAML?

Or did you want us to define a standard for the padding and salting?

Copy link
Member

Choose a reason for hiding this comment

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

I want a standard with json format please.

Copy link
Author

Choose a reason for hiding this comment

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

Comment on lines 562 to 568
w.Write((byte)0x01);
w.Write((byte)0x00);
w.Write((byte)0x01);
w.Write((byte)0xF0);
w.WriteVarBytes(paramBytes);
w.Write((ushort)0);
w.Flush();
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the meaning of this script, some standard?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, could you clarify it @adrian-fjellberg

Copy link
Author

Choose a reason for hiding this comment

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

It is to ensure that the message is not also a valid NEO transaction. I do not think it is a defined standard but it is the way both NEON wallet and NeoLine signs messages.

Do you want me to add comments to the code to clearify?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, its just some prefix.

Copy link
Author

Choose a reason for hiding this comment

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

I can add comments to the code.

Copy link
Author

Choose a reason for hiding this comment

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

Comments added in the latest commit.

@adrian-fjellberg
Copy link
Author

Adrian Fjellberg and others added 4 commits November 8, 2025 14:57
Refactors the `sign message` command to:
- allow only signing only using the secp256r1 curve.
- output the algorithm used to create the payload
- output guide on how to verify the signed message

Renames the `sign` command to `sign transaction` to avoid ambiguous calls with the new `sign message` command.

Refactors the `OnCommand` method on the ConsoleServiceBase class so that parsing of arguments are differed to later in the execution. This is to solve an issue that occurs when ambiguous calls occur, one method might output errors in when parsing of arguments.
…message-sign' into feature/cli-arbitrary-message-sign
/// <param name="jsonObjectToSign">Json object to sign</param>
[ConsoleCommand("sign", Category = "Wallet Commands")]
/// <param name="jsonObjectToSign">The json string that records the transaction information</param>
[ConsoleCommand("sign transaction", Category = "Wallet Commands")]
Copy link
Contributor

Choose a reason for hiding this comment

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

sign tx is shorter.

Copy link
Author

Choose a reason for hiding this comment

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

Agreed. Fixed 👍

@superboyiii
Copy link
Member

Is this armed for Neo4 or next release? @adrian-fjellberg

@superboyiii
Copy link
Member

superboyiii commented Nov 11, 2025

master is now only for Neo N4, master-n3 is for N3, dev is for N3 dev. So I guess you want to merge it into next release. It's better to change to dev. @adrian-fjellberg

@erikzhang
Copy link
Member

dev is not used anymore.

@adrian-fjellberg
Copy link
Author

master is now only for Neo N4, master-n3 is for N3, dev is for N3 dev. So I guess you want to merge it into next release. It's better to change to dev. @adrian-fjellberg

As fast as possilbe beacuse it will be important for nodes to be able to do offline signing in a secure way to claim candidate roles in the new Goevernance platform set to be released soon.

So I should change to master-n3 as per @erikzhang comment then?

using (var w = new BinaryWriter(ms, Encoding.UTF8, true))
{
// We add these 4 bytes to prevent the signature from being a valid transaction
w.Write((byte)0x01);
Copy link
Member

Choose a reason for hiding this comment

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

I would like to follow something like the https://www.cyfrin.io/blog/understanding-ethereum-signature-standards-eip-191-eip-712, domain separator it's important to avoid signature replays between different chains, like testnet and mainnet

Copy link
Member

Choose a reason for hiding this comment

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

public static byte[] GetSignData(this IVerifiable verifiable, uint network)
{
/* Same as:
using MemoryStream ms = new();
using BinaryWriter writer = new(ms);
writer.Write(network);
writer.Write(verifiable.Hash);
writer.Flush();
return ms.ToArray();
*/
var buffer = new byte[SignDataLength];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, network);
verifiable.Hash.Serialize(buffer.AsSpan(sizeof(uint)));
return buffer;
}

Copy link
Member

@shargon shargon Nov 11, 2025

Choose a reason for hiding this comment

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

@adrian-fjellberg we should call this method, let me add some changes. Also this PR should be moved now to https://github.com/neo-project/neo-node

@adrian-fjellberg
Copy link
Author

I only need the posibilty for nodes to sign an arbitrary message... We get 100% value from the current implementation. It satisfies all the needs of the reason behind the proposed change.

The value is:

  • Nodes get to claim their seat in the new Governance platform.
  • New canidates now have a way to sign messages when they are registering from the CLI rather from a wallet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add "sign_message" command to CLI for arbitrary message signing

7 participants