Skip to content

docs: design plan for Turnkey enclave-backed remote signer (non-custodial gateway keys) - #6

Draft
eliteprox wants to merge 1 commit into
masterfrom
cursor/turnkey-remote-signer-plan-14d0
Draft

docs: design plan for Turnkey enclave-backed remote signer (non-custodial gateway keys)#6
eliteprox wants to merge 1 commit into
masterfrom
cursor/turnkey-remote-signer-plan-14d0

Conversation

@eliteprox

Copy link
Copy Markdown
Owner

cc @rickstaa — would appreciate your review on the approach here, particularly §4 (multi-tenancy) and §6 (residual custody gap).

What does this pull request do? Explain your changes. (required)

Adds a design plan (docs only, no code) for signing offchain PM tickets with a user-owned Turnkey secure enclave instead of an operator-held keystore file. The goal is to let a hosted remote signer (the remote_signer.go HTTP service) serve users' own deposit and reserve without the operator ever taking custody of their gateway ETH private key.

The headline finding is that the signature format itself cannot change. I dumped the deployed TicketBroker ABI from the bindings in this repo:

redeemWinningTicket(tuple,bytes,uint256) nonpayable
batchRedeemWinningTickets(tuple[],bytes[],uint256[]) nonpayable
getTicketHash(tuple) pure
fundDeposit / fundReserve / fundDepositAndReserveFor / unlock / withdraw / usedTickets ...

There is no isApprovedSigner, no signer registry, and no ERC-1271 hook. On redemption the contract does ECDSA.recover(toEthSignedMessageHash(ticketHash), sig) and requires the recovered address to equal ticket.sender — the address that owns the deposit and reserve. On top of that, the winning lottery is computed over the signature bytes (keccak256(sig ‖ recipientRand) < winProb in pm/validator.go), so even the signature encoding is protocol-relevant on both sides.

So remote attestation quotes, BLS, or any other "no raw key" signature type are not redeemable on-chain without a new TicketBroker deployment plus a coordinated orchestrator upgrade. What works today is keeping the 65-byte secp256k1/EIP-191 signature byte-identical and changing who holds the key: Turnkey generates and decrypts the key only inside an attested enclave, the user owns the sub-org root quorum, and the operator holds only a revocable delegated API key.

Specific updates (required)

  • Documents why the signature scheme is fixed by the on-chain contract, and what a protocol-level fix would require.
  • Orchestrator: zero changes. Wire format, ProcessPayment, ReceiveTicket, and redemption are untouched.
  • Gateway: zero changes. In remote-signer mode it already holds no key — broadcaster.Sign returns []byte{} when node.Eth == nil, and it uses the cached RemoteEthAddr/InfoSig from the signer.
  • Scopes the actual work to the remote signer node plus pm: a Turnkey-backed pm.Signer, batch signing (CreateTicketBatch can sign up to 100 tickets, currently one round trip each), remote signer multi-tenancy, and replacing the ETH-key state-blob signature with an HMAC.
  • Flags a signature-grinding hazard: because the lottery reads the signature bytes, retrying by re-signing the same ticket hash is grinding, so signing must be idempotent per ticket hash.
  • States the residual custody gap plainly: Turnkey's policy engine exposes only hash_function and encoding for SIGN_RAW_PAYLOAD(S), not the payload, so a delegated key can in principle still sign an unlock()/withdraw() transaction. Includes the mitigations and the two ways to actually close it.

How did you test each of these updates (required)

Docs-only change, no code modified. Verification performed while writing it:

  • Decoded the TicketBroker ABI out of eth/contracts/ticketBroker.go to confirm no approved-signer or ERC-1271 facility exists.
  • Traced the signing and verification paths end to end: pm/sender.gopm/validator.gocrypto/verify.goeth/client_ticketbroker.go, and the gateway/orchestrator handshake in server/rpc.go and server/segment_rpc.go.
  • Confirmed SenderWatcher.GetSenderInfo is address-agnostic and lazily caches per address, so per-tenant deposit/reserve validation needs no watcher changes.
  • Confirmed github.com/tkhq/go-sdk/v2 v2.1.0 resolves on the module proxy, and that ACTIVITY_TYPE_SIGN_RAW_PAYLOADS supports batch signing returning {r, s, v}.
  • Verified every relative file link in the plan resolves to a real path in the repo.

make / ./test.sh were not run: no Go sources are touched by this PR.

Does this pull request close any open issues?

No.

Checklist:

  • Read the contribution guide
  • make runs successfully — n/a, no Go sources changed
  • All tests in ./test.sh pass — n/a, no Go sources changed
  • README and other documentation updated
  • Pending changelog updated — n/a, design doc only, no user-facing behaviour change
Open in Web Open in Cursor 

Design plan for signing offchain PM tickets with a user-owned Turnkey
secure enclave instead of an operator-held keystore, so a hosted remote
signer can serve users' own deposit and reserve without custody of their
gateway ETH keys.

Covers the on-chain TicketBroker constraint that fixes the signature
format, the Turnkey-backed pm.Signer backend, batch signing, remote
signer multi-tenancy, and the residual custody gap with mitigations.

Co-authored-by: John | Elite Encoder <john@eliteencoder.net>
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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