draft-authors-mole-crypto.md says only "let hash2curve be the Hash2Curve function from {{Hash2Curve}}". RFC 9380 defines suites for P-256, P-384, P-521, curve25519, curve448, secp256k1 and BLS12-381; the draft names none of them.
What this crate chose: ristretto255, via curve25519-dalek.
Why: raw curve25519 has cofactor 8. The construction is a stack of sigma protocols (DLEQ in exchange one, the endorsement proof, and a 1-of-n OR proof), and non-prime-order groups make those unsound or malleable in ways that are easy to get wrong. ristretto255 gives a prime-order group with canonical encodings, which is what the proofs need.
The contradiction: draft-jms-mole-protocols-00 §4.1 pins opaque Point[33]. 33 bytes is a SEC1 compressed point on a 256-bit curve — i.e. P-256 or secp256k1. ristretto255 encodes to 32. So the two documents point at different groups:
- protocols draft
Point[33] → 256-bit SEC1 curve
draft-schlesinger-cfrg-act-01 → ristretto255 (32 bytes), by an overlapping author set, and upstream issue #37 (closed) folds ACT into the crypto draft
Workaround in this crate: src/crypto/exp_ihat.rs encodes a point as a 0x00 tag byte followed by the 32-byte ristretto encoding, which fills Point[33] exactly and leaves the wire format the rest of the crate implements untouched. See public_keys_fit_the_drafts_point_width in tests/exp_ihat_profile.rs.
Revisit when the crypto draft names a group. Worth raising upstream as a cross-draft consistency question.
Draft text: https://github.com/Moderation-of-unLinkable-Endorsements/internet-drafts/blob/main/draft-authors-mole-crypto.md
Update — upstream PR #39 (open, unmerged)
PR #39 changes this materially. It adds:
We present the protocol in a way agnostic to what group is used. {{ARCH}} currently instantiates this protocol with P-256, in which case the following two definitions are appropriate.
struct { uncompressed bytes[65] } Point
struct { val bytes[32] } Scalar
Three consequences, in increasing order of how much they matter.
1. The cited P-256 instantiation does not exist
The crypto draft's only statement about which group to use is a forward reference to draft-jms-mole-architecture. That document contains no group instantiation. All 574 lines grepped for p-?256|secp|ristretto|curve|elliptic|instantiat: the only hits are the W3C Anti-Fraud Community Group in the front matter and unrelated prose. The architecture draft explicitly "does not say how to build them."
So after PR #39 the group is still not specified anywhere — it is specified by reference to a document that does not contain it. That is worse than silence, because it reads as settled.
2. Point encoding is now a three-way conflict
| Document |
Declared width |
Implies |
draft-jms-mole-protocols-00 §4.1 |
opaque Point[33] |
SEC1 compressed, 256-bit curve |
draft-authors-mole-crypto (PR #39) |
uncompressed bytes[65] |
SEC1 uncompressed, P-256 |
draft-schlesinger-cfrg-act-01 |
32 bytes |
ristretto255 |
This narrows the original finding rather than widening it: 33 and 65 are the compressed and uncompressed SEC1 widths for the same curve, so protocols and crypto now agree the curve is P-256-shaped and disagree only on compression. ACT remains on a different curve entirely, and upstream issue #37 folds ACT into this draft.
The actionable defect is therefore narrow and concrete: the two MoLE drafts disagree on point compression, and one of them should change.
3. Our ristretto255 choice is a deviation, and its justification no longer holds
This needs stating plainly. The original rationale for ristretto255 was that curve25519 has cofactor 8 and the construction stacks three sigma protocols, where small-subgroup confusion breaks soundness arguments.
P-256 has cofactor 1. If the draft's implied curve is P-256, that entire argument is moot — P-256 is already a prime-order group and is fine for sigma protocols as-is. So crypto::exp_ihat is not filling a vacuum on this point; it is deviating from a weakly-stated P-256 toward a group the drafts do not use.
The deviation is still defensible as a response to the instruction to use curve25519 — ristretto255 is the correct safe way to build sigma protocols over curve25519 — but it is a deviation from the drafts, not a gap-filling guess, and this issue should not pretend otherwise.
Not acting on this yet: PR #39 is unmerged and could change before it lands. Switching the profile to P-256 is a real decision (new dependency, Point[65] vs the crate's Point[33], and it would drop the curve25519 instruction) and should be taken deliberately, not as a reflex to an open PR.
Update — profile switched to P-256
crypto::exp_ihat now uses P-256, not ristretto255. This resolves the part of
this issue that was about our deviation, and narrows what remains to a single
concrete defect in the drafts.
What the switch removed. A compressed SEC1 P-256 point is exactly 33 bytes, so
opaque Point[33] from draft-jms-mole-protocols-00 §4.1 is now the encoding
rather than something the profile pads to fit. The 0x00 tag byte is gone.
tests/exp_ihat_profile.rs asserts the leading byte is 0x02 or 0x03 and that
both parities actually occur, plus that the identity is refused rather than
truncated into a 33-byte lookalike.
That was the only place this crate had to invent an encoding, and aligning on the
drafts' group deleted the invention instead of adding one.
What remains open is the disagreement between the two MoLE drafts:
| Document |
Width |
Encoding |
draft-jms-mole-protocols-00 §4.1 |
opaque Point[33] |
SEC1 compressed |
draft-authors-mole-crypto (PR #39) |
uncompressed bytes[65] |
SEC1 uncompressed |
draft-schlesinger-cfrg-act-01 |
32 |
ristretto255 |
The profile uses compressed, matching the protocols draft, because that is what the
rest of this crate implements and what ihat::Challenge already parses. One of the
two MoLE drafts has to change, and this issue stays open until one does.
And the forward reference is still broken. The crypto draft's only statement
about the group remains "{{ARCH}} currently instantiates this protocol with
P-256", and the architecture draft contains no group instantiation. So P-256 here is
an inference — corroborated by Point[33] and bytes[65] both being P-256 SEC1
widths — not something the drafts state. That inference is load-bearing for the
whole profile now, which is a reason for upstream to write it down.
Update — resolved in our favour by the reference implementation
The MoLE org publishes ihat-rs, a reference implementation by Samuel
Schlesinger, one of the draft authors. Its wire format specifies:
opaque Point[33]; /* a group element, SEC1 compressed form:
0x02/0x03 prefix byte + 32-byte big-endian x-coord */
Compressed, 33 bytes — agreeing with draft-jms-mole-protocols-00 §4.1 and
contradicting PR #39's uncompressed bytes[65]. So this is no longer a question of
which draft to believe: the authors' own code sides with the protocols draft.
crypto::exp_ihat uses compressed and now cross-verifies against ihat-rs in both
directions (interop/), so the encoding is confirmed by execution rather than by
reading.
Remaining upstream action: PR #39 should change bytes[65] to the compressed
form, and the crypto draft should state the group directly instead of forward-
referencing the architecture draft, which still contains no group instantiation.
draft-authors-mole-crypto.mdsays only "let hash2curve be the Hash2Curve function from {{Hash2Curve}}". RFC 9380 defines suites for P-256, P-384, P-521, curve25519, curve448, secp256k1 and BLS12-381; the draft names none of them.What this crate chose: ristretto255, via
curve25519-dalek.Why: raw curve25519 has cofactor 8. The construction is a stack of sigma protocols (DLEQ in exchange one, the endorsement proof, and a 1-of-n OR proof), and non-prime-order groups make those unsound or malleable in ways that are easy to get wrong. ristretto255 gives a prime-order group with canonical encodings, which is what the proofs need.
The contradiction:
draft-jms-mole-protocols-00§4.1 pinsopaque Point[33]. 33 bytes is a SEC1 compressed point on a 256-bit curve — i.e. P-256 or secp256k1. ristretto255 encodes to 32. So the two documents point at different groups:Point[33]→ 256-bit SEC1 curvedraft-schlesinger-cfrg-act-01→ ristretto255 (32 bytes), by an overlapping author set, and upstream issue #37 (closed) folds ACT into the crypto draftWorkaround in this crate:
src/crypto/exp_ihat.rsencodes a point as a0x00tag byte followed by the 32-byte ristretto encoding, which fillsPoint[33]exactly and leaves the wire format the rest of the crate implements untouched. Seepublic_keys_fit_the_drafts_point_widthintests/exp_ihat_profile.rs.Revisit when the crypto draft names a group. Worth raising upstream as a cross-draft consistency question.
Draft text: https://github.com/Moderation-of-unLinkable-Endorsements/internet-drafts/blob/main/draft-authors-mole-crypto.md
Update — upstream PR #39 (open, unmerged)
PR #39 changes this materially. It adds:
Three consequences, in increasing order of how much they matter.
1. The cited P-256 instantiation does not exist
The crypto draft's only statement about which group to use is a forward reference to
draft-jms-mole-architecture. That document contains no group instantiation. All 574 lines grepped forp-?256|secp|ristretto|curve|elliptic|instantiat: the only hits are the W3C Anti-Fraud Community Group in the front matter and unrelated prose. The architecture draft explicitly "does not say how to build them."So after PR #39 the group is still not specified anywhere — it is specified by reference to a document that does not contain it. That is worse than silence, because it reads as settled.
2. Point encoding is now a three-way conflict
draft-jms-mole-protocols-00§4.1opaque Point[33]draft-authors-mole-crypto(PR #39)uncompressed bytes[65]draft-schlesinger-cfrg-act-01This narrows the original finding rather than widening it: 33 and 65 are the compressed and uncompressed SEC1 widths for the same curve, so protocols and crypto now agree the curve is P-256-shaped and disagree only on compression. ACT remains on a different curve entirely, and upstream issue #37 folds ACT into this draft.
The actionable defect is therefore narrow and concrete: the two MoLE drafts disagree on point compression, and one of them should change.
3. Our ristretto255 choice is a deviation, and its justification no longer holds
This needs stating plainly. The original rationale for ristretto255 was that curve25519 has cofactor 8 and the construction stacks three sigma protocols, where small-subgroup confusion breaks soundness arguments.
P-256 has cofactor 1. If the draft's implied curve is P-256, that entire argument is moot — P-256 is already a prime-order group and is fine for sigma protocols as-is. So
crypto::exp_ihatis not filling a vacuum on this point; it is deviating from a weakly-stated P-256 toward a group the drafts do not use.The deviation is still defensible as a response to the instruction to use curve25519 — ristretto255 is the correct safe way to build sigma protocols over curve25519 — but it is a deviation from the drafts, not a gap-filling guess, and this issue should not pretend otherwise.
Not acting on this yet: PR #39 is unmerged and could change before it lands. Switching the profile to P-256 is a real decision (new dependency,
Point[65]vs the crate'sPoint[33], and it would drop the curve25519 instruction) and should be taken deliberately, not as a reflex to an open PR.Update — profile switched to P-256
crypto::exp_ihatnow uses P-256, not ristretto255. This resolves the part ofthis issue that was about our deviation, and narrows what remains to a single
concrete defect in the drafts.
What the switch removed. A compressed SEC1 P-256 point is exactly 33 bytes, so
opaque Point[33]fromdraft-jms-mole-protocols-00§4.1 is now the encodingrather than something the profile pads to fit. The
0x00tag byte is gone.tests/exp_ihat_profile.rsasserts the leading byte is0x02or0x03and thatboth parities actually occur, plus that the identity is refused rather than
truncated into a 33-byte lookalike.
That was the only place this crate had to invent an encoding, and aligning on the
drafts' group deleted the invention instead of adding one.
What remains open is the disagreement between the two MoLE drafts:
draft-jms-mole-protocols-00§4.1opaque Point[33]draft-authors-mole-crypto(PR #39)uncompressed bytes[65]draft-schlesinger-cfrg-act-01The profile uses compressed, matching the protocols draft, because that is what the
rest of this crate implements and what
ihat::Challengealready parses. One of thetwo MoLE drafts has to change, and this issue stays open until one does.
And the forward reference is still broken. The crypto draft's only statement
about the group remains "
{{ARCH}}currently instantiates this protocol withP-256", and the architecture draft contains no group instantiation. So P-256 here is
an inference — corroborated by
Point[33]andbytes[65]both being P-256 SEC1widths — not something the drafts state. That inference is load-bearing for the
whole profile now, which is a reason for upstream to write it down.
Update — resolved in our favour by the reference implementation
The MoLE org publishes
ihat-rs, a reference implementation by SamuelSchlesinger, one of the draft authors. Its wire format specifies:
Compressed, 33 bytes — agreeing with
draft-jms-mole-protocols-00§4.1 andcontradicting PR #39's
uncompressed bytes[65]. So this is no longer a question ofwhich draft to believe: the authors' own code sides with the protocols draft.
crypto::exp_ihatuses compressed and now cross-verifies againstihat-rsin bothdirections (
interop/), so the encoding is confirmed by execution rather than byreading.
Remaining upstream action: PR #39 should change
bytes[65]to the compressedform, and the crypto draft should state the group directly instead of forward-
referencing the architecture draft, which still contains no group instantiation.