@qntx/spark 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ import { n as SparkNetwork, t as LightningAddressStatement } from "./lightning-address-CRULmi9v.mjs";
2
+ import { Transaction } from "@scure/btc-signer";
3
+ //#region src/core/bitcoin/sighash.d.ts
4
+ /** A transaction to sign: raw bytes plus every input's prevout script and amount. */
5
+ type SigningTarget = {
6
+ readonly transaction: Uint8Array;
7
+ readonly inputIndex: number;
8
+ readonly prevouts: ReadonlyArray<{
9
+ script: Uint8Array;
10
+ amount: number;
11
+ }>;
12
+ };
13
+ //#endregion
14
+ //#region src/signer/types.d.ts
15
+ /**
16
+ * A private key the local signer can resolve, addressed by how upstream derives it. The key
17
+ * material never leaves the signer; callers pass only these descriptors.
18
+ *
19
+ * - `leaf`: `LEAF{leafId}` path — hardened child `u32be(sha256(utf8(leafId))[0..4]) mod 2^31` of the
20
+ * signing root (`m/8797555'/a'/1'`).
21
+ * - `static-deposit`: hardened child `index` of the static-deposit root (`m/8797555'/a'/3'`).
22
+ * (`m/8797555'/a'/2'` is upstream's deposit key; single-use deposits bind leaf keys, so it is
23
+ * unused.)
24
+ */
25
+ type SparkLeafKey = {
26
+ readonly type: "leaf";
27
+ readonly leafId: string;
28
+ } | {
29
+ readonly type: "static-deposit";
30
+ readonly index: number;
31
+ };
32
+ /**
33
+ * Any private key a FROST or key-spend job can use. `received` decrypts `ciphertext` with the
34
+ * identity key (ECIES) to recover the 32-byte key the sender sealed for this identity.
35
+ */
36
+ type SparkKey = SparkLeafKey | {
37
+ readonly type: "received";
38
+ readonly ciphertext: Uint8Array;
39
+ };
40
+ /** The user's FROST signing commitment pair: 33-byte compressed `hiding` and `binding` points. */
41
+ type SigningCommitment = {
42
+ readonly hiding: Uint8Array;
43
+ readonly binding: Uint8Array;
44
+ };
45
+ /** An operator's FROST identifier and its commitment for one signing session. */
46
+ type OperatorCommitment = {
47
+ /** 64 lowercase hex characters; the operator's FROST identifier scalar. */
48
+ readonly identifier: string;
49
+ readonly commitment: SigningCommitment;
50
+ };
51
+ /**
52
+ * A user-side FROST signing job. `commitment` must come from {@link SparkSigner.newCommitments}; it
53
+ * is consumed by the call — signing once removes it, so a second job carrying the same commitment
54
+ * fails even if the first signing attempt itself failed.
55
+ */
56
+ type FrostSignJob = {
57
+ readonly key: SparkKey;
58
+ readonly target: SigningTarget;
59
+ /** 33-byte compressed combined (aggregate) verifying key for this session. */
60
+ readonly verifyingKey: Uint8Array;
61
+ readonly commitment: SigningCommitment;
62
+ readonly operatorCommitments: ReadonlyArray<OperatorCommitment>;
63
+ /** Optional 33-byte adaptor point; omit or leave empty for plain signatures. */
64
+ readonly adaptorPublicKey?: Uint8Array;
65
+ };
66
+ /** Aggregation of the user's share (obtained from {@link SparkSigner.signFrost}) with the SOs'. */
67
+ type FrostAggregateJob = {
68
+ readonly key: SparkKey;
69
+ readonly target: SigningTarget;
70
+ readonly verifyingKey: Uint8Array;
71
+ readonly commitment: SigningCommitment;
72
+ /** The user's 32-byte signature share produced by `signFrost` for this session. */
73
+ readonly signatureShare: Uint8Array;
74
+ readonly operators: ReadonlyArray<{
75
+ /** 64 lowercase hex characters. */
76
+ readonly identifier: string;
77
+ readonly commitment: SigningCommitment;
78
+ /** The operator's 32-byte signature share. */
79
+ readonly signatureShare: Uint8Array;
80
+ /** The operator's 33-byte verifying share. */
81
+ readonly publicKey: Uint8Array;
82
+ }>;
83
+ readonly adaptorPublicKey?: Uint8Array;
84
+ };
85
+ /**
86
+ * A statement the signer signs with the identity key. The signer computes every digest itself from
87
+ * the full statement content — there is no sign-arbitrary-hash method.
88
+ *
89
+ * - `so-challenge`: `challenge` is a serialized `spark_authn.Challenge`; decoded strictly (no unknown
90
+ * fields), its `publicKey` must equal the identity public key as a curve point.
91
+ * - `ssp-challenge`: `protectedChallenge` is the base64 SSP session challenge; decoded bytes must be
92
+ * an `ssp_authn.ProtectedChallenge` (unknown fields tolerated) whose `challenge.public_key` is
93
+ * the identity public key in either encoding.
94
+ * - `transfer-package` / `claim-package`: `package` is the serialized `spark.TransferPackage` /
95
+ * `spark.ClaimPackage`; the digest is computed from the decoded message fields.
96
+ * - `static-deposit-claim`: the fixed-quote claim statement (`txid` in display byte order,
97
+ * `sspSignature` is the SSP quote signature's decoded bytes).
98
+ * - `static-deposit-refund`: the refund statement; `txid`/`vout`/`amount` describe the deposit output
99
+ * and `target` is the refund transaction whose sighash becomes the payload.
100
+ * - `watchtower-recovery`: `target` is the recovery transaction; its sighash enters the digest.
101
+ * - `spark-invoice`: `invoice` is a serialized `spark.SparkInvoiceFields`; signed with BIP-340.
102
+ * - `token-transaction`: `partialTransaction` is a serialized `spark_token.PartialTokenTransaction`;
103
+ * the digest is its V3 protoreflecthash, signed with BIP-340 (upstream tokenSignatures:
104
+ * SCHNORR).
105
+ * - `lightning-address`: a Breez LNURL-server v2 message authorizing a Lightning Address
106
+ * register/unregister on `domain`; the digest is `sha256` of the canonical LF-joined message,
107
+ * signed with DER ECDSA.
108
+ */
109
+ type IdentityStatement = {
110
+ readonly kind: "so-challenge";
111
+ readonly challenge: Uint8Array;
112
+ } | {
113
+ readonly kind: "ssp-challenge";
114
+ readonly protectedChallenge: string;
115
+ } | {
116
+ readonly kind: "transfer-package";
117
+ readonly transferId: string;
118
+ readonly package: Uint8Array;
119
+ } | {
120
+ readonly kind: "claim-package";
121
+ readonly transferId: string;
122
+ readonly package: Uint8Array;
123
+ } | {
124
+ readonly kind: "static-deposit-claim";
125
+ readonly network: SparkNetwork;
126
+ /** The deposit transaction id in display (big-endian) byte order. */
127
+ readonly txid: Uint8Array;
128
+ readonly vout: number;
129
+ readonly creditAmountSats: number;
130
+ /** The SSP's quote signature bytes — the payload the statement hashes. */
131
+ readonly sspSignature: Uint8Array;
132
+ } | {
133
+ readonly kind: "static-deposit-refund";
134
+ readonly network: SparkNetwork;
135
+ /** The deposit transaction id in display (big-endian) byte order. */
136
+ readonly txid: Uint8Array;
137
+ readonly vout: number;
138
+ readonly amount: number;
139
+ readonly target: SigningTarget;
140
+ } | {
141
+ readonly kind: "watchtower-recovery";
142
+ readonly network: SparkNetwork;
143
+ readonly leafId: string;
144
+ readonly target: SigningTarget;
145
+ } | {
146
+ readonly kind: "spark-invoice";
147
+ readonly invoice: Uint8Array;
148
+ } | {
149
+ readonly kind: "token-transaction";
150
+ /** Serialized `spark_token.PartialTokenTransaction`. */
151
+ readonly partialTransaction: Uint8Array;
152
+ } | LightningAddressStatement;
153
+ /** A signing operator the signer builds per-operator packages for. */
154
+ type SparkOperator = {
155
+ readonly id: number;
156
+ /** 64 lowercase hex characters; the FROST identifier scalar. */
157
+ readonly identifier: string;
158
+ /** 33-byte compressed secp256k1 public key the packages are ECIES-encrypted to. */
159
+ readonly identityPublicKey: Uint8Array;
160
+ };
161
+ /**
162
+ * PrepareTransfer input: every leaf gets a fresh random recipient key; the operator-visible tweak
163
+ * is `old − new` (mod n) split by VSS and encrypted per operator. Each leaf's `secret_cipher` is
164
+ * sealed to that leaf's own receiver identity key, so one transfer can pay several receivers. The
165
+ * operator set and threshold are the signer's construction-time values — a call cannot retarget the
166
+ * tweak shares to other keys.
167
+ */
168
+ type PrepareTransferRequest = {
169
+ readonly transferId: string;
170
+ readonly leaves: ReadonlyArray<{
171
+ readonly leafId: string;
172
+ readonly key: SparkLeafKey;
173
+ /** 33-byte compressed identity public key the leaf's `secret_cipher` is encrypted to. */
174
+ readonly receiverIdentityPublicKey: Uint8Array;
175
+ }>;
176
+ };
177
+ /** One leaf's transfer output, mirroring the per-leaf fields of `spark.SendLeafKeyTweak`. */
178
+ type PreparedTransferLeaf = {
179
+ readonly leafId: string;
180
+ /** The fresh 33-byte leaf key the receiver will claim with. */
181
+ readonly newPublicKey: Uint8Array;
182
+ /** ECIES(receiverIdentityPublicKey) ciphertext sealing the new 32-byte leaf key. */
183
+ readonly secretCipher: Uint8Array;
184
+ /** 64-byte compact ECDSA signature over `sha256(leafId ‖ transferId ‖ secretCipher)`. */
185
+ readonly signature: Uint8Array;
186
+ };
187
+ /**
188
+ * Sender transfer preparation. `operatorPackages` maps each operator's 64-hex identifier to the
189
+ * ECIES-encrypted `spark.SendLeafKeyTweaks` it can open.
190
+ */
191
+ type PreparedTransfer = {
192
+ /** SO identifier → ECIES(identityPublicKey) of the serialized `spark.SendLeafKeyTweaks`. */
193
+ readonly operatorPackages: Record<string, Uint8Array>;
194
+ readonly leaves: ReadonlyArray<PreparedTransferLeaf>;
195
+ };
196
+ /**
197
+ * PrepareClaim input: each leaf's `secretCipher` is the sender's ECIES-encrypted leaf key. The
198
+ * tweak shares are split across and sealed to the signer's construction-time operator set.
199
+ */
200
+ type PrepareClaimRequest = {
201
+ readonly transferId: string;
202
+ readonly leaves: ReadonlyArray<{
203
+ readonly leafId: string;
204
+ readonly secretCipher: Uint8Array;
205
+ }>;
206
+ };
207
+ /**
208
+ * Receiver claim preparation. `operatorPackages` maps each operator's identifier to the
209
+ * ECIES-encrypted `spark.ClaimLeafKeyTweaks` it can open.
210
+ */
211
+ type PreparedClaim = {
212
+ /** SO identifier → ECIES(identityPublicKey) of the serialized `spark.ClaimLeafKeyTweaks`. */
213
+ readonly operatorPackages: Record<string, Uint8Array>;
214
+ };
215
+ /**
216
+ * Lightning receive preparation. The preimage is generated inside the signer and never leaves;
217
+ * `paymentHash` is `sha256(preimage)` and `operatorPackages` maps each operator's identifier to the
218
+ * ECIES-encrypted `spark.SecretShare` it can open (the V2 store-preimage-share payload).
219
+ */
220
+ type PreparedLightningReceive = {
221
+ readonly paymentHash: Uint8Array;
222
+ /** SO identifier → ECIES(identityPublicKey) of the serialized `spark.SecretShare`. */
223
+ readonly operatorPackages: Record<string, Uint8Array>;
224
+ };
225
+ /**
226
+ * A Lightning HTLC timeout spend: the sequence-lock-leaf path the sender takes once the refund
227
+ * transaction's HTLC output has confirmed for `LIGHTNING_HTLC_SEQUENCE` blocks. `transaction` is
228
+ * the unsigned single-input sweep (`constructHtlcSenderSpend`'s `txBytes`); the signer rebuilds the
229
+ * HTLC output script from `paymentHash`, `hashLockPublicKey` and its own identity key, checks the
230
+ * input's sequence satisfies the CSV, sighashes the sequence-lock leaf and BIP-340-signs.
231
+ */
232
+ type HtlcTimeoutSignRequest = {
233
+ readonly transaction: Uint8Array;
234
+ /** Index of the input spending the HTLC output — 0 for the single-input sweep shape. */
235
+ readonly inputIndex: number;
236
+ /** The HTLC output's amount in sats. */
237
+ readonly prevoutAmountSats: number;
238
+ /** 32-byte payment hash locking the HTLC's hash-lock leaf. */
239
+ readonly paymentHash: Uint8Array;
240
+ /** 33-byte compressed public key on the hash-lock leaf (the SSP identity for sends). */
241
+ readonly hashLockPublicKey: Uint8Array;
242
+ };
243
+ /**
244
+ * The static-deposit claim request: which key index and the statement the SSP expects signed. The
245
+ * deposit secret is sealed to the SSP identity bound at signer construction — a call cannot
246
+ * redirect it to another key.
247
+ */
248
+ type StaticDepositClaimRequest = {
249
+ /** Static-deposit child index (upstream uses 0 for the current deposit address). */
250
+ readonly index: number;
251
+ readonly statement: Extract<IdentityStatement, {
252
+ readonly kind: "static-deposit-claim";
253
+ }>;
254
+ };
255
+ /**
256
+ * The signer's identity surface. Private keys never leave the signer; every method returns protocol
257
+ * values only (Uint8Array, strings, safe integers, plain objects) so an implementation may live
258
+ * behind a binding boundary.
259
+ */
260
+ type SparkSigner = {
261
+ /** The 33-byte compressed secp256k1 identity public key. */
262
+ readonly identityPublicKey: () => Promise<Uint8Array>;
263
+ /** Compressed public keys for the given private-key descriptors, in order. */
264
+ readonly publicKeys: (keys: ReadonlyArray<SparkLeafKey>) => Promise<ReadonlyArray<Uint8Array>>;
265
+ /**
266
+ * Public keys of ECIES-sealed ("received") keys: each ciphertext is decrypted with the identity
267
+ * key (must yield 32 bytes) and its compressed public key returned.
268
+ */
269
+ readonly receivedPublicKeys: (ciphertexts: ReadonlyArray<Uint8Array>) => Promise<ReadonlyArray<Uint8Array>>;
270
+ /**
271
+ * Generates `count` FROST commitment pairs (at most 256). The secret nonces are stored inside the
272
+ * signer keyed by the commitment pair and consumed by {@link signFrost}.
273
+ */
274
+ readonly newCommitments: (count: number) => Promise<ReadonlyArray<SigningCommitment>>;
275
+ /**
276
+ * User-side FROST signature shares, one 32-byte share per job in order (at most 256 jobs). Each
277
+ * job's commitment is consumed — removed before signing, so reuse fails even after an error.
278
+ */
279
+ readonly signFrost: (jobs: ReadonlyArray<FrostSignJob>) => Promise<ReadonlyArray<Uint8Array>>;
280
+ /**
281
+ * Aggregated 64-byte FROST (Schnorr) signatures, one per job in order. The user verifying share
282
+ * is derived from the resolved private key inside the signer.
283
+ */
284
+ readonly aggregateFrost: (jobs: ReadonlyArray<FrostAggregateJob>) => Promise<ReadonlyArray<Uint8Array>>;
285
+ /**
286
+ * Signs a protocol statement with the identity key. DER ECDSA for protocol statements, BIP-340
287
+ * for `spark-invoice`.
288
+ */
289
+ readonly signIdentity: (statement: IdentityStatement) => Promise<Uint8Array>;
290
+ /**
291
+ * BIP-341 key-path signature over the target's sighash: the resolved key is tweaked with
292
+ * `TapTweak` (empty merkle root) and BIP-340-signed with fresh aux randomness. Returns 64 bytes.
293
+ */
294
+ readonly signKeySpend: (key: SparkKey, target: SigningTarget) => Promise<Uint8Array>;
295
+ /**
296
+ * BIP-342 script-path signature over the sequence-lock leaf of a Lightning HTLC output — the
297
+ * sender's timeout path. The signer rebuilds the leaf and the prevout's HTLC output script from
298
+ * `paymentHash`, `hashLockPublicKey` and its own identity key, requires the input's sequence to
299
+ * satisfy the `LIGHTNING_HTLC_SEQUENCE` CSV, then BIP-340-signs with the identity key. Accepts
300
+ * only the single-input sweep shape; returns 64 bytes.
301
+ */
302
+ readonly signHtlcTimeout: (request: HtlcTimeoutSignRequest) => Promise<Uint8Array>;
303
+ /** Builds the sender transfer package: per-leaf fresh keys, VSS tweaks, per-SO ECIES packages. */
304
+ readonly prepareTransfer: (request: PrepareTransferRequest) => Promise<PreparedTransfer>;
305
+ /** Builds the receiver claim package: VSS `received − LEAF{leafId}` tweaks, per-SO ECIES packages. */
306
+ readonly prepareClaim: (request: PrepareClaimRequest) => Promise<PreparedClaim>;
307
+ /** Generates a preimage, splits it by VSS and encrypts one `SecretShare` per operator. */
308
+ readonly prepareLightningReceive: () => Promise<PreparedLightningReceive>;
309
+ /**
310
+ * The static-deposit claim: the child secret key ECIES-sealed to the SSP identity bound at signer
311
+ * construction (the same scheme as transfer leaf secret ciphers) plus the identity-key DER
312
+ * signature over the claim statement. The plaintext key never leaves the signer.
313
+ */
314
+ readonly claimStaticDeposit: (request: StaticDepositClaimRequest) => Promise<{
315
+ readonly encryptedSecretKey: Uint8Array;
316
+ readonly signature: Uint8Array;
317
+ }>;
318
+ /** Wipes every private key, cached secret and unused nonce, then rejects all further calls. */
319
+ readonly dispose: () => void;
320
+ };
321
+ //#endregion
322
+ export { StaticDepositClaimRequest as _, OperatorCommitment as a, PreparedClaim as c, PreparedTransferLeaf as d, SigningCommitment as f, SparkSigner as g, SparkOperator as h, IdentityStatement as i, PreparedLightningReceive as l, SparkLeafKey as m, FrostSignJob as n, PrepareClaimRequest as o, SparkKey as p, HtlcTimeoutSignRequest as r, PrepareTransferRequest as s, FrostAggregateJob as t, PreparedTransfer as u, SigningTarget as v };
package/package.json CHANGED
@@ -1,6 +1,100 @@
1
1
  {
2
2
  "name": "@qntx/spark",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Spark Bitcoin L2 protocol for TypeScript: platform-neutral client, signer and crypto-backend interfaces.",
5
+ "keywords": [
6
+ "bitcoin",
7
+ "l2",
8
+ "lightning",
9
+ "spark"
10
+ ],
11
+ "homepage": "https://github.com/qntx/spark#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/qntx/spark/issues"
14
+ },
15
+ "license": "Apache-2.0",
16
+ "author": "QuantX <x@qntx.org>",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/qntx/spark.git",
20
+ "directory": "packages/spark"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "LICENSE",
25
+ "NOTICE"
26
+ ],
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.mts",
32
+ "import": "./dist/index.mjs",
33
+ "default": "./dist/index.mjs"
34
+ },
35
+ "./crypto": {
36
+ "types": "./dist/crypto.d.mts",
37
+ "import": "./dist/crypto.mjs",
38
+ "default": "./dist/crypto.mjs"
39
+ },
40
+ "./lnurl": {
41
+ "types": "./dist/lnurl.d.mts",
42
+ "import": "./dist/lnurl.mjs",
43
+ "default": "./dist/lnurl.mjs"
44
+ },
45
+ "./server": {
46
+ "types": "./dist/server.d.mts",
47
+ "import": "./dist/server.mjs",
48
+ "default": "./dist/server.mjs"
49
+ },
50
+ "./signer": {
51
+ "types": "./dist/signer.d.mts",
52
+ "import": "./dist/signer.mjs",
53
+ "default": "./dist/signer.mjs"
54
+ },
55
+ "./package.json": "./package.json"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ },
60
+ "scripts": {
61
+ "build": "vp pack",
62
+ "dev": "vp pack --watch",
63
+ "test": "vp test",
64
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.src.json",
65
+ "check:pkg": "vp pack && publint && attw --pack . --profile esm-only",
66
+ "smoke:hermes": "bun run build && bun scripts/hermes-smoke.ts",
67
+ "prepublishOnly": "vp run build"
68
+ },
69
+ "dependencies": {
70
+ "@bufbuild/protobuf": "^2.16.0",
71
+ "@connectrpc/connect": "^2.2.0",
72
+ "@connectrpc/connect-web": "^2.2.0",
73
+ "@noble/ciphers": "2.4.0",
74
+ "@noble/curves": "^2.4.0",
75
+ "@noble/hashes": "^2.4.0",
76
+ "@scure/base": "^2.4.0",
77
+ "@scure/bip32": "^2.4.0",
78
+ "@scure/btc-signer": "^2.4.1"
79
+ },
80
+ "devDependencies": {
81
+ "@arethetypeswrong/cli": "0.18.5",
82
+ "@qntx/spark-wasm": "0.1.0",
83
+ "@qntx/tsconfig": "1.0.1",
84
+ "@types/node": "26.6.5",
85
+ "publint": "0.3.25",
86
+ "typescript": "7.0.2",
87
+ "undici": "8.12.2",
88
+ "vite-plus": "1.2.0"
89
+ },
90
+ "devEngines": {
91
+ "packageManager": {
92
+ "name": "bun",
93
+ "version": "1.4.0",
94
+ "onFail": "ignore"
95
+ }
96
+ },
97
+ "engines": {
98
+ "node": ">=20.19.0"
99
+ }
100
+ }