Skip to content

Upstream: the two MoLE drafts disagree on point compression #1

Description

@OR13

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    exp-ihatConcerns the experimental IHAT profileguessed-parameterA choice this crate invented because the drafts do not specify it

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions