Files
glm-5.3-flash f4b5f896b4 docs: sweep em dashes from all markdown prose
Renames the written-usage rule into practice across the contract surface:
docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files,
taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the
.opencode/agents/ prompts. Every replacement is a sense-preserving
rewrite (colons, semicolons, commas, parentheses, sentence splits, varied),
never a mechanical swap; no spaced hyphens were substituted. Enum fields,
frontmatter, tables, wire data, and cited identifiers survive verbatim;
en dashes inside numeric ranges keep their meaning.

Verification: taskgraph validate (43 tasks), repo grep clean (single
exempt hit: the verification command's own search pattern in AGENTS.md),
cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
2026-11-04 20:28:00 +00:00

101 lines
4.6 KiB
Markdown

---
id: w1-fingerprint
name: "Wave 1: fingerprint computation (the contract invariant)"
status: completed
depends_on: [w1-manifest]
scope: narrow
risk: low
impact: project
level: implementation
---
## Description
Land the fingerprint computation the auth bridge (server) and the
host-key policy path (client) both consume; this is ADR-004
decision 1's contract invariant in code:
- Compute a key's fingerprint via the `ssh-key` re-export
(`HashAlg::Sha256`): the `SHA256:` + **unpadded url-safe base64**
OpenSSH format. The string format is a **contract invariant**:
registration strings from any standard issuer (`ssh-keygen -lf`,
alkcall-side computation) must compare equal to what this crate
computes; parity is proven, not assumed (POC-1 §1).
- The function takes the russh/ssh-key public-key type and returns the
string; it is the single fingerprint path in the crate (no
second formatting anywhere, since a drift here breaks every
identity resolution silently).
- Parity tests against independent-issuer vectors: static ed25519
test keys with their `ssh-keygen -lf`-recorded expected fingerprints
(the POC-1 §1 checkpoint shape). The vectors are committed test
fixtures (throwaway test keys; no secret material).
## Acceptance Criteria
- [x] Single fingerprint function via `HashAlg::Sha256`; `SHA256:` +
unpadded url-safe base64 output
- [x] Parity tests against `ssh-keygen -lf`-recorded vectors pass
(independent-issuer format parity proven)
- [x] No second fingerprint-formatting path exists in `src/`
- [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --check` pass
## References
- docs/architecture/decisions/004-auth-dispositions.md (decision 1:
the format invariant)
- docs/architecture/auth.md §The bridge (step 2)
- docs/research/poc-1-auth-bridge.md §1 (the parity checkpoint)
- docs/research/auth-bridge.md §The mapping (step 2)
## Notes
> Design: `src/fingerprint.rs` exposes `fingerprint(&public_key) ->
> String`, the thin wrapper over the `ssh-key` re-export
> (`key.fingerprint(HashAlg::Sha256).to_string()`; russh 0.65
> re-exports `ssh_key::{PublicKey, HashAlg}` via `russh::keys`), so
> the *computation and formatting stay ssh-key's own code* and parity
> is structural, not re-implemented. `lib.rs` re-exports
> `alkssh::fingerprint`; a grep over `src/` confirms no second
> formatting path (doc mentions only). The russh 0.65 handler shape
> feeds exactly this type: `VerifiedPublicKey.public_key: &
> ssh_key::PublicKey`, with the auth bridge (wave 2,
> w1-host-key-policy) passing that field straight through.
>
> **Fact-check finding (doc correction landed):** the ADR-004 /
> auth.md / task wording "unpadded **url-safe** base64" is a misnomer.
> The ssh-key crate's `Fingerprint` Display emits `SHA256:` +
> *unpadded standard* base64 (base64ct `Base64Unpadded`
> = the standard `+/` alphabet), and OpenSSH does the same, as the
> committed `ssh-keygen -lf` vectors contain `+` and `/`. The binding
> invariant was never "url-safe"; it is byte-parity with standard
> issuers, which POC-1 proved and the committed vectors now pin.
> Corrected the wording in ADR-004 and `auth.md` (the two places that
> spell the format); the parity contract itself is unchanged on both
> sides.
>
> Vectors: 4 throwaway ed25519 keys generated with local ssh-keygen
> (OpenSSH 9.6p1), blobs committed under `tests/vectors/*.pub`,
> expected fingerprints byte-verbatim from `ssh-keygen -lf` recorded
> in `tests/vectors/fingerprints.txt` (provenance header included) and
> inlined in `tests/fingerprint_parity.rs`. Tests cross-check the
> inlined vectors against the committed files and manifest so a stale
> fixture edit cannot silently pass. No secret material committed
> (private halves never left the tmpdir and were discarded).
## Summary
> Landed `src/fingerprint.rs`, the crate's single fingerprint path
> (`alkssh::fingerprint`), delegating computation/formatting to the
> ssh-key re-export behind russh 0.65's keys module with
> `HashAlg::Sha256`. Parity proven, not assumed: 4 committed
> ed25519 fixtures with `ssh-keygen -lf` (OpenSSH 9.6p1) recorded
> fingerprints reproduce byte-identically (`tests/
> fingerprint_parity.rs`, incl. shape/format-of-registered-string
> assertions). Found and corrected a misnomer in the docs: the format
> is unpadded *standard* base64, not url-safe (OpenSSH fingerprints
> contain `+/`; byte-parity with `ssh-keygen` is and always was the
> contract). Verified: `cargo test` (10/10 incl. all-features),
> `cargo clippy --all-targets -- -D warnings` (default +
> all-features), `cargo fmt --check`, `cargo doc --no-deps` clean;
> `taskgraph validate` clean.