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

Retroactive: Add Noir contract function types #1168

Closed
1 task done
Tracked by #1378
kevaundray opened this issue Apr 17, 2023 · 5 comments
Closed
1 task done
Tracked by #1378

Retroactive: Add Noir contract function types #1168

kevaundray opened this issue Apr 17, 2023 · 5 comments
Assignees
Labels
enhancement New feature or request

Comments

@kevaundray
Copy link
Contributor

kevaundray commented Apr 17, 2023

Problem

A Noir program has one type of function - a private function.

The function can have visibility identifiers, specifying whether the function when in a library, can be callable by another Noir program, but the function type itself stays the same.

As of writing this issue, we do not have visibility identifiers for Noir programs. The obvious choice would be to use pub because this is what Rust uses, though we may want to re-consider given the ambiguity around pub -- maybe its fine.

Proposed solution

Types of functions

Secret

These are functions which can only be executed by the client, because they are the only entity with the private information.

Functions are by default secret.

Non-Secret

The naming of functions which are not secret is still under discussion. We are aiming to not call them public, since its not clear if we mean a function which is publicly visible or a function which can be executed in a public context, like an ethereum solidity function. There is a also the added confusion due to zkSNARKs using the terminology "public inputs" to refer to inputs to a proof which can be shared by the prover and verifier.

I will use non-secret in this issue.

Now, simply put non-secret functions are functions which do not require the entity executing it to have private information. This state is shared, similar to public blockchains like Ethereum, so it must also be synchronized/sequenced.

We make the following assumption: Networks will have a sequencer whom they can rely on to execute and sequence public state. This does not have to be the same entity whom is executing the private state, which means that the entity executing their private state only needs to know about their private state which can be parallelized, and not worry about non-secret state which needs to be synchonized.

If you think about Ethereum, this is roughly how it works minus the private information. There are Nodes who will execute non-secret functions and sequence non-secret state mutations.

Ordering

The ordering of non-secret and secret state execution is important. Since a sequencer will be sequencing your non-secret functions, these will happen after the proofs have been created for secret functions. It is therefore not possible in a single-shot for a non-secret function to call a secret function; since execution of non-secret functions happen after secret functions.

We want to make this an error in the Noir compiler

Loss of generality

We make a simple argument that this does not lose any generality in practice.

Imagine the scenario where a user wanted to call a secret function s from a non-secret function ns in a single-shot(transaction). The only case where this is feasible is if the user also held and synchronized the non-secret state too. i.e. the user is the sequencer. This is by definition, since secret state does not need to be synchronized/ordered whereas non-secret state does.

Unconstrained

These are functions which allow the user to perform non-determinism. They are not directly callable from other contracts, since the code in the unconstrained function should only appply to the current contract.
They are still placed in the contract ABI and are callable from entities like wallets. This is what a user will use to, for example, get their balance.

Visibility

External

(This is up for discussion -- and will change after more discussion with @iAmMichaelConnor)

An external function is a function that will be placed in the contract ABI.

  • It can be either a non-secret or secret function.
  • They will be callable from other contracts and callable from functions in the same contract.
Internal

An internal function is a function that can only be called from functions in the same contract.

  • It can be either a non-secret or secret function.
  • Functions are by default internal.

Inlining

Same contract
  • If a secret function calls a secret internal function in the same contract, this is inlined.
  • If a non-secret function calls a non-secret internal function in the same contract, this is inlined.
  • If a secret function calls a secret external function in the same contract, this is not inlined.
  • If a non-secret function calls a non-secret external function in the same contract, this is not inlined.
  • If a secret function calls a non-secret function in the same contract, this is not inlined. (non-secret function can be seen as not happened yet)

non-secret functions are not allowed to call secret functions, even in the same contract.

Rough Idea

  • Two functions of the same type which are internal, can be inlined.
  • Calling a function marked as external will never be inlined, even if the external function is being called from the same contract.

This is because an internal and an external function are processed differently, one can view them as having different ABIs. If we were to allow same contract, external functions to be inlined, then the compiler would need to make the appropriate transformations behind the scene, by determining whether we should inline because it is the same contract and function type or not. The proposed strategy will leave this is in the hand of the developer, making it more explicit.

The downside of such an approach is that developes may end up doing the following pattern:

external add_external(a : Field, b : Field) -> Field {
 add_internal(a,b)
}

add_internal(a : Field, b : Field) -> Field {
  a + b
}

Here we have an internal and external version of the same function, one will be called inside the contract so that it gets inlined and the other will be called from outside of the contract.

Different contract

Note: If two contracts are defined within the same file, the rules still apply as if they were defined in two different files or in entirely different projects.

Calling a function from another contract is never inlined. To emphasize, for example, a secret function in contract A, calling a secret function in contract B, will not be inlined, since that would violate the property that functions should only ever modify their own contract state.

Submission Checklist

  • Once I hit submit, I will assign this issue to the Project Board with the appropriate tags.
@kevaundray kevaundray added the enhancement New feature or request label Apr 17, 2023
@github-project-automation github-project-automation bot moved this to 📋 Backlog in Noir Apr 17, 2023
@kevaundray
Copy link
Contributor Author

The semantics around inlining were copied from a chat I had with @sirasistant -- Gracias ser

@iAmMichaelConnor
Copy link
Collaborator

iAmMichaelConnor commented Apr 17, 2023

Calling a function marked as internal external will never be inlined, even if the external function is being called from the same contract.

Typo.

Also:

In Solidity, if a user wants to call an external function within the same contract, they need to write this.foo() instead of just foo(), to make it clear they're intending to make an 'external call'. If they write foo(), the compiler will complain that they're not allowed to call foo, because it's not internal. I guess this protects devs who are relying on the external and internal keywords for catching accidental bugs.

@kevaundray
Copy link
Contributor Author

kevaundray commented Apr 18, 2023

guess this protects devs who are relying on the external and internal keywords for catching accidental bugs.

Thanks for spotting the typo!

That sounds somewhat confusing since this.foo usually refers to the fact that a method foo is on an object referenced as this.

Note: We don't allow function overloading, so users would need to find some naming scheme for their internal and external functions.

^ This does not preclude bugs btw, just makes them less common due to not having function overloading. Maybe we can have some other annotation to convey the fact that "this should definitely not be inlined"

@sirasistant
Copy link
Contributor

sirasistant commented Jul 26, 2023

Regarding

Now, simply put non-secret functions are functions which do not require the entity executing it to have private information. This state is shared, similar to public blockchains like Ethereum, so it must also be synchronized/sequenced.

We make the following assumption: Networks will have a sequencer whom they can rely on to execute and sequence public state. This does not have to be the same entity whom is executing the private state, which means that the entity executing their private state only needs to know about their private state which can be parallelized, and not worry about non-secret state which needs to be synchonized.

Non secret functions would need to be compiled to brillig to be proven in a brillig VM by a third party, the sequencer, who could just say that the function fails to execute otherwise.

Made a PR for this change here #2052

@Savio-Sou
Copy link
Collaborator

Savio-Sou commented Sep 4, 2023

Closed with:

@github-project-automation github-project-automation bot moved this from 📋 Backlog to ✅ Done in Noir Sep 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Archived in project
Development

No branches or pull requests

4 participants