Skip to content

port message sign to master-n3#924

Merged
shargon merged 44 commits intoneo-project:master-n3from
ajara87:message-sign
Mar 9, 2026
Merged

port message sign to master-n3#924
shargon merged 44 commits intoneo-project:master-n3from
ajara87:message-sign

Conversation

@ajara87
Copy link
Member

@ajara87 ajara87 commented Nov 26, 2025

Porting neo-project/neo#4286 of @adrian-fjellberg
Proposal neo-project/proposals#213

  • Added use of GetSignData [Opcional]

  • Added UTs

    • TestOnSignMessageCommand
    • TestOnSignMessageCommandWithoutPassword
    • TestOnSignMessageCommandWrongPassword
    • TestOnSignMessageCommandWithoutAccount
    • TestOnVerifyMessageCommand
    • TestOnSignWithSignatureReplayOnVerifyWithoutSignatureReplay
    • TestOnVerifyMessageCommand

@ajara87 ajara87 marked this pull request as draft November 26, 2025 06:54
@ajara87 ajara87 mentioned this pull request Nov 27, 2025
@ajara87
Copy link
Member Author

ajara87 commented Nov 27, 2025

Need #926 for UT

@github-actions github-actions bot added the N3 label Nov 27, 2025
@Jim8y
Copy link
Contributor

Jim8y commented Dec 3, 2025

Need #926 for UT

@ajara87 926 is merged

@ajara87
Copy link
Member Author

ajara87 commented Dec 3, 2025

Need #926 for UT

@ajara87 926 is merged

thank you @Jim8y. I have to upload a proposal and the PR is ready to review.

cschuchardt88
cschuchardt88 previously approved these changes Dec 3, 2025
@shargon
Copy link
Member

shargon commented Dec 3, 2025

@cschuchardt88 It's a draft

@ajara87 ajara87 marked this pull request as ready for review December 8, 2025 21:35
@superboyiii
Copy link
Member

superboyiii commented Dec 9, 2025

Message to sign -> Hello World!
CLI command: sign message "Hello world!"
Result:

Signed Payload:
010001f02e36323164323265666630626137333735666362363437363530366634316563362248656c6c6f20776f726c6421220000

    Curve: secp256r1
Algorithm: payload = 010001f0 + VarBytes(Salt + Message) + 0000
Algorithm: Sign(SHA256(network || Hash256(payload)))
           See the online documentation for details on how to verify this signature.
           https://developers.neo.org/docs/n3/node/cli/cli#sign_message

Message used for signing: ""Hello world!""
Message bytes: 2248656c6c6f20776f726c642122

Generated signatures:

    Address: NezDaziDDyWCnSqTVhSrLCxzVZKyVb3qmo
  PublicKey: 021c37a423b5885c4320d501128e1b502e86b40ce1c0dbc9d2f20ba98924228ef9
  Signature: 733abd56cc64154c036648c59527c4013d60b9f6caca33a748c64cb5b8f7f9b1716d1224401939c85c34d689b0d6860655cb9b78999a0eabffee83954b70cabb
       Salt: 621d22eff0ba7375fcb6476506f41ec6

2248656c6c6f20776f726c642122 hex to string is: "Hello world!". This is not good. In most CLI command, "" just means it's string, no other meaning, but sign message makes it combined with salt direclty, people may wrongly input this "".

@superboyiii
Copy link
Member

We need add verify message as well.
For example:

    /// <summary>
    /// Process "verify message" command
    /// </summary>
    /// <param name="message">Original message that was signed</param>
    /// <param name="signature">Signature in hex format</param>
    /// <param name="publicKey">Public key in hex format</param>
    /// <param name="salt">Salt in hex format</param>
    [ConsoleCommand("verify message", Category = "Wallet Commands")]
    private void OnVerifyMessageCommand(string message, string signature, string publicKey, string salt)
    {
        try
        {
            if (message.Length >= 2)
            {
                if ((message[0] == '"' && message[^1] == '"') || (message[0] == '\'' && message[^1] == '\''))
                {
                    message = message[1..^1];
                }
            }

            // Parse public key
            if (!ECPoint.TryParse(publicKey, ECCurve.Secp256r1, out var pubKey))
            {
                ConsoleHelper.Error("Invalid public key format");
                return;
            }

            // Parse signature
            byte[] signatureBytes;
            try
            {
                signatureBytes = signature.HexToBytes();
            }
            catch
            {
                ConsoleHelper.Error("Invalid signature format (must be hex string)");
                return;
            }

            // Validate salt format (should be hex string, typically 32 characters for 16 bytes)
            if (string.IsNullOrEmpty(salt))
            {
                ConsoleHelper.Error("Salt cannot be empty");
                return;
            }

            // Reconstruct payload: 010001f0 + VarBytes(Salt + Message) + 0000
            // Note: salt is used as hex string (lowercase), same as in signing
            var saltHex = salt.ToLowerInvariant();
            var paramBytes = Encoding.UTF8.GetBytes(saltHex + message);
            byte[] payload;
            using (var ms = new MemoryStream())
            using (var w = new BinaryWriter(ms, Encoding.UTF8, true))
            {
                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();
                payload = ms.ToArray();
            }

            // Calculate signData: SHA256(network || Hash256(payload))
            var hash = new UInt256(Crypto.Hash256(payload));
            var signData = hash.GetSignData(NeoSystem.Settings.Network);
            bool isValid = Crypto.VerifySignature(signData, signatureBytes, pubKey);
            var contract = Contract.CreateSignatureContract(pubKey);
            var address = contract.ScriptHash.ToAddress(NeoSystem.Settings.AddressVersion);

            Console.WriteLine();
            ConsoleHelper.Info("Verification Result:");
            Console.WriteLine();
            ConsoleHelper.Info("    Address: ", address);
            ConsoleHelper.Info("  PublicKey: ", pubKey.EncodePoint(true).ToHexString());
            ConsoleHelper.Info("  Signature: ", signature);
            ConsoleHelper.Info("       Salt: ", saltHex);
            ConsoleHelper.Info("     Status: ", isValid ? "Valid" : "Invalid");
            Console.WriteLine();

            if (!isValid)
            {
                ConsoleHelper.Info("Debug Information:");
                Console.WriteLine();
                ConsoleHelper.Info("  Message used: ", $"\"{message}\"");
                ConsoleHelper.Info("  Message bytes: ", Encoding.UTF8.GetBytes(message).ToHexString());
                ConsoleHelper.Info("  Salt+Message: ", $"{saltHex}{message}");
                ConsoleHelper.Info("  Reconstructed Payload: ", payload.ToHexString());
                ConsoleHelper.Info("  Payload Hash256: ", hash.ToString());
                Console.WriteLine();
                ConsoleHelper.Warning("Note: The message must match exactly what was signed.");
                ConsoleHelper.Warning("If you used 'sign message \"Hello world!\"', the actual message signed is 'Hello world!' (without quotes).");
                ConsoleHelper.Warning("If the signed message contains quote characters, you need to include them in the verify command.");
                Console.WriteLine();
            }
        }
        catch (Exception e)
        {
            ConsoleHelper.Error($"Verification failed: {GetExceptionMessage(e)}");
        }
    }

@superboyiii
Copy link
Member

superboyiii commented Dec 10, 2025

And we need rpc sign message & verify message as well. It can be more useful in actual scenarios, especially rpc verify message.

Co-authored-by: Owen <38493437+superboyiii@users.noreply.github.com>
@shargon
Copy link
Member

shargon commented Feb 19, 2026

@superboyiii could you test it please?

@superboyiii
Copy link
Member

@superboyiii could you test it please?

Testing

@codecov
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 95.30201% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.94%. Comparing base (dd4b561) to head (ca4d80d).
⚠️ Report is 1 commits behind head on master-n3.

Files with missing lines Patch % Lines
src/Neo.CLI/CLI/MainService.Wallet.cs 95.23% 5 Missing and 2 partials ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           master-n3     #924      +/-   ##
=============================================
+ Coverage      43.39%   43.94%   +0.54%     
=============================================
  Files            267      267              
  Lines          15284    15432     +148     
  Branches        1918     1935      +17     
=============================================
+ Hits            6632     6781     +149     
+ Misses          8298     8295       -3     
- Partials         354      356       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ajara87 ajara87 requested a review from superboyiii March 8, 2026 21:36
shargon
shargon previously approved these changes Mar 9, 2026
@shargon
Copy link
Member

shargon commented Mar 9, 2026

@ajara87 Please port to N4

@shargon shargon merged commit 3a0ac40 into neo-project:master-n3 Mar 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants