lnurlcash-kit 0.2.1 → 0.3.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 +12 -0
- package/README.md +41 -0
- package/dist/index.d.ts +17 -1
- package/dist/index.js +119 -46
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
Semantic versioning. While the LUD-25 draft is unmerged, `0.x` minor bumps
|
|
4
4
|
may carry breaking changes; pin an exact version.
|
|
5
5
|
|
|
6
|
+
## 0.3.0 - 2026-08-24
|
|
7
|
+
|
|
8
|
+
- Additive bound-mint receipt parsing and validation for sealed signers:
|
|
9
|
+
`InvoiceResult.mint`, `VerifyResult.mint`, `requireBoundMintQuote` and
|
|
10
|
+
`validateBoundMintReceipt`. The quote must commit the requested `h` and
|
|
11
|
+
exact net amount before payment; the settled LUD-21 response must match the
|
|
12
|
+
invoice and commitment and carry a valid ordinary LUD-25 note signature.
|
|
13
|
+
- `noteSignatureMessageForHash`, `noteSignatureDigestForHash`,
|
|
14
|
+
`verifyNoteSignatureHash` and `verifyNoteSignatureHashAgainst` expose the
|
|
15
|
+
existing signature construction when a signer deliberately retains `k1`.
|
|
16
|
+
Existing secret-based helpers delegate to them unchanged.
|
|
17
|
+
|
|
6
18
|
## 0.2.1 - 2026-08-22
|
|
7
19
|
|
|
8
20
|
- `WithdrawRequestInfo` carries `payLink`, the way home a SERVICE may
|
package/README.md
CHANGED
|
@@ -358,6 +358,47 @@ is yours from the moment it exists. The claim GET does show the secret to the
|
|
|
358
358
|
mint it is a claim on, which is a different thing from showing it to whoever
|
|
359
359
|
scanned the QR, and you can still rotate if you want the offline signature.
|
|
360
360
|
|
|
361
|
+
### A sealed signer: confirm without exporting the secret
|
|
362
|
+
|
|
363
|
+
A hardware vault cannot use the claim GET above without handing `k1` to its
|
|
364
|
+
companion. A receipt-capable mint can instead commit the quote to the requested
|
|
365
|
+
`h` and exact net amount, then place the ordinary LUD-25 note signature on its
|
|
366
|
+
settled LUD-21 response. The extension is optional; absence means use the
|
|
367
|
+
unchanged preimage-import-and-rotate flow before showing an invoice.
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import {
|
|
371
|
+
requestInvoice, fetchInvoiceVerification,
|
|
372
|
+
requireBoundMintQuote, validateBoundMintReceipt
|
|
373
|
+
} from 'lnurlcash-kit'
|
|
374
|
+
|
|
375
|
+
const staged = await vault.newSecret() // {id, h}; k1 stays in the vault
|
|
376
|
+
const expectedNetMsat = 21_000
|
|
377
|
+
const quote = await requestInvoice(pay.callback, 21_000, {h: staged.h})
|
|
378
|
+
|
|
379
|
+
// Do this before displaying or paying quote.pr.
|
|
380
|
+
requireBoundMintQuote(quote, staged.h, expectedNetMsat)
|
|
381
|
+
if (!quote.verify) throw new Error('No settlement receipt offered')
|
|
382
|
+
|
|
383
|
+
// After payment, poll quote.verify until settled.
|
|
384
|
+
const verification = await fetchInvoiceVerification(quote.verify)
|
|
385
|
+
const receipt = validateBoundMintReceipt(
|
|
386
|
+
quote,
|
|
387
|
+
verification,
|
|
388
|
+
staged.h,
|
|
389
|
+
expectedNetMsat,
|
|
390
|
+
pinnedMintPubkeys
|
|
391
|
+
)
|
|
392
|
+
await vault.confirm(staged.id, receipt.amountMsat, mintHost, receipt.signature)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
`quote.mint` is `{h, amountMsat}` in the typed API (`amount` on the wire).
|
|
396
|
+
The settled response must repeat that commitment and add `signature` (`sig`
|
|
397
|
+
on the wire). Validation matches the invoice, output and amount, refuses a
|
|
398
|
+
pre-settlement signature, and recovers the signer against the pinned current
|
|
399
|
+
or previous mint keys. The payment preimage remains proof of payment; it never
|
|
400
|
+
replaces the vault's staged secret.
|
|
401
|
+
|
|
361
402
|
## Asking to be paid
|
|
362
403
|
|
|
363
404
|
"Send me 500 sat" today means handing over a Lightning Address, which is a
|
package/dist/index.d.ts
CHANGED
|
@@ -133,6 +133,15 @@ type InvoiceResult = {
|
|
|
133
133
|
verify?: string;
|
|
134
134
|
disposable: boolean;
|
|
135
135
|
mintToHash: boolean;
|
|
136
|
+
mint?: BoundMintCommitment;
|
|
137
|
+
};
|
|
138
|
+
type BoundMintCommitment = {
|
|
139
|
+
h: string;
|
|
140
|
+
amountMsat: number;
|
|
141
|
+
signature?: string;
|
|
142
|
+
};
|
|
143
|
+
type ValidatedBoundMintReceipt = Required<BoundMintCommitment> & {
|
|
144
|
+
pubkey: string;
|
|
136
145
|
};
|
|
137
146
|
type InvoiceRequestOptions = LnurlcashOptions & {
|
|
138
147
|
h?: string;
|
|
@@ -142,8 +151,11 @@ type VerifyResult = {
|
|
|
142
151
|
settled: boolean;
|
|
143
152
|
preimage: string | null;
|
|
144
153
|
pr: string;
|
|
154
|
+
mint?: BoundMintCommitment;
|
|
145
155
|
};
|
|
146
156
|
declare const fetchInvoiceVerification: (verifyUrl: string, options?: LnurlcashOptions) => Promise<VerifyResult>;
|
|
157
|
+
declare const requireBoundMintQuote: (invoice: InvoiceResult, expectedH: string, expectedAmountMsat: number) => BoundMintCommitment;
|
|
158
|
+
declare const validateBoundMintReceipt: (invoice: InvoiceResult, verification: VerifyResult, expectedH: string, expectedAmountMsat: number, mintPubkeys: string | string[]) => ValidatedBoundMintReceipt;
|
|
147
159
|
type MintClaim = {
|
|
148
160
|
state: 'minted' | 'unminted' | 'pending' | 'spent';
|
|
149
161
|
k1: string;
|
|
@@ -226,7 +238,9 @@ declare const decodePaymentRequest: (value: string, { now }?: DecodeOptions) =>
|
|
|
226
238
|
declare const isPaymentRequest: (value: string) => boolean;
|
|
227
239
|
|
|
228
240
|
declare const noteSignatureMessage: (k1: string, amountMsat: number) => string;
|
|
241
|
+
declare const noteSignatureMessageForHash: (h: string, amountMsat: number) => string;
|
|
229
242
|
declare const noteSignatureDigest: (k1: string, amountMsat: number) => Uint8Array;
|
|
243
|
+
declare const noteSignatureDigestForHash: (h: string, amountMsat: number) => Uint8Array;
|
|
230
244
|
type SignatureCheck = {
|
|
231
245
|
valid: true;
|
|
232
246
|
pubkey: string;
|
|
@@ -235,7 +249,9 @@ type SignatureCheck = {
|
|
|
235
249
|
pubkey: null;
|
|
236
250
|
};
|
|
237
251
|
declare const verifyNoteSignatureAgainst: (k1: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => SignatureCheck;
|
|
252
|
+
declare const verifyNoteSignatureHashAgainst: (h: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => SignatureCheck;
|
|
238
253
|
declare const verifyNoteSignature: (k1: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => boolean;
|
|
254
|
+
declare const verifyNoteSignatureHash: (h: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => boolean;
|
|
239
255
|
|
|
240
256
|
declare const isBolt11Invoice: (value: string) => boolean;
|
|
241
257
|
declare const sameInvoice: (a: string, b: string) => boolean;
|
|
@@ -297,4 +313,4 @@ declare const createClient: (options?: LnurlcashOptions) => {
|
|
|
297
313
|
};
|
|
298
314
|
type LnurlcashClient = ReturnType<typeof createClient>;
|
|
299
315
|
|
|
300
|
-
export { AmbiguousMintError, AmbiguousMutationError, type DecodeOptions, 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, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, type PayRequestInfo, type PaymentRequest, type PaymentRequestMethodDetails, PendingNoteError, ProtocolError, type RandomSecret, RequestRefusedError, type RestoreOptions, type RestoreResult, type RestoredNote, type RotateResult, ServiceRejectedError, type SettleForValueOptions, type SettledForValue, type SettledNote, type SignatureCheck, type SplitResult, type VerifyResult, type WithdrawRequestInfo, type WithdrawSuccessResponse, applyMintFee, buildNoteUrl, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodePaymentRequest, defaultRandomSecret, deriveNoteRoot, deriveNoteSecret, derivedSecretSource, describeMintFee, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureMessage, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, requestInvoice, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, verifyNoteSignature, verifyNoteSignatureAgainst, withNewK1, withinMintFeeBand, withoutK1 };
|
|
316
|
+
export { AmbiguousMintError, AmbiguousMutationError, type BoundMintCommitment, type DecodeOptions, 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, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, type PayRequestInfo, type PaymentRequest, type PaymentRequestMethodDetails, PendingNoteError, ProtocolError, type RandomSecret, RequestRefusedError, type RestoreOptions, type RestoreResult, type RestoredNote, type RotateResult, ServiceRejectedError, type SettleForValueOptions, type SettledForValue, type SettledNote, type SignatureCheck, type SplitResult, type ValidatedBoundMintReceipt, type VerifyResult, type WithdrawRequestInfo, type WithdrawSuccessResponse, applyMintFee, buildNoteUrl, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodePaymentRequest, defaultRandomSecret, deriveNoteRoot, deriveNoteSecret, derivedSecretSource, describeMintFee, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
|
package/dist/index.js
CHANGED
|
@@ -627,6 +627,62 @@ var describeMintFee = (fee) => [
|
|
|
627
627
|
fee.baseFeeMsat > 0 ? `${Math.round(fee.baseFeeMsat / 1e3)} sat flat` : null,
|
|
628
628
|
fee.feePpm > 0 ? `${formatFeePercent(fee.feePpm)}% of the amount paid` : null
|
|
629
629
|
].filter(Boolean).join(" + ");
|
|
630
|
+
var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
|
|
631
|
+
var noteSignatureMessage = (k1, amountMsat) => noteSignatureMessageForHash(hashK1(k1), amountMsat);
|
|
632
|
+
var noteSignatureMessageForHash = (h, amountMsat) => `LNURLcash:${amountMsat}:${h.trim().toLowerCase()}`;
|
|
633
|
+
var noteSignatureDigest = (k1, amountMsat) => noteSignatureDigestForHash(hashK1(k1), amountMsat);
|
|
634
|
+
var noteSignatureDigestForHash = (h, amountMsat) => sha256(
|
|
635
|
+
sha256(
|
|
636
|
+
new Uint8Array([
|
|
637
|
+
...LIGHTNING_SIGNED_MESSAGE_PREFIX,
|
|
638
|
+
...utf8ToBytes(noteSignatureMessageForHash(h, amountMsat))
|
|
639
|
+
])
|
|
640
|
+
)
|
|
641
|
+
);
|
|
642
|
+
var NO_MATCH = { valid: false, pubkey: null };
|
|
643
|
+
var verifyNoteSignatureAgainst = (k1, amountMsat, signatureHex, mintPubkeys) => {
|
|
644
|
+
let h;
|
|
645
|
+
try {
|
|
646
|
+
h = hashK1(k1);
|
|
647
|
+
} catch {
|
|
648
|
+
return NO_MATCH;
|
|
649
|
+
}
|
|
650
|
+
return verifyNoteSignatureHashAgainst(h, amountMsat, signatureHex, mintPubkeys);
|
|
651
|
+
};
|
|
652
|
+
var verifyNoteSignatureHashAgainst = (h, amountMsat, signatureHex, mintPubkeys) => {
|
|
653
|
+
const targets = (Array.isArray(mintPubkeys) ? mintPubkeys : [mintPubkeys]).filter((key) => typeof key === "string").map((key) => key.trim().toLowerCase());
|
|
654
|
+
if (targets.length === 0) return NO_MATCH;
|
|
655
|
+
let wireSig;
|
|
656
|
+
try {
|
|
657
|
+
wireSig = hexToBytes(signatureHex);
|
|
658
|
+
} catch {
|
|
659
|
+
return NO_MATCH;
|
|
660
|
+
}
|
|
661
|
+
if (wireSig.length !== 65) return NO_MATCH;
|
|
662
|
+
let digest;
|
|
663
|
+
try {
|
|
664
|
+
if (!/^[0-9a-fA-F]{64}$/.test(h.trim())) return NO_MATCH;
|
|
665
|
+
digest = noteSignatureDigestForHash(h, amountMsat);
|
|
666
|
+
} catch {
|
|
667
|
+
return NO_MATCH;
|
|
668
|
+
}
|
|
669
|
+
const recoveryIdFirst = new Uint8Array([
|
|
670
|
+
wireSig[64],
|
|
671
|
+
...wireSig.subarray(0, 64)
|
|
672
|
+
]);
|
|
673
|
+
for (const candidate of [recoveryIdFirst, wireSig]) {
|
|
674
|
+
try {
|
|
675
|
+
const recovered = bytesToHex(
|
|
676
|
+
secp256k1.recoverPublicKey(candidate, digest, { prehash: false })
|
|
677
|
+
);
|
|
678
|
+
if (targets.includes(recovered)) return { valid: true, pubkey: recovered };
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return NO_MATCH;
|
|
683
|
+
};
|
|
684
|
+
var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureAgainst(k1, amountMsat, signatureHex, mintPubkeys).valid;
|
|
685
|
+
var verifyNoteSignatureHash = (h, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureHashAgainst(h, amountMsat, signatureHex, mintPubkeys).valid;
|
|
630
686
|
|
|
631
687
|
// src/client.ts
|
|
632
688
|
var fetchNoteInfo = async (url, options = {}) => {
|
|
@@ -905,6 +961,18 @@ var fetchPayRequest = async (url, options = {}) => {
|
|
|
905
961
|
mintToHash: asBoolean(body.mintToHash)
|
|
906
962
|
};
|
|
907
963
|
};
|
|
964
|
+
var asBoundMintCommitment = (value) => {
|
|
965
|
+
if (!value || typeof value !== "object") return void 0;
|
|
966
|
+
const raw = value;
|
|
967
|
+
if (typeof raw.h !== "string" || !/^[0-9a-fA-F]{64}$/.test(raw.h) || typeof raw.amount !== "number" || !Number.isSafeInteger(raw.amount) || raw.amount <= 0 || raw.sig !== void 0 && typeof raw.sig !== "string") {
|
|
968
|
+
return void 0;
|
|
969
|
+
}
|
|
970
|
+
return {
|
|
971
|
+
h: raw.h.toLowerCase(),
|
|
972
|
+
amountMsat: raw.amount,
|
|
973
|
+
...typeof raw.sig === "string" ? { signature: raw.sig } : {}
|
|
974
|
+
};
|
|
975
|
+
};
|
|
908
976
|
var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
909
977
|
const cbUrl = new URL(payCallback);
|
|
910
978
|
cbUrl.searchParams.set("amount", String(amountMsat));
|
|
@@ -930,7 +998,8 @@ var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
|
930
998
|
pr: body.pr,
|
|
931
999
|
verify: typeof body.verify === "string" ? body.verify : void 0,
|
|
932
1000
|
disposable: body.disposable !== false,
|
|
933
|
-
mintToHash: body.mintToHash === true
|
|
1001
|
+
mintToHash: body.mintToHash === true,
|
|
1002
|
+
mint: asBoundMintCommitment(body.mint)
|
|
934
1003
|
};
|
|
935
1004
|
};
|
|
936
1005
|
var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
@@ -941,9 +1010,56 @@ var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
|
941
1010
|
return {
|
|
942
1011
|
settled: body.settled,
|
|
943
1012
|
preimage: typeof body.preimage === "string" ? body.preimage : null,
|
|
944
|
-
pr: body.pr
|
|
1013
|
+
pr: body.pr,
|
|
1014
|
+
mint: asBoundMintCommitment(body.mint)
|
|
945
1015
|
};
|
|
946
1016
|
};
|
|
1017
|
+
var requireBoundMintQuote = (invoice, expectedH, expectedAmountMsat) => {
|
|
1018
|
+
const h = expectedH.trim().toLowerCase();
|
|
1019
|
+
if (!isPreimage(h)) throw new ProtocolError("The expected mint output hash is malformed.");
|
|
1020
|
+
if (!Number.isSafeInteger(expectedAmountMsat) || expectedAmountMsat <= 0) {
|
|
1021
|
+
throw new ProtocolError("The expected mint amount must be positive integer millisatoshis.");
|
|
1022
|
+
}
|
|
1023
|
+
if (!invoice.mintToHash || !invoice.mint) {
|
|
1024
|
+
throw new ProtocolError("The service did not commit this quote to a bound mint output.");
|
|
1025
|
+
}
|
|
1026
|
+
if (invoice.mint.h !== h) {
|
|
1027
|
+
throw new ProtocolError("The service committed the quote to a different mint output.");
|
|
1028
|
+
}
|
|
1029
|
+
if (invoice.mint.amountMsat !== expectedAmountMsat) {
|
|
1030
|
+
throw new ProtocolError("The service committed the quote to a different mint amount.");
|
|
1031
|
+
}
|
|
1032
|
+
if (invoice.mint.signature !== void 0) {
|
|
1033
|
+
throw new ProtocolError("The service signed a mint output before the invoice settled.");
|
|
1034
|
+
}
|
|
1035
|
+
return invoice.mint;
|
|
1036
|
+
};
|
|
1037
|
+
var validateBoundMintReceipt = (invoice, verification, expectedH, expectedAmountMsat, mintPubkeys) => {
|
|
1038
|
+
const quote = requireBoundMintQuote(invoice, expectedH, expectedAmountMsat);
|
|
1039
|
+
if (!verification.settled) {
|
|
1040
|
+
throw new ProtocolError("The invoice has not settled.");
|
|
1041
|
+
}
|
|
1042
|
+
if (!sameInvoice(invoice.pr, verification.pr)) {
|
|
1043
|
+
throw new ProtocolError("The settlement receipt names a different invoice.");
|
|
1044
|
+
}
|
|
1045
|
+
const receipt = verification.mint;
|
|
1046
|
+
if (!receipt || receipt.h !== quote.h || receipt.amountMsat !== quote.amountMsat) {
|
|
1047
|
+
throw new ProtocolError("The settlement receipt does not match the mint quote.");
|
|
1048
|
+
}
|
|
1049
|
+
if (!receipt.signature) {
|
|
1050
|
+
throw new ProtocolError("The settled mint receipt has no note signature.");
|
|
1051
|
+
}
|
|
1052
|
+
const checked = verifyNoteSignatureHashAgainst(
|
|
1053
|
+
receipt.h,
|
|
1054
|
+
receipt.amountMsat,
|
|
1055
|
+
receipt.signature,
|
|
1056
|
+
mintPubkeys
|
|
1057
|
+
);
|
|
1058
|
+
if (!checked.valid) {
|
|
1059
|
+
throw new ProtocolError("The settled mint receipt has an invalid note signature.");
|
|
1060
|
+
}
|
|
1061
|
+
return { ...receipt, signature: receipt.signature, pubkey: checked.pubkey };
|
|
1062
|
+
};
|
|
947
1063
|
var claimMintedNote = async (withdrawLink, k1, options = {}) => {
|
|
948
1064
|
const secret = k1.trim().toLowerCase();
|
|
949
1065
|
if (!isPreimage(secret)) {
|
|
@@ -971,49 +1087,6 @@ var claimMintedNote = async (withdrawLink, k1, options = {}) => {
|
|
|
971
1087
|
throw err;
|
|
972
1088
|
}
|
|
973
1089
|
};
|
|
974
|
-
var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
|
|
975
|
-
var noteSignatureMessage = (k1, amountMsat) => `LNURLcash:${amountMsat}:${hashK1(k1)}`;
|
|
976
|
-
var noteSignatureDigest = (k1, amountMsat) => sha256(
|
|
977
|
-
sha256(
|
|
978
|
-
new Uint8Array([
|
|
979
|
-
...LIGHTNING_SIGNED_MESSAGE_PREFIX,
|
|
980
|
-
...utf8ToBytes(noteSignatureMessage(k1, amountMsat))
|
|
981
|
-
])
|
|
982
|
-
)
|
|
983
|
-
);
|
|
984
|
-
var NO_MATCH = { valid: false, pubkey: null };
|
|
985
|
-
var verifyNoteSignatureAgainst = (k1, amountMsat, signatureHex, mintPubkeys) => {
|
|
986
|
-
const targets = (Array.isArray(mintPubkeys) ? mintPubkeys : [mintPubkeys]).filter((key) => typeof key === "string").map((key) => key.trim().toLowerCase());
|
|
987
|
-
if (targets.length === 0) return NO_MATCH;
|
|
988
|
-
let wireSig;
|
|
989
|
-
try {
|
|
990
|
-
wireSig = hexToBytes(signatureHex);
|
|
991
|
-
} catch {
|
|
992
|
-
return NO_MATCH;
|
|
993
|
-
}
|
|
994
|
-
if (wireSig.length !== 65) return NO_MATCH;
|
|
995
|
-
let digest;
|
|
996
|
-
try {
|
|
997
|
-
digest = noteSignatureDigest(k1, amountMsat);
|
|
998
|
-
} catch {
|
|
999
|
-
return NO_MATCH;
|
|
1000
|
-
}
|
|
1001
|
-
const recoveryIdFirst = new Uint8Array([
|
|
1002
|
-
wireSig[64],
|
|
1003
|
-
...wireSig.subarray(0, 64)
|
|
1004
|
-
]);
|
|
1005
|
-
for (const candidate of [recoveryIdFirst, wireSig]) {
|
|
1006
|
-
try {
|
|
1007
|
-
const recovered = bytesToHex(
|
|
1008
|
-
secp256k1.recoverPublicKey(candidate, digest, { prehash: false })
|
|
1009
|
-
);
|
|
1010
|
-
if (targets.includes(recovered)) return { valid: true, pubkey: recovered };
|
|
1011
|
-
} catch {
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
return NO_MATCH;
|
|
1015
|
-
};
|
|
1016
|
-
var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureAgainst(k1, amountMsat, signatureHex, mintPubkeys).valid;
|
|
1017
1090
|
|
|
1018
1091
|
// src/settle.ts
|
|
1019
1092
|
var normaliseHost = (value) => serverOf(value.trim().replace(/^@/, "")).toLowerCase();
|
|
@@ -1119,4 +1192,4 @@ var createClient = (options = {}) => ({
|
|
|
1119
1192
|
settleNoteForValue: (noteUrl, terms) => settleNoteForValue(noteUrl, terms, options)
|
|
1120
1193
|
});
|
|
1121
1194
|
|
|
1122
|
-
export { AmbiguousMintError, AmbiguousMutationError, InsufficientValueError, LnurlcashError, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, applyMintFee, buildNoteUrl, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodePaymentRequest, defaultRandomSecret, deriveNoteRoot, deriveNoteSecret, derivedSecretSource, describeMintFee, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureMessage, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, requestInvoice, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, verifyNoteSignature, verifyNoteSignatureAgainst, withNewK1, withinMintFeeBand, withoutK1 };
|
|
1195
|
+
export { AmbiguousMintError, AmbiguousMutationError, InsufficientValueError, LnurlcashError, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, applyMintFee, buildNoteUrl, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodePaymentRequest, defaultRandomSecret, deriveNoteRoot, deriveNoteSecret, derivedSecretSource, describeMintFee, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lnurlcash-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/node": "^26.2.0",
|
|
63
|
-
"lnurlcash-conformance": "^0.
|
|
63
|
+
"lnurlcash-conformance": "^0.3.0",
|
|
64
64
|
"tsup": "^8.5.0",
|
|
65
65
|
"typescript": "^5.7.0",
|
|
66
66
|
"vitest": "^3.0.0"
|