Skip to content

Address the signing request to the account that must sign it - #141

Open
n13 wants to merge 1 commit into
mainfrom
n13/signing-request-envelope
Open

Address the signing request to the account that must sign it#141
n13 wants to merge 1 commit into
mainfrom
n13/signing-request-envelope

Conversation

@n13

@n13 n13 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What

The cold-signing QR carried the SCALE payload and nothing else. A wallet holding several accounts could not tell which key the request wanted, and one holding none of them could not tell that it held the wrong key — it would sign with whatever it had, producing a signature the chain rejects, or worse, one it accepts from an account the user did not mean to spend from.

The payload now travels in the same JSON envelope the mobile app, the cold wallet app and the Keystone firmware read:

{"v": 1, "signer": "<ss58>", "payload": "0x<scale>"}

Changes

  • src/qr/sign_request.rs (new) — the envelope, encoded and decoded as strictly as the wallets do: exactly these three keys, this version, 0x hex payload, bounded by the same 8 KiB cap as the SDK. The doc comment names SigningRequest in quantus_sdk/lib/src/models/signing_request.dart so the two stay in step.
  • src/cli/cold_signing.rs — the request is wrapped before UR encoding. cold-sign-sim, which plays the cold-wallet side, decodes it and refuses a request addressed to an account other than the wallet it was asked to sign with, which is what the real devices do. The offline end-to-end test now round-trips through the envelope, so it asserts the format actually on the wire.
  • src/cli/signing_qr.rs (new) — quantus signing-qr --from <address>: the first half of the signing flow on its own. Prints the request for an address and stops — nothing submitted, no wallet imported first. It reuses the same context capture, payload builder and animated display as the real flow, so what it prints is what a signer is really asked to read, which is what makes it useful for checking how a device displays a call.

Compatibility

This is a breaking wire change against wallet builds that predate the envelope: they read the bare payload and will refuse this. The Quantus mobile app, cold wallet app and Keystone firmware that speak the envelope are the counterpart.

Testing

  • 288 lib tests pass; ./clippy.sh (fmt + taplo + clippy -D warnings) is clean.
  • Six new tests on the envelope, including that a bare payload — what this CLI used to send — is refused.
  • Verified against live Planck (wss://a1-planck.quantus.cat): the request signing-qr produced parses in the cold wallet's own QuantusPayloadParser as Planck / SEND / 1.5 QUAN to the expected destination, mortal era, correct nonce.

The QR carried the payload and nothing else, so a wallet holding several
accounts could not tell which key the request wanted, and one holding none of
them could not tell that it held the wrong key - it would sign with whatever it
had and produce a signature the chain rejects, or worse, one it accepts from an
account the user did not mean to spend from.

The payload now travels in the same JSON envelope the mobile app, the cold
wallet app and the Keystone firmware read: {v, signer, payload}, refused
outright if it is not exactly that. cold-sign-sim reads it too, and refuses a
request addressed to an account other than the wallet it was asked to sign
with, which is what the devices do.

Adds `quantus signing-qr`: the first half of the cold signing flow on its own -
print the request for an address and stop, submitting nothing and needing no
wallet imported first. It reuses the same context capture, payload builder and
animated display as the real flow, so what it prints is what a signer is really
asked to read, which is what makes it useful for checking how a device
displays a call.
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.

2 participants