Add CLEAR_SIGN transact signing (connector toggle, dual-tx, output decoder) - #29
Merged
Conversation
RailgunSigner.signClearSignTransact drives the full clear-sign session end to end — CS_INIT → NULLIFIER×n → BP_FIELDS → OUT_*×m → FINALIZE — validating the shape up front, streaming the builders in order, length-checking and collecting each output's opaque device response, and parsing the FINALIZE signature + echoed message hash. Turns the pure builders into a usable "sign a transact" call; the raw output responses are returned for the caller to splice into the on-chain transact calldata. Adds buildClearSignOutput (tagged-output dispatcher), the transact request/ result types, parseClearSignOutputResponse, and the device-measured response lengths (OUT tuple 223B, transfer 239B, unshield 32B). Locked with a test that reproduces the device-verified 1×1 unshield golden sequence plus a 2×3 case.
RailgunSigner.signClearSignMultiTransact clear-signs a bundle of sub-transactions
(the txToken != feeToken case = value transfer in token A + broadcaster fee in
token B) in one session: multi-tx CS_INIT, then each sub-tx's NULLIFIER×n ->
BP_FIELDS -> OUT_*×m in order, then one FINALIZE returning a signature per tx
(all under the same key).
Adds buildClearSignInitMultiTx (account ‖ nTx ‖ walletSource(15) ‖ per-tx
{merkleRoot,nIn,nOut}) and parseClearSignFinalizeMulti (256B -> two R8x‖R8y‖S‖
msgHash quads, no 0x60 prefix). Locked against the device-verified 88B multi-init
and 256B dual-FINALIZE vectors, plus a full two-tx session ordering test.
Add signClearSignTransact and signClearSignMultiTransact to LedgerController, delegating to a RailgunSigner on the serialized device queue behind the app- readiness gate (same pattern as the 7702 methods). This keeps the engine-facing HardwareConnector / EngineLedgerConnector structural locks untouched — clear-sign is a RAILGUN-app signing flow, so it lives on the controller. Also correct the buildGetViewingKey doc: device testing shows INS 0x13 ignores P1 (returns the key for both 0x00 and 0x01), so the prior "P1=0x01 → SW_WRONG_P1P2" claim was wrong. We still send 0x00.
- Pre-build the entire session before sending any APDU, so field-width/range errors (nullifier length, bound-params, tokenHash/0zk/memo, minGasPrice) throw before a session is opened on the stateful device — not mid-session. - Cap multi-tx CS_INIT at nTx <= 2 (device CS_MAX_TXS), so >2 txs fail host-side before any APDU rather than at the device. - Tests: pre-flight rejection sends no APDU (single + dual), OUT_* wrong-length response is rejected, and >2 transactions are rejected.
Extend the connector sign fn with an optional plaintext transact (4th arg):
when provided, the device clear-signs it (reviewing recipients/tokens/amounts)
and the result carries the device outputs; otherwise it blind-signs as before.
The return type is Signature extended with an optional { msgHash, outputs }, so
existing blind callers are unaffected, and the RAILGUN engine can opt into
clear-signing by passing the transact and reading the outputs for calldata.
Threaded through both the raw HardwareConnector and the engine adapter (via the
controller's signClearSignTransact). No frozen _Reference lock change: both the
interface and the locks reference the shared sign-fn alias, so it updates in
lockstep.
…layout) decodeClearSignOutput turns a raw OUT_* device response into structured fields using the byte layout the firmware author's reference (clear-sign-apdus.js) documents: tuple(208) = random(16) ‖ Blind1(32) ‖ Blind2(32) ‖ IV(16) ‖ tag(16) ‖ ciphertext(96), + senderRandom(15) [+ annotationIv(16) for transfer]; unshield = commitment(32). These are the fields the RAILGUN engine assembles into the on-chain transact calldata (that assembly is protocol/ABI-specific — engine side).
The clear-sign toggle on sign() covers single-tx (one signature); the txToken != feeToken case returns one signature per tx, which doesn't fit that return. Add signClearMultiTransact to the connector interface (raw + engine adapter), delegating to the signer/controller on the serialized queue. Deliberate frozen-lock update: the method is added to CommonConnectorBase and to all three _Reference locks (HardwareConnector, EngineLedgerConnector, Legacy).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Grows the CLEAR_SIGN transact surface from the 0.3.0 builders into full signing.
Requires firmware 1.6.1 (clear-sign-v1) on the device. Experimental; verified
against device-measured response layouts — a live on-hardware round-trip is still
pending.
What's in it
(
CS_INIT → NULLIFIER×n → BP_FIELDS → OUT_* → FINALIZE), collects each outputresponse, and parses the 129-byte finalize (signature + message hash). Exposed
through the signer and the controller.
signflow.signtakes an optionalplaintext transact; when supplied, the device reviews the recipients, tokens, and
amounts and generates the output ciphertexts, and the result carries the device
message hash and per-output responses alongside the signature. Without it,
signblind-signs exactly as before — a pure addition; the structural-identity locks are
unchanged.
signClearMultiTransact— onesignature per sub-transaction, capped at the device maximum of two.
decodeClearSignOutputdecodes a rawOUT_*response into its structuredfields (random, sender/recipient blinding keys, IV, tag, ciphertext, senderRandom,
and the transfer annotation IV).
0xb007(RAILGUN_CLEAR_SIGN_STATE) maps to a clear, best-effort error.Testing
547 unit tests pass (MockTransport with device-measured fixtures); no type errors;
frozen structural-identity locks intact. CHANGELOG updated for 0.4.0; the release is
cut separately from
mainviayarn release:minorafter merge.