Skip to content

feat(keystore): sign through the backend — the key never leaves (#46, B.2) - #67

Open
SuperstrongBE wants to merge 1 commit into
mainfrom
feat/signdigest-provider
Open

feat(keystore): sign through the backend — the key never leaves (#46, B.2)#67
SuperstrongBE wants to merge 1 commit into
mainfrom
feat/signdigest-provider

Conversation

@SuperstrongBE

Copy link
Copy Markdown
Owner

PR B.2 of the implementation plan (Wave 1, Track B) — the core deliverable of #46: the private key no longer crosses the keystore boundary, even locally.

What changed

Piece Before After
XPR signer privateKeyProvider hands the WIF to JsSignatureProvider takes the KeystoreBackend; a new KeystoreSignatureProvider computes the Antelope digest and calls backend.signDigest
EncryptedFileKeystore transitional withSecret escape hatch deleted — real signDigest (65-byte [recoveryId, r, s], scalar decoded per call and wiped) + verifyKeyBinding (#39: pubkey derived in-backend, compared to the declared PUB_K1_/legacy EOS string)
Registry createSigner(wiring, PrivateKeyProvider) createSigner(wiring, KeystoreBackend); PrivateKeyProvider deleted
New src/keystore/k1.ts: PVT_K1_/legacy-WIF decode (checksum-verified), PUB_K1/EOS derivation, Antelope-canonical signing (RFC6979 ground via extraEntropy until r/s clear the canonicality bits)

New deps: @noble/curves + @scure/base (audited, zero-dep, the standard curve stack). Existing keystore files load unchanged — both stored secret formats (PVT_K1_… from keygen, legacy 5… WIF) are supported.

Crypto validation — before landing, not after

  • Pre-landing harness: 200 random digests signed via the noble path — 200/200 verified AND recovered by @proton/js's own Signature/PublicKey classes; public-key derivation byte-identical to the SDK's (PUB_K1_6MRyAjQq8ud7… from the canonical test WIF).
  • Permanent parity suite test/k1-signing.test.ts: 50-digest verify+recover loop, derivation parity (SDK + 5 fresh keygen pairs), canonicality bits, malformed WIF/digest refusals, legacy-EOS derivation.
  • test/xpr-signer.test.ts now runs the signer against a real unlocked encrypted-file backend end to end (same 9 behavioral tests — WYSIWYS round-trip guard, INV-009/013/014 — all green).

Grep-proof

No privateKeyProvider / withSecret / WIF decode / JsSignatureProvider on the signing path outside src/keystore/. The one remaining JsSignatureProvider is keygen.ts — key generation (a brand-new key, before any keystore exists), not signing.

Numbers

238/238 tests · typecheck ✅ · build + CLI smoke ✅

This completes the #46 Phase-A trio (B.1 interface #66 → B.2 here). Vault/KMS backends are now drop-ins (Phase B). INV-002 extends to "no key material across the keystore boundary at all" on the runtime signing path.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RJ7hqFnC9Nnyf2Db2h3WTb

… B.2)

The XPR signer no longer receives key material. Signing now flows
through KeystoreBackend.signDigest:

- keystore/k1.ts: K1 primitives over the backend's STORAGE formats —
  PVT_K1_/legacy-WIF decode (checksum-verified), PUB_K1/legacy-EOS
  derivation, and Antelope-canonical signing (RFC6979 ground via
  extraEntropy until r/s clear the canonicality bits). Built on
  @noble/curves + @scure/base (audited, zero-dep).
- EncryptedFileKeystore implements the real signDigest (65-byte
  [recoveryId, r, s], scalar decoded per call and wiped) and
  verifyKeyBinding (#39: derives the pubkey in-backend and compares to
  the declared PUB_K1/EOS string — no export). The TRANSITIONAL
  withSecret escape hatch is deleted.
- chains/xpr/signatureProvider.ts: KeystoreSignatureProvider computes
  the Antelope signing digest (chainId ‖ packed_trx ‖ cfd|zero32), asks
  the backend to sign, and adds the header byte + SIG_K1 encoding.
- XprSignerOptions: privateKeyProvider is GONE — the signer takes the
  KeystoreBackend; apiFactory's seam is now (key, options). The
  registry's createSigner takes the backend; the daemon runner passes
  it straight through. PrivateKeyProvider is deleted from the registry.

Crypto validated empirically before landing: 200 random digests signed
with the noble path all verify AND recover through @proton/js's own
Signature/PublicKey classes; pub derivation is byte-identical to the
SDK's. The permanent parity suite (test/k1-signing.test.ts) pins 50
digests + derivation parity + canonicality bits + malformed-input
refusals. Signer tests now run against a REAL unlocked encrypted-file
backend end to end.

Grep-proof: no privateKeyProvider / withSecret / wif decode /
JsSignatureProvider on the signing path outside src/keystore (keygen's
JsSignatureProvider remains — key GENERATION, before any keystore
exists). 238 tests green.

Part of #46. INV-002 now extends to "no key material across the
keystore boundary at all" on the runtime signing path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJ7hqFnC9Nnyf2Db2h3WTb
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.

1 participant