lnurlcash-kit 0.12.1 → 0.13.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 +24 -0
- package/README.md +14 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14 -8
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0 - 2026-09-11
|
|
4
|
+
|
|
5
|
+
**A plain note is unsigned.** LUD-25 Part 2 certifies `cp1` notes only, and
|
|
6
|
+
the reference mint and moneyer now answer a rotate, split or merge to a hash
|
|
7
|
+
output with a bare `{"status":"OK"}`. This library follows.
|
|
8
|
+
|
|
9
|
+
- `requireSignatures` now defaults to **false**. A hash output that comes
|
|
10
|
+
back unsigned is the spec, not a fault: `signature` (and `changeSignature`)
|
|
11
|
+
is undefined, and no `UnverifiableNoteError` is raised. Set it true to
|
|
12
|
+
keep demanding the old Part 1 signature over the hash.
|
|
13
|
+
- A `cp1` output is owed its `cs1` certificate regardless of the option: a
|
|
14
|
+
rotate, split or merge naming one that comes back without `sig` (or
|
|
15
|
+
`sig2` for a `cp1` change) raises `UnverifiableNoteError`, carrying the
|
|
16
|
+
fresh secrets as before.
|
|
17
|
+
- New option `requireMintPubkey`, default true, takes over the
|
|
18
|
+
`withdrawRequest` check that `requireSignatures` used to carry. A Part
|
|
19
|
+
1-only mint that publishes no `mintPubkey` is admitted with it set false.
|
|
20
|
+
- Graded by lnurlcash-conformance 0.10.0's response vectors, which now
|
|
21
|
+
carry `output`/`change: "cp1"` on the cases that mint a `cp1` note.
|
|
22
|
+
|
|
23
|
+
If you relied on the default to refuse unsigned plain notes, set
|
|
24
|
+
`requireSignatures: true`; if you only ever wanted verifiable notes, hold
|
|
25
|
+
`cp1` notes, which are the only kind the spec makes verifiable.
|
|
26
|
+
|
|
3
27
|
## 0.12.1 - 2026-09-11
|
|
4
28
|
|
|
5
29
|
- `fetchNoteInfo` compares the mint's echoed `k1` with the one it asked
|
package/README.md
CHANGED
|
@@ -58,14 +58,14 @@ console.log(info.maxWithdrawable, 'msat')
|
|
|
58
58
|
// that GET put the secret on the wire, so rotate it
|
|
59
59
|
const fresh = await rotateNote(info.callback, info.k1)
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
61
|
+
// A plain note comes back unsigned: LUD-25 Part 2 certifies cp1 notes
|
|
62
|
+
// only, and a hash has nothing to attest to without disclosing the secret.
|
|
63
|
+
// To hold something a recipient can check offline, rotate into a cp1 key
|
|
64
|
+
// (see Part 2 below) and verify its cs1 against info.mintPubkey.
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Every request function takes options last — `fetch`, `timeoutMs`, `offline`,
|
|
68
|
-
`randomSecret`, `requireSignatures`, `mutationRetries`.
|
|
68
|
+
`randomSecret`, `requireSignatures`, `requireMintPubkey`, `mutationRetries`.
|
|
69
69
|
`createClient(options)` binds one set once:
|
|
70
70
|
|
|
71
71
|
```ts
|
|
@@ -189,12 +189,15 @@ sig = 65 bytes, r || s || recovery_id
|
|
|
189
189
|
```
|
|
190
190
|
|
|
191
191
|
A `withdrawRequest` publishing no `mintPubkey`, or one that is not a 33-byte
|
|
192
|
-
compressed secp256k1 key, is refused with a `ProtocolError
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
compressed secp256k1 key, is refused with a `ProtocolError`; pass
|
|
193
|
+
`requireMintPubkey: false` for a Part 1-only mint that publishes none. A
|
|
194
|
+
mutation to a `cp1` output the service confirms but does not certify raises
|
|
195
|
+
`UnverifiableNoteError` — which **carries the fresh secrets**, because the
|
|
196
|
+
mutation landed and the note it minted is real; read them with
|
|
197
|
+
`newSecretsOf` and persist them before anything else. A mutation to a plain
|
|
198
|
+
hash output is unsigned by design and comes back with `signature`
|
|
199
|
+
undefined; pass `requireSignatures: true` to demand the old Part 1
|
|
200
|
+
signature over the hash instead, as mints issued before the Part 2 rewrite.
|
|
198
201
|
|
|
199
202
|
`verifyNoteSignature` recovers the pubkey and compares it to `mintPubkey`.
|
|
200
203
|
It accepts the recovery id at either end, because lnurl-mint once emitted
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -690,7 +690,8 @@ var resolveOptions = (options = {}) => ({
|
|
|
690
690
|
timeoutMs: options.timeoutMs ?? 3e4,
|
|
691
691
|
offline: options.offline ?? false,
|
|
692
692
|
randomSecret: options.randomSecret ?? defaultRandomSecret,
|
|
693
|
-
requireSignatures: options.requireSignatures ??
|
|
693
|
+
requireSignatures: options.requireSignatures ?? false,
|
|
694
|
+
requireMintPubkey: options.requireMintPubkey ?? true,
|
|
694
695
|
// A negative or non-finite count is read as none rather than thrown on:
|
|
695
696
|
// this is a resilience knob, and refusing the whole operation over it
|
|
696
697
|
// would be a worse answer than not retrying.
|
|
@@ -964,7 +965,7 @@ var fetchNoteInfo = async (url, options = {}) => {
|
|
|
964
965
|
}
|
|
965
966
|
assertWithdrawRequestShape(body, {
|
|
966
967
|
requireK1: true,
|
|
967
|
-
requireMintPubkey: opts.
|
|
968
|
+
requireMintPubkey: opts.requireMintPubkey
|
|
968
969
|
});
|
|
969
970
|
const queried = noteK1(url);
|
|
970
971
|
const echoed = noteIdOf(body.k1);
|
|
@@ -991,7 +992,7 @@ var fetchNoteInfoByHash = async (withdrawLink, h, options = {}) => {
|
|
|
991
992
|
}
|
|
992
993
|
assertWithdrawRequestShape(body, {
|
|
993
994
|
requireK1: false,
|
|
994
|
-
requireMintPubkey: opts.
|
|
995
|
+
requireMintPubkey: opts.requireMintPubkey
|
|
995
996
|
});
|
|
996
997
|
const info = body;
|
|
997
998
|
const payLink = sameOriginPayLink(body.payLink, reqUrl);
|
|
@@ -1139,8 +1140,13 @@ var replayableCallbackRequest = async (callback, params, options) => {
|
|
|
1139
1140
|
}
|
|
1140
1141
|
throw lastError;
|
|
1141
1142
|
};
|
|
1142
|
-
var requireSignature = (value, options, what) => {
|
|
1143
|
+
var requireSignature = (value, options, what, output) => {
|
|
1143
1144
|
if (typeof value === "string" && value.length > 0) return value;
|
|
1145
|
+
if (isCp1(output)) {
|
|
1146
|
+
throw new UnverifiableNoteError(
|
|
1147
|
+
`The service confirmed the ${what} to a cp1 output but returned no cs1 certificate, which LUD-25 Part 2 requires, so the note it just minted cannot be verified offline. The note exists - keep the key.`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1144
1150
|
if (!resolveOptions(options).requireSignatures) return void 0;
|
|
1145
1151
|
throw new UnverifiableNoteError(
|
|
1146
1152
|
`The service confirmed the ${what} but returned no signature, so the note it just minted cannot be verified offline. The note exists - keep the secret.`
|
|
@@ -1173,7 +1179,7 @@ var rotateNoteWithHash = async (callback, k1, h, options = {}) => {
|
|
|
1173
1179
|
],
|
|
1174
1180
|
options
|
|
1175
1181
|
);
|
|
1176
|
-
return { signature: requireSignature(body.sig, options, "rotate") };
|
|
1182
|
+
return { signature: requireSignature(body.sig, options, "rotate", h) };
|
|
1177
1183
|
};
|
|
1178
1184
|
var splitNoteWithHash = async (callback, k1s, amountMsat, h, h2, options = {}) => {
|
|
1179
1185
|
const body = await replayableCallbackRequest(
|
|
@@ -1187,8 +1193,8 @@ var splitNoteWithHash = async (callback, k1s, amountMsat, h, h2, options = {}) =
|
|
|
1187
1193
|
options
|
|
1188
1194
|
);
|
|
1189
1195
|
return {
|
|
1190
|
-
signature: requireSignature(body.sig, options, "split"),
|
|
1191
|
-
changeSignature: requireSignature(body.sig2, options, "split's change")
|
|
1196
|
+
signature: requireSignature(body.sig, options, "split", h),
|
|
1197
|
+
changeSignature: requireSignature(body.sig2, options, "split's change", h2)
|
|
1192
1198
|
};
|
|
1193
1199
|
};
|
|
1194
1200
|
var mergeNotesWithHash = async (callback, k1s, h, options = {}) => {
|
|
@@ -1197,7 +1203,7 @@ var mergeNotesWithHash = async (callback, k1s, h, options = {}) => {
|
|
|
1197
1203
|
[...k1s.map((k1) => ["k1", k1]), outputParam(h, 1)],
|
|
1198
1204
|
options
|
|
1199
1205
|
);
|
|
1200
|
-
return { signature: requireSignature(body.sig, options, "merge") };
|
|
1206
|
+
return { signature: requireSignature(body.sig, options, "merge", h) };
|
|
1201
1207
|
};
|
|
1202
1208
|
var keepingOutputs = (err, newSecrets) => {
|
|
1203
1209
|
if (err instanceof NoteSpentError || err instanceof NoteUnknownError) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lnurlcash-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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.10.0",
|
|
64
64
|
"tsup": "^8.5.0",
|
|
65
65
|
"typescript": "^5.7.0",
|
|
66
66
|
"vitest": "^3.0.0"
|