feat(wallet)!: apply FRC-0102 envelope to sign/verify by default#6967
feat(wallet)!: apply FRC-0102 envelope to sign/verify by default#69670xDevNinja wants to merge 1 commit intoChainSafe:mainfrom
Conversation
`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
WalkthroughThe changes add FRC-0102 message envelope wrapping support to Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify 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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
CHANGELOG.mdsrc/wallet/subcommands/wallet_cmd.rs
| 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); |
There was a problem hiding this comment.
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.
Summary of changes
Changes introduced in this pull request:
\x19Filecoin Signed Message:\n<len>) inforest-wallet signandforest-wallet verifybefore 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).--rawflag to bothsignandverifythat restores the previous raw-bytes behaviour.--rawis also the correct choice when signing on-chain Filecoin messages, which must not be wrapped.wrap_frc0102(&[u8]) -> Vec<u8>helper with aconst FRC_0102_FILECOIN_PREFIX: &[u8]inwallet_cmd.rs.secp256k1round-trip that assertsverify(wrapped)succeeds andverify(raw)fails.CHANGELOG.md:Breakingentry 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/WalletVerifyRPCs 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--rawto avoid a double envelope.Reference issue to close (if applicable)
Closes #6442
Other information and links
sign/verifywrap by default +--rawopt-out).Change checklist
Outside contributions
AI Usage Disclosure
This PR was prepared with assistance from Claude Code (Anthropic). Extent:
cargo fmt --all -- --check,cargo clippy --profile quick --all-targets --no-deps -- -D warnings(withFOREST_F3_SIDECAR_FFI_BUILD_OPT_OUT=1), andcargo test --lib -- wrap_frc0102 frc0102_roundtrip(7/7 pass) are all green.wallet_sign/wallet_verifythat receives the already-wrapped bytes.Summary by CodeRabbit
Release Notes
Breaking Changes
forest-wallet signandforest-wallet verifynow wrap signed messages using the FRC-0102 envelope by default. Signatures are incompatible with pre-FRC-0102 releases unless the--rawflag is used.New Features
--rawflag to sign and verify commands to disable message envelope wrapping.