From 0ed3a6b36f70fb3e2d46dc60dde8524cb7ae84dc Mon Sep 17 00:00:00 2001 From: zy0n Date: Fri, 24 Jul 2026 19:08:00 +0000 Subject: [PATCH] docs: cover the 0.3.0 surface (viewing pubkey / RAILGUN address / CLEAR_SIGN builders / customizable chain-scoped 7702 path) --- README.md | 13 ++++++++----- docs/api/signers.md | 26 ++++++++++++++++++++------ docs/examples.md | 20 +++++++++++--------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 21ce785..33005a9 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ State-machine-driven hardware wallet connector for RAILGUN — Ledger-first, bro ## What It Does - Connects to Ledger devices via WebHID (browser) or Node HID (scripts/tests) -- Signs RAILGUN transactions (BabyJubjub EdDSA via custom Ledger app) +- Signs RAILGUN transactions (BabyJubjub EdDSA via custom Ledger app), with CLEAR_SIGN transact builders (experimental) +- Reads the viewing public key and the canonical `0zk1…` address for on-device display/verify - Signs ETH transactions, messages, EIP-712 typed data, fixed shield ownership markers, and EIP-7702 authorizations -- Preloads and signs with the RAILGUN app 7702 EOA path `m/7702'/1984'/account'/chainId/ephemeralIndex` +- Preloads and signs with a caller-customizable, chain-scoped RAILGUN 7702 EOA path `m/7702'/1984'/account'/chainId'/ephemeralIndex'` (a distinct EOA per chain) - Installs sideloaded apps via SCP02/SCP03 secure channel - Exposes a `HardwareConnector` interface compatible with the RAILGUN engine - Drives its behavior through a pure finite state machine — no framework required @@ -126,13 +127,13 @@ specific app builds. ## RAILGUN 7702 Hardware Signing (under development) -The SDK exposes the RAILGUN-app hardware path for EIP-7702 signer preload, authorization signing, and RelayAdapt7702 EIP-712 digest signing. The firmware derives Ethereum EOAs from: +The SDK exposes the RAILGUN-app hardware path for EIP-7702 signer preload, authorization signing, and RelayAdapt7702 EIP-712 digest signing. The firmware derives Ethereum EOAs from a caller-chosen path: ```text -m/7702'/1984'/railgunAccountIndex'/chainId/ephemeralIndex +m/7702'/1984'/account'/chainId'/ephemeralIndex' ``` -The host sends the trailing three path words to the RAILGUN app as `W0 || W1 || W2`. The app hardens `W0` internally, derives the EOA, and signs against that same suffix for all 7702 operations. +The host sends the trailing three path words to the RAILGUN app as `W0(account) || W1(chainId) || W2(ephemeralIndex)`; the firmware hardens all three, derives the EOA, and signs against that same suffix for all 7702 operations. All three words are caller-customizable, and because `chainId` is one of them each chain derives a **distinct** EOA (chain-scoped — a wallet runs on many chains at once without reusing a 7702 address). Each word is a hardened index and must fit in 31 bits, so chains with `chainId >= 2**31` are rejected. | Operation | SDK API | RAILGUN APDU | |-----------|---------|--------------| @@ -196,6 +197,8 @@ All public types and functions are re-exported from `src/index.ts`. Key exports: | `createLedgerController` | Headless, framework-agnostic controller | | `transition`, `createInitialContext` | Pure FSM for custom integration | | `RailgunSigner`, `EthSigner` | Direct signer access | +| `RailgunSigner.getViewingPublicKey` / `getRailgunAddress` | On-device display/verify of the viewing pubkey (INS 0x10) and `0zk1…` address (INS 0x14) | +| `buildClearSignInit` … `buildClearSignFinalize`, `validateClearSignShape`, `parseClearSignFinalize` | CLEAR_SIGN transact protocol builders (INS 0x11, experimental) | | `RAILGUN_SHIELD_MESSAGE` | Fixed replayable ETH-app ownership marker used by the shield ownership flow | | `createEngineLedgerConnector` | Session-aware engine adapter with shield and ETH tx signing hooks | | `RailgunSigner.get7702Signer` | Direct engine-compatible 7702 signer from a RAILGUN app signer | diff --git a/docs/api/signers.md b/docs/api/signers.md index d59e802..cc8c04c 100644 --- a/docs/api/signers.md +++ b/docs/api/signers.md @@ -40,19 +40,33 @@ const signature = await signer.sign(poseidonHash); // { R8: [x, y], S } — conf > viewing secret required by the current engine format. Never log it or route it through > `onProgress`/telemetry. +### Display / verify accessors + +Two commands read public identifiers behind an on-device confirmation (the device shows the +value so the user can compare it against an out-of-band source). Neither exposes a secret. + +| Method | Returns | Notes | +|--------|---------|-------| +| `getViewingPublicKey()` | `Uint8Array` (32B) | Compressed Ed25519 viewing **public** key (`INS 0x10`). Display/verify only — does **not** export the viewing secret; wallet loading still uses `getWalletArtifacts()`. | +| `getRailgunAddress()` | `string` | The canonical 127-char `0zk1…` address (`INS 0x14`) — a device-confirmed cross-check of the host-derived address. | + ### Ethereum / EIP-7702 methods (under development) -The RAILGUN app derives Ethereum EOAs from the firmware path -`m/7702'/1984'/account'/chainId/ephemeralIndex`. These methods are gated on the app's +The RAILGUN app derives Ethereum EOAs from a **caller-chosen** path +`m/7702'/1984'/account'/chainId'/ephemeralIndex'`. All three trailing words — +`account` (W0), `chainId` (W1), `ephemeralIndex` (W2) — are settable, and because `chainId` +is one of them, **each chain derives a distinct EOA** (chain-scoped: a wallet can run on many +chains at once without reusing a 7702 address). Each word is a hardened index and must fit in +31 bits, so chains with `chainId >= 2**31` are rejected. These methods are gated on the app's advertised capabilities and throw `APP_VERSION_MISMATCH` if unsupported. | Method | Purpose | |--------|---------| | `getEthereumAddress(display?)` | Derive the RAILGUN-app EOA address + public key. | -| `prepareEthereumSigner(request)` | Preload/derive the EOA for a `{ railgunAccountIndex, chainId, ephemeralIndex }` session (binds later signatures to the same path). | -| `signEip7702Authorization(request)` | Sign an EIP-7702 authorization (`INS 0x08`). | -| `signEthereumTxHash(hash, options?)` | Sign a 32-byte Ethereum digest (`INS 0x09`). `options.display` defaults to `true` (clear signing). | -| `get7702Signer(request, options?)` | Return an engine-compatible RelayAdapt7702 hooked signer. | +| `prepareEthereumSigner(request)` | Preload/derive the EOA for a `{ railgunAccountIndex, chainId, ephemeralIndex }` session (binds later signatures to the same chain-scoped path). | +| `signEip7702Authorization(request)` | Sign an EIP-7702 authorization (`INS 0x08`). Rejects an explicit `path` whose chainId word (W1) disagrees with the authorization `chainId`. | +| `signEthereumTxHash(hash, options?)` | Sign a 32-byte Ethereum digest (`INS 0x09`). `options.display` defaults to `true` (clear signing); firmware requires `P1 = 0x01`, so standalone `display: false` is rejected. | +| `get7702Signer(request, options?)` | Return an engine-compatible RelayAdapt7702 hooked signer. `request` = `{ chainId, ephemeralIndex, railgunAccountIndex? }` — all three path words are customizable (`railgunAccountIndex` defaults to the signer's account). | **Guidance:** prefer `display: true` (clear signing) so the device shows context; treat blind/hash-only signing (`display: false`) as an explicit, audited opt-in. For RAILGUN diff --git a/docs/examples.md b/docs/examples.md index 9b82855..eec25d4 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -84,21 +84,23 @@ if (capabilities.eip7702Authorization && capabilities.ethereumTxHash) { const outerTxSignature = await signer.signEthereumTxHash(txHashBytes, { session, - display: false, + display: true, }); } ``` -`prepareEthereumSigner()` binds the app-native Ethereum signing context to the -firmware-supported 7702 EOA path -`m/7702'/1984'/railgunAccountIndex'/chainId/ephemeralIndex`. The RAILGUN app's -`INS 0x07` preload APDU sends those trailing three words to derive the same EOA -that `INS 0x08` and `INS 0x09` later sign with. +`prepareEthereumSigner()` binds the app-native Ethereum signing context to a +**caller-chosen** 7702 EOA path +`m/7702'/1984'/account'/chainId'/ephemeralIndex'`. All three trailing words are +customizable, and because `chainId` is word W1 each chain derives a **distinct** EOA +(chain-scoped — one wallet across many chains without reusing an address). The RAILGUN +app's `INS 0x07` preload APDU sends those three words to derive the same EOA that +`INS 0x08` and `INS 0x09` later sign with. To vary the account per signer, use +`get7702Signer({ chainId, ephemeralIndex, railgunAccountIndex })`. `signEip7702Authorization()` uses the custom RAILGUN app `INS 0x08` APDU. -`signEthereumTxHash()` uses `INS 0x09`; `display: true` requires the profile's -`clear` signing capability, while `display: false` uses the gated/blind signing -capability for the second step of a 7702 authorization flow. +`signEthereumTxHash()` uses `INS 0x09` with `P1 = 0x01` (on-device review); firmware +1.6.1 rejects a standalone `P1 = 0x00`, so pass `display: true`. For an EIP-712 routing experiment through the standard Ethereum app, use the custom-path helpers and verify the returned ETH-app address matches the RAILGUN