Skip to content

fn-114 follow-up: migrate caller sites off Ed25519-hardcoded APIs, remove transitional crate-level allows #175

@bordumb

Description

@bordumb

Context

Epic fn-114 (Thread curve-tagged types end-to-end, curve-agnostic refactor) landed core infrastructure: TypedSignerKey, DevicePublicKey::verify, decode_public_key_{hex,bytes}, CryptoProvider::verify_p256, clippy deny-list across 7 clippy.toml files, PKCS8 invariant harness. See .flow/specs/fn-114.md for the full epic.

During landing, ~20 caller crates + test/bench/fuzz/binary roots carry a transitional #![allow(clippy::disallowed_methods)] attribute (with // fn-114: ... removed in fn-114.40 after Phase 4 sweeps marker) because 60+ call sites still call the banned Ed25519-only APIs. The tracked removal list is .flow/fn-114-dirty-crates.txt.

This issue covers fn-114.40 and the sub-tasks fn-114.22/23/24/25/26/29/31/32/33 that were deferred during Phase 4. The critical silent-correctness hazards (S1-S5, D1-D3) were addressed during fn-114; this follow-up is type-hygiene and escape-hatch removal, not a correctness fix.

Scope: 274 offender occurrences across 68 files

Top offenders by file (from rg against the deny-list patterns):

File Hits
crates/auths-storage/src/git/adapter.rs 16
crates/auths-keri/src/validate.rs 14
crates/auths-id/src/keri/inception.rs 10
crates/auths-mobile-ffi/src/lib.rs 10
crates/auths-transparency/src/verify.rs 9
crates/auths-id/src/keri/rotation.rs 8
crates/auths-core/benches/crypto.rs 7
crates/auths-id/tests/cases/rotation_edge_cases.rs 6
crates/auths-core/src/witness/server.rs 6
crates/auths-id/src/identity/helpers.rs 6
crates/auths-pairing-protocol/src/response.rs 6
packages/auths-{node,python}/src/{identity,pairing,sign,verify}.rs 4-4-3-1 each
... 56 more files with 1-5 hits each

Hits NOT in scope (these are permanent sanctioned allows — leave alone):

  • crates/auths-crypto/src/key_ops.rs (11)
  • crates/auths-crypto/src/ring_provider.rs (5)
  • crates/auths-crypto/src/{key_material,did_key}.rs — definition sites, not call sites. Strings matched the grep but no banned methods are invoked.

Migration mapping (drop-in replacements)

Generated from fn-114.9 decision task:

Banned Replacement
ring::signature::Ed25519KeyPair::from_pkcs8(bytes) auths_crypto::TypedSignerKey::from_pkcs8(bytes)? then .sign(msg) / .public_key()
ring::signature::Ed25519KeyPair::from_seed_unchecked(seed) let typed = auths_crypto::TypedSeed::Ed25519(*seed); auths_crypto::sign(&typed, msg)
ring::signature::Ed25519KeyPair::generate_pkcs8(rng) auths_id::keri::inception::generate_keypair_for_init(curve)
ring::signature::UnparsedPublicKey::new(&ED25519, pk).verify(msg, sig) let dpk = auths_verifier::decode_public_key_bytes(pk)?; dpk.verify(msg, sig, &provider).await?
auths_crypto::parse_ed25519_seed(bytes) auths_crypto::parse_key_material(bytes)?.seed (returns TypedSeed)
auths_crypto::parse_ed25519_key_material(bytes) auths_crypto::parse_key_material(bytes)? -> ParsedKey { seed, public_key }
auths_core::crypto::provider_bridge::sign_ed25519_sync(seed, msg) auths_crypto::sign(&typed_seed, msg)
auths_core::crypto::provider_bridge::ed25519_public_key_from_seed_sync(seed) auths_crypto::public_key(&typed_seed) or TypedSignerKey::from_seed(seed)?.public_key()
auths_crypto::did_key_to_ed25519(did) auths_crypto::did_key_decode(did)? -> DecodedDidKey::{Ed25519, P256}
auths_id::identity::resolve::ed25519_to_did_key(pk) auths_crypto::did_key_decode + DecodedDidKey variants

Docstrings, rustdoc, and test-fixture comments that happen to mention these symbols don't need changes — only actual fn call() sites.

Suggested ordering (minimizes ripple)

  1. Leaf call sites first — tests/fixtures/builders/fakes that construct keypairs locally for test use. Migrate them per-file, remove per-file or per-module #[allow] as each file goes clean.

    • crates/auths-core/tests/cases/{witness,key_export}.rs
    • crates/auths-id/tests/cases/{keri,lifecycle,recovery,rotation_edge_cases,proptest_keri}.rs
    • crates/auths-sdk/tests/cases/rotation.rs
    • crates/auths-infra-http/tests/cases/witness.rs
    • crates/auths-infra-rekor/tests/cases/rekor_integration.rs
    • crates/auths-transparency/tests/cases/verify.rs
    • crates/auths-storage/tests/cases/{concurrent_batch,concurrent_writes,mock_ed25519_keypairs}.rs
    • Once a crate's test integration root has zero in-tree banned calls, remove #![allow(clippy::disallowed_methods)] from its tests/integration.rs.
  2. Benches/fuzz — same pattern, smaller scope:

    • crates/auths-core/benches/crypto.rs
    • crates/auths-storage/benches/registry.rs
    • crates/auths-verifier/fuzz/fuzz_targets/did_parse.rs
  3. Production callers — by crate, bottom-up:

    • auths-core: agent/{core,session,handle}.rs, crypto/{signer,provider_bridge,ssh/keys}.rs, signing.rs, witness/server.rs, testing/builder.rs
    • auths-id: identity/{helpers,mod,resolve,rotate}.rs, keri/{inception,rotation}.rs, storage/receipts.rs, domain/keri_resolve.rs, testing/fixtures.rs
    • auths-keri: keys.rs (1), validate.rs (14 — biggest single file)
    • auths-sdk: domains/{identity/rotation,signing/service}.rs, workflows/rotation.rs, keys.rs, testing/fakes/transparency_log.rs
    • auths-storage: git/{adapter,identity_adapter}.rs
    • auths-transparency: verify.rs
    • auths-radicle: attestation.rs
    • auths-pairing-protocol: {protocol,response,token}.rs (response + token were partially migrated in fn-114.21 but still have hits)
    • auths-cli: src/bin/sign.rs (3), plus command paths
    • auths-verifier: src/verify.rs (1 site: auths_crypto::did_key_to_ed25519)
    • auths-mobile-ffi: lib.rs (10 — also dedupes compute_next_commitment per fn-114.41)
  4. Bindings — outside main workspace (separate Cargo roots):

    • packages/auths-node/src/{identity,pairing,sign,verify}.rs (~10 sites)
    • packages/auths-python/src/{identity,pairing,sign}.rs (~8 sites)
    • Run cd packages/auths-node && cargo clippy --all-features -- -D warnings per package.
  5. Remove allow attributes as each scope clears:

    • Per-file: delete #![allow(clippy::disallowed_methods)] at the top of migrated source files.
    • Per-crate: when every .rs under src/ is clean, delete the #![allow] in lib.rs. Same for tests/integration.rs, benches/*.rs, fuzz target roots, and src/bin/*.rs binaries.
    • For packages/auths-node/src/lib.rs specifically: the #![allow] currently comes AFTER #![deny(clippy::all)] so it overrides. Remove both the allow line and the ordering comment when done.

Verification

After each scope clears:

cargo clippy -p <crate> --all-features --tests -- -D warnings
# or for packages:
cd packages/<pkg> && cargo clippy --all-features -- -D warnings

After full migration:

# Workspace
cargo clippy --workspace --all-features --tests -- -D warnings
# Packages (separate)
(cd packages/auths-node && cargo clippy --all-features -- -D warnings)
(cd packages/auths-python && cargo clippy --all-features -- -D warnings)
# Final invariant grep (from fn-114.42)
rg -n 'parse_ed25519_seed|parse_ed25519_key_material|build_ed25519_pkcs8_v2|sign_ed25519_sync|ed25519_public_key_from_seed_sync|did_key_to_ed25519|ed25519_to_did_key|encode_seed_as_pkcs8' crates/ packages/ \
  | rg -v 'tests/|benches/|docs/|CHANGELOG|key_material.rs:|key_ops.rs:|ring_provider.rs:'
# Expect: zero hits (or only hits inside sanctioned modules)

Also run the PKCS8 invariant harness with --include-ignored — the S3/S4 hazard demonstrations at crates/auths-crypto/tests/cases/pkcs8_roundtrip.rs carry UNGATE IN fn-114.18 markers. As call sites migrate, remove the #[ignore] per site until the whole harness runs on plain cargo test.

Definition of done

  • Zero #![allow(clippy::disallowed_methods)] transitional markers remain (grep fn-114.*removed in fn-114.40 returns empty)
  • .flow/fn-114-dirty-crates.txt fully empty (or deleted)
  • Only permanent allows remain: crates/auths-crypto/src/{key_ops,ring_provider}.rs with INVARIANT: comments
  • Final invariant grep (above) returns zero hits outside sanctioned modules
  • cargo clippy --workspace --all-features --tests -- -D warnings clean
  • Both bindings (packages/auths-{node,python}) clippy clean
  • crates/auths-crypto/tests/cases/pkcs8_roundtrip.rs — the 2 ignored hazard demos either deleted (preferred — the hazard path is unreachable) or un-ignored (test passes because the path is gone)
  • CHANGELOG.md entry documenting the follow-up
  • .flow/specs/fn-114.md "Outcome" section updated to mark Phase 7 fully closed

Useful references

  • .flow/specs/fn-114.md — epic spec with all architectural decisions
  • .flow/fn-114-dirty-crates.txt — authoritative list of allow sites to remove
  • .flow/tasks/fn-114.{22,23,24,25,26,29,31,32,33,40}.md — per-site migration notes from the deferred Phase 4 tasks
  • scripts/check-clippy-sync.sh — verifies deny-list stays in sync across 7 clippy.toml files
  • crates/auths-crypto/src/key_ops.rs — canonical TypedSignerKey implementation to model migrations after
  • crates/auths-verifier/src/core.rs:408DevicePublicKey::verify (async, takes &dyn CryptoProvider)
  • fn-114.21's pairing-protocol migration (crates/auths-pairing-protocol/src/{token,response}.rs) is a good pattern for sync call sites that need curve dispatch without going async

Estimated effort

4-8 hours for a focused LLM session with cargo build -p <crate> --all-features 2>&1 | grep '^error\[E' -A 10 as the tight feedback loop. No architectural changes needed — purely replacing call-site A with equivalent call-site B and removing allow attributes as scopes clear.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions