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

System.Crypto.CheckData interop #2866

Open
roman-khimov opened this issue Jun 3, 2023 · 6 comments
Open

System.Crypto.CheckData interop #2866

roman-khimov opened this issue Jun 3, 2023 · 6 comments
Labels
Discussion Initial issue state - proposed but not yet accepted

Comments

@roman-khimov
Copy link
Contributor

Summary or problem description
We've got two basic mechanisms to verify some permissions or action validity in a contract:

  • System.Runtime.CheckWitness that checks for scoped witness of the current transaction
  • CryptoLib.VerifyWithECDsa that can help with signatures of some non-transaction data

The first one is the standard thing that works for any Neo account. The second one only works with some single keys (but can handle secp256k1 curve). Usually the second one is used to check some auxiliary non-Neo-related data like cross-chain headers or state data, while regular Neo accounts provide witness (usually, signature(s)) for Neo transactions.

Creating a transaction for Neo blockchain implies answering to several questions:

  1. Who is paying for it?
  2. What nonce/fees/other technical fields are?
  3. What is the script this transaction will execute?

There are cases where we'd like to free the signer from answering to this questions. The first one can be solved with #2577 (and NeoFS sidechain uses it already), but the other two are somewhat more problematic. They assume some knowledge of Neo transaction structure and (!) NeoVM on the signing side. Of course transaction can be prepared elsewhere, but passing some base64 for wallet to sign is not a good idea in general, signer needs to know what exactly he is signing. This problem intersects strongly with neo-project/proposals#68 (and #2835 duplicate of it), but as mentioned there, it's not that easy in Neo context.

So going from an intent of doing something application-specific to transaction requires some effort. Some contracts try dealing with it by using simple signatures they can check with CryptoLib.VerifyWithECDsa. The data to sign can be anything application wants (like serialized NeoFS container structure) and this structure can be as app-friendly as it can be (only containing app-relevant data), but the approach itself inherently means that only simple single-key accounts could be used, application can't rely on Neo account system in this case and can't have multisignature, contract-based or non-standard accounts using this scheme.

Do you have any solution you want to propose?
Neo account witness in general is three things: account hash (address), invocation script and verification script (which is omitted for contracts), so verification requires executing some code which usually calls System.Crypto.CheckSig or System.Crypto.CheckMultisig. Both operate with the so-called "script container" (usually a transaction), serialized representation of which should be signed. So to be able to verify something we need to run two VM scripts with some specific script container.

We have an ability to run arbitrary read-only scripts in VM since 3.5.0 (System.Runtime.LoadScript from #2756). The only thing left to implement is ability to provide some data to check, overriding ScriptContainer for the execution since both System.Crypto.CheckSig and System.Crypto.CheckMultisig should work. The proposal is to implement this in a System.Crypto.CheckData interop.

It should accept:

  • data to check (or a hash of it)
  • address to check this data against
  • invocation script
  • verification script (can be null for contracts)

And it should return a simple boolean true/false result (as verification scripts usually do).

This allows to implement dApp-specific trust schemes that are easy to sign on clients that have absolutely no idea what's going on in the blockchain.

Example 1
NeoFS currently only allows single-key accounts and that's exactly because of the limitation described above. Creating a container via NeoFS SDK doesn't require managing transactions since we want to provide an API to manage data and not dealing with sidechain. But internally it's a sidechain transaction that calls NeoFS container contract. Going via System.Crypto.CheckData would allow to have containers owned by multisignature accounts.

Example 2
Consider neo-project/proposals#152 problem. While I understand people there want a very specific compatible solution, let's imagine how the problem of "allowing a spender to spend x amount of funds on behalf of an owner" could be solved with System.Crypto.CheckData (but notice that this is just an example, not a proposal for a fancy transferFrom).

It can be done with just a single transferFrom(spender Hash160, from Hash160, to Hash160, amount Integer, data Any, token String, invocation ByteArray, verification ByteArray) accepting a JWT-like token:

{
  "iss": "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn",
  "sub": "NMUedC8TSV2rE17wGguSvPk9XcmHSaT275",
  "exp": 3560000,
  "nbf": 3550000,
  "seq": 42,
  "asset": "NiHURyS83nX2mpxtA7xq84cGxVbHojj5Wc",
  "amount": 100
}

The implementation will:

  • check for spender witness
  • StdLib.JsonDeserialize() the token
  • check that the asset is the same as System.Runtime.GetExecutingScriptHash
  • check that from is the same as iss and spender is the same as sub
  • check that the token is already valid wrt nbf (in blocks, of course) and has not yet expired wrt exp (also in blocks)
  • check the sequence number of the token in seq is the next one for the issuer to use (that's to prevent replays, random IDs can be used, but counter (while adding some limitations) requires less storage)
  • check the requested amount against token's amount
  • and check the token of course, using System.Crypto.CheckData(token, from, invocation, verification)
  • if all good, transfer the requested amount of assets of course

So we have a scheme that only adds a single method, can work with a signature for a very simple and easy to understand structure and doesn't even require any transactions on the from side.

Neo Version

  • Neo 3

Where in the software does this update applies to?

  • Application Engine
@roman-khimov roman-khimov added the Discussion Initial issue state - proposed but not yet accepted label Jun 3, 2023
@roman-khimov
Copy link
Contributor Author

This can also help for NeoFS withdrawals. As we've said numerous times, in absence of #1573 we have a huge problem with GAS withdrawals from NeoFS mainnet contract. It requires confirmation from all of the alphabet IR nodes, so at the moment this implies collecting votes in the contract itself and consequently leads to a hefty GAS penalty for doing so. Instead a check can be signed in the sidechain and transferred to mainnet contract to withdraw with a single transaction.

@vncoelho
Copy link
Member

Hi @roman-khimov,

perhaps this CheckData interop is a good direction to go.

Is not the current System.Runtime.LoadScript able to handle all this if we use specific calls inside it? If we pass a complex script with all rules.

@roman-khimov
Copy link
Contributor Author

We can pass invocation/verification scripts (most likely a concatenation of those) into LoadScript now and get a result, but the problem is that Checksig/Checkmultisig calls will check for a signature of script container (transaction) and not some abstract data. Of course we can have arbitrary logic in scripts, but how do we tie this logic to Neo accounts if this logic is not a verification script?

@roman-khimov
Copy link
Contributor Author

One drawback of this approach is that signature checks are moved to execution phase, so they inherently become sequential (unlike transaction verification that can be performed concurrently). For some applications it's still OK this way, for some performance requirements can be higher.

@vncoelho
Copy link
Member

That is something I personally loved on NEO2, the ability of account abstraction was huge.
However, @roman-khimov , as you recently mentioned in your comment, make everything on the execution phase is not a good direction.

@roman-khimov
Copy link
Contributor Author

The only other way here is moving this into transaction. Make it a container for (an array of)

data to check (or a hash of it)
address to check this data against
invocation script
verification script (can be null for contracts)

Either in version 1 or just via some new attribute. This way the check could be performed as a part of a regular transaction verification and other code (like entry script) could access this data from transaction. Then no interop is needed, but this goes deeper at the same time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion Initial issue state - proposed but not yet accepted
Projects
None yet
Development

No branches or pull requests

2 participants