-
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
EcRecover #3633
base: HF_Echidna
Are you sure you want to change the base?
EcRecover #3633
Conversation
case NamedCurveHash.secp256k1SHA256: | ||
{ | ||
return Crypto.ECRecover(ECCurve.Secp256k1, signature, messageHash, | ||
// TODO: only accept 65? |
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.
64bytes signatures MAY store a parity bit in the first bit of the s
value which is always 0.
From what I deduce, you assume the parity value to be 0 if the size is 64 so checking the first bit of s
will not change the current behavior if this is not a compressed signature (i.e. parity will always be 0) But in case it is a compressed ERC-2098 signature, it will be supported out-of-the-box
Note: this is predicated on the assumption that N3 contracts might need to verify signatures that originate in NEOX where EIP standards might apply (eg: someone migrates a platform written for Ethereum to NeoX)
for reference, this is the compressed signature standard I am referring to: https://eips.ethereum.org/EIPS/eip-2098
src/Neo/Cryptography/Crypto.cs
Outdated
default: throw new InvalidOperationException("Invalid signature format"); | ||
} | ||
|
||
// Validate values |
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.
on the topic of validation, maybe having the s value in the lower half of the domain would be a good guard against signature maleability. At least that's how that was addressed in ethereum where the wallets only generate (r,s) pairs with s lower than n/2 (where n is the order of the curve). Because a pair (r, n-s) is also a valid solution leading to possible issues.
} | ||
} | ||
} | ||
catch { } |
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.
Several lines below we do throw new NotSupportedException(nameof(ch.Hasher));
if hashing algorythm is not supported. This exception should be properly thrown, so we don't need to catch it here.
Maybe we can actually have two methods: ==Specification== ===Native Contract Interface=== Two methods will be added to CryptoLib in ====Method 1: Recovery from Signature==== { "name": "secp256k1Recover", "safe": true, "parameters": [ { "name": "message", "type": "ByteArray" }, { "name": "hasher", "type": "Integer" }, { "name": "signature", "type": "ByteArray" } ], "returntype": "ByteArray" } ====Method 2: Recovery from Components==== { "name": "secp256k1Recover", "safe": true, "parameters": [ { "name": "message", "type": "ByteArray" }, { "name": "hasher", "type": "Integer" }, { "name": "r", "type": "ByteArray" }, { "name": "s", "type": "ByteArray" }, { "name": "v", "type": "Integer" } ], "returntype": "ByteArray" } ===Method Specification=== Both methods
|
src/Neo/Cryptography/Crypto.cs
Outdated
if (r.SignValue < 0) throw new ArgumentException("r should be positive"); | ||
if (s.SignValue < 0) throw new ArgumentException("s should be positive"); |
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.
if (r.CompareTo(BigInteger.One) < 0 || r.CompareTo(curve.N) > 0) throw new ArgumentException($"Invalid r value {r}; expected [1, {curve.N}]");
if (s.CompareTo(BigInteger.One) < 0 || s.CompareTo(curve.N) > 0) throw new ArgumentException($"Invalid s value {s}; expected [1, {curve.N}]");
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.
Optionally we can require s < n / 2 if v is not specified, for Ethereum
Co-authored-by: Anna Shaleva <[email protected]>
@Jim8y could you change it? |
# Conflicts: # src/Neo/Cryptography/Crypto.cs # src/Neo/SmartContract/Native/CryptoLib.cs
src/Neo/Cryptography/Crypto.cs
Outdated
if (signature.Length == 65) | ||
{ | ||
// Format: r[32] || s[32] || v[1] | ||
r = new BigInteger(1, signature.Take(32).ToArray()); |
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.
Use ranges, it's better
Co-authored-by: Christopher Schuchardt <[email protected]>
Co-authored-by: Christopher Schuchardt <[email protected]>
Co-authored-by: Christopher Schuchardt <[email protected]>
@OT-kraftchain hi, in https://eips.ethereum.org/EIPS/eip-2098, there are two test cases, but we are not able to recover the correct pubkey, do you mind to take a look? For other test cases this pr works fine, but just can not pass the test cases provided by EIP-2098 |
Neither the following codes are working in Console.WriteLine($"Expected PubKey: {expectedPubKey1.ToHexString()}");
var message1 = Encoding.UTF8.GetBytes("Hello World");
var messageHash1 = new byte[] { 0x19 }.Concat(Encoding.UTF8.GetBytes($"Ethereum Signed Message\n{message1.Count()}")).Concat(message1.Keccak256()).ToArray().Keccak256();
Console.WriteLine($"Message Hash: {Convert.ToHexString(messageHash1)}");
// Signature values from EIP-2098 test case
var r = "68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90".HexToBytes();
var s = "7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064".HexToBytes();
var signature1 = new byte[65];
Array.Copy(r, 0, signature1, 0, 32);
Array.Copy(s, 0, signature1, 32, 32);
signature1[64] = 27; |
Yeah, sure, I'll have a look asap |
// Test from https://eips.ethereum.org/EIPS/eip-2098 | ||
var privateKey = "1234567890123456789012345678901234567890123456789012345678901234".HexToBytes(); | ||
|
||
var expectedPubKey1 = (Neo.Cryptography.ECC.ECCurve.Secp256k1.G * privateKey).ToArray(); |
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.
Expected values
pubKeyX - 0xe90c7d3640a1568839c31b70a893ab6714ef8415b9de90cedfc1c8f353a6983e
pubKeyY - 0x625529392df7fa514bdd65a2003f6619567d79bee89830e63e932dbd42362d34
eth address for reference - 0x2e988A386a799F506693793c6A5AF6B54dfAaBfB
|
||
Console.WriteLine($"Expected PubKey: {expectedPubKey1.ToHexString()}"); | ||
var message1 = Encoding.UTF8.GetBytes("Hello World"); | ||
var messageHash1 = message1.Keccak256(); |
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.
This is supposed to be an Ethereum signed message which is not explicitly stated in ERC-2098
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
Expected value: 0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2
Fixed EIP-2098 tests, but failed in tests creating wallet in neo-cli. Cannot recognize the reason. |
final version: ==Specification== ===Native Contract Interface=== Three methods will be added to CryptoLib in ====Method 1: Recovery from Message and Signature==== { "name": "secp256k1Recover", "safe": true, "parameters": [ { "name": "message", "type": "ByteArray" }, { "name": "hasher", "type": "Integer" }, { "name": "signature", "type": "ByteArray" } ], "returntype": "ByteArray" } ====Method 2: Recovery from Message Components==== { "name": "secp256k1Recover", "safe": true, "parameters": [ { "name": "message", "type": "ByteArray" }, { "name": "hasher", "type": "Integer" }, { "name": "r", "type": "ByteArray" }, { "name": "s", "type": "ByteArray" }, { "name": "v", "type": "Integer" } ], "returntype": "ByteArray" } ====Method 3: Recovery from Pre-computed Hash==== { "name": "secp256k1Recover", "safe": true, "parameters": [ { "name": "hash", "type": "ByteArray" }, { "name": "signature", "type": "ByteArray" } ], "returntype": "ByteArray" } ===Method Specification=== The methods
|
Co-authored-by: Hecate2 <[email protected]>
I think the name |
I remember the public enum Hasher : byte |
Well I told you all so, once again!!! 😃 When it comes to standards and naming conventions no one listens to me. You dont care -- I dont care. Maybe bad english is the problem. |
Bro, i would care for sure, but as you said, maybe english is the problem, but once you point it out and fix it in a pr, i promise you i will support you. |
We can change the name
|
/// <param name="hasher">The hash algorithm to be used (SHA256 or Keccak256).</param> | ||
/// <param name="signature">The 65-byte signature in format: r[32] + s[32] + v[1]. 64-bytes for eip-2098, where v must be 27 or 28.</param> | ||
/// <returns>The recovered public key in compressed format, or null if recovery fails.</returns> | ||
[ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 10, Name = "secp256k1Recover")] |
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.
We should find the right price
@OT-kraftchain coredev team prefers to keep only one method in the native contract, can you please pick one that works for you the best. |
Description
Close #3628
Type of change
How Has This Been Tested?
Test Configuration:
Checklist: