fix(identity-wallet): address PR review — critical ceremony bugs and logging improvements
CRITICAL FIXES:
- [Critical 1] rotation_key_public now sends device_key.key_id instead of device_key.multibase (line 315)
- device_key.multibase is raw base58btc without 'did:key:' prefix; relay validates field starts with 'did:key:z'
- device_key.key_id contains the full did:key: URI required by relay
- [Critical 2] signed_creation_op changed from String to serde_json::Value (line 54, 315-319)
- String type caused double-encoded JSON: reqwest serializes String as JSON literal, relay received \"json\" and verify failed
- Now parses genesis_op.signed_op_json at call site to deserialize once before sending
- Errors map to SigningFailed with structured error logging
- [Critical 3] Removed #[serde(rename_all = "camelCase")] from CreateDidResponse (line 58)
- Relay serializes session_token in snake_case per HTTP API conventions
- With camelCase rename, client received empty string for session_token field
- Now correctly deserializes relay's snake_case response
HIGH-PRIORITY FIXES:
- [High 4] Error bodies now logged on non-success responses (line 270-271, 326-329)
- GET /v1/relay/keys non-success: logs status and body as tracing::error\! instead of silently returning RelayKeyFetchFailed
- POST /v1/dids non-success: logs status and body with structured error field
- Improves observability when relay returns error details
- [High 5] Removed #[serde(default)] from RelaySigningKey fields (line 41-45)
- public_key and algorithm fields now required; serde fails loudly if relay truncates response
- Prevents silent contract violation where client accepts empty strings
- [High 6] Promoted warn\! to error\! for critical failures (lines 280, 329, 334, 337, 350)
- Step 3 genesis op signing failure: error\! (was warn\!)
- Step 5 POST /v1/dids deserialization on 2xx: error\! (was warn\!)
- Step 6 session-token persistence: error\! (was warn\!)
- Step 7 DID persistence: error\! with did field for forensics (was warn\!)
- [High 7] Fixed DIDCeremonyError::KeyNotFound #[error] message (line 133)
- Old: 'device key not found; call get_or_create before ceremony' (misleading, variant used for all failures)
- New: 'failed to get or create device key' (accurate for all failure modes)
MEDIUM-PRIORITY FIXES:
- [Medium 8] Fixed byte-count comment in insert_test_key (get_relay_signing_key.rs:65-68)
- Old: '60 zero-bytes base64-encoded + padding = 84 chars' (wrong: 60 bytes → 80 chars, 63 bytes → 84 chars)
- New: Correctly states '84-char placeholder encodes 63 bytes (with padding characters)'
- [Medium 9] Log DID in Step 7 keychain failure (lib.rs:350)
- Added did field to error\! log for forensic trail when DID persistence fails
- If write fails after relay created DID, DID value now captured in logs
- [Medium 10] Added 64-byte signature length validation (plc.rs:216-221)
- build_did_plc_genesis_op_with_external_signer now validates callback returns exactly 64 bytes
- Wrong-length signature silently produces incorrect DID; now returns CryptoError::PlcOperation
- [Medium 11] Added doc comment to build_did_plc_genesis_op wrapper (plc.rs:253-263)
- Clarifies this is a convenience wrapper for extractable keys using inline signing callback
- Documents delegation to external_signer variant
- [Medium 12] Fixed Step 3 comment about Secure Enclave (lib.rs:281)
- Old: 'private key never leaves the SE' (inaccurate on Simulator/macOS)
- New: 'On device, private key never leaves SE; on Simulator and macOS, software key used instead'
- [Medium 13] Tightened publicKey assertion in test (get_relay_signing_key.rs:111)
- Old: assert\!(json["publicKey"].is_string(), ...) (only checks presence)
- New: assert_eq\!(json["publicKey"], "zTestPublicKey123") (verifies actual value)
VERIFICATION:
✓ cargo build -p relay -p crypto: success
✓ cargo test -p relay get_relay: 4/4 tests pass
✓ cargo test -p crypto: 44/44 tests pass
✓ cargo test -p identity-wallet --lib -- tests:: --skip device_key: 21/21 serialization tests pass
✓ cargo clippy --workspace -- -D warnings: no warnings introduced
✓ cargo fmt --all --check: all files properly formatted