Skip to content

docs: specify info parameter in SD-APKE pseudocode and protocol steps - #331

Open
rocodes wants to merge 1 commit into
mainfrom
246-info-param
Open

docs: specify info parameter in SD-APKE pseudocode and protocol steps#331
rocodes wants to merge 1 commit into
mainfrom
246-info-param

Conversation

@rocodes

@rocodes rocodes commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Towards #246 , docs only:

Spec change:

  • Specify full sd-apke pubkey is passed in info param

And clarification changes:

  • Adjust comments and pseudocode (lightly) in AuthEnc/AuthDec section to avoid the info=info+something references to avoid confusion
  • Add more detail to info param description, move existing description to AuthEnc/AuthDec section

And formatting:

  • Adjust subheading format (bold vs nested subheading) to make for easier cross referencing of sections via automatic anchor generation
  • (ran the linter on the docs after it complained, which is why there are formatting changes and a bigger diff than maybe needed)

@cfm cfm added this to SecureDrop Aug 5, 2026
@cfm cfm moved this to In Progress in SecureDrop Aug 5, 2026
@cfm cfm removed this from SecureDrop Aug 5, 2026
@rocodes
rocodes marked this pull request as ready for review August 6, 2026 09:46
@rocodes
rocodes requested review from a team as code owners August 6, 2026 09:46
@rocodes rocodes moved this to Ready For Review in SecureDrop Aug 6, 2026
…. Adjust subsection formatting for easier cross-references.
@cfm cfm self-assigned this Aug 6, 2026
@cfm cfm changed the title docs: specify info parameter in SD-APKE pseuducode and protocol steps docs: specify info parameter in SD-APKE pseudocode and protocol steps Aug 7, 2026
@cfm cfm moved this from Ready For Review to Under Review in SecureDrop Aug 7, 2026
@cfm

cfm commented Aug 7, 2026

Copy link
Copy Markdown
Member

Thanks for drafting this, @rocodes! Substance looks great; I'll suggest some edits next week.

@cfm cfm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much clearer now; thank you! Apart from a couple of comments I'll list here, this is mostly all proofreading. And might even look like it in Google Docs—sorry for all the noise here.

More substantively:

Comment thread docs/protocol.md
- $\text{AKEM}$ as above
- $\text{KEM}_{PQ} =$ ML-KEM-768
- $\text{pskAPKE}$ as above
- $\text{pskAPKE}$ (HPKE SealAuthPSK), as above

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we want to unfold the pskAPKE reference like this we need to make it more explicit, e.g.—

Suggested change
- $\text{pskAPKE}$ (HPKE SealAuthPSK), as above
- $\text{pskAPKE}$, effectively wrapping HPKE's [single-shot `SealAuthPSK()` and `OpenAuthPSK()` APIs][RFC 9180 §6.1] as described above

—since the function calls and signatures don't line up literally.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side note, I wonder if wanting to offer this shortcut is a sign that it's time to cut out the pskAPKE proof abstraction from the specification entirely and just refer to the HPKE APIs directly. We get the least practical value here out of pskAPKE—in contrast to SD-PKE and SD-APKE, whose concepts and terminology continue to show up elsewhere in documentation and code.

Just a thought—definitely not a task for this pull request.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯, I am in favour of removing as many abstraction layers as possible and always have been; I would have supported calling it HPKE.Base with a parenthetical note to the paper instead of sd-pke too if this was acceptable to the group, but for now if you let me know what amount of departure from paper you'll accept I will make whatever change.

My view is we are making a spec for implementors of this protocol, and implementors are expected to use existing crypto libraries, so by this logic I also wondered about removing the DHKEM/AuthEncap explanation or moving it to the glossary, since implementors should use Hpke/DHKEM, not implement HPKE DHKEM.

Comment thread docs/protocol.md
| $`(sk_S^{APKE}, pk_S^{APKE}) \gets^{\$} \text{KGen}()`$ | Generate keys |
| $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info)`$ | Encrypt a message $m$ with associated data $ad$ and $info$ |
| $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info)`$ | Decrypt a message $m$ with associated data $ad$ and $info$ |
Additionally, senders and receivers are presumed to possess a $\text{fetch}$ keypair, generated with $\text{Ristretto255.KGen}$, and to have access to the other party's public key portion, $\text{pk}_{fetch}$, as they do with message keys.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think standards-style normative language would be clearer here:

Suggested change
Additionally, senders and receivers are presumed to possess a $\text{fetch}$ keypair, generated with $\text{Ristretto255.KGen}$, and to have access to the other party's public key portion, $\text{pk}_{fetch}$, as they do with message keys.
Senders and receivers MUST possess a ristretto255 fetching keypair $(sk^{fetch}, pk^{fetch}$ and have access to the other party's public fetching key $pk^{fetch}$, as they do with message keys.

Comment thread docs/protocol.md
Comment on lines +414 to +415
| $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info_incl=pk_R^{fetch})`$ | Encrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information |
| $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info_incl=pk_R^{fetch})`$ | Decrypt a message $m$ with associated data $ad$, including $info$ as part of the additional authenticated information |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notation and consistency:

Suggested change
| $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info_incl=pk_R^{fetch})`$ | Encrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information |
| $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info_incl=pk_R^{fetch})`$ | Decrypt a message $m$ with associated data $ad$, including $info$ as part of the additional authenticated information |
| $`((c_1, c'), c_2) \gets^{\$} \text{AuthEnc}(sk_S^{APKE} = (sk_S^{AKEM}, sk_S^{PQ}), pk_R^{APKE} = (pk_R^{AKEM}, pk_R^{PQ}), m, ad, info\_incl=pk_R^{fetch})`$ | Encrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information |
| $`m \gets \text{AuthDec}(sk_R^{APKE} = (sk_R^{AKEM}, sk_R^{PQ}), pk_S^{APKE} = (pk_S^{AKEM}, pk_S^{PQ}), ((c_1, c'), c_2), ad, info\_incl=pk_R^{fetch})`$ | Decrypt a message $m$ with associated data $ad$, including $pk_R^{fetch}$ as part of the additional authenticated information |

Comment thread docs/protocol.md
Comment on lines +430 to +431
m, ad, info_incl=pkR_fetch): # Sender commits to recipient fetch pubkey here
pkS = sk.public()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for clarity we have to rebuild pkS from the tuple values:

Suggested change
m, ad, info_incl=pkR_fetch): # Sender commits to recipient fetch pubkey here
pkS = sk.public()
m, ad,
info_incl=pkR_fetch): # Sender commits to recipient's fetching key
pkS = (skS1.public(), skS2.public())

Comment thread docs/protocol.md
Comment on lines +433 to +435
# pskAEnc `info` param binds all of: c2, 'info' (pkR_fetch), and pkS to encryption context
info_param = c2 + info + pkS
(c1, cp) = pskAEnc(skS=skS1, pkR=pkR1, psk=K2, m=m, ad=ad, info=info_param) # where cp = c'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm following the distinction you're introducing between AuthEnc(info_incl) and pskAEnc(info), I think this would be:

Suggested change
# pskAEnc `info` param binds all of: c2, 'info' (pkR_fetch), and pkS to encryption context
info_param = c2 + info + pkS
(c1, cp) = pskAEnc(skS=skS1, pkR=pkR1, psk=K2, m=m, ad=ad, info=info_param) # where cp = c'
# pskAEnc `info` parameter binds all of: c2, `info_incl` (pkR_fetch), and pkS to encryption context
info_param = c2 + info_incl + pkS
(c1, cp) = pskAEnc(skS=skS1, pkR=pkR1, psk=K2, m=m, ad=ad, info=info_param) # where cp = c'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think we need to make sure to use this same order everywhere else.

Comment thread docs/protocol.md
This `info` parameter MUST NOT be transmitted with the ciphertext by the underlying AEAD, since it contains cleartext public keys, which are identifying; comformant implementations of HPKE pass the `info` parameter to the KeySchedule but do not transmit it with the ciphertext. The recipient MUST locally reconstruct the `info` parameter based on the information in the PKE ciphertext and their knowledge of their own $fetch$ key, and supply the reconstructed `info` parameter to successfully decrypt the message.

_Why this `info` parameter?_ $ Via the `info` parameter, sender binds to the ciphertext authenticated material (ie, not sent inside the AEAD ciphertext and not authenticated via DH-AKEM.AuthEncap):
The SD-APKE ciphertext is sender authenticated using classical DH-AKEM implicit authentication, and provides hybrid (post-quantum/traditional) message encryption via a quantum-resistent shared secret, `pskAPKE`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding your reference correctly:

Suggested change
The SD-APKE ciphertext is sender authenticated using classical DH-AKEM implicit authentication, and provides hybrid (post-quantum/traditional) message encryption via a quantum-resistent shared secret, `pskAPKE`.
The SD-APKE ciphertext is sender authenticated using classical DH-AKEM implicit authentication, and provides hybrid (post-quantum/traditional) message encryption via a quantum-resistant public-key encryption scheme, `pskAPKE`.

Comment thread docs/protocol.md
| Sender SD-APKE.MLKEM.pub | Attach to receive replies | Inside SD-PKE ct (unauthenticated) | **commit to in `info`** | Key-swapping |
| Receiver fetch pubkey | Send to intended recipient | Not transmitted, but DH share used in message hint | **commit to in `info`** | Ciphertext relay/hint swap by impersonator |
| PSK ciphertext ($`c2`$) | Receiver decaps() to learn PSK | Transmitted in message envelope (unauthenticated) | **commit to it in `info` to bind it to ciphertext** | [Re-encaps attacks](https://durumcrustulum.com/2024/02/24/how-to-hold-kems/#re-encapsulation-attacks) |
The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term $fetch$ and $PKE$ public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term $fetch$ and $PKE$ public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity.
The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term fetching and SD-PKE public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity.

Comment thread docs/protocol.md
| Receiver fetch pubkey | Send to intended recipient | Not transmitted, but DH share used in message hint | **commit to in `info`** | Ciphertext relay/hint swap by impersonator |
| PSK ciphertext ($`c2`$) | Receiver decaps() to learn PSK | Transmitted in message envelope (unauthenticated) | **commit to it in `info` to bind it to ciphertext** | [Re-encaps attacks](https://durumcrustulum.com/2024/02/24/how-to-hold-kems/#re-encapsulation-attacks) |
The SD-APKE ciphertext carries a [structured plaintext message](#message-formats) including the sender's long-term $fetch$ and $PKE$ public keys, which must be enclosed by the source so that they can receive replies, and are enclosed by the journalist for parity.
Despite the name, `pskAPKE` is not a true 'pre-shared' key, and functions more like a [KEM combiner](https://datatracker.ietf.org/doc/draft-ounsworth-cfrg-kem-combiners/); the naming convention from [related work](https://eprint.iacr.org/2023/1480) is retained.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Despite the name, `pskAPKE` is not a true 'pre-shared' key, and functions more like a [KEM combiner](https://datatracker.ietf.org/doc/draft-ounsworth-cfrg-kem-combiners/); the naming convention from [related work](https://eprint.iacr.org/2023/1480) is retained.
Despite the name, `pskAPKE` is not a true PSK PKE, and functions more like a [KEM combiner](https://datatracker.ietf.org/doc/draft-ounsworth-cfrg-kem-combiners/). Our terminology follows Alwen et al. (2023), ["The Pre-Shared Key Modes of HPKE"][alwen-2023].

Comment thread docs/protocol.md
**Metadata Ciphertext (SD-PKE Ciphertext).** Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's $PKE$ key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]).
##### Metadata Ciphertext (SD-PKE Ciphertext)

Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's $PKE$ key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's $PKE$ key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]).
Because decrypting the SD-APKE ciphertext requires the recipient to know the sender's long-term APKE public key, an SD-PKE ciphertext (metadata ciphertext) delivers this SD-APKE public key, encrypted to the recipient's SD-PKE key, thus keeping the sender's identity hidden from the server, as described in HPKE's metadata protection guidance ([RFC 9180 §9.9]).

Comment thread docs/protocol.md
| | |     $`pk_S^{APKE} \gets \text{SD-PKE.Dec}(sk_R^{PKE}, ct^{PKE}, -, -)`$ |
| | |     If $pk_S^{APKE} \neq \bot$: break |
| | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ |
| | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ * |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A pattern that I like for inline notes (as opposed to footnotes) is to do:

Suggested change
| | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ * |
| | | $`pt \gets \text{SD-APKE.AuthDec}(sk_R^{APKE}, pk_S^{APKE}, ct^{APKE}, NR, pk_R^{fetch})`$ [1] |

And then after the table or diagram:

**Notes:**
1. $SD-APKE.AuthDec$ reconstructs the `info` parameter used by the sender by concatenating the PQ encapsulated shared secret, decrypted $`pk_S^{APKE}`$, and the receiver's own $`pk_R^{fetch}`$. See [info parameter](#pskaenc-info-parameter)

It's a little cleaner than asterisks even if there's just the one note in the list. But up to you.

@cfm cfm assigned rocodes and unassigned cfm Aug 11, 2026
@rocodes rocodes moved this from Under Review to In Progress in SecureDrop Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants