The Problem
The Rust core (drs-core) uses ed25519-dalek with verify_strict(). The Go verifier (drs-verify) uses stdlib crypto/ed25519.Verify(). These do not accept the same set of signatures.
verify_strict rejects:
- Non-canonical S values (
S >= L, where L is the Ed25519 group order)
- Small-order public keys (8 torsion points)
- Uses the cofactored verification equation:
[8][S]B = [8]R + [8][k]A
Go's crypto/ed25519.Verify does none of this. It accepts signatures that Rust rejects.
This means a signature verified by Go may fail when verified by Rust — or worse, an attacker can craft a signature that Go accepts but Rust rejects, creating inconsistent verification outcomes across layers.
Where It Breaks
| Layer |
File |
Call |
| Rust |
drs-core/src/crypto/ed25519.rs |
verify_strict() |
| Go |
drs-verify/pkg/verify/chain.go:456 |
ed25519.Verify(pubKey, msg, sig) |
What Must Change
-
Document the divergence — Identify the exact differences between Go stdlib ed25519.Verify and ed25519-dalek verify_strict. Which non-canonical inputs does each accept or reject?
-
Evaluate stricter Go verification — Options:
- Use
filippo.io/edwards25519 to implement strict verification in Go
- Use
ed25519consensus from the Cosmos SDK (implements ZIP-215 + cofactored verification)
- Accept the divergence and document it as a known difference
-
Add test vectors — Cover the edge cases: non-canonical S values, small-order keys, mixed-order points. Both layers must produce the same result for the same input.
-
Write the assessment in drs-verify/docs/component.md as required by the audit.
Severity
MEDIUM
In practice, legitimate Ed25519 implementations produce canonical signatures. The risk is adversarial: a party deliberately crafting non-canonical signatures to exploit the divergence. The blast radius depends on whether both layers verify the same bundles in production.
The Problem
The Rust core (
drs-core) usesed25519-dalekwithverify_strict(). The Go verifier (drs-verify) uses stdlibcrypto/ed25519.Verify(). These do not accept the same set of signatures.verify_strictrejects:S >= L, whereLis the Ed25519 group order)[8][S]B = [8]R + [8][k]AGo's
crypto/ed25519.Verifydoes none of this. It accepts signatures that Rust rejects.This means a signature verified by Go may fail when verified by Rust — or worse, an attacker can craft a signature that Go accepts but Rust rejects, creating inconsistent verification outcomes across layers.
Where It Breaks
drs-core/src/crypto/ed25519.rsverify_strict()drs-verify/pkg/verify/chain.go:456ed25519.Verify(pubKey, msg, sig)What Must Change
Document the divergence — Identify the exact differences between Go stdlib
ed25519.Verifyanded25519-dalek verify_strict. Which non-canonical inputs does each accept or reject?Evaluate stricter Go verification — Options:
filippo.io/edwards25519to implement strict verification in Goed25519consensusfrom the Cosmos SDK (implements ZIP-215 + cofactored verification)Add test vectors — Cover the edge cases: non-canonical S values, small-order keys, mixed-order points. Both layers must produce the same result for the same input.
Write the assessment in
drs-verify/docs/component.mdas required by the audit.Severity
MEDIUM
In practice, legitimate Ed25519 implementations produce canonical signatures. The risk is adversarial: a party deliberately crafting non-canonical signatures to exploit the divergence. The blast radius depends on whether both layers verify the same bundles in production.