Skip to content

fix(backend)!: derive OnRamper signed wallet addresses from caller principal#13167

Closed
AntonioVentilii wants to merge 15 commits into
mainfrom
feat/onramper-derive-server-side
Closed

fix(backend)!: derive OnRamper signed wallet addresses from caller principal#13167
AntonioVentilii wants to merge 15 commits into
mainfrom
feat/onramper-derive-server-side

Conversation

@AntonioVentilii

Copy link
Copy Markdown
Collaborator

Motivation

sign_onramper_widget_url was an open signing oracle: it HMAC-signed the wallets / network_wallets / wallet_address_tags parameters exactly as supplied by any authenticated caller, without checking the addresses belonged to msg_caller(). An attacker could get the backend to sign their own address and phish a victim into funding it via a valid, OISY-branded OnRamper URL — the HMAC is OnRamper's sole URL-integrity check, so a valid signature reads as OISY authorization.

This closes the oracle by deriving the caller's own receiving addresses server-side and signing only those. A caller can now only ever obtain a signature bound to their own wallet.

Spec: docs/ai/spec-driven-development/specs/2026-06-22-fix-onramper-signing-oracle.md (#13164). Stacked on #13166 (the address encoders).

Changes

  • Endpoint is now argument-less and async. sign_onramper_widget_url takes no request; it derives the caller's BTC/ETH/ICP/SOL receiving addresses from msg_caller() and signs them as networkWallets. The caller_is_not_anonymous guard and per-caller rate limiter are unchanged.
  • Server-side derivation via management-canister public-key reads (signer/service.rs): ETH (secp256k1 schema 1 + keccak/EIP-55) and SOL (Ed25519 schnorr + base58) reuse the same canister_id = cfs_canister_id pattern as the existing BTC derivation; ICP is local principal2account. These are pubkey reads — no chain-fusion-signer call, no allow_signing/allowance flow, no signing fee. A chain is omitted if its derivation fails (ICP always succeeds), and AddressDerivationFailed is returned only if nothing derives.
  • Breaking Candid change: removed wallets / network_wallets / wallet_address_tags from the request (type removed); added the AddressDerivationFailed error variant; backend.did + src/declarations/backend/ regenerated via npm run generate.
  • Frontend no longer derives or sends wallet addresses: buildOnramperLink and the canister/api layer call the endpoint with the identity only; OnramperWidget.svelte drops the address-store wiring; the now-dead mapOnramperNetworkWallets util and Onramper*Wallet* types are removed.
  • docs/ai/PRODUCT.md gains a Buy (OnRamper) section with the explicit negative guarantee.

Tests

  • Backend unit (encoders, in feat(backend): add ETH/SOL public-key-to-address encoders #13166) + signer::service cover the pubkey→address encoding against known vectors.
  • Integration (tests/it/onramper.rs): anonymous rejection, SecretNotConfigured, rate limit, deterministic signing of the caller's own derived addresses, and a positive anti-oracle proof — two distinct callers get different signed content (a caller can only sign their own addresses).
  • Frontend: onramper.utils.spec.ts rewritten for the argument-less call; affected buy specs pass.
  • Gates: cargo fmt / lint.rust.sh / lint.did.sh, npm run check (clean except a pre-existing unrelated sol-instructions-system.utils.ts error not in this diff), scoped ESLint, and the affected vitest specs all pass. The heavy pocketIC integration suite runs in CI.

Parity note: byte-for-byte parity between the backend-derived addresses and the wallet's own addresses must be confirmed on staging before the buy flow is un-gated — there is no signer wasm in the test harness to assert it automatically (by design — production never calls the signer).

BREAKING CHANGE: sign_onramper_widget_url no longer accepts wallets / network_wallets / wallet_address_tags and takes no arguments; it derives and signs the caller's own addresses. Clients must stop sending wallet parameters (frontend binding regenerated in this PR).

Base automatically changed from feat/onramper-address-encoders to main June 23, 2026 09:58
@AntonioVentilii AntonioVentilii marked this pull request as ready for review June 23, 2026 11:56
@AntonioVentilii AntonioVentilii requested a review from a team as a code owner June 23, 2026 11:56
Copilot AI review requested due to automatic review settings June 23, 2026 11:56
@zeropath-ai

zeropath-ai Bot commented Jun 23, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to a9eedad.

Security Overview
Detected Code Changes
Change Type Relevant files
Documentation changes ► docs/ai/PRODUCT.md
    Add documentation for OnRamper widget and signing process
Refactor ► src/backend/backend.did
    Simplify external_refs type
    Remove unused OnramperSignedEntry type
    Update sign_onramper_widget_url signature and arguments
    Update SignOnramperWidgetUrlError variant
Enhancement ► src/backend/backend.did
    Update sign_onramper_widget_url description
Refactor ► src/backend/src/api/onramper.rs
    Remove unused imports
    Update sign_onramper_widget_url signature and arguments
Enhancement ► src/backend/src/api/onramper.rs
    Update sign_onramper_widget_url function description
    Derive caller's addresses server-side
Refactor ► src/backend/src/lib.rs
    Remove unused import SignOnramperWidgetUrlRequest
Refactor ► src/backend/src/onramper/service.rs
    Update sign_onramper_widget_url signature and arguments
    Remove unused OnramperSignedEntry type
    Implement address derivation for multiple networks
Enhancement ► src/backend/src/onramper/service.rs
    Update function documentation
Refactor ► src/backend/src/signer/mod.rs
    Add new exports for signer functions
Refactor ► src/backend/src/signer/service.rs
    Add new functions for deriving ETH and SOL addresses
    Add constants for derivation schemas
    Update cfs_ecdsa_pubkey_of to accept schema
Refactor ► src/backend/tests/it/onramper.rs
    Update test setup and assertions for new sign_onramper_widget_url signature
    Remove unused sample_request function
    Update test descriptions
Refactor ► src/declarations/backend/backend.did
    Remove unused OnramperSignedEntry type
    Update sign_onramper_widget_url signature and arguments
    Add AddressDerivationFailed variant to SignOnramperWidgetUrlError
Refactor ► src/declarations/backend/backend.did.d.ts
    Remove OnramperSignedEntry interface
    Update ActiveUserTransaction interface
    Update SignOnramperWidgetUrlError type
    Update sign_onramper_widget_url method signature
    Remove SignOnramperWidgetUrlRequest interface
Refactor ► src/declarations/backend/backend.factory.certified.did.js
    Remove OnramperSignedEntry definition
    Update ActiveUserTransaction definition
    Update SignOnramperWidgetUrlError definition
    Update sign_onramper_widget_url function definition
    Remove SignOnramperWidgetUrlRequest definition
Refactor ► src/declarations/backend/backend.factory.did.js
    Remove OnramperSignedEntry definition
    Update ActiveUserTransaction definition
    Update SignOnramperWidgetUrlError definition
    Update sign_onramper_widget_url function definition
    Remove SignOnramperWidgetUrlRequest definition
Refactor ► src/frontend/src/lib/api/backend.api.ts
    Remove SignOnramperWidgetUrlRequest type and related imports
    Update signOnramperWidgetUrl function signature

Copilot AI left a comment

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.

Pull request overview

Closes an OnRamper URL-signing oracle by making the backend derive the authenticated caller’s receiving addresses (BTC/ETH/ICP/SOL) server-side and signing only those, removing all caller-supplied wallet-address inputs and updating the frontend + generated bindings accordingly.

Changes:

  • Backend sign_onramper_widget_url is now argument-less + async, derives caller addresses via management-canister public-key reads, and adds AddressDerivationFailed.
  • Frontend no longer derives/sends wallet addresses; it just calls the signing endpoint with the user identity and appends signed_query verbatim.
  • Regenerates Candid + TS declaration artifacts and updates product docs to state the negative guarantee (cannot sign attacker-chosen addresses).

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/shared/src/types/onramper.rs Removes request type; adds AddressDerivationFailed and updates error docs.
src/frontend/src/tests/lib/utils/onramper.utils.spec.ts Updates unit tests for the argument-less signing call and backend-supplied signed query.
src/frontend/src/lib/utils/onramper.utils.ts Removes wallet args; calls backend signing endpoint with identity only.
src/frontend/src/lib/types/onramper.ts Removes now-dead OnRamper wallet address/wallet types.
src/frontend/src/lib/types/api.ts Removes OnRamper signing request parameter types.
src/frontend/src/lib/components/onramper/OnramperWidget.svelte Drops address-store wiring; relies on backend-derived addresses.
src/frontend/src/lib/canisters/backend.errors.ts Maps new AddressDerivationFailed error variant.
src/frontend/src/lib/canisters/backend.canister.ts Updates canister wrapper to call sign_onramper_widget_url() with no args.
src/frontend/src/lib/api/backend.api.ts Updates API wrapper to call the no-arg canister method.
src/declarations/backend/backend.factory.did.js Regenerated backend IDL factory (no-arg endpoint; type removals).
src/declarations/backend/backend.factory.certified.did.js Regenerated certified IDL factory (no-arg endpoint; type removals).
src/declarations/backend/backend.did.d.ts Regenerated TS declarations for breaking Candid changes + new error variant.
src/declarations/backend/backend.did Regenerated Candid for breaking endpoint signature and type cleanup.
src/backend/tests/it/onramper.rs Updates integration tests for caller-bound, server-derived signing.
src/backend/src/signer/service.rs Adds ETH/SOL principal→address derivation via management-canister pubkey reads (schema-aware).
src/backend/src/signer/mod.rs Re-exports new signer helpers used by OnRamper service wiring.
src/backend/src/onramper/service.rs Derives caller networkWallets server-side and signs only those.
src/backend/src/lib.rs Removes obsolete request-type import.
src/backend/src/api/onramper.rs Makes sign_onramper_widget_url async + no-arg, binds to msg_caller().
src/backend/backend.did Regenerated backend Candid mirror (breaking endpoint signature and type cleanup).
docs/ai/PRODUCT.md Documents Buy (OnRamper) behavior + explicit anti-oracle guarantee.

Comment thread src/backend/tests/it/onramper.rs
Comment thread src/backend/src/onramper/service.rs
@AntonioVentilii

Copy link
Copy Markdown
Collaborator Author

Superseded by a cleaner decomposition of the same fix, switched to the validate-ownership design (the FE keeps sending its derived addresses; the backend re-derives from the caller principal, verifies they match, and signs its own derived values — rejecting on mismatch or underivable network). This is non-breaking (the request shape is unchanged), so it splits cleanly:

Encoders already merged in #13166. Closing this in favor of that stack.

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.

3 participants