@qntx/spark 0.0.0-stage → 0.1.0

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,106 @@
1
+ import "@bufbuild/protobuf/codegenv2";
2
+ import "@bufbuild/protobuf/wkt";
3
+ import { Message, UnknownEnum } from "@bufbuild/protobuf";
4
+ //#region src/proto/gen/common_pb.d.ts
5
+ /**
6
+ * A signature tagged with the scheme used to produce it, which determines how to verify it.
7
+ *
8
+ * @generated from message common.Signature
9
+ */
10
+ type Signature = Message<"common.Signature"> & {
11
+ /**
12
+ * @generated from field: common.SignatureScheme scheme = 1;
13
+ */
14
+ scheme: SignatureScheme;
15
+ /**
16
+ * @generated from field: bytes signature = 2;
17
+ */
18
+ signature: Uint8Array;
19
+ };
20
+ /**
21
+ * The scheme used to produce a Signature.
22
+ *
23
+ * @generated from enum common.SignatureScheme
24
+ */
25
+ declare const SignatureScheme: {
26
+ /**
27
+ * @generated from enum value: SIGNATURE_SCHEME_UNSPECIFIED = 0;
28
+ */
29
+ readonly UNSPECIFIED: 0;
30
+ /**
31
+ * secp256k1 ECDSA, strict DER encoding (variable length).
32
+ *
33
+ * @generated from enum value: SIGNATURE_SCHEME_ECDSA = 1;
34
+ */
35
+ readonly ECDSA: 1;
36
+ /**
37
+ * BIP-340 Schnorr over secp256k1, 64 bytes.
38
+ *
39
+ * @generated from enum value: SIGNATURE_SCHEME_SCHNORR = 2;
40
+ */
41
+ readonly SCHNORR: 2;
42
+ };
43
+ /**
44
+ * The scheme used to produce a Signature.
45
+ *
46
+ * @generated from enum common.SignatureScheme
47
+ */
48
+ type SignatureScheme = (typeof SignatureScheme)[keyof typeof SignatureScheme] | UnknownEnum;
49
+ //#endregion
50
+ //#region src/core/network.d.ts
51
+ /** Spark networks: Bitcoin mainnet, or the public regtest deployment. */
52
+ type SparkNetwork = "mainnet" | "regtest";
53
+ /** One Spark signing operator. Values come from the upstream wallet configuration. */
54
+ type SparkOperatorConfig = {
55
+ readonly id: number;
56
+ /** 64 lowercase hex characters; the FROST identifier scalar. */
57
+ readonly identifier: string;
58
+ readonly address: string;
59
+ /** 33-byte compressed secp256k1 public key. */
60
+ readonly identityPublicKey: Uint8Array;
61
+ };
62
+ type SparkNetworkConfig = {
63
+ readonly network: SparkNetwork;
64
+ readonly operators: ReadonlyArray<SparkOperatorConfig>;
65
+ readonly threshold: number;
66
+ /** Identifier of the coordinator operator (the `…01` identifier upstream). */
67
+ readonly coordinator: string;
68
+ readonly ssp: {
69
+ readonly baseUrl: string;
70
+ /** 33-byte compressed secp256k1 public key of the SSP service. */
71
+ readonly identityPublicKey: Uint8Array;
72
+ /** GraphQL path appended to `baseUrl` by the SSP client (upstream schema endpoint). */
73
+ readonly graphqlPath: string;
74
+ };
75
+ /** Token output parameters stamped on every token transaction (upstream wallet-config defaults). */
76
+ readonly tokens: {
77
+ readonly withdrawBondSats: number;
78
+ readonly withdrawRelativeBlockLocktime: number;
79
+ readonly validityDurationSeconds: number;
80
+ };
81
+ };
82
+ /**
83
+ * Static network parameters for `network`. Each call builds a fresh config — fresh `Uint8Array`
84
+ * keys — so callers can never corrupt shared module state. Throws {@link SparkInputError} on an
85
+ * unknown name.
86
+ */
87
+ declare function sparkNetworkConfig(network: SparkNetwork): SparkNetworkConfig;
88
+ //#endregion
89
+ //#region src/core/lightning-address.d.ts
90
+ /** The statement shape `SparkSigner.signIdentity` accepts for Lightning Address requests. */
91
+ type LightningAddressStatement = {
92
+ readonly kind: "lightning-address";
93
+ readonly action: "register";
94
+ readonly domain: string;
95
+ readonly username: string;
96
+ readonly description: string;
97
+ readonly timestamp: number;
98
+ } | {
99
+ readonly kind: "lightning-address";
100
+ readonly action: "unregister";
101
+ readonly domain: string;
102
+ readonly username: string;
103
+ readonly timestamp: number;
104
+ };
105
+ //#endregion
106
+ export { sparkNetworkConfig as a, SparkOperatorConfig as i, SparkNetwork as n, Signature as o, SparkNetworkConfig as r, LightningAddressStatement as t };
@@ -0,0 +1,92 @@
1
+ import { r as SparkInputError } from "./errors-5xLkExNL.mjs";
2
+ import { bytesToHex, concatBytes, utf8ToBytes } from "@noble/hashes/utils.js";
3
+ import { sha256 } from "@noble/hashes/sha2.js";
4
+ //#region src/core/lightning-address.ts
5
+ const VERSION = "breez-lnurl:v2";
6
+ /**
7
+ * LUD-16 strict username rule: lowercase alphanumerics plus `-`, `_`, `.` and `+`, 1–64 characters.
8
+ * Callers lowercase before validating — uppercase is an error, never silently folded.
9
+ */
10
+ const LIGHTNING_ADDRESS_USERNAME_PATTERN = /^[a-z0-9\-_.+]{1,64}$/u;
11
+ const HOST_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/u;
12
+ function fail(message) {
13
+ throw new SparkInputError(`lightning address: ${message}`);
14
+ }
15
+ /** A registration name: LUD-16 local-part, already lowercase. */
16
+ function checkLightningAddressUsername(username) {
17
+ if (!LIGHTNING_ADDRESS_USERNAME_PATTERN.test(username)) fail("invalid username");
18
+ }
19
+ /**
20
+ * The domain a message binds to: non-empty lowercase host labels. The check must forbid LF — an LF
21
+ * in the domain would let one tuple read as another.
22
+ */
23
+ function checkLightningAddressDomain(domain) {
24
+ if (domain.length === 0 || domain.length > 253) fail("invalid domain");
25
+ for (const label of domain.split(".")) if (!HOST_LABEL.test(label)) fail("invalid domain");
26
+ }
27
+ function checkTimestamp(timestamp) {
28
+ if (!Number.isSafeInteger(timestamp) || timestamp < 0) fail("invalid timestamp");
29
+ }
30
+ /** `sha256` of the exact description bytes, lowercase hex — the description enters hashed. */
31
+ function lightningAddressDescriptionHash(description) {
32
+ return bytesToHex(sha256(utf8ToBytes(description)));
33
+ }
34
+ /**
35
+ * `POST /lnurlpay/{pubkey}`: the message claiming `username` on `domain`, fields joined by LF:
36
+ * `breez-lnurl:v2` ‖ `register` ‖ domain ‖ username ‖ sha256(description) ‖ timestamp.
37
+ */
38
+ function lightningAddressRegisterMessage(domain, username, description, timestamp) {
39
+ checkLightningAddressDomain(domain);
40
+ checkLightningAddressUsername(username);
41
+ checkTimestamp(timestamp);
42
+ return [
43
+ VERSION,
44
+ "register",
45
+ domain,
46
+ username,
47
+ lightningAddressDescriptionHash(description),
48
+ String(timestamp)
49
+ ].join("\n");
50
+ }
51
+ /**
52
+ * `DELETE /lnurlpay/{pubkey}`: the message giving up `username` on `domain`: `breez-lnurl:v2` ‖
53
+ * `unregister` ‖ domain ‖ username ‖ timestamp.
54
+ */
55
+ function lightningAddressUnregisterMessage(domain, username, timestamp) {
56
+ checkLightningAddressDomain(domain);
57
+ checkLightningAddressUsername(username);
58
+ checkTimestamp(timestamp);
59
+ return [
60
+ VERSION,
61
+ "unregister",
62
+ domain,
63
+ username,
64
+ String(timestamp)
65
+ ].join("\n");
66
+ }
67
+ /** Builds the canonical message a {@link LightningAddressStatement} signs, after validation. */
68
+ function lightningAddressMessage(statement) {
69
+ if (statement.action === "register") return lightningAddressRegisterMessage(statement.domain, statement.username, statement.description, statement.timestamp);
70
+ return lightningAddressUnregisterMessage(statement.domain, statement.username, statement.timestamp);
71
+ }
72
+ /** `sha256(message)` — the digest the identity key signs and the server verifies. */
73
+ function lightningAddressMessageDigest(message) {
74
+ return sha256(utf8ToBytes(message));
75
+ }
76
+ /**
77
+ * The replay-protection claim key: `sha256(pubkeyHex ‖ 0x00 ‖ message)`. The pubkey is the
78
+ * lowercase compressed-hex string; the 0x00 separator keeps the pubkey/message boundary unambiguous
79
+ * (Breez `statement_hash`).
80
+ */
81
+ function lightningAddressStatementHash(pubkeyHex, message) {
82
+ return bytesToHex(sha256(concatBytes(utf8ToBytes(pubkeyHex), Uint8Array.of(0), utf8ToBytes(message))));
83
+ }
84
+ /**
85
+ * Whether a statement `timestamp` (seconds) is inside the accept window around `nowSecs`, bounded
86
+ * symmetrically so a device whose clock runs fast still works.
87
+ */
88
+ function lightningAddressTimestampFresh(timestamp, nowSecs) {
89
+ return Number.isSafeInteger(timestamp) && timestamp >= 0 && Math.abs(timestamp - nowSecs) <= 600;
90
+ }
91
+ //#endregion
92
+ export { lightningAddressRegisterMessage as a, lightningAddressUnregisterMessage as c, lightningAddressMessageDigest as i, checkLightningAddressDomain as n, lightningAddressStatementHash as o, lightningAddressMessage as r, lightningAddressTimestampFresh as s, LIGHTNING_ADDRESS_USERNAME_PATTERN as t };