Skip to content

feat(crypto): Ed25519 verifier choice — BouncyCastle (default) or i2p, both with libsodium's outcomes - #751

Open
satran004 wants to merge 2 commits into
masterfrom
feat/ed25519-bc-libsodium-verify
Open

satran004 wants to merge 2 commits into
masterfrom
feat/ed25519-bc-libsodium-verify

Conversation

@satran004

Copy link
Copy Markdown
Member

Summary

Ed25519 verify now has two interchangeable implementations behind a new Ed25519Verifier interface. Both give a cardano-node's outcomes, which come from libsodium's crypto_sign_ed25519_verify_detached.

To select a verifier, use CryptoConfiguration.INSTANCE.setEd25519Verifier(...) globally, or new EdDSASigningProvider(verifier) for one provider. Signing, extended-key signing, KES and VRF keep using i2p. KES leaf verification goes through the configured verifier.

BouncyCastle's own Ed25519.verify is not used. It is cofactored and accepts mixed-order inputs that libsodium rejects: 7,696 of 100,212 cases in our differential corpus.

Details

  • Ed25519InputChecks (package-private): the libsodium checks. They reject a non-canonical S, a small-order R or key, and a non-canonical key.

  • Ed25519Equation (package-private): libsodium's frombytes_negate_vartime, slide, double_scalarmult_vartime, tobytes and byte compare, on X25519Field.

  • Load-time self-check. On first use it runs three vectors:

    • RFC 8032 test 1 (accept);
    • an order-8 R (reject; BouncyCastle's own verify accepts it);
    • cancelling small-order parts (accept).

    If the bcprov on the classpath disagrees, or lacks X25519Field, verify throws a LinkageError that names the bcprov version and jar and says to use 1.67+ or switch to I2pEd25519Verifier. Only a definite outcome is cached; a transient OOM or StackOverflow is retried.

  • Both verifiers agree on a null message:

    • a key that doesn't decode → false;
    • a decodable key → CryptoException.
  • setEd25519Verifier(null) is rejected.

  • bcprov is bumped to 1.86.

  • Docs: "Choosing the Ed25519 verifier" in www/src/content/docs/reference/crypto-api.md.

Testing

  • Libsodium parity. Test resources: 2,371 libsodium 1.0.18 corpus vectors, 220 fixed vectors (RFC 8032 and small-order cases), and probes. Both verifiers must match libsodium on every vector.

  • Wider differential runs, outside the repo: about 100k cases, plus RFC 8032 and Wycheproof. 0 mismatches against libsodium across bcprov 1.67–1.86.

  • Builds: :crypto:test 88/88 and :crypto-ext:test 149/149 on JDK 17. In the wider build, the only failure is RocksDbJmtCrashRecoveryTest ("Crash worker timed out"), which also fails on unmodified master 5ca123e.

  • Yano downstream, with this branch backported onto Yano v0.8.0-pre7. Results are identical with the baseline, BouncyCastle and i2p verifiers:

    Suite Result
    Ledger conformance 174 pass, 1 skip
    Node-diversity reports 4 pass
    Ledger rules 389 pass, 2 skip
    Runtime 273 pass, 1 probe failure (also on baseline)
    core-api 10 pass

    That covers about 11k Ed25519 verifies per run, including KES and opcert checks. Per-caller counts are identical.

  • Reviews: an independent Claude review (approve; its P3s are fixed in 5c0ebe3) and two Codex adversarial reviews (both approve).

Compatibility

Note for Yano: moving to current master also needs the existing getNewMembersAndTerms() → Map<Credential, Long> change in GovernanceValidationRule. That is unrelated to this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MJrQU97MHKeQpEH7XcJT9A

satran004 and others added 2 commits November 11, 2026 09:50
…hecks and equation

EdDSASigningProvider.verify now goes through an Ed25519Verifier. The default,
BouncyCastleEd25519Verifier, runs libsodium's input checks and its
cofactorless equation on BouncyCastle's field arithmetic and allocates about
6 KB per verification instead of about 630 KB. It checks itself against
libsodium's verdicts on first use and fails with a LinkageError naming the
bcprov version if they differ. I2pEd25519Verifier is the previous verifier;
select it with CryptoConfiguration.INSTANCE.setEd25519Verifier. Signing stays
on the i2p fork. bcprov goes to 1.86.

Tests check both verifiers against libsodium's verdicts (2,368 generated
cases, RFC 8032, Wycheproof, speccheck) and against each other on 4,000
seeded cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJrQU97MHKeQpEH7XcJT9A
…rdening

- BouncyCastleEd25519Verifier decodes the key before it reads the message, so
  a key that is not a curve point gives false with a null message, as
  I2pEd25519Verifier does; a null message with a key that decodes throws
  CryptoException in both.
- On a bcprov without the X25519Field methods the equation uses (before 1.67)
  the LinkageError names the bcprov version and what is needed.
- The self-check's outcome is kept once definite; a transient error such as
  an OutOfMemoryError no longer disables verification for the life of the JVM.
- setEd25519Verifier refuses null. The verify javadoc mentions the
  LinkageError, and crypto-api.md says how to choose the verifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJrQU97MHKeQpEH7XcJT9A
@sonarqubecloud

Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

Initial automated review · Aster

Hi @satran004, thanks for the PR! 🙏 This is an automatic first check by an AI reviewer. It does not replace maintainer review and may be wrong.

Reviewed commit: 5c0ebe3f7547dfaae425c2182508f3d552a76d0a

Issues found:

  • crypto/src/main/java/com/bloxbean/cardano/client/crypto/api/impl/Ed25519Equation.java:74 — If table setup fails on first use, later verifications get a generic NoClassDefFoundError even if the original failure was temporary. Could we move table setup into the retryable initialization path?

Suggestions:

  • crypto/src/main/java/com/bloxbean/cardano/client/crypto/config/CryptoConfiguration.java:16 — Could we make the verifier field volatile so a runtime switch is visible to verification threads?

Not tested: I reviewed the code and CI without running builds, tests, or the libsodium corpus.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants