port message sign to master-n3#924
Conversation
|
Need #926 for UT |
cd5f7be to
87b6864
Compare
|
@cschuchardt88 It's a draft |
|
Message to sign ->
|
|
We need add verify message as well. /// <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)}");
}
} |
|
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>
| // 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); |
There was a problem hiding this comment.
The message can contain the salt. I think we can remove the salt, and recommend to add it in the NEP
There was a problem hiding this comment.
@roman-khimov what do you think?
Same as previous, this code just does what wallets were doing for years. They salted the message, good for them, OK for me.
What is
010001f0?
Magic. Chosen years ago to ensure that signed message is never a transaction. Still works for N3 since we don't have transaction version 1 (yet).
Refs:
There was a problem hiding this comment.
Compared to salt, I prefer adding a timestamp. Additionally, the timestamp, message content, etc., can be encapsulated into structured data.
There was a problem hiding this comment.
Maybe we should not bother it so much~~~ as long as it works.
There was a problem hiding this comment.
@roman-khimov the thing is that we added SignData call, this add a signature protection that makes incompatible with the previous versions
There was a problem hiding this comment.
But who needs an incompatible version of this thing? As noticed previously, even NeoFS is compatible with this scheme:
https://github.com/nspcc-dev/neofs-sdk-go/blob/23f5f0a8f3de952108d36cdbe589d0a2910ce8be/crypto/ecdsa/wallet_connect.go#L108
because when we played with gateways/wallets it was quickly discovered that they do not allow signing arbitrary data, they can sign messages in this format. So we adapted to it with this additional signature scheme. I don't see why node wallet can't do the same. There is nothing inherently wrong here, mostly it depends on what is signed and how the message signed is used, but that's not a wallet responsibility.
|
@shargon please add this one to 3.9. |
neo-project/neo#4091 (comment) |
|
@ajara87 We need rpc |
Sure! I'll do It in another PR |
Pull Request Test Coverage Report for Build 21714050411Details
💛 - Coveralls |
|
Tested OK. |
|
|
||
| // Calculate signData: SHA256(network || Hash256(payload)) | ||
| var hash = new UInt256(Crypto.Hash256(payload)); | ||
| var signData = hash.GetSignData(NeoSystem.Settings.Network); |
There was a problem hiding this comment.
This already makes incompatible with the wallets version. I don't see that these wallets or providers have previously tried to make their "standard" official (by nep o github issue), so I don't understand why we have to respect a project's own version
Porting neo-project/neo#4286 of @adrian-fjellberg
Proposal neo-project/proposals#213
Added use of GetSignData
Added UTs