This builds on Charlie & Phil's work [here](https://hackmd.io/4chZkbHdT5G1OKF-1VchOw?both), but attempts to do everything via Ethereum calldata (or blobs), rather than use any other external services like Nym or Waku.
---
The problem with using Nym or Waku is:
- Some apps will wish to constrain the caller to correctly handshaking, encrypt, and 'tag' encrypted event data with a clue.
- A sequencer can be forced to include encryped event data to L1, because that's where the rollup is finalised.
- But a sequencer (nor the caller) cannot be forced to send such data to Nym or Waku, because they're separate networks. There would need to be some kind of consensus spanning the Aztec network and the Nym/Waku networks.
This problem goes away if an app is willing to assume 'senders' will be honest, and always send encrypted data via nym/waku. But Joe has given examples of apps where a malicious user could withold encrypted data from another user, in order to extort them.
---
The below scheme gives a way of handshaking via L1, but doesn't solve these main problems:
- A user would need to download all handshake data -- which is at least 64-bytes per handshake -- to identify that the handshake is aimed at them.
- E.g. >= 64-bytes per handshake detection, 100m users, 100 handshakes per user. That's 640GB of data to download.
---
Alice sending to Bob:
**Setup for Alice**
`ovk_a = outgoing_viewing_key`
**Setup for Bob:**
Random:
`hsk_b = hello_secret_key`
`msk_b = messaging_secret_key`
`ivsk_b = incoming_viewing_secret_key`
Compute:
`Hpk_b = Hello_public_key = hsk_b * G`
`Mpk_b = Messaging_public_key = msk_b * G`
`Ivpk_b = Incoming_viewing_public_key = ivk_b * G`
Publish:
`Hpk_b`
`Mpk_b`
`Ivpk_b`
... against Bob's address (e.g. register this data in some canonical 'handshake' contract in a mapping from Bob's address).
**Alice reaching out to Bob, to 'handshake':**
Random:
`esk = ephemeral_secret_key`
Compute:
`Epk = Ephemeral_public_key`
`Hello = esk * Hpk_b`
`I_said_hello = hsk_a * Epk`
`Omss = Ongoing_messaging_shared_secret = epk * Mpk_b`
`incoming_shared_secret = epk * Ivpk_b`
`incoming_encryption_key = blake2b_256(incoming_shared_secret, Epk)` (Note: not sure if `Epk` needed, but zcash does this).
`incoming_ciphertext = encrypt([ alice_address ], incoming_encryption_key)`
`outgoing_encryption_key = blake2b_256(ovk_a, Epk)`
`outgoing_ciphertext = encrypt([ Mpk_b, esk ], outgoing_encryption_key);`
Publish:
`Epk`
`Hello`
`I_said_hello`
`incoming_ciphertext`
`outgoing_ciphertext`
> No one learns who the `Hello` is for, or from.
> Note:
> - A sender might not want to be known to a recipient (e.g. anonymous donations), so this handshake doesn't use Alice's private key to generate the 'ongoing messaging shared secret'.
> - Alice may provide an encryption of her public key to Bob, if she wants, via the `incoming_ciphertext`.
> - `I_said_hello` allows Alice to re-sync and identify when she said "Hello".
> - `outgoing_ciphertext` provides `esk`, which will allow Alice to re-derive the `Omss = ongoing_messaging_shared_secret` if she ever wipes her DB.
**Bob scans all `Hello` tags**
> Note: this is much less effort than scanning all encrypted messages. The 'hello' function which _emits_ `(Epk, Hello, ...)` can be a public function, to the contract address and event name can be filtered for, by Bob's node software. Bob then only needs to perform 'trial hello discovery' on that filtered set of events.
For each `(Epk, Hello, incoming_ciphertext)` tuple, compute:
```js
for ([Epk, Hello, incoming_ciphertext] in helloEvents) {
let ComputedHello = hsk_b * Epk;
if (ComputedHello == Hello) {
// Someone's saying Hello to Bob:
// Derive the Ongoing_messaging_shared_secret:
let ongoing_messaging_shared_secret = msk_b * Epk;
// Decrypt the incoming_ciphertext:
let incoming_shared_secret = ivsk_b * Epk;
let incoming_encryption_key = blake2b_256(incoming_shared_secret, Epk);
let [ alice_address ] = decrypt(incoming_ciphertext, incoming_encryption_key);
db.write(alice_address, ongoing_messaging_shared_secret);
}
}
```
**Alice scans after wiping her DB**
> Note: Alice MUST retain the `ovk_a = outgoing_viewing_key`
For each `(Epk, I_said_hello, outgoing_ciphertext)` tuple, compute:
```js
for ([Epk, I_said_hello, outgoing_ciphertext] in helloEvents) {
let Computed_i_said_hello = hsk_a * Epk;
if (Computed_i_said_hello == I_said_hello) {
// Alice said Hello!
// decrypt the outgoing_ciphertext:
let outgoing_encryption_key = blake2b_256(ovk_a, Epk);
let [ Mpk_b, esk ] = decrypt(outgoing_ciphertext, outgoing_encryption_key);
// From `Mpk_b` Alice now knows she said Hello to Bob!
// Compute the ongoing messaging shared secret:
let ongoing_messaging_shared_secret = esk * Mpk_b;
db.write(Mpk_b, ongoing_messaging_shared_secret);
}
}
```
---
Some time passes
**Alice wants to send some encrypted data to Bob**
Alice derives a new ephemeral keypair (not the same as the ones used to say "Hello", above!!):
Random:
`esk_i`
Compute:
`Epk_i = esk_i * G`
`incoming_shared_secret_i = esk_i * Ivpk_b`
`incoming_encryption_key_i = blake2b_256(encryption_shared_secret_i, Epk_i)` (Note: not sure if `Epk` needed, but zcash does this).
`incoming_ciphertext = encrypt(message, incoming_encryption_key_i)` (The `message` is from some app).
> Not shown: Alice creating an `ougoing_ciphertext` for her future reference.
Alice must use an incremented shared secret as a 'tag' for every message:
`tag_i = HMAC(ongoing_messaging_shared_secret, i)`
Publish:
`tag_i`
`Epk_i`
`incoming_ciphertext`
**Bob wants to find messages that have been sent to him**
He can precompute a sequence of tags, for each person who has said "Hello" to him, using the `ongoing_message_shared_secret` that was shared with him.
He can then sift through every single message on-chain comparing the emitted `tag` values against his precomputed list of tags, until he finds matches. This just requires comparison checks, rather than any hashing or elliptic curve computations.
Chat GPT tells me a consumer laptop can perform 48 billion 256-bit comparisons per second. BUT, of course, the problem for the user is _downloading_ all that data!
> Note: A 3rd party could possibly provide this service of finding matches, via PIR.
If Bob finds a matching `tag_i` for a particular counterparty, he can proceed with decrypting the message:
Compute:
```js
// Assuming Bob has matched with the `tag_i` associated with this encrypted data:
// Decrypt the incoming_ciphertext:
let incoming_shared_secret = ivsk_b * Epk_i;
let incoming_encryption_key = blake2b_256(incoming_shared_secret, Epk_i);
let [ message ] = decrypt(incoming_ciphertext, incoming_encryption_key);
```