@arkade-os/lnurl-client 0.0.0-stage → 0.7.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/README.md +499 -3
- package/dist/addresses-CPAzIbJ_.d.ts +514 -0
- package/dist/arkade.d.ts +333 -0
- package/dist/arkade.js +558 -0
- package/dist/browser.d.ts +6 -0
- package/dist/browser.js +60 -0
- package/dist/chunk-4ELYU6DY.js +1417 -0
- package/dist/chunk-H7W5RHP2.js +58 -0
- package/dist/hd.d.ts +16 -0
- package/dist/hd.js +389 -0
- package/dist/index.d.ts +796 -0
- package/dist/index.js +86 -0
- package/package.json +59 -6
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// packages/client/src/errors.ts
|
|
2
|
+
var LnurlError = class extends Error {
|
|
3
|
+
/** Human-readable failure reason, taken from the wire `reason`/`error` field. */
|
|
4
|
+
reason;
|
|
5
|
+
/** HTTP status when the failure came with one; absent for LUD-06 bodies answered at HTTP 200. */
|
|
6
|
+
httpStatus;
|
|
7
|
+
/** Machine-readable server code, when the management surface sent one. */
|
|
8
|
+
code;
|
|
9
|
+
/** True if and only if `httpStatus === 429`; every other failure is terminal. */
|
|
10
|
+
retryable;
|
|
11
|
+
/**
|
|
12
|
+
* Creates an `LnurlError`.
|
|
13
|
+
*
|
|
14
|
+
* @param reason - Human-readable failure reason, also used as the message.
|
|
15
|
+
* @param opts - Optional `httpStatus` and `code` captured from the response.
|
|
16
|
+
*/
|
|
17
|
+
constructor(reason, opts) {
|
|
18
|
+
super(reason);
|
|
19
|
+
this.name = "LnurlError";
|
|
20
|
+
this.reason = reason;
|
|
21
|
+
this.httpStatus = opts?.httpStatus;
|
|
22
|
+
this.code = opts?.code;
|
|
23
|
+
this.retryable = opts?.httpStatus === 429;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var LnurlTransportError = class extends Error {
|
|
27
|
+
/**
|
|
28
|
+
* Creates an `LnurlTransportError`.
|
|
29
|
+
*
|
|
30
|
+
* @param message - Short description of which transport step failed.
|
|
31
|
+
* @param opts - Optional `cause` carrying the original thrown value.
|
|
32
|
+
*/
|
|
33
|
+
constructor(message, opts) {
|
|
34
|
+
super(message, opts);
|
|
35
|
+
this.name = "LnurlTransportError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var LnurlTimeoutError = class extends Error {
|
|
39
|
+
/** The most recent verify snapshot seen before the deadline, when one was seen. */
|
|
40
|
+
lastSnapshot;
|
|
41
|
+
/**
|
|
42
|
+
* Creates an `LnurlTimeoutError`.
|
|
43
|
+
*
|
|
44
|
+
* @param message - Short description including the timeout that elapsed.
|
|
45
|
+
* @param opts - Optional `lastSnapshot` with the latest verify state.
|
|
46
|
+
*/
|
|
47
|
+
constructor(message, opts) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "LnurlTimeoutError";
|
|
50
|
+
this.lastSnapshot = opts?.lastSnapshot;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
LnurlError,
|
|
56
|
+
LnurlTransportError,
|
|
57
|
+
LnurlTimeoutError
|
|
58
|
+
};
|
package/dist/hd.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as _arkade_os_sdk from '@arkade-os/sdk';
|
|
2
|
+
import { IContractManager } from '@arkade-os/sdk';
|
|
3
|
+
import { b as HdReceiveConfig, c as HdReceiveAllocation } from './addresses-CPAzIbJ_.js';
|
|
4
|
+
export { H as HdReceiveAllocationPage } from './addresses-CPAzIbJ_.js';
|
|
5
|
+
|
|
6
|
+
interface HdReceiveBranch {
|
|
7
|
+
hd: HdReceiveConfig;
|
|
8
|
+
signingDescriptor(index: number): string;
|
|
9
|
+
}
|
|
10
|
+
declare function createHdReceiveBranch(params: Omit<HdReceiveConfig, "version" | "descriptor"> & {
|
|
11
|
+
accountDescriptor: string;
|
|
12
|
+
branchPath: string;
|
|
13
|
+
}): HdReceiveBranch;
|
|
14
|
+
declare function restoreHdReceiveAllocation(manager: Pick<IContractManager, "createContract">, allocation: HdReceiveAllocation, branch: HdReceiveBranch): Promise<_arkade_os_sdk.Contract>;
|
|
15
|
+
|
|
16
|
+
export { HdReceiveAllocation, type HdReceiveBranch, HdReceiveConfig, createHdReceiveBranch, restoreHdReceiveAllocation };
|
package/dist/hd.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LnurlError
|
|
3
|
+
} from "./chunk-H7W5RHP2.js";
|
|
4
|
+
|
|
5
|
+
// node_modules/.pnpm/@scure+bip32@2.4.0/node_modules/@scure/bip32/index.js
|
|
6
|
+
import { secp256k1 as secp } from "@noble/curves/secp256k1.js";
|
|
7
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
8
|
+
import { ripemd160 } from "@noble/hashes/legacy.js";
|
|
9
|
+
import { sha256, sha512 } from "@noble/hashes/sha2.js";
|
|
10
|
+
import { abytes, concatBytes, createView } from "@noble/hashes/utils.js";
|
|
11
|
+
import { createBase58check } from "@scure/base";
|
|
12
|
+
var Point = /* @__PURE__ */ (() => secp.Point)();
|
|
13
|
+
var Fn = /* @__PURE__ */ (() => Point.Fn)();
|
|
14
|
+
var base58check = /* @__PURE__ */ createBase58check(sha256);
|
|
15
|
+
var MASTER_SECRET = /* @__PURE__ */ (() => {
|
|
16
|
+
return Uint8Array.from("Bitcoin seed".split(""), (char) => char.charCodeAt(0));
|
|
17
|
+
})();
|
|
18
|
+
var BITCOIN_VERSIONS = { private: 76066276, public: 76067358 };
|
|
19
|
+
var HARDENED_OFFSET = 2147483648;
|
|
20
|
+
var MAX_DEPTH = 255;
|
|
21
|
+
var hash160 = (data) => ripemd160(sha256(data));
|
|
22
|
+
var fromU32 = (data) => createView(data).getUint32(0, false);
|
|
23
|
+
var toU32 = (n, title = "number") => {
|
|
24
|
+
if (typeof n !== "number")
|
|
25
|
+
throw new TypeError(`"${title}" expected number, got type=${typeof n}`);
|
|
26
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1)
|
|
27
|
+
throw new RangeError(`"${title}" expected integer in range 0..2**32-1, got ${n}`);
|
|
28
|
+
const buf = new Uint8Array(4);
|
|
29
|
+
createView(buf).setUint32(0, n, false);
|
|
30
|
+
return buf;
|
|
31
|
+
};
|
|
32
|
+
var validateVersions = (versions, title = "versions") => {
|
|
33
|
+
if (!(typeof versions === "object" && versions !== null))
|
|
34
|
+
throw new Error("versions must be an object");
|
|
35
|
+
toU32(versions.private, `${title}.private`);
|
|
36
|
+
toU32(versions.public, `${title}.public`);
|
|
37
|
+
return versions;
|
|
38
|
+
};
|
|
39
|
+
var HDKey = class _HDKey {
|
|
40
|
+
get fingerprint() {
|
|
41
|
+
if (!this._pubHash) {
|
|
42
|
+
throw new Error("No publicKey set!");
|
|
43
|
+
}
|
|
44
|
+
return fromU32(this._pubHash);
|
|
45
|
+
}
|
|
46
|
+
get identifier() {
|
|
47
|
+
return this._pubHash ? Uint8Array.from(this._pubHash) : void 0;
|
|
48
|
+
}
|
|
49
|
+
get pubKeyHash() {
|
|
50
|
+
return this._pubHash ? Uint8Array.from(this._pubHash) : void 0;
|
|
51
|
+
}
|
|
52
|
+
get privateKey() {
|
|
53
|
+
return this._privateKey ? Uint8Array.from(this._privateKey) : null;
|
|
54
|
+
}
|
|
55
|
+
get publicKey() {
|
|
56
|
+
return this._publicKey ? Uint8Array.from(this._publicKey) : null;
|
|
57
|
+
}
|
|
58
|
+
get chainCode() {
|
|
59
|
+
return this._chainCode ? Uint8Array.from(this._chainCode) : null;
|
|
60
|
+
}
|
|
61
|
+
get privateExtendedKey() {
|
|
62
|
+
const priv = this._privateKey;
|
|
63
|
+
if (!priv) {
|
|
64
|
+
throw new Error("No private key");
|
|
65
|
+
}
|
|
66
|
+
return base58check.encode(this.serialize(this.versions.private, concatBytes(Uint8Array.of(0), priv)));
|
|
67
|
+
}
|
|
68
|
+
get publicExtendedKey() {
|
|
69
|
+
if (!this._publicKey) {
|
|
70
|
+
throw new Error("No public key");
|
|
71
|
+
}
|
|
72
|
+
return base58check.encode(this.serialize(this.versions.public, this._publicKey));
|
|
73
|
+
}
|
|
74
|
+
static fromMasterSeed(seed, versions = BITCOIN_VERSIONS) {
|
|
75
|
+
abytes(seed);
|
|
76
|
+
versions = validateVersions(versions);
|
|
77
|
+
if (8 * seed.length < 128 || 8 * seed.length > 512) {
|
|
78
|
+
throw new RangeError("HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got " + seed.length);
|
|
79
|
+
}
|
|
80
|
+
const I = hmac(sha512, MASTER_SECRET, seed);
|
|
81
|
+
const privateKey = I.slice(0, 32);
|
|
82
|
+
const chainCode = I.slice(32);
|
|
83
|
+
return new _HDKey({ versions, chainCode, privateKey });
|
|
84
|
+
}
|
|
85
|
+
static fromExtendedKey(base58key, versions = BITCOIN_VERSIONS) {
|
|
86
|
+
versions = validateVersions(versions);
|
|
87
|
+
const keyBuffer = base58check.decode(base58key);
|
|
88
|
+
if (keyBuffer.length !== 78) {
|
|
89
|
+
throw new Error(`HDKey: invalid extended key length: expected 78 bytes, got ${keyBuffer.length}`);
|
|
90
|
+
}
|
|
91
|
+
const keyView = createView(keyBuffer);
|
|
92
|
+
const version = keyView.getUint32(0, false);
|
|
93
|
+
const opt = {
|
|
94
|
+
versions,
|
|
95
|
+
depth: keyBuffer[4],
|
|
96
|
+
parentFingerprint: keyView.getUint32(5, false),
|
|
97
|
+
index: keyView.getUint32(9, false),
|
|
98
|
+
chainCode: keyBuffer.slice(13, 45)
|
|
99
|
+
};
|
|
100
|
+
const key = keyBuffer.slice(45);
|
|
101
|
+
const isPriv = key[0] === 0;
|
|
102
|
+
if (version !== versions[isPriv ? "private" : "public"]) {
|
|
103
|
+
throw new Error("Version mismatch");
|
|
104
|
+
}
|
|
105
|
+
if (isPriv) {
|
|
106
|
+
return new _HDKey({ ...opt, privateKey: key.slice(1) });
|
|
107
|
+
} else {
|
|
108
|
+
return new _HDKey({ ...opt, publicKey: key });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
static fromJSON(json) {
|
|
112
|
+
return _HDKey.fromExtendedKey("xpriv" in json ? json.xpriv : json.xpub);
|
|
113
|
+
}
|
|
114
|
+
versions;
|
|
115
|
+
depth = 0;
|
|
116
|
+
index = 0;
|
|
117
|
+
parentFingerprint = 0;
|
|
118
|
+
_chainCode = null;
|
|
119
|
+
_privateKey;
|
|
120
|
+
_publicKey;
|
|
121
|
+
_pubHash;
|
|
122
|
+
constructor(opt) {
|
|
123
|
+
if (!opt || typeof opt !== "object") {
|
|
124
|
+
throw new Error("HDKey.constructor must not be called directly");
|
|
125
|
+
}
|
|
126
|
+
const depth = opt.depth ?? 0;
|
|
127
|
+
const index = opt.index ?? 0;
|
|
128
|
+
const parentFingerprint = opt.parentFingerprint ?? 0;
|
|
129
|
+
if (!Number.isSafeInteger(depth) || depth < 0 || depth > MAX_DEPTH) {
|
|
130
|
+
throw new RangeError("HDKey: depth must be an integer in range 0..255");
|
|
131
|
+
}
|
|
132
|
+
toU32(index, "index");
|
|
133
|
+
toU32(parentFingerprint, "parentFingerprint");
|
|
134
|
+
if (depth === 0 && (index !== 0 || parentFingerprint !== 0)) {
|
|
135
|
+
throw new Error("HDKey: zero depth with non-zero index/parent fingerprint");
|
|
136
|
+
}
|
|
137
|
+
this.versions = opt.versions ? validateVersions(opt.versions) : BITCOIN_VERSIONS;
|
|
138
|
+
this.depth = depth;
|
|
139
|
+
if (opt.chainCode)
|
|
140
|
+
abytes(opt.chainCode, 32);
|
|
141
|
+
this._chainCode = opt.chainCode ? Uint8Array.from(opt.chainCode) : null;
|
|
142
|
+
this.index = index;
|
|
143
|
+
this.parentFingerprint = parentFingerprint;
|
|
144
|
+
if (opt.publicKey && opt.privateKey) {
|
|
145
|
+
throw new Error("HDKey: publicKey and privateKey at same time.");
|
|
146
|
+
}
|
|
147
|
+
if (opt.privateKey) {
|
|
148
|
+
if (!secp.utils.isValidSecretKey(opt.privateKey))
|
|
149
|
+
throw new Error("Invalid private key");
|
|
150
|
+
this._privateKey = Uint8Array.from(opt.privateKey);
|
|
151
|
+
this._publicKey = secp.getPublicKey(this._privateKey, true);
|
|
152
|
+
} else if (opt.publicKey) {
|
|
153
|
+
this._publicKey = Point.fromBytes(opt.publicKey).toBytes(true);
|
|
154
|
+
} else {
|
|
155
|
+
throw new Error("HDKey: no public or private key provided");
|
|
156
|
+
}
|
|
157
|
+
this._pubHash = hash160(this._publicKey);
|
|
158
|
+
}
|
|
159
|
+
derive(path) {
|
|
160
|
+
if (!/^[mM]'?/.test(path)) {
|
|
161
|
+
throw new Error('Path must start with "m" or "M"');
|
|
162
|
+
}
|
|
163
|
+
if (/^[mM]'?$/.test(path)) {
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
const parts = path.replace(/^[mM]'?\//, "").split("/");
|
|
167
|
+
if (parts.length > MAX_DEPTH - this.depth) {
|
|
168
|
+
throw new Error("HDKey: path exceeds the serializable depth 255");
|
|
169
|
+
}
|
|
170
|
+
let child = this;
|
|
171
|
+
for (const c of parts) {
|
|
172
|
+
const m = /^(\d+)('?)$/.exec(c);
|
|
173
|
+
const m1 = m && m[1];
|
|
174
|
+
if (!m || m.length !== 3 || typeof m1 !== "string")
|
|
175
|
+
throw new Error("invalid child index: " + c);
|
|
176
|
+
let idx = +m1;
|
|
177
|
+
if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) {
|
|
178
|
+
throw new Error("Invalid index");
|
|
179
|
+
}
|
|
180
|
+
if (m[2] === "'") {
|
|
181
|
+
idx += HARDENED_OFFSET;
|
|
182
|
+
}
|
|
183
|
+
child = child.deriveChild(idx);
|
|
184
|
+
}
|
|
185
|
+
return child;
|
|
186
|
+
}
|
|
187
|
+
deriveChild(index) {
|
|
188
|
+
return this._deriveChild(index);
|
|
189
|
+
}
|
|
190
|
+
/** Test-only implementation seam. Production callers must use deriveChild(). */
|
|
191
|
+
_deriveChild(index, _I) {
|
|
192
|
+
if (!this._publicKey || !this._chainCode) {
|
|
193
|
+
throw new Error("No publicKey or chainCode set");
|
|
194
|
+
}
|
|
195
|
+
let data = toU32(index, "index");
|
|
196
|
+
if (index >= HARDENED_OFFSET) {
|
|
197
|
+
const priv = this._privateKey;
|
|
198
|
+
if (!priv) {
|
|
199
|
+
throw new Error("Could not derive hardened child key");
|
|
200
|
+
}
|
|
201
|
+
data = concatBytes(Uint8Array.of(0), priv, data);
|
|
202
|
+
} else {
|
|
203
|
+
data = concatBytes(this._publicKey, data);
|
|
204
|
+
}
|
|
205
|
+
const out = _I || hmac(sha512, this._chainCode, data);
|
|
206
|
+
abytes(out, 64);
|
|
207
|
+
const childTweak = out.slice(0, 32);
|
|
208
|
+
const chainCode = out.slice(32);
|
|
209
|
+
const opt = {
|
|
210
|
+
versions: this.versions,
|
|
211
|
+
chainCode,
|
|
212
|
+
depth: this.depth + 1,
|
|
213
|
+
parentFingerprint: this.fingerprint,
|
|
214
|
+
index
|
|
215
|
+
};
|
|
216
|
+
if (opt.depth > MAX_DEPTH) {
|
|
217
|
+
throw new Error("HDKey: depth exceeds the serializable value 255");
|
|
218
|
+
}
|
|
219
|
+
const retry = () => {
|
|
220
|
+
const maxIndex = this._privateKey ? 2 ** 32 - 1 : HARDENED_OFFSET - 1;
|
|
221
|
+
if (index >= maxIndex) {
|
|
222
|
+
throw new Error(`HDKey: cannot retry child derivation at index ${index}`);
|
|
223
|
+
}
|
|
224
|
+
return this.deriveChild(index + 1);
|
|
225
|
+
};
|
|
226
|
+
const ctweak = Fn.fromBytes(childTweak, true);
|
|
227
|
+
if (!Fn.isValid(ctweak))
|
|
228
|
+
return retry();
|
|
229
|
+
if (this._privateKey) {
|
|
230
|
+
const added = Fn.create(Fn.fromBytes(this._privateKey) + ctweak);
|
|
231
|
+
if (!Fn.isValidNot0(added))
|
|
232
|
+
return retry();
|
|
233
|
+
opt.privateKey = Fn.toBytes(added);
|
|
234
|
+
} else {
|
|
235
|
+
const point = Point.fromBytes(this._publicKey);
|
|
236
|
+
const added = ctweak === 0n ? point : point.add(Point.BASE.multiply(ctweak));
|
|
237
|
+
if (added.equals(Point.ZERO))
|
|
238
|
+
return retry();
|
|
239
|
+
opt.publicKey = added.toBytes(true);
|
|
240
|
+
}
|
|
241
|
+
return new _HDKey(opt);
|
|
242
|
+
}
|
|
243
|
+
sign(hash) {
|
|
244
|
+
if (!this._privateKey) {
|
|
245
|
+
throw new Error("No privateKey set!");
|
|
246
|
+
}
|
|
247
|
+
abytes(hash, 32);
|
|
248
|
+
return secp.sign(hash, this._privateKey, { prehash: false });
|
|
249
|
+
}
|
|
250
|
+
verify(hash, signature) {
|
|
251
|
+
abytes(hash, 32);
|
|
252
|
+
abytes(signature, 64);
|
|
253
|
+
if (!this._publicKey) {
|
|
254
|
+
throw new Error("No publicKey set!");
|
|
255
|
+
}
|
|
256
|
+
return secp.verify(signature, hash, this._publicKey, { prehash: false });
|
|
257
|
+
}
|
|
258
|
+
wipePrivateData() {
|
|
259
|
+
if (this._privateKey) {
|
|
260
|
+
this._privateKey.fill(0);
|
|
261
|
+
this._privateKey = void 0;
|
|
262
|
+
}
|
|
263
|
+
return this;
|
|
264
|
+
}
|
|
265
|
+
// TODO(v3): Make automatic JSON serialization public-only so JSON.stringify cannot expose xpriv.
|
|
266
|
+
toJSON() {
|
|
267
|
+
return this.toPrivateJSON();
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Explicitly exports private key material. Treat the returned value as a secret.
|
|
271
|
+
*/
|
|
272
|
+
toPrivateJSON() {
|
|
273
|
+
return {
|
|
274
|
+
xpriv: this.privateExtendedKey,
|
|
275
|
+
xpub: this.publicExtendedKey
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
serialize(version, key) {
|
|
279
|
+
if (!this._chainCode) {
|
|
280
|
+
throw new Error("No chainCode set");
|
|
281
|
+
}
|
|
282
|
+
abytes(key, 33);
|
|
283
|
+
return concatBytes(toU32(version, "version"), new Uint8Array([this.depth]), toU32(this.parentFingerprint, "parentFingerprint"), toU32(this.index, "index"), this._chainCode, key);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// packages/client/src/hd.ts
|
|
288
|
+
import { hex } from "@scure/base";
|
|
289
|
+
import {
|
|
290
|
+
DefaultVtxo,
|
|
291
|
+
deriveDescriptorLeafCompressedPubKey,
|
|
292
|
+
parseHDDescriptor,
|
|
293
|
+
timelockToSequence
|
|
294
|
+
} from "@arkade-os/sdk";
|
|
295
|
+
var TESTNET_BIP32 = { public: 70617039, private: 70615956 };
|
|
296
|
+
var INDEX_LIMIT = 2147483648;
|
|
297
|
+
function indexOf(index) {
|
|
298
|
+
if (!Number.isSafeInteger(index) || index < 0 || index >= INDEX_LIMIT) {
|
|
299
|
+
throw new LnurlError("HD index must be a non-hardened integer");
|
|
300
|
+
}
|
|
301
|
+
return index;
|
|
302
|
+
}
|
|
303
|
+
function createHdReceiveBranch(params) {
|
|
304
|
+
const parsed = parseHDDescriptor(params.accountDescriptor);
|
|
305
|
+
if (!parsed || !params.accountDescriptor.endsWith("/*)") || !/^(xpub|tpub)/.test(parsed.xpub)) {
|
|
306
|
+
throw new LnurlError("accountDescriptor must be a public HD template");
|
|
307
|
+
}
|
|
308
|
+
if (!/^(0|[1-9]\d*)(\/(0|[1-9]\d*))*$/.test(params.branchPath)) {
|
|
309
|
+
throw new LnurlError("branchPath must name a dedicated non-hardened branch below the account xpub");
|
|
310
|
+
}
|
|
311
|
+
const path = params.branchPath.split("/").map(Number);
|
|
312
|
+
path.forEach(indexOf);
|
|
313
|
+
if (path[0] === Number(parsed.derivationPath.split("/")[0]) || path[0] === 1) {
|
|
314
|
+
throw new LnurlError("HD receive must not expose the ordinary receive or change branch");
|
|
315
|
+
}
|
|
316
|
+
const testnet = parsed.xpub.startsWith("tpub");
|
|
317
|
+
if (params.hrp === "tark" !== testnet) throw new LnurlError("HD descriptor network does not match hrp");
|
|
318
|
+
const node = HDKey.fromExtendedKey(parsed.xpub, testnet ? TESTNET_BIP32 : void 0);
|
|
319
|
+
if (node.depth !== parsed.basePath.split("/").length) throw new LnurlError("HD key origin depth does not match xpub");
|
|
320
|
+
const branch = node.derive(`m/${params.branchPath}`);
|
|
321
|
+
if (branch.depth < 4) throw new LnurlError("HD receive branch must be below account depth");
|
|
322
|
+
const basePath = parsed.basePath.split("/").map((part) => `${Number(part.replace(/['hH]$/, ""))}${/['hH]$/.test(part) ? "'" : ""}`).join("/");
|
|
323
|
+
const fingerprint = parsed.fingerprint.toLowerCase();
|
|
324
|
+
const origin = `${basePath}/${params.branchPath}`;
|
|
325
|
+
const descriptor = `tr([${fingerprint}/${origin}]${branch.publicExtendedKey}/*)`;
|
|
326
|
+
const hd = {
|
|
327
|
+
version: 1,
|
|
328
|
+
descriptor,
|
|
329
|
+
serverPubKey: params.serverPubKey.toLowerCase(),
|
|
330
|
+
hrp: params.hrp,
|
|
331
|
+
exitDelay: { ...params.exitDelay }
|
|
332
|
+
};
|
|
333
|
+
const result = {
|
|
334
|
+
hd,
|
|
335
|
+
// Keep the account xpub local: SDK signing ownership compares the embedded xpub.
|
|
336
|
+
signingDescriptor: (index) => `tr([${fingerprint}/${basePath}]${parsed.xpub}/${params.branchPath}/${indexOf(index)})`
|
|
337
|
+
};
|
|
338
|
+
hdReceiveContract(result, 0);
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
function hdReceiveContract(branch, index) {
|
|
342
|
+
const hd = branch.hd;
|
|
343
|
+
if (hd.version !== 1 || !/^[0-9a-f]{64}$/i.test(hd.serverPubKey) || !["ark", "tark"].includes(hd.hrp) || !["seconds", "blocks"].includes(hd.exitDelay.type) || !Number.isSafeInteger(hd.exitDelay.value) || hd.exitDelay.value <= 0) {
|
|
344
|
+
throw new LnurlError("Invalid HD receive script configuration");
|
|
345
|
+
}
|
|
346
|
+
const units = hd.exitDelay.type === "seconds" ? hd.exitDelay.value / 512 : hd.exitDelay.value;
|
|
347
|
+
if (!Number.isInteger(units) || units > 65535) throw new LnurlError("HD receive exit delay is outside BIP68 bounds");
|
|
348
|
+
const descriptor = hd.descriptor.replace("*", String(indexOf(index)));
|
|
349
|
+
const claimPublicKey = deriveDescriptorLeafCompressedPubKey(descriptor);
|
|
350
|
+
const localPublicKey = deriveDescriptorLeafCompressedPubKey(branch.signingDescriptor(index));
|
|
351
|
+
if (hex.encode(claimPublicKey) !== hex.encode(localPublicKey)) throw new LnurlError("HD signing branch does not match receive branch");
|
|
352
|
+
const script = new DefaultVtxo.Script({
|
|
353
|
+
pubKey: claimPublicKey.slice(1),
|
|
354
|
+
serverPubKey: hex.decode(hd.serverPubKey),
|
|
355
|
+
csvTimelock: { type: hd.exitDelay.type, value: BigInt(hd.exitDelay.value) }
|
|
356
|
+
});
|
|
357
|
+
return { descriptor, claimPublicKey, script, address: script.address(hd.hrp, hex.decode(hd.serverPubKey)).encode() };
|
|
358
|
+
}
|
|
359
|
+
async function restoreHdReceiveAllocation(manager, allocation, branch) {
|
|
360
|
+
const hd = branch.hd;
|
|
361
|
+
if (allocation.hd.version !== hd.version || allocation.hd.descriptor !== hd.descriptor || allocation.hd.serverPubKey !== hd.serverPubKey || allocation.hd.hrp !== hd.hrp || allocation.hd.exitDelay.type !== hd.exitDelay.type || allocation.hd.exitDelay.value !== hd.exitDelay.value) {
|
|
362
|
+
throw new LnurlError("Allocation belongs to a different HD receive configuration");
|
|
363
|
+
}
|
|
364
|
+
const derived = hdReceiveContract(branch, allocation.index);
|
|
365
|
+
if (allocation.descriptor !== derived.descriptor || allocation.arkadeAddress !== derived.address || allocation.claimPublicKey !== hex.encode(derived.claimPublicKey)) {
|
|
366
|
+
throw new LnurlError("HD allocation does not match its derived receive script");
|
|
367
|
+
}
|
|
368
|
+
return manager.createContract({
|
|
369
|
+
type: "default",
|
|
370
|
+
watch: "awaiting-funds",
|
|
371
|
+
script: hex.encode(derived.script.pkScript),
|
|
372
|
+
address: derived.address,
|
|
373
|
+
params: {
|
|
374
|
+
pubKey: hex.encode(derived.claimPublicKey.slice(1)),
|
|
375
|
+
serverPubKey: hd.serverPubKey,
|
|
376
|
+
csvTimelock: timelockToSequence(derived.script.options.csvTimelock).toString()
|
|
377
|
+
},
|
|
378
|
+
metadata: { signingDescriptor: branch.signingDescriptor(allocation.index) }
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
export {
|
|
382
|
+
createHdReceiveBranch,
|
|
383
|
+
restoreHdReceiveAllocation
|
|
384
|
+
};
|
|
385
|
+
/*! Bundled license information:
|
|
386
|
+
|
|
387
|
+
@scure/bip32/index.js:
|
|
388
|
+
(*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) *)
|
|
389
|
+
*/
|