lnurlcash-kit 0.10.0 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 - 2026-09-11
4
+
5
+ **The wire calls take LUD-25 Part 2 notes.**
6
+
7
+ - A `ck1` goes anywhere a `k1` does. `resolveNoteInput` accepts a note URL
8
+ carrying one, and rotate, split, merge, melt and `fetchNoteInfo` pass it
9
+ through.
10
+ - A `cp1` goes anywhere an output does. `requestInvoice` sends it as the
11
+ comment alone. The `*WithHash` calls send it as `p1`/`p2`, while a hash
12
+ keeps `h`/`h2`, which every mint understands. That is lnurl-wallet's rule.
13
+ - `fetchNoteInfoByHash` and `buildNoteInfoUrlByHash` take a `cp1`, sent as
14
+ `p`.
15
+ - `noteIdOf(k1)` is the id a mint files a note under, for either kind.
16
+ `noteLookupOf(k1)` is what to look it up by without disclosing it.
17
+ - `noteSignatureMessage` and `noteSignatureDigest` build the message over the
18
+ key for a `ck1`.
19
+
3
20
  ## 0.10.0 - 2026-09-11
4
21
 
5
22
  **LUD-25 Part 2 building blocks.** Notes keyed by a public key and spent by a
package/README.md CHANGED
@@ -382,10 +382,14 @@ mint recovers `pk` from it to find the note. The mint's certificate, `cs1…`,
382
382
  is the same signature mints already make, over `hex(pk)` instead of a hash.
383
383
  So a recipient can check a note offline with nothing but `ck1` and `cs1`.
384
384
 
385
- This release has the building blocks: the four encodings, key derivation and
386
- ownership signatures. The wire calls that mint to `cp1` and spend with `ck1`
387
- come next. The names match lnurl-wallet's `src/lib`, so moving to it later is
388
- an import change.
385
+ The wire calls take both kinds. A `ck1` goes anywhere a `k1` does: a note
386
+ URL, `fetchNoteInfo`, rotate, split, merge and melt. A `cp1` goes anywhere an
387
+ output does: `requestInvoice`'s `h`, and the output of every `*WithHash` call,
388
+ where it is sent as `p1`/`p2` while a hash keeps `h`/`h2`, the same rule as
389
+ lnurl-wallet. `noteIdOf(k1)` gives the id a mint files either kind under, and
390
+ `noteLookupOf(k1)` what to pass `fetchNoteInfoByHash` to check a note without
391
+ disclosing it. The names match lnurl-wallet's `src/lib`, so moving to it
392
+ later is an import change.
389
393
 
390
394
  ```ts
391
395
  import {
package/dist/index.d.ts CHANGED
@@ -293,6 +293,8 @@ declare const deriveNotePubkey: (branchPubkeyXOnly: Uint8Array, chainCode: Uint8
293
293
  declare const deriveNoteSecretKey: (branchPrivateKey: Uint8Array, chainCode: Uint8Array, index: number) => Uint8Array;
294
294
  declare const signNoteOwnership: (secretKey: Uint8Array) => Uint8Array;
295
295
  declare const recoverNoteOwnershipPubkey: (signature: Uint8Array) => Uint8Array | null;
296
+ declare const noteIdOf: (k1: string) => string | null;
297
+ declare const noteLookupOf: (k1: string) => string | null;
296
298
  declare const deriveCashAddressNode: (root: CashNode, host: string) => CashNode;
297
299
  declare const cashNodeToCx1: (node: CashNode) => Cx1;
298
300
 
@@ -403,4 +405,4 @@ declare const createClient: (options?: LnurlcashOptions) => {
403
405
  };
404
406
  type LnurlcashClient = ReturnType<typeof createClient>;
405
407
 
406
- export { AmbiguousMintError, AmbiguousMutationError, type BoundMintCommitment, type CashNode, type Cx1, type DecodeOptions, HashLookupUnsupportedError, type HashedMutationResult, type HashedSplitResult, InsufficientValueError, type InvoiceRequestOptions, type InvoiceResult, type LnurlcashClient, LnurlcashError, type LnurlcashOptions, type MeltResult, type MintAddressInfo, type MintClaim, type MintContact, type MintFee, type MintFeeBand, type NoteInfoByHash, type NoteScheme, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, type PayRequestInfo, type PaymentRequest, type PaymentRequestMethodDetails, PendingNoteError, ProtocolError, type RandomSecret, RequestRefusedError, type RestoreOptions, type RestoreResult, type RestoredNote, type RotateResult, type SeedRestoreOptions, type SeedRestoreResult, ServiceRejectedError, type SettleForValueOptions, type SettledForValue, type SettledNote, type SignatureCheck, type SplitResult, type UnresolvedIndex, UnverifiableNoteError, type ValidatedBoundMintReceipt, type VerifyResult, type WithdrawRequestInfo, type WithdrawSuccessResponse, applyMintFee, buildNoteInfoUrlByHash, buildNoteUrl, cashDomainIndices, cashNodeFromHex, cashNodeToCx1, cashNodeToHex, cashSecretAt, cashSecretSource, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodeCk1, decodeCp1, decodeCs1, decodeCx1, decodePaymentRequest, defaultRandomSecret, deriveCashAddressNode, deriveCashChild, deriveCashDomainNode, deriveCashRoot, deriveCashSecret, deriveNotePubkey, deriveNoteRoot, deriveNoteSecret, deriveNoteSecretKey, derivedSecretSource, describeMintFee, encodeCk1, encodeCp1, encodeCs1, encodeCx1, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchNoteInfoByHash, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isCk1, isCp1, isCs1, isCx1, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeBatches, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, namesMintOutput, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, recoverNoteOwnershipPubkey, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreFromSeed, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, signNoteOwnership, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
408
+ export { AmbiguousMintError, AmbiguousMutationError, type BoundMintCommitment, type CashNode, type Cx1, type DecodeOptions, HashLookupUnsupportedError, type HashedMutationResult, type HashedSplitResult, InsufficientValueError, type InvoiceRequestOptions, type InvoiceResult, type LnurlcashClient, LnurlcashError, type LnurlcashOptions, type MeltResult, type MintAddressInfo, type MintClaim, type MintContact, type MintFee, type MintFeeBand, type NoteInfoByHash, type NoteScheme, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, type PayRequestInfo, type PaymentRequest, type PaymentRequestMethodDetails, PendingNoteError, ProtocolError, type RandomSecret, RequestRefusedError, type RestoreOptions, type RestoreResult, type RestoredNote, type RotateResult, type SeedRestoreOptions, type SeedRestoreResult, ServiceRejectedError, type SettleForValueOptions, type SettledForValue, type SettledNote, type SignatureCheck, type SplitResult, type UnresolvedIndex, UnverifiableNoteError, type ValidatedBoundMintReceipt, type VerifyResult, type WithdrawRequestInfo, type WithdrawSuccessResponse, applyMintFee, buildNoteInfoUrlByHash, buildNoteUrl, cashDomainIndices, cashNodeFromHex, cashNodeToCx1, cashNodeToHex, cashSecretAt, cashSecretSource, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodeCk1, decodeCp1, decodeCs1, decodeCx1, decodePaymentRequest, defaultRandomSecret, deriveCashAddressNode, deriveCashChild, deriveCashDomainNode, deriveCashRoot, deriveCashSecret, deriveNotePubkey, deriveNoteRoot, deriveNoteSecret, deriveNoteSecretKey, derivedSecretSource, describeMintFee, encodeCk1, encodeCp1, encodeCs1, encodeCx1, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchNoteInfoByHash, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isCk1, isCp1, isCs1, isCx1, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeBatches, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, namesMintOutput, newSecretsOf, noteDeclaredAmount, noteIdOf, noteK1, noteLookupOf, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, recoverNoteOwnershipPubkey, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreFromSeed, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, signNoteOwnership, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { bech32, base64urlnopad, bech32m } from '@scure/base';
2
- import { hmac } from '@noble/hashes/hmac.js';
2
+ import { secp256k1 } from '@noble/curves/secp256k1.js';
3
3
  import { sha256, sha512 } from '@noble/hashes/sha2.js';
4
4
  import { utf8ToBytes, bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
5
- import { secp256k1 } from '@noble/curves/secp256k1.js';
5
+ import { hmac } from '@noble/hashes/hmac.js';
6
6
 
7
7
  // src/urls.ts
8
8
  var isBech32Lnurl = (data) => data.trim().toUpperCase().startsWith("LNURL1");
@@ -120,104 +120,6 @@ var serverOf = (url) => {
120
120
  return url;
121
121
  }
122
122
  };
123
- var hashK1 = (k1) => bytesToHex(sha256(hexToBytes(k1)));
124
- var defaultRandomSecret = () => bytesToHex(crypto.getRandomValues(new Uint8Array(32)));
125
- var isPreimage = (value) => /^[0-9a-fA-F]{64}$/.test(value.trim());
126
- var NOTE_DERIVATION_DOMAIN = utf8ToBytes("lnurlcash-note-v1");
127
- var deriveNoteRoot = (seed) => hmac(sha256, NOTE_DERIVATION_DOMAIN, seed);
128
- var requireIndex = (index) => {
129
- if (!Number.isSafeInteger(index) || index < 0) {
130
- throw new RangeError(
131
- `A note index must be a non-negative integer, not ${index}.`
132
- );
133
- }
134
- return index;
135
- };
136
- var deriveNoteSecret = (root, host, index) => bytesToHex(hmac(sha256, root, utf8ToBytes(`${host}:${requireIndex(index)}`)));
137
- var derivedSecretSource = (root, host, start = 0) => {
138
- let next = requireIndex(start);
139
- const source = (() => deriveNoteSecret(root, host, next++));
140
- source.index = () => next;
141
- return source;
142
- };
143
-
144
- // src/note.ts
145
- var noteK1 = (url) => {
146
- try {
147
- return new URL(url).searchParams.get("k1")?.toLowerCase() ?? null;
148
- } catch {
149
- return null;
150
- }
151
- };
152
- var requireNoteK1 = (url) => {
153
- const k1 = noteK1(url);
154
- if (!k1) {
155
- throw new Error(
156
- "This note carries no secret in its URL - it may be held on a device."
157
- );
158
- }
159
- return k1;
160
- };
161
- var noteDeclaredAmount = (url) => {
162
- try {
163
- const raw = new URL(url).searchParams.get("amount");
164
- if (raw === null) return null;
165
- const n = Number(raw);
166
- return Number.isFinite(n) ? n : null;
167
- } catch {
168
- return null;
169
- }
170
- };
171
- var noteSignature = (url) => {
172
- try {
173
- return new URL(url).searchParams.get("sig");
174
- } catch {
175
- return null;
176
- }
177
- };
178
- var resolveNoteInput = (value) => {
179
- const url = resolveLnurlInput(value);
180
- const k1 = url ? noteK1(url) : null;
181
- if (!url || !k1 || !isPreimage(k1)) return null;
182
- return url;
183
- };
184
- var isValidNoteInput = (value) => resolveNoteInput(value) !== null;
185
- var buildNoteInfoUrlByHash = (withdrawLink, h) => {
186
- const hex = h.trim().toLowerCase();
187
- if (!/^[0-9a-f]{64}$/.test(hex)) {
188
- throw new Error("A note hash must be 32 bytes of hex.");
189
- }
190
- const url = new URL(fromLud17(withdrawLink.trim()));
191
- url.searchParams.delete("k1");
192
- url.searchParams.delete("amount");
193
- url.searchParams.delete("sig");
194
- url.searchParams.set("h", hex);
195
- return url.toString();
196
- };
197
- var buildNoteUrl = (withdrawLink, k1, amountMsat) => {
198
- const url = new URL(fromLud17(withdrawLink.trim()));
199
- url.searchParams.set("k1", k1.trim().toLowerCase());
200
- if (amountMsat !== void 0) {
201
- url.searchParams.set("amount", String(amountMsat));
202
- }
203
- return url.toString();
204
- };
205
- var withNewK1 = (url, k1, amountMsat, signature) => {
206
- const newUrl = new URL(url);
207
- newUrl.searchParams.set("k1", k1.toLowerCase());
208
- newUrl.searchParams.set("amount", String(amountMsat));
209
- if (signature) newUrl.searchParams.set("sig", signature);
210
- else newUrl.searchParams.delete("sig");
211
- return newUrl.toString();
212
- };
213
- var withoutK1 = (url, amountMsat, signature) => {
214
- const newUrl = new URL(url);
215
- newUrl.searchParams.delete("k1");
216
- newUrl.searchParams.set("amount", String(amountMsat));
217
- if (signature) newUrl.searchParams.set("sig", signature);
218
- else newUrl.searchParams.delete("sig");
219
- return newUrl.toString();
220
- };
221
123
  var HARDENED = 2147483648;
222
124
  var CURVE_N = secp256k1.Point.Fn.ORDER;
223
125
  var MASTER_KEY_DOMAIN = utf8ToBytes("Bitcoin seed");
@@ -272,7 +174,7 @@ var cashDomainIndices = (root, host) => {
272
174
  return [0, 4, 8, 12].map((offset) => readUint32BE(material, offset));
273
175
  };
274
176
  var deriveCashDomainNode = (root, host) => cashDomainIndices(root, host).reduce(deriveCashChild, root);
275
- var requireIndex2 = (index) => {
177
+ var requireIndex = (index) => {
276
178
  if (!Number.isSafeInteger(index) || index < 0 || index >= HARDENED) {
277
179
  throw new RangeError(
278
180
  `A note index must be an integer in [0, 2^31), not ${index}.`
@@ -281,7 +183,7 @@ var requireIndex2 = (index) => {
281
183
  return index;
282
184
  };
283
185
  var cashSecretAt = (domainNode, index) => bytesToHex(
284
- deriveCashChild(domainNode, requireIndex2(index) + HARDENED).privateKey
186
+ deriveCashChild(domainNode, requireIndex(index) + HARDENED).privateKey
285
187
  );
286
188
  var deriveCashSecret = (root, host, index) => cashSecretAt(deriveCashDomainNode(root, host), index);
287
189
  var cashNodeToHex = (node) => bytesToHex(node.privateKey) + bytesToHex(node.chainCode);
@@ -296,11 +198,33 @@ var cashNodeFromHex = (hex) => {
296
198
  };
297
199
  var cashSecretSource = (root, host, start = 0) => {
298
200
  const domainNode = deriveCashDomainNode(root, host);
299
- let next = requireIndex2(start);
201
+ let next = requireIndex(start);
300
202
  const source = (() => cashSecretAt(domainNode, next++));
301
203
  source.index = () => next;
302
204
  return source;
303
205
  };
206
+ var hashK1 = (k1) => bytesToHex(sha256(hexToBytes(k1)));
207
+ var defaultRandomSecret = () => bytesToHex(crypto.getRandomValues(new Uint8Array(32)));
208
+ var isPreimage = (value) => /^[0-9a-fA-F]{64}$/.test(value.trim());
209
+ var NOTE_DERIVATION_DOMAIN = utf8ToBytes("lnurlcash-note-v1");
210
+ var deriveNoteRoot = (seed) => hmac(sha256, NOTE_DERIVATION_DOMAIN, seed);
211
+ var requireIndex2 = (index) => {
212
+ if (!Number.isSafeInteger(index) || index < 0) {
213
+ throw new RangeError(
214
+ `A note index must be a non-negative integer, not ${index}.`
215
+ );
216
+ }
217
+ return index;
218
+ };
219
+ var deriveNoteSecret = (root, host, index) => bytesToHex(hmac(sha256, root, utf8ToBytes(`${host}:${requireIndex2(index)}`)));
220
+ var derivedSecretSource = (root, host, start = 0) => {
221
+ let next = requireIndex2(start);
222
+ const source = (() => deriveNoteSecret(root, host, next++));
223
+ source.index = () => next;
224
+ return source;
225
+ };
226
+
227
+ // src/recoverable.ts
304
228
  var encodeFixed = (hrp, bytes, length) => {
305
229
  if (bytes.length !== length) {
306
230
  throw new RangeError(`A ${hrp}1 payload is ${length} bytes, not ${bytes.length}.`);
@@ -404,12 +328,104 @@ var recoverNoteOwnershipPubkey = (signature) => {
404
328
  return null;
405
329
  }
406
330
  };
331
+ var noteIdOf = (k1) => {
332
+ if (typeof k1 !== "string") return null;
333
+ const value = k1.trim().toLowerCase();
334
+ if (isPreimage(value)) return hashK1(value);
335
+ const signature = decodeCk1(value);
336
+ const pubkey = signature ? recoverNoteOwnershipPubkey(signature) : null;
337
+ return pubkey ? bytesToHex(pubkey) : null;
338
+ };
339
+ var noteLookupOf = (k1) => {
340
+ const id = noteIdOf(k1);
341
+ if (id === null) return null;
342
+ return isCk1(k1.trim().toLowerCase()) ? encodeCp1(hexToBytes(id)) : id;
343
+ };
407
344
  var deriveCashAddressNode = (root, host) => deriveCashDomainNode(deriveCashChild(root, 1 + 2147483648), host);
408
345
  var cashNodeToCx1 = (node) => ({
409
346
  pubkeyXOnly: secp256k1.getPublicKey(node.privateKey, true).slice(1),
410
347
  chainCode: node.chainCode.slice()
411
348
  });
412
349
 
350
+ // src/note.ts
351
+ var noteK1 = (url) => {
352
+ try {
353
+ return new URL(url).searchParams.get("k1")?.toLowerCase() ?? null;
354
+ } catch {
355
+ return null;
356
+ }
357
+ };
358
+ var requireNoteK1 = (url) => {
359
+ const k1 = noteK1(url);
360
+ if (!k1) {
361
+ throw new Error(
362
+ "This note carries no secret in its URL - it may be held on a device."
363
+ );
364
+ }
365
+ return k1;
366
+ };
367
+ var noteDeclaredAmount = (url) => {
368
+ try {
369
+ const raw = new URL(url).searchParams.get("amount");
370
+ if (raw === null) return null;
371
+ const n = Number(raw);
372
+ return Number.isFinite(n) ? n : null;
373
+ } catch {
374
+ return null;
375
+ }
376
+ };
377
+ var noteSignature = (url) => {
378
+ try {
379
+ return new URL(url).searchParams.get("sig");
380
+ } catch {
381
+ return null;
382
+ }
383
+ };
384
+ var resolveNoteInput = (value) => {
385
+ const url = resolveLnurlInput(value);
386
+ const k1 = url ? noteK1(url) : null;
387
+ if (!url || !k1 || noteIdOf(k1) === null) return null;
388
+ return url;
389
+ };
390
+ var isValidNoteInput = (value) => resolveNoteInput(value) !== null;
391
+ var buildNoteInfoUrlByHash = (withdrawLink, h) => {
392
+ const value = h.trim().toLowerCase();
393
+ const key = isCp1(value);
394
+ if (!key && !/^[0-9a-f]{64}$/.test(value)) {
395
+ throw new Error("A note hash must be 32 bytes of hex, or a cp1 key.");
396
+ }
397
+ const url = new URL(fromLud17(withdrawLink.trim()));
398
+ url.searchParams.delete("k1");
399
+ url.searchParams.delete("amount");
400
+ url.searchParams.delete("sig");
401
+ url.searchParams.set(key ? "p" : "h", value);
402
+ return url.toString();
403
+ };
404
+ var buildNoteUrl = (withdrawLink, k1, amountMsat) => {
405
+ const url = new URL(fromLud17(withdrawLink.trim()));
406
+ url.searchParams.set("k1", k1.trim().toLowerCase());
407
+ if (amountMsat !== void 0) {
408
+ url.searchParams.set("amount", String(amountMsat));
409
+ }
410
+ return url.toString();
411
+ };
412
+ var withNewK1 = (url, k1, amountMsat, signature) => {
413
+ const newUrl = new URL(url);
414
+ newUrl.searchParams.set("k1", k1.toLowerCase());
415
+ newUrl.searchParams.set("amount", String(amountMsat));
416
+ if (signature) newUrl.searchParams.set("sig", signature);
417
+ else newUrl.searchParams.delete("sig");
418
+ return newUrl.toString();
419
+ };
420
+ var withoutK1 = (url, amountMsat, signature) => {
421
+ const newUrl = new URL(url);
422
+ newUrl.searchParams.delete("k1");
423
+ newUrl.searchParams.set("amount", String(amountMsat));
424
+ if (signature) newUrl.searchParams.set("sig", signature);
425
+ else newUrl.searchParams.delete("sig");
426
+ return newUrl.toString();
427
+ };
428
+
413
429
  // src/errors.ts
414
430
  var LnurlcashError = class extends Error {
415
431
  constructor(message) {
@@ -851,9 +867,14 @@ var describeMintFee = (fee) => [
851
867
  fee.feePpm > 0 ? `${formatFeePercent(fee.feePpm)}% of the amount paid` : null
852
868
  ].filter(Boolean).join(" + ");
853
869
  var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
854
- var noteSignatureMessage = (k1, amountMsat) => noteSignatureMessageForHash(hashK1(k1), amountMsat);
870
+ var requireNoteId = (k1) => {
871
+ const id = noteIdOf(k1);
872
+ if (id === null) throw new Error("A k1 is 32 bytes of hex or a ck1.");
873
+ return id;
874
+ };
875
+ var noteSignatureMessage = (k1, amountMsat) => noteSignatureMessageForHash(requireNoteId(k1), amountMsat);
855
876
  var noteSignatureMessageForHash = (h, amountMsat) => `LNURLcash:${amountMsat}:${h.trim().toLowerCase()}`;
856
- var noteSignatureDigest = (k1, amountMsat) => noteSignatureDigestForHash(hashK1(k1), amountMsat);
877
+ var noteSignatureDigest = (k1, amountMsat) => noteSignatureDigestForHash(requireNoteId(k1), amountMsat);
857
878
  var noteSignatureDigestForHash = (h, amountMsat) => sha256(
858
879
  sha256(
859
880
  new Uint8Array([
@@ -864,22 +885,10 @@ var noteSignatureDigestForHash = (h, amountMsat) => sha256(
864
885
  );
865
886
  var NO_MATCH = { valid: false, pubkey: null };
866
887
  var verifyNoteSignatureAgainst = (k1, amountMsat, signatureHex, mintPubkeys) => {
867
- const h = signedNoteId(k1);
888
+ const h = noteIdOf(k1);
868
889
  if (h === null) return NO_MATCH;
869
890
  return verifyNoteSignatureHashAgainst(h, amountMsat, signatureHex, mintPubkeys);
870
891
  };
871
- var signedNoteId = (k1) => {
872
- const ownership = decodeCk1(k1);
873
- if (ownership) {
874
- const pubkey = recoverNoteOwnershipPubkey(ownership);
875
- return pubkey ? bytesToHex(pubkey) : null;
876
- }
877
- try {
878
- return hashK1(k1);
879
- } catch {
880
- return null;
881
- }
882
- };
883
892
  var verifyNoteSignatureHashAgainst = (h, amountMsat, signatureHex, mintPubkeys) => {
884
893
  const targets = (Array.isArray(mintPubkeys) ? mintPubkeys : [mintPubkeys]).filter((key) => typeof key === "string").map((key) => key.trim().toLowerCase());
885
894
  if (targets.length === 0) return NO_MATCH;
@@ -1141,12 +1150,16 @@ var meltNote = async (callback, k1, pr, options = {}) => {
1141
1150
  pr: typeof body.pr === "string" ? body.pr : void 0
1142
1151
  };
1143
1152
  };
1153
+ var outputParam = (value, which) => {
1154
+ const cp1 = isCp1(value.trim().toLowerCase());
1155
+ return [cp1 ? `p${which}` : which === 1 ? "h" : "h2", value];
1156
+ };
1144
1157
  var rotateNoteWithHash = async (callback, k1, h, options = {}) => {
1145
1158
  const body = await replayableCallbackRequest(
1146
1159
  callback,
1147
1160
  [
1148
1161
  ["k1", k1],
1149
- ["h", h]
1162
+ outputParam(h, 1)
1150
1163
  ],
1151
1164
  options
1152
1165
  );
@@ -1158,8 +1171,8 @@ var splitNoteWithHash = async (callback, k1s, amountMsat, h, h2, options = {}) =
1158
1171
  [
1159
1172
  ...k1s.map((k1) => ["k1", k1]),
1160
1173
  ["amount", String(amountMsat)],
1161
- ["h", h],
1162
- ["h2", h2]
1174
+ outputParam(h, 1),
1175
+ outputParam(h2, 2)
1163
1176
  ],
1164
1177
  options
1165
1178
  );
@@ -1171,7 +1184,7 @@ var splitNoteWithHash = async (callback, k1s, amountMsat, h, h2, options = {}) =
1171
1184
  var mergeNotesWithHash = async (callback, k1s, h, options = {}) => {
1172
1185
  const body = await replayableCallbackRequest(
1173
1186
  callback,
1174
- [...k1s.map((k1) => ["k1", k1]), ["h", h]],
1187
+ [...k1s.map((k1) => ["k1", k1]), outputParam(h, 1)],
1175
1188
  options
1176
1189
  );
1177
1190
  return { signature: requireSignature(body.sig, options, "merge") };
@@ -1367,14 +1380,17 @@ var requestInvoice = async (payCallback, amountMsat, options = {}) => {
1367
1380
  const cbUrl = new URL(payCallback);
1368
1381
  cbUrl.searchParams.set("amount", String(amountMsat));
1369
1382
  if (options.h !== void 0) {
1370
- if (!isPreimage(options.h)) {
1383
+ const h = options.h.trim().toLowerCase();
1384
+ if (isCp1(h)) {
1385
+ cbUrl.searchParams.set("comment", h);
1386
+ } else if (isPreimage(h)) {
1387
+ cbUrl.searchParams.set("comment", h);
1388
+ cbUrl.searchParams.set("h", h);
1389
+ } else {
1371
1390
  throw new RequestRefusedError(
1372
- "An output hash must be 32 bytes of hex - no invoice was requested."
1391
+ "An output must be 32 bytes of hex or a cp1 key - no invoice was requested."
1373
1392
  );
1374
1393
  }
1375
- const h = options.h.trim().toLowerCase();
1376
- cbUrl.searchParams.set("comment", h);
1377
- cbUrl.searchParams.set("h", h);
1378
1394
  }
1379
1395
  const body = await lnurlFetch(cbUrl, resolveOptions(options));
1380
1396
  if (typeof body?.pr !== "string") {
@@ -1722,4 +1738,4 @@ var createClient = (options = {}) => ({
1722
1738
  settleNoteForValue: (noteUrl, terms) => settleNoteForValue(noteUrl, terms, options)
1723
1739
  });
1724
1740
 
1725
- export { AmbiguousMintError, AmbiguousMutationError, HashLookupUnsupportedError, InsufficientValueError, LnurlcashError, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, UnverifiableNoteError, applyMintFee, buildNoteInfoUrlByHash, buildNoteUrl, cashDomainIndices, cashNodeFromHex, cashNodeToCx1, cashNodeToHex, cashSecretAt, cashSecretSource, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodeCk1, decodeCp1, decodeCs1, decodeCx1, decodePaymentRequest, defaultRandomSecret, deriveCashAddressNode, deriveCashChild, deriveCashDomainNode, deriveCashRoot, deriveCashSecret, deriveNotePubkey, deriveNoteRoot, deriveNoteSecret, deriveNoteSecretKey, derivedSecretSource, describeMintFee, encodeCk1, encodeCp1, encodeCs1, encodeCx1, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchNoteInfoByHash, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isCk1, isCp1, isCs1, isCx1, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeBatches, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, namesMintOutput, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, recoverNoteOwnershipPubkey, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreFromSeed, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, signNoteOwnership, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
1741
+ export { AmbiguousMintError, AmbiguousMutationError, HashLookupUnsupportedError, InsufficientValueError, LnurlcashError, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, UnverifiableNoteError, applyMintFee, buildNoteInfoUrlByHash, buildNoteUrl, cashDomainIndices, cashNodeFromHex, cashNodeToCx1, cashNodeToHex, cashSecretAt, cashSecretSource, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodeCk1, decodeCp1, decodeCs1, decodeCx1, decodePaymentRequest, defaultRandomSecret, deriveCashAddressNode, deriveCashChild, deriveCashDomainNode, deriveCashRoot, deriveCashSecret, deriveNotePubkey, deriveNoteRoot, deriveNoteSecret, deriveNoteSecretKey, derivedSecretSource, describeMintFee, encodeCk1, encodeCp1, encodeCs1, encodeCx1, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchNoteInfoByHash, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isCk1, isCp1, isCs1, isCx1, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeBatches, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, namesMintOutput, newSecretsOf, noteDeclaredAmount, noteIdOf, noteK1, noteLookupOf, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, recoverNoteOwnershipPubkey, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreFromSeed, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, signNoteOwnership, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
package/llms.txt CHANGED
@@ -64,6 +64,11 @@ deriveNoteSecretKey(p, chainCode, i) -> sk negates p first when P has odd y
64
64
  signNoteOwnership(sk) -> 65 bytes RFC6979 over fixed "LNURLcash"
65
65
  recoverNoteOwnershipPubkey(sig) -> pk | null
66
66
  verifyNoteSignature also takes a ck1 as k1 and a cs1 as the signature
67
+ noteIdOf(k1) -> id | null sha256(k1) for a secret, the recovered key for a
68
+ ck1. One note has many valid ck1 strings: compare notes by this, not by k1
69
+ noteLookupOf(k1) -> string hash, or cp1 for a ck1; for fetchNoteInfoByHash
70
+ Wire: a ck1 goes wherever a k1 does; a cp1 wherever an output does
71
+ (requestInvoice h, *WithHash outputs: cp1 sent as p1/p2, hash as h/h2)
67
72
  deriveNoteRoot(seedBytes) -> Uint8Array LEGACY, pre-spec. Do not mint under
68
73
  deriveNoteSecret(root, host, index) -> k1 it; still scanned so old notes live.
69
74
  derivedSecretSource(root, host, start) -> RandomSecret & {index()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lnurlcash-kit",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "LNURLcash (LUD-25) bearer note client for TypeScript - mint, rotate, split, merge, melt, and verify offline",
5
5
  "author": "TheCryptoDonkey",
6
6
  "license": "MIT",