Skip to content

Add NSpark.RemoteSigner: contract for serving ISparkSigner remotely - #4

Open
Jossec101 wants to merge 5 commits into
spark/submodule-buildfrom
spark/signer-contract
Open

Jossec101 wants to merge 5 commits into
spark/submodule-buildfrom
spark/signer-contract

Conversation

@Jossec101

@Jossec101 Jossec101 commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator
  • Adds NSpark.RemoteSigner, so the Spark keys can live in a separate service (like an AWS Lambda).
  • Includes both sides: the client the app uses and the handler the signer service uses.
  • The signer side only signs for a fixed set of operators and receivers, and refuses risky operations.
  • Adds test fixtures so both sides always speak the same format.
  • Adds WithdrawLeavesAsync: exits exactly the given leaves (for example what one payment brought in), with no leaf swap, so a remote signer can sign it.
  • A Lightning receive request now reports the transfer that paid it, and a transfer lists its leaves.
  • A cooperative exit now signs both direct refunds for a leaf with a direct node transaction, even at node timelock 0. The operators refused the exit of a leaf renewed at timelock 0 (a root leaf) when only one was signed, as the reference SDK does.
  • Docs: a renewal keeps the leaf's id.
  • Withdrawals take an exit speed (ExitSpeed.Fast, the default, Medium or Slow): the fee is quoted at that speed and the SSP is asked for it. Slow is the cheapest and confirms last.

Stack created with GitHub Stacks CLI • Give Feedback 💬

A small fork-only project shared by a signer service (e.g. an AWS Lambda holding
the seed) and its client, so both sides stay in lockstep:
- SparkSignerContract: POST /spark/{op} routes, request records, envelope, version.
- RemoteSparkSigner: client ISparkSigner over a pluggable transport.
- SparkSignerHttpHandler + SparkSignerDispatcher + SparkSignerPolicy: service side,
  pinning the operator set and threshold, allowlisting receivers, refusing
  adaptor signatures; nonce, key-export and ECIES ops are not served at all.
- Golden request fixtures and end-to-end contract tests against a real SparkSigner.
@Jossec101
Jossec101 added this pull request to stack #5 October 9, 2026 23:21
@Jossec101
Jossec101 marked this pull request as ready for review October 10, 2026 09:49
- SparkTransfer.Leaves: the leaves a transfer moves, so a claimed
  transfer says exactly what it brought in.
- GetLightningReceiveRequestAsync: the SSP's receive request with the
  transfer that paid it (spark_id, total). The UserRequest query is now
  named as the reference SDK names it.
- WithdrawLeavesAsync: exit exactly the given leaves in one cooperative
  exit. They already add up, so the SSP is never asked to swap leaves,
  and a remote signer, which serves no adaptor signatures, can sign it.
  Leaves that are not owned or spendable are refused before anything
  moves (SparkLeavesNotSpendableException).
The operators renew a leaf in place, keeping its id; the split node a node renewal adds is a new parent,
not a new leaf. So the leaf ids a transfer reported stay the ones to exit: a leaf is not spendable only
when it is spent, locked, frozen, or due a renewal that failed.
A zero-timelock renewal leaves a leaf with a node transaction at timelock 0 and a direct node transaction.
For such a leaf the operators' cooperative-exit check takes both direct refunds or none, and requires them;
only a claim exempts a zero-timelock node from the direct refund. Signing just the direct-from-CPFP refund,
as the reference SDK does, got the exit refused ("both direct refund txs are required when either direct
refund tx is provided"), so a renewed root leaf could never be exited. A cooperative exit now signs a direct
refund whenever the leaf has a direct node transaction. Verified against the local operators and SSP by
NodeGuard's renewal E2E.
The SSP prices a cooperative exit per speed (the same user fee, plus an L1 broadcast fee at the fee rate the
speed targets) and request_coop_exit takes the speed, but NSpark always quoted and asked for FAST. Every
withdrawal (WithdrawAsync, WithdrawLeavesAsync, WithdrawAllAsync, QuoteWithdrawAllAsync) and GetFeeQuoteAsync
now take an ExitSpeed, FAST by default: the quote, the fee bound and the request use it. A speed the SSP has no
estimate for is refused.
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