Skip to content

Add Product key signature scheme - #111

Merged
knzeng-e merged 5 commits into
devfrom
feat/product-key-signature-scheme
Aug 3, 2026
Merged

Add Product key signature scheme#111
knzeng-e merged 5 commits into
devfrom
feat/product-key-signature-scheme

Conversation

@knzeng-e

Copy link
Copy Markdown
Owner

Outcome

Adds an API-side product-sr25519-v1 signature scheme for Dotify session sign-in and protected content-key requests.

Product-host clients can now sign the same canonical Dotify request message bytes with the app-scoped Product account, send the Product public key, and have the backend bind that public key to the requester H160 before nonce consumption and runtime access checks. The existing standalone eip191 path remains the default for omitted signatureScheme.

Issue and context

Refs #85.

Stacked on #110, which adds the experimental Product CDM runtime adapter. This PR addresses the next backend authentication boundary from the Product roadmap: Product identity must not become a protected playback account unless the API can verify the Product signature and prove that the Product account maps to the same H160 address used by musicAccCanAccess.

Local scope docs:

  • docs/backlog/polkadot-product-readiness-and-killer-dapp-roadmap.md
  • docs/explanation/product-devnet-architecture.md
  • docs/operations/deployment-configuration.md
  • docs/product/ux-signature-flows.md

Architecture and key concepts

flowchart LR
  Client[Standalone or Product client] --> Auth[/POST /api/auth/session/]
  Client --> Key[/POST /api/tracks/:hash/key-request/]
  Auth --> Verifier[Signature verifier]
  Key --> Verifier
  Verifier --> EIP[EIP-191 verifyMessage]
  Verifier --> Product[Product sr25519 + H160 binding]
  Product --> Nonce[Consume nonce]
  EIP --> Nonce
  Nonce --> Access[musicAccCanAccess]
  Access --> KeyVault[CONTENT_KEY_MASTER_SECRET derivation]
Loading

The backend now has two request schemes:

  • eip191: default/backward-compatible standalone wallet path using viem.verifyMessage against the requester H160.
  • product-sr25519-v1: Product-host path requiring signature plus productPublicKey; the backend verifies sr25519 over the canonical Dotify message bytes and derives H160 from the Product public key using the Product SDK / pallet-revive mapping.

The canonical message format did not change. This avoids splitting Dotify's access protocol by host environment: the signer changes, but the payload still binds action, purpose, content hash, requester, chain, nonce, and expiry.

How it works

POST /api/auth/session and POST /api/tracks/:contentHash/key-request now validate a discriminated signature shape.

For product-sr25519-v1:

  1. The request must include a 32-byte productPublicKey and 64-byte sr25519 signature.
  2. The verifier derives the H160 requester from that public key.
  3. The derived H160 must match the requester's H160 address.
  4. The sr25519 signature must verify over the exact canonical Dotify message bytes.
  5. Only then is the nonce consumed.
  6. Key requests still run the normal on-chain musicAccCanAccess check before key derivation.

Unknown schemes fail at the API schema boundary before signature verification or access checks. Product public-key mismatches fail before nonce consumption.

Design decisions and tradeoffs

The API supports Product signatures before the Product frontend sends them. That keeps the security boundary reviewable independently from UI wiring and prevents frontend work from needing to invent the backend contract.

Alternatives considered:

  • Treat Product identity as equivalent to EIP-191: rejected because Product accounts are sr25519/Substrate accounts, not Ethereum personal-sign accounts.
  • Accept a frontend-supplied H160 without deriving it server-side: rejected because the backend would be trusting the client for the address binding that protects key delivery.
  • Add broad Product SDK dependencies to the API: rejected. The API only needs sr25519 verification and the documented public-key-to-H160 derivation, so the dependency stays narrow.
  • Support ed25519/ecdsa Product account variants immediately: deferred until Product host evidence proves those signer shapes are needed for Dotify.

Security, failure, and operations

Security invariants preserved:

  • No Product signature is accepted unless its public key derives to the requester H160.
  • Unknown signature schemes fail closed.
  • Malformed Product public keys/signatures fail closed.
  • Nonces are consumed only after expiry, domain, signature, and Product H160 binding pass.
  • Session sign-in still grants identity only; every protected key request still runs musicAccCanAccess.
  • Room guests still never receive content keys.
  • No Netlify or Fly dashboard variable enables this path; it is request-protocol behavior.

Operational docs now call out that no new env variable is required and that the shipped Product frontend still needs a follow-up to submit this Product proof shape.

npm audit --omit=dev --audit-level=moderate currently fails on existing production dependency advisories in fast-uri, find-my-way, and viem/ws. The new @scure/sr25519 dependency is not listed in that audit output. I did not broaden this PR into dependency remediation.

Review guide

Suggested order

  1. services/api/src/services/signatures.ts - verify the scheme dispatch, Product sr25519 verification, H160 derivation, and nonce-consumption ordering.
  2. services/api/src/routes/auth.ts and services/api/src/routes/keys.ts - verify request schemas reject unknown/malformed Product schemes before verifier/access calls.
  3. services/api/src/services/signatures.test.ts, services/api/src/routes/auth.test.ts, and services/api/src/routes/keys.test.ts - verify real sr25519 positive/negative cases plus HTTP boundary behavior.
  4. docs/product/ux-signature-flows.md, docs/operations/deployment-configuration.md, docs/explanation/product-devnet-architecture.md, README.md, spec.md, web/README.md, and docs/index.html - verify the docs distinguish API readiness from shipped frontend Product playback.

Verify carefully

  • Does the backend derive the Product H160 itself instead of trusting client input?
  • Can a malformed Product proof or public-key mismatch consume a nonce?
  • Does omitted signatureScheme remain backward-compatible with existing EIP-191 clients?
  • Are Product session tokens still subject to per-track runtime access checks?
  • Do docs avoid claiming the Product frontend already uses this path?

Validation

Evidence What it proves
cd services/api && npm run typecheck API TypeScript accepts the new discriminated signature request shapes.
cd services/api && npm test 86 API tests pass, including Product sr25519 signing, payload tamper rejection, H160 mismatch rejection, route schema rejection, and existing key/session/free flows.
cd services/api && npm run build Production API build compiles with the new verifier dependency.
git diff --check No whitespace errors in the final diff.
node scripts/backlog-sync.mjs --check --offline Backlog sync passes; existing warnings remain for active item 24 without GitHub mapping and duplicate backlog doc 08.
cd services/api && npm audit --omit=dev --audit-level=moderate Fails on existing high-severity advisories in fast-uri, find-my-way, and viem/ws; new @scure/sr25519 is not in the reported vulnerable paths.

Known limitations and follow-ups

This PR should remain Refs #85, not Closes #85.

Remaining before #85 can close:

  • wire the Product frontend to sign session/key requests with the app-scoped Product account and submit product-sr25519-v1 payloads;
  • run real Product host smoke tests for key/session signing;
  • wire a real cdm.json manifest and generated Product contract types into Product mode;
  • deploy/register Dotify runtime packages through CDM on Product DevNet;
  • run real host-signed transaction smoke tests;
  • prototype Product resource allocation;
  • prototype bounded Statement Store presence.

Metadata checklist

  • Backlog issue linked with correct close/reference semantics: Refs [Product SDK][P1] Prove Product host compatibility before integration #85
  • Local backlog document linked
  • Added to Project 5 (Dotify sprints) - connector does not expose Project v2 mutation and local gh auth is invalid in this session
  • Project Priority, Track, Phase, Type, and Backlog doc mirror the issue - same Project v2 limitation
  • Workflow status matches draft/review state
  • Assignee set: knzeng-e
  • Applicable labels set: dotify-backlog, product-sdk
  • Applicable milestone set, or confirmed none exists
  • Reviewers requested when ownership is known
  • Draft/ready state is intentional

@knzeng-e knzeng-e added dotify-backlog Tracked by docs/backlog/backlog.json and Project 5 product-sdk Polkadot Product SDK / Host / Playground integration labels Jul 28, 2026 — with ChatGPT Codex Connector
@knzeng-e knzeng-e self-assigned this Jul 28, 2026
@knzeng-e knzeng-e mentioned this pull request Jul 29, 2026
15 tasks
@knzeng-e
knzeng-e changed the base branch from feat/product-cdm-runtime-adapter to dev August 3, 2026 13:33
@knzeng-e
knzeng-e marked this pull request as ready for review August 3, 2026 13:33
@knzeng-e
knzeng-e merged commit 20a76bd into dev Aug 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dotify-backlog Tracked by docs/backlog/backlog.json and Project 5 product-sdk Polkadot Product SDK / Host / Playground integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant