Skip to content

feat(wallet)!: apply FRC-0102 envelope to sign/verify by default#6967

Open
0xDevNinja wants to merge 1 commit intoChainSafe:mainfrom
0xDevNinja:0xdevninja/issue-6442-frc0102-wallet
Open

feat(wallet)!: apply FRC-0102 envelope to sign/verify by default#6967
0xDevNinja wants to merge 1 commit intoChainSafe:mainfrom
0xDevNinja:0xdevninja/issue-6442-frc0102-wallet

Conversation

@0xDevNinja
Copy link
Copy Markdown

@0xDevNinja 0xDevNinja commented Apr 24, 2026

Summary of changes

Changes introduced in this pull request:

  • Wrap the decoded message with the FRC-0102 Filecoin signing envelope (\x19Filecoin Signed Message:\n<len>) in forest-wallet sign and forest-wallet verify before handing it to the signer/verifier. Aligns Forest with Ledger Filecoin, Filsnap, iso-filecoin and Lotus (feat(cli): add FRC-102 compatible EIP-191 signing via --fevm flag (#13256) filecoin-project/lotus#13388).
  • Add a --raw flag to both sign and verify that restores the previous raw-bytes behaviour. --raw is also the correct choice when signing on-chain Filecoin messages, which must not be wrapped.
  • Add a wrap_frc0102(&[u8]) -> Vec<u8> helper with a const FRC_0102_FILECOIN_PREFIX: &[u8] in wallet_cmd.rs.
  • Expand the unit test suite with 7 cases: empty/short/long/binary/length-boundary/embedded-newline format checks, plus a secp256k1 round-trip that asserts verify(wrapped) succeeds and verify(raw) fails.
  • CHANGELOG.md: Breaking entry that also calls out (a) pre-FRC-0102 nodes cannot verify the new default without --raw, and (b) on-chain messages must still use --raw.

The envelope is applied client-side only. Forest's and Lotus's WalletSign / WalletVerify RPCs treat their input as opaque bytes and do not wrap, so a single wrap here is correct against today's daemons. If that ever changes server-side, users will want --raw to avoid a double envelope.

Reference issue to close (if applicable)

Closes #6442

Other information and links

Change checklist

  • I have performed a self-review of my own code,
  • I have made corresponding changes to the documentation. All new code adheres to the team's documentation standards,
  • I have added tests that prove my fix is effective or that my feature works (if possible),
  • I have made sure the CHANGELOG is up-to-date. All user-facing changes should be reflected in this document.

Outside contributions

  • I have read and agree to the CONTRIBUTING document.
  • I have read and agree to the AI Policy document. I understand that failure to comply with the guidelines will lead to rejection of the pull request.

AI Usage Disclosure

This PR was prepared with assistance from Claude Code (Anthropic). Extent:

  • Issue triage, reading of the Lotus reference implementation (feat(cli): add FRC-102 compatible EIP-191 signing via --fevm flag (#13256) filecoin-project/lotus#13388), implementation, self-review (two passes with an independent reviewer agent), test design, and PR drafting were AI-assisted.
  • All changes were compiled and tested locally by me before pushing — cargo fmt --all -- --check, cargo clippy --profile quick --all-targets --no-deps -- -D warnings (with FOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT=1), and cargo test --lib -- wrap_frc0102 frc0102_roundtrip (7/7 pass) are all green.
  • End-to-end CLI verification against a running daemon has not been performed locally; that path goes through an unchanged wallet_sign / wallet_verify that receives the already-wrapped bytes.
  • I have reviewed every diff line and take full responsibility for correctness.

Summary by CodeRabbit

Release Notes

  • Breaking Changes

    • forest-wallet sign and forest-wallet verify now wrap signed messages using the FRC-0102 envelope by default. Signatures are incompatible with pre-FRC-0102 releases unless the --raw flag is used.
  • New Features

    • Added --raw flag to sign and verify commands to disable message envelope wrapping.

`forest-wallet sign` and `forest-wallet verify` now wrap the decoded
message with the FRC-0102 Filecoin signing envelope
(`\x19Filecoin Signed Message:\n<len>`) before handing it to the signer
/ verifier. This aligns Forest with the Ledger Filecoin app, Filsnap,
iso-filecoin and Lotus (filecoin-project/lotus#13388), so signatures
produced by any of these wallets over the same bytes now round-trip.

A new `--raw` flag restores the prior raw-bytes behaviour on both
commands. `--raw` is also the correct choice when signing on-chain
Filecoin messages, which must not be wrapped.

The envelope is applied client-side only; the Forest and Lotus
`WalletSign` / `WalletVerify` RPCs treat their input as opaque bytes,
so a single wrap here is correct against today's daemons.

BREAKING CHANGE: signatures produced by the default invocation are not
verifiable by pre-FRC-0102 Forest or Lotus releases without `--raw` on
the verifier side.

Refs ChainSafe#6442
@0xDevNinja 0xDevNinja requested a review from a team as a code owner April 24, 2026 10:39
@0xDevNinja 0xDevNinja requested review from akaladarshi and hanabi1224 and removed request for a team April 24, 2026 10:39
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Walkthrough

The changes add FRC-0102 message envelope wrapping support to forest-wallet sign and verify commands with a --raw flag for backward compatibility. Implementation includes envelope wrapping logic, internal constants, and test coverage to verify both wrapped and raw message handling.

Changes

Cohort / File(s) Summary
FRC-0102 Envelope Implementation
src/wallet/subcommands/wallet_cmd.rs
Implements --raw flag for sign/verify commands; adds wrap_frc0102 function with FRC-0102 envelope prefix constant; conditionally wraps decoded message bytes; extends tests with envelope formatting assertions and cryptographic roundtrip verification.
Breaking Change Notice
CHANGELOG.md
Adds breaking change notification documenting that forest-wallet sign/verify now wrap signed messages using FRC-0102 envelope by default, requiring --raw flag for pre-FRC-0102 compatibility.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • hanabi1224
  • akaladarshi
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing FRC-0102 envelope wrapping as the default behavior for wallet sign/verify operations, with a breaking change indicator (!).
Linked Issues check ✅ Passed The pull request successfully addresses all three completion criteria from issue #6442: Implementation (FRC-0102 wrapping with --raw flag), Tests (expanded with seven test cases), and Docs (CHANGELOG.md updated).
Out of Scope Changes check ✅ Passed All changes are directly scoped to FRC-0102 envelope implementation for wallet sign/verify. No unrelated modifications or scope creep detected in CHANGELOG.md or wallet_cmd.rs.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/wallet/subcommands/wallet_cmd.rs`:
- Around line 475-491: The Sign branch is base64-encoding the wrapped message
(BASE64_STANDARD.encode) before sending to backend.wallet_sign, which causes
remote wallets to sign the base64 text bytes instead of the raw/wrapped bytes
used by verify; change the flow in the Self::Sign arm so that
backend.wallet_sign receives the raw byte payload (i.e., the result of
wrap_frc0102(&message) or message when raw is true) rather than the base64
text—move or remove the BASE64_STANDARD.encode call and only base64-encode
when/if the backend API truly expects a base64 string, ensuring
functions/variables mentioned (Self::Sign, wrap_frc0102, BASE64_STANDARD.encode,
backend.wallet_sign) are updated consistently so sign and verify operate on
identical byte sequences.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8b670a84-4ce2-4479-bc7e-ef7c7caf0fd7

📥 Commits

Reviewing files that changed from the base of the PR and between 65c39a0 and 24b9f00.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • src/wallet/subcommands/wallet_cmd.rs

Comment on lines +475 to 491
Self::Sign {
address,
message,
raw,
} => {
let StrictAddress(address) = StrictAddress::from_str(&address)
.with_context(|| format!("Invalid address: {address}"))?;

let message = hex::decode(message).context("Message has to be a hex string")?;
// FRC-0102 envelope is applied client-side. The local keystore
// and the remote `WalletSign` RPC both treat their input as
// opaque bytes — neither wraps — so a single wrap here is
// correct against today's Forest/Lotus daemons. If a daemon
// ever starts wrapping server-side, this will need revisiting
// (prefer `--raw` in that case to avoid a double envelope).
let message = if raw { message } else { wrap_frc0102(&message) };
let message = BASE64_STANDARD.encode(message);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remote wallet sign is signing a different payload shape than wallet verify.

Line 491 base64-encodes the wrapped message before calling backend.wallet_sign(...). In remote mode, WalletSign signs received bytes as-is, so it signs base64 text bytes, while verify uses wrapped/raw bytes directly (Line 513). This can break sign/verify parity for remote wallets.

Proposed fix
-    async fn wallet_sign(&self, address: Address, message: String) -> anyhow::Result<Signature> {
+    async fn wallet_sign(&self, address: Address, message: Vec<u8>) -> anyhow::Result<Signature> {
         if let Some(keystore) = &self.local {
             let key = crate::key_management::try_find_key(&address, keystore)?;
 
             Ok(crate::key_management::sign(
                 *key.key_info.key_type(),
                 key.key_info.private_key(),
-                &BASE64_STANDARD.decode(message)?,
+                &message,
             )?)
         } else {
-            Ok(WalletSign::call(&self.remote, (address, message.into_bytes())).await?)
+            Ok(WalletSign::call(&self.remote, (address, message)).await?)
         }
     }
-                let message = BASE64_STANDARD.encode(message);
                 let signature = backend.wallet_sign(address, message).await?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/wallet/subcommands/wallet_cmd.rs` around lines 475 - 491, The Sign branch
is base64-encoding the wrapped message (BASE64_STANDARD.encode) before sending
to backend.wallet_sign, which causes remote wallets to sign the base64 text
bytes instead of the raw/wrapped bytes used by verify; change the flow in the
Self::Sign arm so that backend.wallet_sign receives the raw byte payload (i.e.,
the result of wrap_frc0102(&message) or message when raw is true) rather than
the base64 text—move or remove the BASE64_STANDARD.encode call and only
base64-encode when/if the backend API truly expects a base64 string, ensuring
functions/variables mentioned (Self::Sign, wrap_frc0102, BASE64_STANDARD.encode,
backend.wallet_sign) are updated consistently so sign and verify operate on
identical byte sequences.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement FRC-102 in forest-wallet for consistency across Filecoin wallets

1 participant