r/crypto • u/Nervous_Ad_2710 • 9h ago
Title: Break my stdlib-only Ed25519 verifier (Python, RFC 8032, cofactorless strict)
I maintain SMRFORGE, an open-source (MIT) tool that seals simulation results into evidence bundles and ships a standalone offline verifier. The verifier re-implements RFC 8032 Ed25519 verification in the Python standard library with its own field arithmetic. I know the advice against rolling your own. That is why I am asking people who do this for a living to try to break it.
Each bundle section (inputs, result, provenance) is canonicalized and hashed with SHA-256. The section digests roll up into bundle_sha256, which is SHA-256 of the canonical digest list. The signature is over the ASCII string "sha256:<hex>" rather than the raw 32 bytes. The verifier first checks the signature under the public key embedded in the bundle. That is the integrity verdict. It then compares the embedded key to a pinned key as a separate origin verdict. So the verifier does process arbitrary keys, which is why the input rules below matter.
Decisions I want reviewed
- Cofactorless and strict. It checks [s]B == R + [k]A exactly, with k = SHA-512(R || A || M) read little-endian. There is no cofactor multiply.
- It rejects a point encoding with y >= p, a scalar s >= L, and any point not on the curve.
- It rejects small-order points where [8]A or [8]R is the identity. It also rejects a recovered x == 0 with the sign bit set, per RFC 8032 section 5.1.3 step 4.
This intentionally differs from libsodium, which is cofactored, and from original ref10, which is permissive, on small-order and mixed-order inputs. The right reference is a cofactorless strict verifier such as ed25519-dalek verify_strict, or the strict profile in Chalkias, Garillot, and Nikolaenko, "Taming the Many EdDSAs." The challenge is to find a (public key, message, signature) where my accept/reject disagrees with that profile.
About testing. There are 24 in-tree vectors, but the verifier itself produced their expected outcomes. They are regression and self-consistency checks, not independent known-answer tests. The independent checks were cross-validation against OpenSSL on random keys and messages in both directions, and the Chalkias et al. speccheck vectors. Every copy was run against those during development, but they are not vendored in the repo. There are five copies of the verifier, four in Python that largely mirror each other and one separate JavaScript implementation in the browser checker.
Known weaknesses. The RFC 3161 timestamp token is only shape-checked offline (valid base64 and a leading DER SEQUENCE tag). A separate openssl tool verifies it against a pinned TSA CA. And this is hand-rolled Ed25519 in pure Python.
My questions. Is rolling section digests into one signed bundle_sha256 sound? Should the trust-critical path use a vetted library, with the stdlib code kept only as a cross-check? Is shape-checking the timestamp offline and deferring crypto verification to openssl an acceptable split?
AI disclosure. I used Claude to write most of the verifier and to check this post against the source. I used Grok to draft and tidy the post. The prompts are in a comment below.
Sources
- Verifier: https://gitlab.com/smrforge/smrforge/-/blob/23ce423/smrf_verify/verify.py#L450
- Vectors: https://gitlab.com/smrforge/smrforge/-/blob/23ce423/conformance/ed25519/small_order_vectors.json
- Generator: https://gitlab.com/smrforge/smrforge/-/blob/23ce423/tools/gen_ed25519_kat.py
- RFC 8032: https://www.rfc-editor.org/rfc/rfc8032#section-5.1.7
- Taming the Many EdDSAs: https://eprint.iacr.org/2020/1244
- ed25519-dalek verify_strict: https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.VerifyingKey.html#method.verify_strict
-3
u/Nervous_Ad_2710 9h ago
**AI disclosure and prompts (per rule 8)**
**Claude, writing the verifier.** I used Claude to write most of the Ed25519 verifier. Main prompts:
> [PASTE: prompt for the initial stdlib-only RFC 8032 verifier]
> [PASTE: prompt for small-order and 5.1.3 x=0 rejection (ADR-044)]
> [PASTE: prompt for the OpenSSL cross-validation and speccheck runs]
I reviewed each change, ran it against OpenSSL on random keys and messages in both directions, and ran it against the Chalkias et al. speccheck vectors.
**Claude, checking this post.** I asked Claude to check the post's technical claims against the source. The constraints in the Grok prompt below came out of that check.
> [PASTE: prompt asking Claude to verify the post against the repo]
**Grok, drafting the post.** I used Grok to draft the post and then rewrote it in my own words. Prompt, lightly trimmed:
> Draft an r/crypto post (the cryptography subreddit, not cryptocurrency). It is a review request plus a break-it challenge for my open-source, stdlib-only Ed25519 verifier. Use only facts I give you or that you can verify at gitlab.com/smrforge/smrforge. Mark anything else [VERIFY]. Do not invent numbers or behavior.
>
> Hard accuracy constraints: the verifier is cofactorless and checks [s]B == R + [k]A with k = SHA-512(R || A || M) little-endian. Do not use libsodium as the reference, since it is cofactored. Use a cofactorless strict verifier such as ed25519-dalek verify_strict or eprint 2020/1244. It rejects y >= p, s >= L, off-curve points, small-order A or R, and the RFC 8032 5.1.3 x == 0 with sign bit case. The signature is over the ASCII string "sha256:<hex>" of a rolled-up bundle digest. It verifies under the embedded key first, then compares to a pinned key. The 24 in-tree vectors are self-generated regression vectors, not independent KATs. There are five copies, four Python and one JavaScript.
>
> Include honest weaknesses: RFC 3161 tokens are only shape-checked offline, and the implementation is hand-rolled pure Python. Ask whether the digest rollup is sound, whether to swap in a vetted library, and whether the offline/openssl timestamp split is acceptable. Follow the sub's rules, link source files directly, disclose AI use. Plain, humble tone, no em dashes, under 500 words.
1
u/entronid 9h ago
ianac but why? pure-python ed25519 verifiers exist and are written by professional cryptographers