Closed zac-williamson closed 2 years ago
Great to see you standardising this and making great progress in the corresponding Aztec protocol!
Couple of thoughts / questions:
If _proofType
always maps to whether or not a _proofType
is of a balancing relationship(s) or not, I’m wondering why this needs to be stored on chain (i.e. the mapping from _proofType
to _isBalanced
)? Did you have a use-case for this in mind (i.e. where a caller would not know the details of the _proofType
it was using, but would need to know whether the specific _proofType
satisfied a balancing relationship)?
Should a call to setProof
and setCommonReferenceString
clear existing proofs (for the specific type in the first case, and all types in the second case)? Otherwise a caller to validateProofByHash
would not have any guarantees about the terms under which the outputs which it is validating were proven.
You mention that the reason validateProof
needs to take in a _sender
address is to avoid a possible front-running attack - could you elaborate a bit on this with an example?
The links under the Implementation
section are out of date although the contracts can be easily found in your repo.
With clearProofByHashes
do you foresee the validateProof
and validateProofByHash
always happening inside a single transaction (and hence no one can call this function in-between to grief the caller of validateProofByHash
)? If that is the case, and in both instances it is smart contracts calling out to the Cryptography Engine
then I guess validateProofByHash
would only be needed if the smart contract calling this function didn't trust the smart contract that was asserting the proofOutput
which seems unusual unless this contract could be upgraded or otherwise tampered with.
Great to see you standardising this and making great progress in the corresponding Aztec protocol!
Couple of thoughts / questions:
- If
_proofType
always maps to whether or not a_proofType
is of a balancing relationship(s) or not, I’m wondering why this needs to be stored on chain (i.e. the mapping from_proofType
to_isBalanced
)? Did you have a use-case for this in mind (i.e. where a caller would not know the details of the_proofType
it was using, but would need to know whether the specific_proofType
satisfied a balancing relationship)?- Should a call to
setProof
andsetCommonReferenceString
clear existing proofs (for the specific type in the first case, and all types in the second case)? Otherwise a caller tovalidateProofByHash
would not have any guarantees about the terms under which the outputs which it is validating were proven.- You mention that the reason
validateProof
needs to take in a_sender
address is to avoid a possible front-running attack - could you elaborate a bit on this with an example?- The links under the
Implementation
section are out of date although the contracts can be easily found in your repo.- With
clearProofByHashes
do you foresee thevalidateProof
andvalidateProofByHash
always happening inside a single transaction (and hence no one can call this function in-between to grief the caller ofvalidateProofByHash
)? If that is the case, and in both instances it is smart contracts calling out to theCryptography Engine
then I guessvalidateProofByHash
would only be needed if the smart contract calling this function didn't trust the smart contract that was asserting theproofOutput
which seems unusual unless this contract could be upgraded or otherwise tampered with.
Heya! Sorry for the slow reply.
any smart contract that intends to manipulate a registry of AZTEC notes based on proofs from the Cryptography Engine (e.g. ERC1724) will need to know whether a given proof type satisfies a balancing relationship. isBalanced
enables a contract to validate this, without having to have foreknowledge about the proofs supported by the Cryptography Engine
Thanks for bringing that up. That issue might get resolved because we're thinking of changing setProof
so that existing proofs cannot be modified, so that developers can have surety over a given proof if they intend to use it in their smart contract. You're correct that we'll need to invalidate previous proofs if the CRS gets updated. I'll update the EIP to reflect this.
If I construct a valid zero-knowledge proof and then broadcast it to the network, theoretically an attacker could grab my proof out of the mining pool and broadcast it themselves, and use it for their own purposes. By integrating _sender
into the zero-knowledge proof, the proof is only valid when broadcast from a single account.
Thanks for identifying that, I'll update the links
This is correct. If I'm building a zero-knowledge Dapp that interacts with confidential assets, these assets cannot assume any instructions sent to them from my Dapp are legitimate, and must validate the instructions with the Cryptography Engine.
There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.
This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.
Cryptography Engine Standard
Simple Summary
This EIP defines the interface and behaviours of a zero-knowledge proof validation engine, supporting multiple compatible types of zero-knowledge proof. The Cryptography Engine enables developers to construct customized transaction semantics for confidential digital digital assets.
Abstract
This standard defines a mechanism by which multiple confidential digital assets and confidential DApps can efficiently communicate with one another, whilst enabling developers to customize the transaction semantics of their confidential smart contract.
The Cryptography Engine acts as a validator for a set of mutually compatible zero-knowledge proofs that conform to the AZTEC protocol. These proofs can be used by digital asset builders to construct confidential transaction semantics for digital assets and dApps. By subscribing to the same Cryptography Engine, smart contracts can efficiently communicate with one another while preserving confidentiality.
Motivation
Confidential transactions, where the values inside a transaction are encrypted, are made possible through zero-knowledge proofs. Currently, existing zero-knowledge proofs define unilateral transactions - transfers of value of one asset type only, issued by a single user.
While useful, there is a significant shortfall between the functionality of current confidential digital assets and public assets. Specifically, when comparing confidential digital assets with the ERC20 token standard, the following functionality is missing:
Bridging the Gap with the Cryptography Engine
The AZTEC protocol enables confidential transactions on Ethereum and the construction of confidential digital assets. At the core of the protocol is the AZTEC commitment function - a method of encrypting data that enables the highly efficient construction and verification of range proofs.
This in turn enables highly efficient Sigma protocols - simple zero-knowledge proofs that validate relationships between encrypted numbers via homomorphic arithmetic.
The AZTEC protocol's "join-split" transaction enables basic unilateral confidential transfers of value. If a digital asset builder wishes to define more advanced confidential transaction semantics, these can be expressed as a Sigma protocol layered on top of a "join-split" transaction.
The Cryptography Engine defines a set of these Sigma protocols, that developers can use in a modular fashion to construct complex confidential transaction semantics.
Cross-Asset Interoperability
Confidential settlement, where an exchange of value between different assets occurs confidentially, is necessary for a wide degree of financial applications. However this is, traditionally, a computationally expensive endeavour: every smart contract in a transaction sequence must validate its own zero-knowledge proof in order to prevent double spending. However this results in redundant computation - the proof statements that these smart contracts are validating will overlap significantly.
This problem is solved by using a single verification engine. A confidential AZTEC transaction must satisfy a balancing relationship - the transaction inputs must be equal to the transaction outputs. If multiple smart contracts require the same balancing relationship to be satisfied, the Cryptography Engine can identify this and prevent redundant computation from being performed. To summarise:
For example consider a confidential decentralized exchange dApp
In the above example, the bilateral swap zero-knowledge proof costs approximately 500,000 gas to verify. If each confidential asset also required their own zero-knowledge proof, this would add over 1,000,000 gas to the transaction's gas cost.
Security and Trust
The Cryptography Engine's AZTEC proofs all utilize the same common reference string. As a consequence, all confidential smart contracts that use the Cryptography Engine can share the same single trusted setup - a trusted setup is not required per dApp, and all dApps can share the same security assumptions.
Example Set of Zero-Knowledge Proofs
The following is an initial set of AZTEC protocol proofs that an MVP Cryptography Engine can support. As more use-cases and requirements become apparent, Sigma protocols can be developed that satisfy these use-cases and then added to the Cryptography Engine.
join-split
bilateral-swap
dividend
public-range
private-range
Note: the gas costs above do NOT include the costs associated with sending the transaction or paying for the input data.
Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Every ERC-1723 compliant contract MUST implement the following interface:
The token contract MUST implement the above interface to be compatible with the standard. The implementation MUST follow the specifications described below.
Methods
validateProofByHash
After a dApp calls
validateProof
, it may issueconfidentialTransferFrom
instructions to one or more confidential digital assets, supplying abytes proofOutput
object as a transfer instruction.This digital asset can then compute the
keccak256
hash ofbytes proofOutput
and query whether this instruction satisfies a balancing relationship by callingvalidateProofByHash
.If
bytes32 _proofHash
comes from a satisfying balancing relationship from a proof sent byaddress _sender
, with type_proofType
, the Cryptography Engine MUST return true. Ifbytes32 _proofHash
does not come from a satisfying balancing relationship, the Cryptography Engine MUST returnfalse
.setCommonReferenceString
Changes the Cryptography Engine's AZTEC common reference string. This string is generated via a trusted setup ceremony, and can be created via a multiparty computation protocol. The same restrictions that apply to
setProof
should apply tosetCommonReferenceString
.setProof
Maps a given
_proofType
to the address of a validator smart contract. This is a privileged action, as providing faulty validator smart contracts fatally undermines the security of the Cryptography Engine. Ideally this method is restricted by a consensus mechanism, where the protocol's stakeholders decide the proof types and validator smart contracts supported by the Cryptography Engine.validateProof
Validate an AZTEC zero-knowledge proof according to the proof's
_proofType
, proof's_proofData
and the message_sender
.If the proof is not valid, this method MUST throw an error.
If the proof is valid,
bytes proofOutputs
MUST be formatted according to the Cryptography Engine's ABI specification.The field
address _sender
corresponds to the address of the entity issuing the original transaction - it is the responsibility of the contract calling the Cryptography Engine to correctly supply this variable. To do otherwise does not affect the security of the Cryptography Engine or its zero-knowledge proofs, however it makes the contract callingvalidateProof
vulnerable to front-running attacks.The Cryptography Engine then MUST record the correctness of
bytes proofOutput
and the_proofType
against a unique combination of the following components:keccak256
hash ofbytes proofOutput
_proofType
msg.sender
(not_sender
)clearProofByHashes
Function is designed to utilize EIP-1283 to reduce gas costs. It is highly likely that any storage variables set by
validateProof
are only required for the duration of a single transaction.E.g. a decentralized exchange validating a swap proof and sending transfer instructions to two confidential assets.
This method allows the calling smart contract to recover most of the gas spent by setting
validatedProofs
, by clearing any set state variables before the transaction terminates.Motivation and Rationale for
uint16 _proofType
The
uint16 _proofType
variable defines which zero-knowledge proof to verify. It functions in a similar way to a function signature, where IDs are represented by 16-bit integers instead of 4-bytes of a keccak256 hash.The rationale behind this is to provide digital asset builders with an efficient method to define the set of zero-knowledge proofs that their asset subscribes to, without having to set a storage variable for every proof. For the
uint16
type one can use a bit-filter to completely define the set of proofs the asset listens to.The potential downside is being limited to 65535 proofs. Every proof supported by the crypto-engine must be extensively vetted before being integrated into the engine, with a formal soundness proof - a single insecure proof renders the entire cryptosystem insecure. As a result, a maximum cap of 65535 proofs seems reasonable, as one would question the security of such a broad cryptography engine.
ABI Encoding of
proofOutputs
Due to the nature of zero-knowledge cryptography, the data structure of a zero-knowledge proof is relatively complex. To abstract this away from users and developers, AZTEC zero-knowledge proofs supplied to the Cryptography Engine are encoded as a
bytes
argument.It falls to the Cryptography Engine to process this
bytes
argument and present, as an output to a valid proof, transfer instructions to the sender via:bytes proofOutput
. A transfer instruction involves the following:These transfer instructions are not simple. The natural instinct is to encode this data as a struct, however ABI encoding for structs is still experimental and should not be included in a standard.
To this end,
bytes
types are used to define the structure ofproofOutputs
and its constituent components. A JSON schema of how these types are encoded is provided below. The two key custom types used are an encoding for an AZTEC note,aztecNote
, as well as the encoding for aproofOutput
. Encoded data is NOT packed.Any implementation of the Cryptography Engine spec MUST format its output according to this specification.
In order to allow developers to easily manipulate
proofOutputs
and its child components, utilities libraries are provided to convert this data into its constituent Solidity types.JSON Schemas
aztecNote
proofOutput
Cryptography Engine Utilities
Implementation
See the following resources for a work in progress implementation:
To see more code, head to the AZTEC monorepo. Many thanks to @PaulRBerg, @thomas-waite, @ArnSch and the @AztecProtocol team for their contributions to this document.
Copyright
Work released under LGPL-3.0.