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)
-
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.
-
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
-
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)
-
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.
-
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
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:408 — DevicePublicKey::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.
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 7clippy.tomlfiles, PKCS8 invariant harness. See.flow/specs/fn-114.mdfor 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 sweepsmarker) 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
rgagainst the deny-list patterns):crates/auths-storage/src/git/adapter.rscrates/auths-keri/src/validate.rscrates/auths-id/src/keri/inception.rscrates/auths-mobile-ffi/src/lib.rscrates/auths-transparency/src/verify.rscrates/auths-id/src/keri/rotation.rscrates/auths-core/benches/crypto.rscrates/auths-id/tests/cases/rotation_edge_cases.rscrates/auths-core/src/witness/server.rscrates/auths-id/src/identity/helpers.rscrates/auths-pairing-protocol/src/response.rspackages/auths-{node,python}/src/{identity,pairing,sign,verify}.rsHits 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.9decision task: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(returnsTypedSeed)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)orTypedSignerKey::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+DecodedDidKeyvariantsDocstrings, rustdoc, and test-fixture comments that happen to mention these symbols don't need changes — only actual
fn call()sites.Suggested ordering (minimizes ripple)
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}.rscrates/auths-id/tests/cases/{keri,lifecycle,recovery,rotation_edge_cases,proptest_keri}.rscrates/auths-sdk/tests/cases/rotation.rscrates/auths-infra-http/tests/cases/witness.rscrates/auths-infra-rekor/tests/cases/rekor_integration.rscrates/auths-transparency/tests/cases/verify.rscrates/auths-storage/tests/cases/{concurrent_batch,concurrent_writes,mock_ed25519_keypairs}.rs#![allow(clippy::disallowed_methods)]from itstests/integration.rs.Benches/fuzz — same pattern, smaller scope:
crates/auths-core/benches/crypto.rscrates/auths-storage/benches/registry.rscrates/auths-verifier/fuzz/fuzz_targets/did_parse.rsProduction callers — by crate, bottom-up:
agent/{core,session,handle}.rs,crypto/{signer,provider_bridge,ssh/keys}.rs,signing.rs,witness/server.rs,testing/builder.rsidentity/{helpers,mod,resolve,rotate}.rs,keri/{inception,rotation}.rs,storage/receipts.rs,domain/keri_resolve.rs,testing/fixtures.rskeys.rs(1),validate.rs(14 — biggest single file)domains/{identity/rotation,signing/service}.rs,workflows/rotation.rs,keys.rs,testing/fakes/transparency_log.rsgit/{adapter,identity_adapter}.rsverify.rsattestation.rs{protocol,response,token}.rs(response + token were partially migrated in fn-114.21 but still have hits)src/bin/sign.rs(3), plus command pathssrc/verify.rs(1 site:auths_crypto::did_key_to_ed25519)lib.rs(10 — also dedupescompute_next_commitmentper fn-114.41)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)cd packages/auths-node && cargo clippy --all-features -- -D warningsper package.Remove allow attributes as each scope clears:
#![allow(clippy::disallowed_methods)]at the top of migrated source files..rsundersrc/is clean, delete the#![allow]inlib.rs. Same fortests/integration.rs,benches/*.rs, fuzz target roots, andsrc/bin/*.rsbinaries.packages/auths-node/src/lib.rsspecifically: 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:
After full migration:
Also run the PKCS8 invariant harness with
--include-ignored— the S3/S4 hazard demonstrations atcrates/auths-crypto/tests/cases/pkcs8_roundtrip.rscarryUNGATE IN fn-114.18markers. As call sites migrate, remove the#[ignore]per site until the whole harness runs on plaincargo test.Definition of done
#![allow(clippy::disallowed_methods)]transitional markers remain (grepfn-114.*removed in fn-114.40returns empty).flow/fn-114-dirty-crates.txtfully empty (or deleted)crates/auths-crypto/src/{key_ops,ring_provider}.rswithINVARIANT:commentscargo clippy --workspace --all-features --tests -- -D warningscleanpackages/auths-{node,python}) clippy cleancrates/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.mdentry documenting the follow-up.flow/specs/fn-114.md"Outcome" section updated to mark Phase 7 fully closedUseful 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 tasksscripts/check-clippy-sync.sh— verifies deny-list stays in sync across 7clippy.tomlfilescrates/auths-crypto/src/key_ops.rs— canonicalTypedSignerKeyimplementation to model migrations aftercrates/auths-verifier/src/core.rs:408—DevicePublicKey::verify(async, takes&dyn CryptoProvider)crates/auths-pairing-protocol/src/{token,response}.rs) is a good pattern for sync call sites that need curve dispatch without going asyncEstimated effort
4-8 hours for a focused LLM session with
cargo build -p <crate> --all-features 2>&1 | grep '^error\[E' -A 10as 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.