Add wallet Sign Message flow for CEX ownership verification - #6065
Conversation
97138ed to
7624da7
Compare
7624da7 to
d59ad8d
Compare
📸 Test evidence (followup: user-selectable signing address, Ravencoin UTXO wallet on iOS sim)agent proof 1209296431612665 01 signmsg default address agent proof 1209296431612665 02 signature default address agent proof 1209296431612665 03 address edited signature cleared agent proof 1209296431612665 04 signature after reset Captured by the agent's in-app test run (build-and-test). |
📸 Test evidence: BIP-137 signature formatagent proof 1209296431612665 01 format selector default agent proof 1209296431612665 02 standard signature agent proof 1209296431612665 03 bip137 signature agent proof 1209296431612665 04 non segwit hidden Captured by the agent's in-app test run (build-and-test). |
bb08a13 to
8870e5a
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8870e5a. Configure here.
📸 Test evidence (after Bugbot fixes): BIP-137 on HEAD + Taproot guardagent proof 1209296431612665 01 format selector default agent proof 1209296431612665 03 bip137 signature agent proof 1209296431612665 04 non segwit hidden agent proof 1209296431612665 05 taproot rejected Captured by the agent's in-app test run (build-and-test). |
4b7a06e to
752e288
Compare
Adds a Sign Message option to the wallet list menu for Bitcoin-family (UTXO) wallets. The new scene shows the wallet's receive address, lets the user paste an exchange-provided message, signs it with the wallet key, and returns a copyable signature. This lets users prove control of a self-hosted wallet for CEX/CASP withdrawal checks (EU Travel Rule) without manual ID/selfie verification.
Add signMessageInput and signMessageButton testIDs to the Sign Message scene so UI automation can drive the message field and sign action by a stable selector.
752e288 to
b0e8f18
Compare
There was a problem hiding this comment.
Claude Code Review
Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.
Tip: disable this comment in your organization's Code Review settings.
|
Nice flow, and the phishing-safety copy is a good call. My feedback is mostly architectural — where this logic lives rather than whether it works. But first, a blocking issue that isn't visible from this PR alone. This PR and edge-currency-plugins#454 silently break each other
Running this PR's
Nothing catches this: the two PRs are in different repos, and the unit tests here only exercise the helper against synthetic legacy headers. Whichever lands second breaks the other. The segwit map
So the new map is missing 8 segwit chains. Moving it to The Header rewriting in the GUIBeyond being awkward in a generic surface, this is a reimplementation of what #454 puts in the plugin — and that duplication is exactly what causes the conflict above.
Suggested shapePush the format choice down through wallet.signMessage(message, {
otherParams: { publicAddress: address, signatureFormat: 'bip137' }
})The plugin then owns the whole decision: it maps format + This also resolves #454 cleanly. Emitting BIP-137 unconditionally is a silent behavior change for existing Smaller things
RelatedWhile reviewing #454 I audited the |
The Sign Message scene now shows the wallet's receive address in an editable field instead of a read-only row. Exchanges typically ask a user to prove control of the specific address they already provided (often a previously-used one), so the user can replace the default with that address. The wallet must control whichever address is entered; the plugin signs with the key derived from that address's stored derivation path and rejects any address it does not own, surfaced as a clear error. Editing the address clears any prior signature, and a Use default address link restores the auto-detected receive address.
Let users on SegWit chains (Bitcoin, Litecoin, DigiByte) choose between the Standard (Electrum) and BIP-137 signature formats. BIP-137 re-encodes the signature header byte by address script type (native SegWit 39-42, nested SegWit 35-38) so strict external verifiers recognize the address type. The option is hidden on non-SegWit UTXO chains, and legacy addresses are never remapped.
b0e8f18 to
217c535
Compare
|
Adopted your suggested shape. Both PRs are updated: edge-react-gui 217c535 and edge-currency-plugins#454 4cb6e2bb. The blocking issueConfirmed, and it is worse than only-on-the-default-path: the scene defaults to the
The keymanager spec now pins both directions on bitcoin and litecoin: the default encoding produces the SAME The segwit map and header rewriting
Your DigiByte point checks out against the source: Smaller things
VerifiedRan on the iOS sim against a build with #454 linked in via
Identical r/s, header byte only. Both recovered and verified against that address with One caveat#6065 depends on #454's runtime behavior. Against a published plugin without it, RelatedThe |




















CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
EdgeApp/edge-currency-plugins#454 — the plugin owns the signature header-byte encoding and reads the
signatureFormatoption this PR sends. Against a published plugin without #454 the unknown key is dropped by the cleaner, so BIP-137 silently falls back to a legacy header. The two ship together; the dep bump lands once #454 publishes.Requirements
If you have made any visual changes to the GUI. Make sure you have:
Description
Adds an in-app Sign Message flow so a user can prove ownership of a self-hosted wallet by signing an exchange-provided message. This addresses EU Travel Rule / TFR withdrawal checks where CEXs/CASPs (Kraken, Coinbase, Bitvavo, Bitpanda, OKX, Bringin, NiceHash, etc.) require a signed message from the destination wallet instead of a manual ID/selfie proof.
BTC-family (UTXO), per the task scope.
What it does
SignMessageScenewith an editable signing address, a multiline message input, and a Sign Message button. The signature renders in a copyable card.Implementation notes
signMessageis used rather thansignBytesbecause for BitcoinsignBytesbase64-re-encodes the bytes before signing, which would produce a signature over the wrong data.signMessagesigns the literal UTF-8 message the exchange verifies (same pattern already used bybityProviderandEdgeProviderServer).signatureFormat: 'electrum' | 'bip137'throughotherParamsand does not rewrite the signature. Fix currency symbol #454 derives the script type from the address' own derivation path, so nothing here has to infer an address encoding from the address string.SPECIAL_CURRENCY_INFO.hasSegwit, which was completed for the 6 chains it was missing relative to the plugin's authoritativecoinInfo.segwit. Note this flag also drives PaymentProto's 1.8x required-fee-rate buffer, so those chains now get that buffer too.AddressNotOwnedErrorrather than matching error message text.Files
SignMessageScene.tsx(new scene)routerTypes.tsx,Main.tsx(route + scene registration)WalletListMenuActions.tsx,WalletListMenuModal.tsx(menu entry, UTXO-scoped)WalletAndCurrencyConstants.ts(hasSegwitdocumented and completed)en_US.ts(strings)Asana: https://app.asana.com/0/1215088146871429/1209296431612665
Testing
Driven end-to-end on the iOS simulator (edge-funds, My Bitcoin) against a build with #454 linked in via
updot, signing with the wallet's own bip84 receive addressbc1q7f5wkp5k0847utsec7p8v59cqj7et6wrzz7dv4:INoC+bq5tWf/…azdA1tk=KNoC+bq5tWf/…azdA1tk=Identical r/s, header byte only. Both were recovered and verified against that address with
bitcoinMessage.verify(..., checkSegwitAlways: true). Proof screenshots attached below. Jest suite passes (92 suites / 557 tests / 107 snapshots),tsc --noEmitclean.Earlier rounds, also on the iOS sim: entering an address the wallet does not own surfaces the friendly error, and the editable-address / default-restore behavior was verified by David Coen on a cheese build.
Note
Medium Risk
The flow invokes wallet signing with user-supplied messages and addresses, so correctness and phishing UX matter, but keys are not exported and scope is limited to existing UTXO
signMessageplugins.Overview
Adds a Sign Message entry to the wallet list menu for supported Bitcoin-family (UTXO) plugins, wired through navigation to a new
SignMessageScene.The scene lets users paste an exchange-provided message, choose an editable signing address (defaulting to the wallet’s receive address, with optional Use default address), and on SegWit-capable chains pick Standard (Electrum) vs BIP-137 signature encoding. Signing calls
wallet.signMessagewithpublicAddressandsignatureFormat; the copyable signature is cleared when address, message, or format changes, andAddressNotOwnedErroris mapped to a friendly error.Currency metadata gains a documented
hasSegwitflag and updates several UTXO coins so the format selector only appears where SegWit applies. CHANGELOG and locale strings document the feature.Reviewed by Cursor Bugbot for commit 217c535. Bugbot is set up for automated code reviews on this repo. Configure here.