-
Notifications
You must be signed in to change notification settings - Fork 270
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
docs(spec): hashing and keys #5478
Changes from all commits
22372e2
028fc47
2cfe519
45ed927
889cc40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
$$ | ||
|
||
|
||
\gdef\sk{\color{red}{sk}\color{black}{}} | ||
\gdef\seed{\color{red}\text{{seed}}\color{black}{}} | ||
|
||
\gdef\nskm{\color{red}{nsk_m}\color{black}{}} | ||
\gdef\tskm{\color{red}{tsk_m}\color{black}{}} | ||
\gdef\ivskm{\color{red}{ivsk_m}\color{black}{}} | ||
\gdef\ovskm{\color{red}{ovsk_m}\color{black}{}} | ||
|
||
\gdef\Npkm{\color{green}{Npk_m}\color{black}{}} | ||
\gdef\Tpkm{\color{green}{Tpk_m}\color{black}{}} | ||
\gdef\Ivpkm{\color{green}{Ivpk_m}\color{black}{}} | ||
\gdef\Ovpkm{\color{green}{Ovpk_m}\color{black}{}} | ||
|
||
|
||
\gdef\address{\color{green}{address}\color{black}{}} | ||
\gdef\codehash{\color{green}{code\_hash}\color{black}{}} | ||
\gdef\constructorhash{\color{green}{constructor\_hash}\color{black}{}} | ||
\gdef\classid{\color{green}{class\id}\color{black}{}} | ||
|
||
|
||
\gdef\nskapp{\color{red}{nsk_{app}}\color{black}{}} | ||
\gdef\tskapp{\color{red}{tsk_{app}}\color{black}{}} | ||
\gdef\ivskapp{\color{red}{ivsk_{app}}\color{black}{}} | ||
\gdef\ovskapp{\color{red}{ovsk_{app}}\color{black}{}} | ||
|
||
\gdef\Nkapp{\color{orange}{Nk_{app}}\color{black}{}} | ||
|
||
\gdef\Npkapp{\color{green}{Npk_{app}}\color{black}{}} | ||
|
||
|
||
\gdef\Ivpkapp{\color{green}{Ivpk_{app}}\color{black}{}} | ||
|
||
|
||
\gdef\happL{\color{green}{h_{app}^L}\color{black}{}} | ||
\gdef\happn{\color{green}{h_{app}^n}\color{black}{}} | ||
\gdef\happiv{\color{green}{h_{app}^{iv}}\color{black}{}} | ||
|
||
|
||
\gdef\d{\color{green}{d}\color{black}{}} | ||
\gdef\Gd{\color{green}{G_d}\color{black}{}} | ||
|
||
\gdef\Ivpkappd{\color{violet}{Ivpk_{app,d}}\color{black}{}} | ||
\gdef\shareableIvpkappd{\color{violet}{\widetilde{Ivpk_{app,d}}}\color{black}{}} | ||
\gdef\Ivpkmd{\color{violet}{Ivpk_{m,d}}\color{black}{}} | ||
\gdef\shareableIvpkmd{\color{violet}{\widetilde{Ivpk_{m,d}}}\color{black}{}} | ||
|
||
|
||
\gdef\ivskappstealth{\color{red}{ivsk_{app,stealth}}\color{black}{}} | ||
\gdef\Ivpkappdstealth{\color{violet}{Ivpk_{app,d,stealth}}\color{black}{}} | ||
\gdef\Pkappdstealth{\color{violet}{Pk_{app,d,stealth}}\color{black}{}} | ||
\gdef\ivskmstealth{\color{red}{ivsk_{m,stealth}}\color{black}{}} | ||
\gdef\Ivpkmdstealth{\color{violet}{Ivpk_{m,d,stealth}}\color{black}{}} | ||
\gdef\Pkmdstealth{\color{violet}{Pk_{m,d,stealth}}\color{black}{}} | ||
|
||
\gdef\hstealth{\color{violet}{h_{stealth}}\color{black}{}} | ||
|
||
|
||
\gdef\esk{\color{red}{esk}\color{black}{}} | ||
\gdef\Epk{\color{green}{Epk}\color{black}{}} | ||
\gdef\Epkd{\color{green}{Epk_d}\color{black}{}} | ||
\gdef\eskheader{\color{red}{esk_{header}}\color{black}{}} | ||
\gdef\Epkheader{\color{green}{Epk_{header}}\color{black}{}} | ||
\gdef\Epkdheader{\color{green}{Epk_{d,header}}\color{black}{}} | ||
|
||
\gdef\sharedsecret{\color{violet}{\text{S}}\color{black}{}} | ||
\gdef\sharedsecretmheader{\color{violet}{\text{S_{m,header}}}\color{black}{}} | ||
\gdef\sharedsecretappheader{\color{violet}{\text{S_{app,header}}}\color{black}{}} | ||
|
||
|
||
\gdef\hmencheader{\color{violet}{h_{m,enc,header}}\color{black}{}} | ||
\gdef\happencheader{\color{violet}{h_{app,enc,header}}\color{black}{}} | ||
\gdef\hmenc{\color{violet}{h_{m,enc}}\color{black}{}} | ||
\gdef\happenc{\color{violet}{h_{app,enc}}\color{black}{}} | ||
\gdef\incomingenckey{\color{violet}{h_{incoming\_enc\_key}}\color{black}{}} | ||
|
||
|
||
\gdef\plaintext{\color{red}{\text{plaintext}}\color{black}{}} | ||
\gdef\ciphertext{\color{green}{\text{ciphertext}}\color{black}{}} | ||
\gdef\ciphertextheader{\color{green}{\text{ciphertext\_header}}\color{black}{}} | ||
\gdef\payload{\color{green}{\text{payload}}\color{black}{}} | ||
|
||
|
||
\gdef\tagg{\color{green}{\text{tag}}\color{black}{}} | ||
\gdef\Taghs{\color{green}{\text{Tag}_{hs}}\color{black}{}} | ||
|
||
|
||
$$ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ title: Address | |
|
||
An address is computed as the hash of the following fields: | ||
|
||
<!-- TODO: discrepancy between this hash preimage and the contract classes page pseudocode, which includes a version --> | ||
|
||
<!-- prettier-ignore --> | ||
| Field | Type | Description | | ||
|----------|----------|----------| | ||
|
@@ -27,21 +29,65 @@ We may remove the `portal_contract_address` as a first-class citizen. | |
|
||
The hashing scheme for the address should then ensure that checks that are more frequent can be done cheaply, and that data shared out of band is kept manageable. We define the hash to be computed as follows: | ||
|
||
``` | ||
salted_initialization_hash = pedersen([salt, initialization_hash, deployer as Field, portal_contract_address as Field], GENERATOR__SALTED_INITIALIZATION_HASH) | ||
partial_address = pedersen([contract_class_id, salted_initialization_hash], GENERATOR__CONTRACT_PARTIAL_ADDRESS_V1) | ||
address = pedersen([public_keys_hash, partial_address], GENERATOR__CONTRACT_ADDRESS_V1) | ||
``` | ||
<!-- TODO: missing `version` from hashing! --> | ||
|
||
:::warning | ||
Some of these draft domain separators might be too many bits; they need to fit inside a single field element. Version numbers might not be needed until we roll the _next_ version. | ||
::: | ||
|
||
```rust | ||
address_crh( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does crh stand for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Collision-resistant hash. It was a half-hearted attempt at reminding us what properties we want from each hash. The zcash spec names hashes similarly, so I took inspiration from there. We can just call it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
version: Field, | ||
salt: Field, | ||
deployer: AztecAddress, | ||
contract_class_id: Field, | ||
initialization_hash: Field, | ||
portal_contract_address: EthereumAddress, | ||
public_keys_hash: Field, | ||
) -> Field { | ||
|
||
let salted_initialization_hash: Field = poseidon2( | ||
be_string_to_field("az_salted_initialization_hash_v1"), | ||
|
||
The `public_keys` array can vary depending on the format of keys used by the address, but it is suggested it includes the master keys defined in the [keys section](./keys.md). | ||
salt, | ||
initialization_hash, | ||
deployer.to_field(), | ||
be_bits_to_field(portal_contract_address) | ||
); | ||
|
||
let partial_address: Field = poseidon2( | ||
be_string_to_field("az_contract_partial_address_v1"), | ||
|
||
contract_class_id, | ||
salted_initialization_hash | ||
); | ||
|
||
let address: Field = poseidon2( | ||
be_string_to_field("az_contract_address_v1"), | ||
|
||
public_keys_hash, | ||
partial_address | ||
); | ||
|
||
address | ||
} | ||
``` | ||
public_keys_hash = pedersen([ | ||
nullifier_pubkey.x, nullifier_pubkey.y, | ||
tagging_pubkey.x, tagging_pubkey.y, | ||
incoming_view_pubkey.x, incoming_view_pubkey.y, | ||
outgoing_view_pubkey.x, outgoing_view_pubkey.y | ||
], GENERATOR__PUBLIC_KEYS) | ||
|
||
The `public_keys` array can vary depending on the format of keys used by the address, but it is suggested it includes the master keys defined in the [keys section](./keys.mdx). For example: | ||
|
||
```rust | ||
let public_keys_hash: Field = poseidon2( | ||
be_string_to_field("az_public_keys_hash"), // TODO: does this need some unique ID, to disambiguate from other approaches people might have for other public keys? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say this depends on the precompile that's going to be validating this set of keys, assuming we go with that design. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. What domain separator should we use for this particular keys scheme? :) |
||
|
||
nullifier_pubkey.x, | ||
nullifier_pubkey.y, | ||
tagging_pubkey.x, | ||
tagging_pubkey.y, | ||
incoming_view_pubkey.x, | ||
incoming_view_pubkey.y, | ||
outgoing_view_pubkey.x, | ||
outgoing_view_pubkey.y | ||
); | ||
``` | ||
|
||
This recommended hash format is compatible with the [encryption precompiles](./precompiles.md#encryption-and-tagging-precompiles) initially defined in the protocol and advertised in the canonical [registry](../pre-compiled-contracts/registry.md) for private message delivery. An address that chooses to use a different format for its keys will not be compatible with apps that rely on the registry for note encryption. Nevertheless, new precompiles introduced in future versions of the protocol could use different public keys formats. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<!-- @dev: if you want to import the preamble, remember to make the importing file a `.mdx` file. --> | ||
|
||
import LatexPreamble from "../0-keys-latex-preamble.md"; | ||
<LatexPreamble />; | ||
|
||
## Deriving diversified public keys | ||
|
||
A diversified public key can be derived from Alice's keys, to enhance Alice's transaction privacy. If Alice's counterparties' databases are compromised, it enables Alice to retain privacy from such leakages. Diversified public keys are used for generating diversified addresses. | ||
|
||
Basically, Alice must personally derive and provide Bob and Charlie with random-looking addresses (for Alice). Because Alice is the one deriving these Diversified Addresses (they can _only_ be derived by Alice), if Bob and Charlie chose to later collude, they would not be able to convince each-other that they'd interacted with Alice. | ||
|
||
This is not to be confused with 'Stealth Addresses', which 'flip' who derives: Bob and Charlie would each derive a random-looking Stealth Address for Alice. Alice would then discover her new Stealth Addresses through decryption. | ||
|
||
> All of the key information below is Alice's | ||
|
||
Alice derives a 'diversified' incoming viewing public key, and sends it to Bob: | ||
|
||
<!-- prettier-ignore --> | ||
| Thing | Derivation | Name | Comments | | ||
|---|---|---|---| | ||
$\d$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ |diversifier | | ||
$\Gd$ | $\d \cdot G$ | diversified generator | | ||
$\Ivpkmd$ | $\ivskm \cdot \Gd$ | Diversified incoming viewing public key | | ||
|
||
> Notice: when $\d = 1$, $\Ivpkmd = \Ivpkm$. Often, it will be unncessary to diversify the below data, but we keep $\d$ around for the most generality. | ||
|
||
## Deriving stealth public keys | ||
|
||
> All of the key information below is Alice's | ||
|
||
Stealth Public Keys are used for generating Stealth Addresses. For Bob to derive a Stealth Address for Alice, Bob derives: | ||
|
||
<!-- prettier-ignore --> | ||
| Thing | Derivation | Name | Comments | | ||
|---|---|---|---| | ||
$\d$ | Given by Alice | (Diversifier) | Remember, in most cases, $\d=1$ is sufficient. | ||
$\Gd$ | $\d \cdot G$ | (Diversified) generator | Remember, when $\d = 1$, $\Gd = G$. | ||
$\esk_{stealth}$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret, for deriving the stealth key shared secret | | ||
$\Epkd,_{stealth}$ | $\esk_{stealth} \cdot \Gd$ | (Diversified) Ephemeral public key, for deriving the stealth key shared secret | | ||
$\sharedsecret_{m, stealth}$ | $\esk_{stealth} \cdot \Ivpkmd$ | Stealth key shared secret | | ||
$\hstealth$ | $\text{pos2}(\text{``az\_stealth\_key''}, \sharedsecret_{m, stealth})$ | stealth key | | ||
$\Ivpkmdstealth$ | $\hstealth \cdot \Gd + \Ivpkmd$ | (Diversified) Stealth viewing public key | | ||
|
||
Having derived a Stealth Address for Alice, Bob can now share it with Alice as follows: | ||
|
||
<!-- prettier-ignore --> | ||
| Thing | Derivation | Name | Comments | | ||
|---|---|---|---| | ||
$\tagg_{m, i}^{Bob \rightarrow Alice}$ | See earlier in this doc. | | Derive the next tag in the $Bob\rightarrow Alice$ sequence.<br />Note: we illustrate with a _master_ tag sequence, but an app-specific tag sequence could also be used (in which case an encryption of the app_address in a ciphertext header wouldn't be required; it could just be inferred from the tag used). | | ||
$\esk_{header}$ | $\stackrel{rand}{\leftarrow} \mathbb{F}$ | ephemeral secret key, for deriving the ciphertext header shared secret | | ||
$\Epkd,_{header}$ | $\esk_{header} \cdot \Gd$ | (Diversified) Ephemeral public key, for deriving the ciphertext header shared secret | | ||
$\sharedsecret_{m,header}$ | $\esk_{header} \cdot \Ivpkm$ | Ciphertext header shared secret | TODO: we might need to use a different ephemeral keypair from the one used to derive the stealth address. | | ||
$\hmencheader$ | $\text{pos2}(\text{``az\_enc\_key''}, \sharedsecret_{m,header})$ | ciphertext header encryption key | ||
$\ciphertextheader$ | $\text{encrypt}^{\Ivpkm}_{\hmencheader}$(app\_address) | | TODO: diversify this? | | ||
$\payload$ | [ $\tagg_{m, i}^{Bob \rightarrow Alice}$, $\Epkd,_{header}$, $\ciphertextheader$, $\Epkd,_{stealth}$ ] | | ||
|
||
Alice can learn about her new Stealth Address as follows. First, she would identify the transaction has intended for her, either by observing $\tagg_{m, i}^{Bob \rightarrow Alice}$ on-chain herself (and then downloading the rest of the payload which accompanies the tag), or by making a privacy-preserving request to a server, to retrieve the payload which accompanies the tag. Assuming the $\payload$ has been identified as Alice's, we proceed: | ||
|
||
<!-- prettier-ignore --> | ||
| Thing | Derivation | Name | | ||
|---|---|---| | ||
$\sharedsecret_{m,header}$ | $\ivskm \cdot \Epkd,_{header}$ | Ciphertext header shared secret | | ||
$\hmencheader$ | $\text{pos2}(\text{``az\_enc\_key''}, \sharedsecret_{m,header})$ | ciphertext header encryption key | | ||
app_address | $\text{decrypt}_{\hmencheader}^{\ivskm}(\ciphertextheader)$ | | ||
$\ivskm$ | See derivations above. Use the decrypted app_address in the derivation. | app-specific incoming viewing secret key | | ||
$\sharedsecret_{m, stealth}$ | $\ivskm \cdot \Epkd,_{stealth}$ | Stealth key shared secret | | ||
$\hstealth$ | $\text{pos2}(\text{``az\_stealth\_key''}, \sharedsecret_{m, stealth})$ | stealth key | | ||
$\ivskmstealth$ | $\hstealth + \ivskm$ | | ||
$\Ivpkmdstealth$ | $\ivskmstealth \cdot \Gd$ | (Diversified) Stealth viewing public key | |
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.
Why not use a hash (could be sha256) of the string as domain separator, so we can avoid this problem and also embed the version in there?
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.
Somewhat related: should we also inject the chain id into the domain separator, as eip712 does?
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 Noir can evaluate a hash function at compile time, that approach would be nice. I think I'd like the string to be explicitly written in the Noir function, for easier maintainability/auditabiility. If we just paste the output of the hash function into Noir, I anticipate that will lead to maintenance pain and bugs.
I'm not sure of the answer to this. I imagined that two chains could have identical domain separators for everything. Any hash which needs to distinguish by chain_id will include the chain_id in the hash preimage? (E.g. tx signatures, and block headers(?))