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 secp256k1 + keccak256 to signature verification. #3205

Closed
wants to merge 8 commits into from

Conversation

Jim8y
Copy link
Contributor

@Jim8y Jim8y commented Apr 25, 2024

Description

This pr focus on adding support to secp256k1 signed transactions and hashed with Keccak256.

Fixes # (issue)

Type of change

  • 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?

Test Configuration:

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

@Jim8y Jim8y marked this pull request as draft April 25, 2024 03:41
Comment on lines +22 to +26
protected internal readonly static Dictionary<byte, ECCurve> ECCurveSelection = new(){
{ 0x00, ECCurve.Secp256r1 },
{ 0x01, ECCurve.Secp256k1 },
};

Copy link
Contributor

Choose a reason for hiding this comment

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

(This may not be the best place to put this dictionary)

Copy link
Contributor

@roman-khimov roman-khimov left a comment

Choose a reason for hiding this comment

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

Why?

@Jim8y
Copy link
Contributor Author

Jim8y commented Apr 25, 2024

Why?

Hey @roman-khimov, this feature is required by some exchanges that only support secp256k1 to sign transactions, its hardware restrained and can not be updated. Thus, we need to add support to verify secp256k1 signed transactions. But we only verify it, we dont construct it.

@roman-khimov
Copy link
Contributor

How come it wasn't a problem for years of Neo existence?

This raises so many compatibility questions that I don't even know where to begin. Addresses/wallets/script parsers/NeoFS accounts/NeoX accounts.

Likely the issue can be solved with crypto.verifyWithECDsa that has support for k1 (proxy contract that does actions based on incoming payloads signed with k1 key).

@Jim8y
Copy link
Contributor Author

Jim8y commented Apr 27, 2024

New syscall:

System.Crypto.CheckSigV2

System.Crypto.CheckMultisigV2

Data Type:

InvocationScript: Old format
VerificationScript: 1 byte curve| 1 byte hash | Old format

Curve:

0x00 r1,
0x01 k1,

Hash:

0x00 SHA256,
0x01 Keccak256

Exampe:

            using ScriptBuilder verificationScriptBuilder = new();
            verificationScriptBuilder.EmitRaw([0x01]); // Push curve secp256K1
            verificationScriptBuilder.EmitRaw([0x01]); // Push Keccak256 hasher
            verificationScriptBuilder.EmitPush(pubKey); // Push pubkey
            verificationScriptBuilder.EmitSysCall(ApplicationEngine.System_Crypto_CheckSigV2);

Price:

SignatureContractCostV2:
SignatureContractCost() + (isV2 ? ApplicationEngine.OpCodePriceTable[(byte)OpCode.PUSH1] * 2 : 0);

MultiSignatureContractCostV2:
MultiSignatureContractCost() + (isV2 ? ApplicationEngine.OpCodePriceTable[(byte)OpCode.PUSH1] * 2 : 0);

How to check if a verification script is V2:

       private static ReadOnlySpan<byte> ParseScriptV2(ReadOnlySpan<byte> script, out bool? isV2, out ECCurve? curve, out Hasher? hasher)
        {
            if (script.Length < 40) throw new FormatException("The verification script is too short.");
            isV2 = BinaryPrimitives.ReadUInt32LittleEndian(script[^4..]) == ApplicationEngine.System_Crypto_CheckSigV2 ||
                BinaryPrimitives.ReadUInt32LittleEndian(script[^4..]) == ApplicationEngine.System_Crypto_CheckMultisigV2;
            curve = ECCurve.Secp256r1;
            hasher = Hasher.SHA256;
            if (!isV2.Value)
            {
                return script;
            }

            curve = script[0] == 0x01 ? ECCurve.Secp256k1 : ECCurve.Secp256r1;
            hasher = script[1] == 0x01 ? Hasher.Keccak256 : Hasher.SHA256;
            return script[2..];
        }

@Jim8y
Copy link
Contributor Author

Jim8y commented Apr 27, 2024

Note: Still need to add it to the hard fork.


public static byte[] Keccak256(this ReadOnlySpan<byte> value)
{
return Keccak256(value.ToArray());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return Keccak256(value.ToArray());
return Keccak256([.. value]);


public static byte[] Keccak256(this Span<byte> value)
{
return Keccak256(value.ToArray());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return Keccak256(value.ToArray());
return Keccak256([.. value]);

@@ -415,21 +415,21 @@ public virtual VerifyResult VerifyStateIndependent(ProtocolSettings settings)
UInt160[] hashes = GetScriptHashesForVerifying(null);
for (int i = 0; i < hashes.Length; i++)
{
if (IsSignatureContract(witnesses[i].VerificationScript.Span))
if (IsSignatureContract(witnesses[i].VerificationScript.Span, out bool? isV2, out ECCurve? curve, out Hasher? hasher))
Copy link
Member

Choose a reason for hiding this comment

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

Dont add ? nullable until project is convert to nullable project.

{
if (hashes[i] != witnesses[i].ScriptHash) return VerifyResult.Invalid;
var pubkey = witnesses[i].VerificationScript.Span[2..35];
var pubkey = isV2.Value ? witnesses[i].VerificationScript.Span[4..37] : witnesses[i].VerificationScript.Span[2..35];
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
var pubkey = isV2.Value ? witnesses[i].VerificationScript.Span[4..37] : witnesses[i].VerificationScript.Span[2..35];
var pubkey = isV2.Value ? witnesses[i].VerificationScript[4..37] : witnesses[i].VerificationScript[2..35];

try
{
if (!Crypto.VerifySignature(this.GetSignData(settings.Network), witnesses[i].InvocationScript.Span[2..], pubkey, ECCurve.Secp256r1))
if (!Crypto.VerifySignature(this.GetSignData(settings.Network), witnesses[i].InvocationScript.Span[2..], pubkey, curve, hasher.Value))
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (!Crypto.VerifySignature(this.GetSignData(settings.Network), witnesses[i].InvocationScript.Span[2..], pubkey, curve, hasher.Value))
if (!Crypto.VerifySignature(this.GetSignData(settings.Network), witnesses[i].InvocationScript[2..], pubkey, curve, hasher.Value))

return VerifyResult.InvalidSignature;
}
catch
{
return VerifyResult.Invalid;
}
}
else if (IsMultiSigContract(witnesses[i].VerificationScript.Span, out var m, out ECPoint[] points))
else if (IsMultiSigContract(witnesses[i].VerificationScript.Span, out var m, out ECPoint[] points, out isV2, out curve, out hasher))
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
else if (IsMultiSigContract(witnesses[i].VerificationScript.Span, out var m, out ECPoint[] points, out isV2, out curve, out hasher))
else if (IsMultiSigContract(witnesses[i].VerificationScript, out var m, out ECPoint[] points, out isV2, out curve, out hasher))

@AnnaShaleva
Copy link
Member

This raises so many compatibility questions that I don't even know where to begin. Addresses/wallets/script parsers/NeoFS accounts/NeoX accounts.

Just to be in sync with Discord: we've discussed it with @roman-khimov and prepared an alternative concept that do not require core protocol modifications and do not require custom verification contract deployment. The solution is rather simple: use custom witness verification script with a call to native CryptoLib's verifyWithECDsa. See the nspcc-dev/neo-go#3425 (it is an updated version) and don't hesitate to comment, I'll port it to the core in the nearest future.

@cschuchardt88
Copy link
Member

cschuchardt88 commented Apr 28, 2024

Can't they use NeoX to bridge to N3? Isnt that the point of NeoX?

@shargon
Copy link
Member

shargon commented Apr 29, 2024

It's possible to use his verifyWithECDsa as verification script without store the contract in the blockchain, isn't it?

@roman-khimov roman-khimov mentioned this pull request May 2, 2024
AnnaShaleva added a commit that referenced this pull request May 3, 2024
A port of
nspcc-dev/neo-go@1e2b438.

This commit contains minor protocol extension needed for custom
Koblitz-based verification scripts (an alternative to
#3205).

Replace native CryptoLib's verifyWithECDsa `curve` parameter by
`curveHash` parameter which is a enum over supported pairs of named
curves and hash functions.

Even though this change is a compatible extension of the protocol, it
changes the genesis state due to parameter renaming (CryptoLib's
manifest is changed). But we're going to resync chain in 3.7 release
anyway, so it's not a big deal.

Also, we need to check mainnet and testnet compatibility in case if
anyone has ever called verifyWithECDsa with 24 or 25 `curve` value.

Signed-off-by: Anna Shaleva <[email protected]>
AnnaShaleva added a commit that referenced this pull request May 3, 2024
Koblitz-based and Keccak-based transaction witness verification for
single signature and multisignature ported from
nspcc-dev/neo-go#3425.

An alternative to #3205.

Signed-off-by: Anna Shaleva <[email protected]>
AnnaShaleva added a commit that referenced this pull request May 6, 2024
A port of
nspcc-dev/neo-go@1e2b438.

This commit contains minor protocol extension needed for custom
Koblitz-based verification scripts (an alternative to
#3205).

Replace native CryptoLib's verifyWithECDsa `curve` parameter by
`curveHash` parameter which is a enum over supported pairs of named
curves and hash functions. NamedCurve enum mark as deprecated and
replaced by NamedCurveHash with compatible behaviour.

Even though this change is a compatible extension of the protocol, it
changes the genesis state due to parameter renaming (CryptoLib's
manifest is changed). But we're going to resync chain in 3.7 release
anyway, so it's not a big deal.

Also, we need to check mainnet and testnet compatibility in case if
anyone has ever called verifyWithECDsa with 24 or 25 `curve` value.

Signed-off-by: Anna Shaleva <[email protected]>
AnnaShaleva added a commit that referenced this pull request May 6, 2024
Koblitz-based and Keccak-based transaction witness verification for
single signature and multisignature ported from
nspcc-dev/neo-go#3425.

An alternative to #3205.

Signed-off-by: Anna Shaleva <[email protected]>
NGDAdmin pushed a commit that referenced this pull request May 10, 2024
…#3209)

* Native: extend CryptoLib's verifyWithECDsa with hasher parameter

A port of
nspcc-dev/neo-go@1e2b438.

This commit contains minor protocol extension needed for custom
Koblitz-based verification scripts (an alternative to
#3205).

Replace native CryptoLib's verifyWithECDsa `curve` parameter by
`curveHash` parameter which is a enum over supported pairs of named
curves and hash functions. NamedCurve enum mark as deprecated and
replaced by NamedCurveHash with compatible behaviour.

Even though this change is a compatible extension of the protocol, it
changes the genesis state due to parameter renaming (CryptoLib's
manifest is changed). But we're going to resync chain in 3.7 release
anyway, so it's not a big deal.

Also, we need to check mainnet and testnet compatibility in case if
anyone has ever called verifyWithECDsa with 24 or 25 `curve` value.

Signed-off-by: Anna Shaleva <[email protected]>

* SmartContract: add extension to ScriptBuilder for System.Contract.Call

Group the set of common operations required to emit
System.Contract.Call appcall.

Signed-off-by: Anna Shaleva <[email protected]>

* Native: add an example of custom Koblitz signature verification

Koblitz-based and Keccak-based transaction witness verification for
single signature and multisignature ported from
nspcc-dev/neo-go#3425.

An alternative to #3205.

Signed-off-by: Anna Shaleva <[email protected]>

* SmartContract: make multisig koblitz easier to parse

1. Make prologue be exactly the same as regular CheckMultisig.
2. But instead of "SYSCALL System.Crypto.CheckMultisig" do INITSLOT and K check.
3. This makes all of the code from INITSLOT below be independent of N/M, so
   one can parse the script beginning in the same way CheckMultisig is parsed and
   then just compare the rest of it with some known-good blob.
4. The script becomes a tiny bit larger now, but properties above are too good.

Ported from
nspcc-dev/neo-go@34ee294.

Signed-off-by: Anna Shaleva <[email protected]>

* SmartContract: use ABORT in Koblitz multisig

Make the script a bit shorter. ABORTMSG would cost a bit more.

Ported from
nspcc-dev/neo-go@fb16891.
Ref.
nspcc-dev/neo-go#3425 (comment).

Signed-off-by: Anna Shaleva <[email protected]>

* SmartContract: reduce callflag scope for Koblitz verification scripts

All flag is too wide. A port of
nspcc-dev/neo-go@fe292f3.

Ref.
nspcc-dev/neo-go#3425 (comment).

Signed-off-by: Anna Shaleva <[email protected]>

* Native: add tests for CryptoLib's verifyWithECDsa

No functional changes, just add more unit-tests.

Signed-off-by: Anna Shaleva <[email protected]>

* Native: update NamedCurveHash values for Keccak256 hasher

Use 122 and 123 respectively for secp256k1Keccak256 and
secp256r1Keccak256.

Signed-off-by: Anna Shaleva <[email protected]>

* SmartContract: move EmitAppCallNoArgs to the testing code

We're not going to implement custom Koblitz witness generation at the
core, and thus, the only user of this API is testing code.

Signed-off-by: Anna Shaleva <[email protected]>

* Apply suggestions from code review

clean ut lines

* fix names

* Cryptography: cache ECDomainParameters for Secp256r1 and Secp256k1

Signed-off-by: Anna Shaleva <[email protected]>

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

* Update tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs

---------

Signed-off-by: Anna Shaleva <[email protected]>
Co-authored-by: Shargon <[email protected]>
Co-authored-by: Jimmy <[email protected]>
@NGDAdmin
Copy link
Collaborator

Close because of #3209

@NGDAdmin NGDAdmin closed this May 10, 2024
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.

7 participants