Skip to content

Commit

Permalink
spec: flesh out more the encrypted parts of a tx (#4105)
Browse files Browse the repository at this point in the history
Signed-off-by: redshiftzero <[email protected]>
Co-authored-by: katelyn martin <[email protected]>
  • Loading branch information
redshiftzero and cratelyn authored Mar 28, 2024
1 parent d3014e4 commit cba996f
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 17 deletions.
2 changes: 1 addition & 1 deletion crates/core/component/dex/src/swap/plaintext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub struct SwapPlaintext {
pub delta_2_i: Amount,
// Prepaid fee to claim the swap
pub claim_fee: Fee,
// Address to receive the Swap NFT and SwapClaim outputs
// Address to receive the SwapClaim outputs
pub claim_address: Address,
// Swap rseed
pub rseed: Rseed,
Expand Down
1 change: 1 addition & 0 deletions docs/protocol/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
- [Transaction Signing](./transactions/signing.md)
- [Action Invariants](./transactions/invariants.md)
- [Action Reference](./transactions/actions.md)
- [Transaction Memo](./transactions/memo.md)
- [Multi-Asset Shielded Pool](./shielded_pool.md)
- [Note Plaintexts](./shielded_pool/note_plaintexts.md)
- [Note Commitments](./shielded_pool/note_commitments.md)
Expand Down
12 changes: 6 additions & 6 deletions docs/protocol/src/addresses_keys/transaction_crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ For the symmetric encryption described in this section, we use ChaCha20-Poly1305
It is security-critical that `(key, nonce)` pairs are not reused. We do this by either:

* deriving per-action keys from ephemeral shared secrets using a fixed nonce (used for notes and memo keys),
* deriving per-action keys from the outgoing viewing key using a nonce derived from a commitment.
* deriving per-action keys from the outgoing viewing key using a nonce derived from a commitment (used for swaps).

We use each key at maximum once with a given nonce. We describe the nonces and keys below.

Expand All @@ -24,7 +24,7 @@ We use a different nonce to encrypt each type of item using the following `[u8;

We have several keys involved in transaction-level symmetric cryptography:
* A *shared secret* derived between sender and recipient,
* A *per-action payload key* used to encrypt a single note, or memo key (one of each type).
* A *per-action payload key* used to encrypt a single note or memo key (one of each type).
It is derived from the shared secret. It is used for a single action.
* A *per-action swap payload key* used to encrypt a single swap. It is derived from the outgoing viewing key. It is used for a single action.
* A *random payload key* used to encrypt a memo. It is generated randomly and is shared between all
Expand All @@ -49,7 +49,7 @@ key exchange between:

This allows both sender and recipient to generate the shared secret based on the keys they possess.

### Per-action Payload Key: Notes and Memo Keys
### [Per-action Payload Key: Notes and Memo Keys](#per-action-payload-note-memo-key)

The symmetric per-action payload key is a 32-byte key derived from the `shared_secret`, the $epk$ and
personalization string "Penumbra_Payload":
Expand All @@ -61,7 +61,7 @@ action_payload_key = BLAKE2b-512("Penumbra_Payload", shared_secret, epk)
This symmetric key is then used with the nonces specified above to encrypt a memo key or note.
It should not be used to encrypt two items of the same type.

### Per-action Payload Key: Swaps
### [Per-action Payload Key: Swaps](#per-action-swap-key)

The symmetric per-action payload key is a 32-byte key derived from the outgoing viewing
key `ovk`, the swap commitment `cm`, and personalization string "Penumbra_Payswap":
Expand All @@ -74,7 +74,7 @@ This symmetric key is used with the nonces specified above to encrypt a swap onl
We use the outgoing viewing key for swaps since third parties cannot create
swaps for a given recipient, i.e. the user only sends swaps to themselves.

### Random Payload Key: Memos
### [Random Payload Key: Memo](#random-memo-key)

The random payload key is a 32-byte key generated randomly. This
symmetric key is used with the nonces specified above to encrypt memos only.
Expand All @@ -96,7 +96,7 @@ outgoing cipher key is to enable the sender to view their outgoing transactions
using only their outgoing viewing key. The outgoing cipher key is used to encrypt data that
they will need to later reconstruct any outgoing transaction details.

### OVK Wrapped Key
### [OVK Wrapped Key](#ovk-wrapped-key)

To decrypt outgoing notes or memo keys, the sender needs to store the shared secret encrypted
using the outgoing cipher key described above. This encrypted data,
Expand Down
38 changes: 38 additions & 0 deletions docs/protocol/src/dex/action/swap.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,44 @@

Each swap contains a SwapBody and a zk-SNARK swap proof.

## [Swap Body](#swap-body)

The body of an `Swap` has five parts:

1. A `SwapPayload`, which consists of the swap commitment and the `SwapCiphertext`,
2. A fee balance commitment, which commits to the value balance of the pre-paid fee;
3. The `TradingPair` which is the canonical representation of the two asset IDs involved in the trade,
4. `delta_1_i`, the amount of the _first_ asset ID being traded,
5. `delta_2_i`, the amount of the _second_ asset ID being traded.

The `SwapCiphertext` is 272 bytes in length, and is encrypted symmetrically using the
payload key derived from the `OutgoingViewingKey` and the swap commitment as
described [here](../../addresses_keys/transaction_crypto.md#per-action-swap-key).

The corresponding plaintext, `SwapPlaintext` consists of:

* the `TradingPair` which as above is the canonical representation of the two asset IDs involved in the trade,
* `delta_1_i`, the amount of the _first_ asset ID being traded,
* `delta_2_i`, the amount of the _second_ asset ID being traded,
* the value of the pre-paid claim fee used to claim the outputs of the swap,
* the address used to claim the outputs of the swap,
* the swap `Rseed`, which is used to derive the `Rseed` for the two output notes.

The output notes for the `Swap` can be computed given the `SwapPlaintext` and the
`BatchSwapOutputData` from that block. The `Rseed` for each output note are computed from
the `SwapPlaintext` using rate-1 Poseidon hashing with domain separators $ds_1$ and $ds_2$ defined as the `Fq` element constructed using:

`ds_1 = from_le_bytes(BLAKE2b-512(b"penumbra.swapclaim.output1.blinding")) mod q`

`ds_2 = from_le_bytes(BLAKE2b-512(b"penumbra.swapclaim.output2.blinding")) mod q`

The rseed for each note is then constructed using the above domain separator and
hashing together the swap rseed $rseed$:

`rseed_1 = hash_1(ds_1, (rseed))`

`rseed_2 = hash_1(ds_2, (rseed))`

## Invariants

The invariants that the Swap upholds are described below.
Expand Down
4 changes: 2 additions & 2 deletions docs/protocol/src/dex/action/swap_claim.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ The invariants that the SwapClaim upholds are described below.

5. You can only claim swap outputs once via:

5.1. A swap's transmission key binds to the nullifier key as described in the [Diversified Address Integrity](#diversified-address-integrity) section, and all components of a positioned swap, along with this key, are hashed to derive the nullifier, in circuit as described below in the [Nullifier Integrity](#nullifier-integrity) section.
5.1. A swap's transmission key binds to the nullifier key as described in the [Nullifier Key Linking](#nullifier-key-linking) section, and all components of a positioned swap, along with this key, are hashed to derive the nullifier, in circuit as described below in the [Nullifier Integrity](#nullifier-integrity) section.

5.2. In the `ActionHandler` for `check_stateful` we check that the nullifier is unspent.

Expand Down Expand Up @@ -121,7 +121,7 @@ using the witnessed values above and where `ds` is a constant domain separator:

as described in [Nullifiers](../../sct/nullifiers.md).

### Nullifier Key Linking
### [Nullifier Key Linking](#nullifier-key-linking)

The zk-SNARK certifies that the diversified address $pk_d$ associated with the
swap being claimed was derived as:
Expand Down
9 changes: 9 additions & 0 deletions docs/protocol/src/shielded_pool/action/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Each output contains an OutputBody and a zk-SNARK output proof.

## [Output Body](#output-body)

The body of an `Output` has four parts:

1. A `NotePayload`, which consists of the note commitment, the `NoteCiphertext`, and an ephemeral public key used to encrypt the note;
2. A balance commitment, which commits to the value balance of the output note;
3. The ovk wrapped key, which enables the _sender_ to later decrypt the `NoteCiphertext` using their `OutgoingViewingKey`;
4. The wrapped memo key, which enables one who can decrypt the `NoteCiphertext` to additionally decrypt the [`MemoCiphertext`](../../transactions/memo.md) on the tranasction.

## Invariants

The invariants that the Output upholds are described below.
Expand Down
10 changes: 5 additions & 5 deletions docs/protocol/src/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ or providing additional metadata. All actions in the transaction are executed to

1. A list of actions effecting changes to the chain state;
2. A set of `TransactionParameters` describing conditions under which the actions may be executed;
3. A set of `DetectionData` that helps third-party servers detect transactions using [Fuzzy Message Detection](./crypto/fmd.md);
4. A `MemoCiphertext` with an encrypted memo visible only to the sender and receiver(s) of the transaction.
3. An optional set of `DetectionData` that helps third-party servers detect transactions using [Fuzzy Message Detection](./crypto/fmd.md);
4. An optional `MemoCiphertext` with an [encrypted memo](./transactions/memo.md) visible only to the sender and receiver(s) of the transaction.

The [Transaction Signing](./transactions/signing.md) section describes transaction authorization.

## Actions and Value Balance

The primary content of a transaction is its list of actions. Each action is executed in sequence, and effects changes to the chain state.
The primary content of a transaction is its list of actions. Each action is executed in sequence, and effects changes to the chain state.

Crucially, each action makes a shielded contribution to the transaction's value
balance by means of a _balance commitment_, using the commitment scheme for asset values described in detail in the [Asset Model](./assets.md).
balance by means of a _balance commitment_, using the commitment scheme for asset values described in detail in the [Asset Model](./assets.md).

Some actions, like a `Spend`, consume state fragments from the chain and release
value into the transaction, while others, like `Output`, consume value from the
Expand All @@ -33,7 +33,7 @@ prove knowledge of an opening to the commitment, i.e., producing $\widetilde{v}$
such that $$C = [\widetilde{v}] \widetilde{V} = \operatorname{Commit}(0,
\widetilde{v}).$$ But this is exactly what it means to create a Schnorr
signature for the verification key $C$, because a Schnorr signature is a proof
of knowledge of the signing key in the context of the message.
of knowledge of the signing key in the context of the message.

Therefore, we can prove that a value commitment is a commitment to $0$ by
treating it as a `decaf377-rdsa` verification key and using the corresponding
Expand Down
6 changes: 3 additions & 3 deletions docs/protocol/src/transactions/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ contributions together.
| ------ | ----------- | ------------- | -- |
| [`shielded_pool.v1.Spend`](../shielded_pool/action/spend.md) | Spends a note previously included on-chain, releasing its value into the transaction | $+$ (value of spent note) | |
| [`shielded_pool.v1.Output`](../shielded_pool/action/output.md) | Produces a new note controlled by a specified address and adds it to the chain state | $-$ (value of new note) | |
| [`dex.v1.Swap`](../dex/action/swap.md) | Submits a swap intent to the chain for batch execution | $-$ (prepaid claim fee) | $-$ (swap inputs)
| [`dex.v1.Swap`](../dex/action/swap.md) | Submits a swap intent to the chain for batch execution | $-$ (prepaid claim fee) | $-$ (swap inputs)
| [`dex.v1.SwapClaim`](../dex/action/swap_claim.md) | Claims the outputs of a swap once the clearing price is known, producing new output notes directly | | $+$ (prepaid claim fee) |
| `stake.v1.ValidatorDefinition` | Uploads a new validator definition to the chain | | |
| `stake.v1.Delegate` | Delegates stake to a validator, exchanging the staking token for that validator's delegation token | | $-$ (staking token) $+$ (delegation token)
| `stake.v1.Undelegate` | Undelegates stake from a validator, exchanging delegation tokens for unbonding tokens | | $-$ (delegation token) $+$ (unbonding token) |
| `stake.v1.UndelegateClaim` | Converts unbonding tokens to staking tokens after unbonding completes, at a 1:1 rate unless there are slashing penalties | $-$ (unbonding token) $+$ (staking token) |
| [`stake.v1.UndelegateClaim`](../stake/action/undelegate_claim.md) | Converts unbonding tokens to staking tokens after unbonding completes, at a 1:1 rate unless there are slashing penalties | $-$ (unbonding token) $+$ (staking token) |
| `ibc.v1.IbcRelay` | Relays IBC messages from a counterparty chain | | |
| `ibc.v1.Ics20Withdrawal` | Initiates an outbound ICS-20 token transfer | | $-$ (transfer amount) |
| `dex.v1.PositionOpen` | Opens a liquidity position | | $-$ (initial reserves) $+$ (opened LPNFT) |
Expand All @@ -42,7 +42,7 @@ contributions together.
| `governance.v1.ProposalSubmit` | Submits a governance proposal for voting | TODO | TODO |
| `governance.v1.ProposalWithdraw` | Withdraws a governance proposal from voting | TODO | TODO |
| `governance.v1.ValidatorVote` | Performs a governance vote as a validator | TODO | TODO |
| `governance.v1.DelegatorVote` | Performs a governance vote as a delegator | TODO | TODO |
| [`governance.v1.DelegatorVote`](../governance/action/delegator_vote.md) | Performs a governance vote as a delegator | TODO | TODO |
| `governance.v1.ProposalDepositClaim` | Claims a proposal deposit once voting has finished | TODO | TODO |
| `governance.v1.CommunityPoolSpend` | Spends funds from the community pool | | $+$ (spent value) |
| `governance.v1.CommunityPoolOutput` | Like `Output`, but transparent | | $-$ (value of new note)
Expand Down
16 changes: 16 additions & 0 deletions docs/protocol/src/transactions/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Transaction Memo

The transaction-level memo field is optional, and will be present _if and only if_ the transaction has outputs. A consensus rule will reject transactions with memos that have no outputs, and transactions that have outputs but lack a memo.

## Memo Plaintext

The plaintext of the memo contains:

* a return address (80 bytes for Penumbra addresses)
* a text string that is 432 bytes in length

## Privacy

The transaction-level encrypted memo is visible only to the sender and receiver(s) of the transaction.

Each memo is encrypted using the *Memo Key*, a symmetric ChaCha20-Poly1305 key generated randomly as described [here](../addresses_keys/transaction_crypto.md#random-memo-key). The Memo Key is then encrypted using data from each output following [this section](../addresses_keys/transaction_crypto.md#per-action-payload-key-notes-and-memo-key). This *Wrapped Memo Key* is then added to each individual [Output](../shielded_pool/action/output.md#output-body).

0 comments on commit cba996f

Please sign in to comment.