Conversation
There was a problem hiding this comment.
Code Review
This pull request implements the WalletConnect v2 signing provider within the ironclaw_wallet_external crate, enabling out-of-band signing for EVM and ed25519-based chains. The implementation includes CAIP-2 chain resolution, namespace pinning to prevent scope broadening, and a verification flow that binds proofs to specific sessions and nonces. Additionally, architectural tests were added to ensure the workspace remains OpenSSL-free. Feedback from the review highlights critical security risks associated with manual byte-based string indexing in hex decoding logic, which could lead to panics on non-ASCII input. It was also recommended to implement case-insensitive comparisons for EVM addresses to avoid unexpected verification failures.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 343eb2525a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Make the defense-in-depth session-account string compare in verify_resume case-insensitive (eq_ignore_ascii_case): EVM addresses are case-insensitive hex and a WC session may settle the account in EIP-55 mixed case while the gate stores it lowercase, so a pure-casing difference must not spuriously reject. The authoritative signer binding remains the byte-exact chain-signature recovery in verify_chain_signature. Adds a regression test (evm_session_account_casing_mismatch_still_verifies). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
henrypark133
left a comment
There was a problem hiding this comment.
Code Review (multi-agent)
Intent: Implement WalletConnect v2 signing provider backend with namespace pinning and domain-separated attestation digest verification
Stats: 16 findings (from 24 raw) across 10 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, pattern-refactor. Reviewers failed: none. Body-only: 5
Security (2)
-
Medium Duplicate bs58 crate versions (0.4.0 and 0.5.1) in lockfile (
Cargo.lock:1250-1260, confidence 75) — anchor:Cargo.lock:1253
The diff introduces bs58 0.4.0 while 0.5.1 already exists. Having two versions of a cryptographic encoding library can lead to subtle interoperability issues.
Fix:Consolidate to a single bs58 version by updating the dependent crate to use 0.5.1. -
Medium Diff truncated — source code changes not visible for full security analysis (
diff:1-1, confidence 75) — anchor:context_bundle:diff_truncated=true(no diff position — body only)
The diff is truncated, so the actual implementation of WalletConnectSigningProvider cannot be fully reviewed.
Fix:Provide the full diff or review the source files directly.
Bugs (2)
-
Medium encode_walletconnect_proof silently swallows serialization errors (
crates/ironclaw_wallet_external/src/walletconnect/proof.rs:67-69, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/proof.rs:68
serde_json::to_vec(payload).unwrap_or_default() returns empty Vec on any serialization failure.
Fix:Return Result<Vec<u8>, SigningProviderError> and map the serde_json error to ProofInvalid.
Also flagged by: local-patterns/Medium -
Medium SessionBindingStore silently drops all operations on mutex poison (
crates/ironclaw_wallet_external/src/walletconnect/session.rs:93-119, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/session.rs:94
record(), peek(), and take() all silently do nothing when the Mutex is poisoned.
Fix:Use .expect() on the lock result so a poisoned mutex panics immediately.
Also flagged by: performance/Medium
Also flagged by: tests/Medium
Also flagged by: performance/Medium
Tests (5)
-
High verify_evm non-32-byte signed_payload error path not exercised (
crates/ironclaw_wallet_external/src/walletconnect/signer.rs:67-110, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/signer.rs:75
verify_evm returns ProofInvalid when signed_payload is not exactly 32 bytes. No test constructs a WC proof with a wrong-length signed_payload for EVM.
Fix:tests::walletconnect_verify_resume::evm_signed_payload_wrong_length_is_proof_invalid
Also flagged by: tests/High -
Medium recovery_id_from_v invalid v-byte error path not exercised (
crates/ironclaw_wallet_external/src/walletconnect/signer.rs:162-186, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/signer.rs:172
recovery_id_from_v rejects v values outside {0,1,27,28,>=35}. No test constructs a proof with an invalid v byte.
Fix:tests::walletconnect_verify_resume::evm_invalid_recovery_id_v_is_proof_invalid -
High verify_chain_signature Solana missing public_key error path not exercised (
crates/ironclaw_wallet_external/src/walletconnect/signer.rs:46-65, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/signer.rs:49
verify_chain_signature returns ProofInvalid when public_key is None for Solana/ed25519 families. All existing Solana tests provide public_key.
Fix:tests::walletconnect_verify_resume::solana_missing_public_key_is_proof_invalid -
Medium verify_ed25519 wrong-length signature and public_key error paths not exercised (
crates/ironclaw_wallet_external/src/walletconnect/signer.rs:112-160, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/signer.rs:118
verify_ed25519 returns ProofInvalid for non-64-byte signatures and non-32-byte public keys. No test constructs a Solana proof with wrong-length crypto fields.
Fix:tests::walletconnect_verify_resume::solana_wrong_length_crypto_fields_is_proof_invalid -
Low WalletConnectSigningProvider::initiate not tested (
crates/ironclaw_wallet_external/src/walletconnect/mod.rs:124-160, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/mod.rs:124
The initiate method is a public trait implementation that returns AwaitingUserAction. No test calls initiate.
Fix:tests::walletconnect_verify_resume::initiate_returns_awaiting_user_action
Conventions (4)
-
Medium Hex parsing regresses to panic on non-ASCII Unicode input (
crates/ironclaw_wallet_external/src/injected/evm.rs:95-110, confidence 100) — anchor:CLAUDE.md:21 — No .unwrap() or .expect() in production code(no diff position — body only)
The diff replaces the byte-based panic-free hex parser with u8::from_str_radix on &str which WILL PANIC on non-ASCII even-byte input. The old code was explicitly designed as panic-free for untrusted relay/wallet input. Tests verifying this were removed.
Fix:Revert to the byte-based hex parser (as_bytes() + per-byte nibble matching). Re-add the removed Unicode panic-free tests. -
Medium Hex parsing regresses to panic on non-ASCII Unicode input (
crates/ironclaw_wallet_external/src/injected/solana.rs:64-79, confidence 100) — anchor:CLAUDE.md:21 — No .unwrap() or .expect() in production code(no diff position — body only)
Same regression as evm.rs: replaces byte-based panic-free hex parser with u8::from_str_radix on &str slices.
Fix:Revert to the byte-based panic-free hex parser. -
Medium Hex decode helper regresses to panic on non-ASCII Unicode input (
crates/ironclaw_wallet_external/src/injected/mod.rs:219-233, confidence 100) — anchor:CLAUDE.md:21 — No .unwrap() or .expect() in production code(no diff position — body only)
Same regression: hex_decode replaces byte-based parsing with u8::from_str_radix on &str, panicking on non-ASCII even-byte JSON strings.
Fix:Revert to byte-based panic-free hex parsing.
Also flagged by: local-patterns/Medium -
Medium Removes panic-free Unicode hex parsing tests without rationale (
crates/ironclaw_wallet_external/tests/verify_resume.rs:347-454, confidence 75) — anchor:AGENTS.md:79-80 — Test through the caller, not just the helper(no diff position — body only)
The diff removes four tests that verified panic-free behavior on attacker-supplied Unicode input. These tests covered a documented security invariant.
Fix:Either retain the tests or add rationale explaining why Unicode panic-free parsing is no longer a requirement.
Local Patterns (1)
- Low SessionBindingStore::new() is redundant boilerplate over derive(Default) (
crates/ironclaw_wallet_external/src/walletconnect/session.rs:86-89, confidence 75) — anchor:crates/ironclaw_wallet_external/src/injected/mod.rs:94-98
SessionBindingStore derives Default and new() is just Self::default().
Fix:Remove SessionBindingStore::new() and let callers use SessionBindingStore::default().
Maintainability (2)
-
Medium Three copies of hex serialization/deserialization in one crate (
crates/ironclaw_wallet_external/src/walletconnect/mod.rs:300-352, confidence 100) — anchor:crates/ironclaw_wallet_external/src/walletconnect/mod.rs:300
Near-identical hex (de)serialization logic is duplicated three times.
Fix:Extract a single crate-level hex module.
Also flagged by: tests/Medium -
Medium encode_walletconnect_proof silently swallows serialization errors (
crates/ironclaw_wallet_external/src/walletconnect/mod.rs:67-69, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/mod.rs:68
encode_walletconnect_proof returns Vec with no error path and uses unwrap_or_default().
Fix:Change the return type to Result<Vec<u8>, SigningProviderError>.
Also flagged by: conventions/Medium
Bugs: - encode_walletconnect_proof now returns Result<Vec<u8>, SigningProviderError> instead of unwrap_or_default(), surfacing serialization failures at the encode site rather than emitting an empty body that later mis-decodes. - SessionBindingStore record/peek/take recover a poisoned mutex via into_inner() instead of silently no-op'ing (the dropped-operation bug), while staying panic-free per the production no-.expect() rule. The HashMap stays consistent across a holder panic, so recovering the guard is the safest fix and avoids an induced-panic DoS. Tests (new fail-closed coverage): - evm_signed_payload_wrong_length_is_proof_invalid - evm_invalid_recovery_id_v_is_proof_invalid - solana_missing_public_key_is_proof_invalid - solana_wrong_length_crypto_fields_is_proof_invalid - initiate_returns_awaiting_user_action + initiate_unsupported_chain_fails_closed Maintainability: - decode_hex_fixed delegates byte-based panic-free nibble decode to the shared hex_bytes::hex_decode, removing the duplicated nibble matcher. PRESERVE invariants untouched: case-insensitive-but-exact EVM session-account match, ApprovedTxHash binding, openssl-free pinned walletconnect-rs fork rev, deterministic resume. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Review response — henrypark133 CHANGES_REQUESTED (16 findings)Pushed
|
7fd6252 to
0c7224a
Compare
5b3b0f8 to
772e0f2
Compare
|
Low — |
|
Low — Relay URL validation and session expiry are not present in PR9, both correctly deferred to PR10. One note for the PR10 author: when the live relay connection is wired, the relay hostname should be validated against an allowlist (e.g. |
henrypark133
left a comment
There was a problem hiding this comment.
PR9/10 attested-signing WalletConnect v2 backend: approve.
The two security cores — namespace pinning (enforce_pinned_scope) and verify_resume's fail-closed chain of hash binding (T20) → session+nonce binding (T18) → signed-payload binding (#1) → chain-signature verification (T17) → one-shot grant CAS (T20) — are correctly implemented and thoroughly tested. CAIP-2 grammar validation, CAIP-10 account chain-pinning, singleton-exact scope enforcement, EVM ecrecover / ed25519 verification, and the peek-first / consume-on-success binding discipline are all sound. The openssl-free posture and architecture boundary tests are well-enforced.
Findings (no Critical/High):
- Medium (session.rs:85):
record()doc says "propagate poison by panicking" but code callspoison.into_inner()which recovers. Misleading doc for a security-critical store. - Low (Cargo.toml:37): PR body cites fork rev
c6b528ebut Cargo.toml/lock pin7078fd3— stale description. - Low (deny.toml): no
[[bans.skip]]for newbs58/ordered-floatversion duplicates from relay_rpc; will produce CI warnings. - Low (architectural, PR10 tracking): relay URL should be allowlisted;
SessionBindinghas no TTL — noted for PR10 author.
0c7224a to
4b04f16
Compare
772e0f2 to
040bb95
Compare
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
4b04f16 to
baf5be0
Compare
040bb95 to
7e24581
Compare
|
Superseded by #6755, part of consolidating the 20-PR The content of this PR is carried forward in full, re-based on current Closing to keep the queue honest. The review history stays on this PR and remains readable; reopen if the consolidation is rejected. |
What this PR is
WalletConnect v2 backend in
ironclaw_wallet_external, over the openssl-free forktracecommons/walletconnect-rs(relay_client/relay_rpc,cacaodeliberately disabled).Cascade changes
workspace_graph_is_openssl_freepasses with the WalletConnect fork in the tree.deny.tomlrebuilt asmain's + the fork allowance. The branch's copy had a staleallow-gitentry (astral-sh/ruff.git);maincurrently usessamuelcolvin/ruff.git. Takingmain's file and appending only thetracecommons/walletconnect-rsline avoids reverting main-side policy drift.VerifiedProof::newis now 3-arg(ProviderId::WalletConnect, *approved_tx_hash, proof)— binding the provider and the approved hash into the proof is what makes it unforgeable; the old 1-arg form would have dropped that. Plus theGrantError::InvalidTimestamp/InvalidExpiryfail-closed mapping.Verification
15 + 20 + 27 crate tests pass; boundary test green including
workspace_graph_is_openssl_free; clippy clean.