@hazbase/simplicity 0.4.5 → 0.4.7

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,371 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STRICT_POLICY_VERIFIER_MAX_OUTPUTS = exports.STRICT_POLICY_VERIFIER_MAX_INPUTS = exports.STRICT_POLICY_AUTHORITY_SET_DOMAIN = exports.STRICT_POLICY_SNAPSHOT_APPROVAL_DOMAIN = exports.STRICT_POLICY_SNAPSHOT_APPROVAL_SCHEMA = exports.STRICT_POLICY_SNAPSHOT_SCHEMA = void 0;
4
+ exports.normalizeStrictPolicySnapshotPayload = normalizeStrictPolicySnapshotPayload;
5
+ exports.normalizeStrictPolicySnapshotAuthority = normalizeStrictPolicySnapshotAuthority;
6
+ exports.normalizeStrictPolicySnapshotApprovalProof = normalizeStrictPolicySnapshotApprovalProof;
7
+ exports.normalizeStrictPolicyHolderCovenantParams = normalizeStrictPolicyHolderCovenantParams;
8
+ exports.normalizeStrictPolicyVerifierCovenantParams = normalizeStrictPolicyVerifierCovenantParams;
9
+ exports.strictPolicyHolderCovenantTemplatePath = strictPolicyHolderCovenantTemplatePath;
10
+ exports.renderStrictPolicyHolderCovenantSource = renderStrictPolicyHolderCovenantSource;
11
+ exports.compileStrictPolicyHolderCovenant = compileStrictPolicyHolderCovenant;
12
+ exports.strictPolicyVerifierCovenantTemplatePath = strictPolicyVerifierCovenantTemplatePath;
13
+ exports.renderStrictPolicyVerifierCovenantSource = renderStrictPolicyVerifierCovenantSource;
14
+ exports.compileStrictPolicyVerifierCovenant = compileStrictPolicyVerifierCovenant;
15
+ exports.taggedHashHexUtf8 = taggedHashHexUtf8;
16
+ exports.computeStrictPolicyAuthoritySetHash = computeStrictPolicyAuthoritySetHash;
17
+ exports.prepareStrictPolicySnapshot = prepareStrictPolicySnapshot;
18
+ exports.finalizeStrictPolicySnapshot = finalizeStrictPolicySnapshot;
19
+ exports.verifyStrictPolicySnapshot = verifyStrictPolicySnapshot;
20
+ const node_crypto_1 = require("node:crypto");
21
+ const errors_1 = require("../core/errors");
22
+ const schnorr_1 = require("../core/schnorr");
23
+ const summary_1 = require("../core/summary");
24
+ const templating_1 = require("../core/templating");
25
+ const dampPolicy_1 = require("./dampPolicy");
26
+ // These imports support Node-only template compilation. Keep their dynamic
27
+ // importer lazy so merely importing the snapshot verifier remains compatible
28
+ // with Chrome extension CSP.
29
+ function nativeImport(specifier) {
30
+ const importModule = new Function("value", "return import(value);");
31
+ return importModule(specifier);
32
+ }
33
+ async function readTemplateFile(templatePath) {
34
+ const fileSystem = await nativeImport("node:fs/promises");
35
+ return fileSystem.readFile(templatePath, "utf8");
36
+ }
37
+ async function loadCompiler() {
38
+ return await nativeImport(`${__dirname}/../core/compiler.js`);
39
+ }
40
+ exports.STRICT_POLICY_SNAPSHOT_SCHEMA = "hazbase_strict_policy_snapshot_v1";
41
+ exports.STRICT_POLICY_SNAPSHOT_APPROVAL_SCHEMA = "hazbase_strict_policy_snapshot_approval_v1";
42
+ exports.STRICT_POLICY_SNAPSHOT_APPROVAL_DOMAIN = "HAZBASE-RWA-POLICY-SNAPSHOT-V1";
43
+ exports.STRICT_POLICY_AUTHORITY_SET_DOMAIN = "HAZBASE-RWA-POLICY-AUTHORITY-SET-V1";
44
+ exports.STRICT_POLICY_VERIFIER_MAX_INPUTS = 16;
45
+ exports.STRICT_POLICY_VERIFIER_MAX_OUTPUTS = 8;
46
+ function validationError(field, message) {
47
+ throw new errors_1.ValidationError(`${field} ${message}`, {
48
+ code: "STRICT_POLICY_SNAPSHOT_INVALID",
49
+ field,
50
+ });
51
+ }
52
+ function recordValue(value, field) {
53
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
54
+ validationError(field, "must be an object");
55
+ }
56
+ return value;
57
+ }
58
+ function stringValue(value, field) {
59
+ if (typeof value !== "string" || value.trim().length === 0) {
60
+ validationError(field, "must be a non-empty string");
61
+ }
62
+ return value.trim();
63
+ }
64
+ function literalValue(value, field, allowed) {
65
+ const normalized = stringValue(value, field);
66
+ if (!allowed.includes(normalized)) {
67
+ validationError(field, `must be one of ${allowed.join(", ")}`);
68
+ }
69
+ return normalized;
70
+ }
71
+ function hexValue(value, field, byteLength) {
72
+ const normalized = stringValue(value, field).toLowerCase().replace(/^0x/u, "");
73
+ if (!/^[0-9a-f]+$/u.test(normalized) || normalized.length !== byteLength * 2) {
74
+ validationError(field, `must be ${byteLength} bytes of hex`);
75
+ }
76
+ return normalized;
77
+ }
78
+ function decimalValue(value, field, allowZero = true) {
79
+ const normalized = stringValue(value, field);
80
+ if (!/^(0|[1-9][0-9]*)$/u.test(normalized)) {
81
+ validationError(field, "must be a canonical unsigned decimal string");
82
+ }
83
+ if (!allowZero && normalized === "0") {
84
+ validationError(field, "must be greater than zero");
85
+ }
86
+ return normalized;
87
+ }
88
+ function u64DecimalValue(value, field, allowZero = true) {
89
+ const normalized = decimalValue(value, field, allowZero);
90
+ if (BigInt(normalized) > 0xffffffffffffffffn) {
91
+ validationError(field, "must fit in an unsigned 64-bit integer");
92
+ }
93
+ return normalized;
94
+ }
95
+ function timestampValue(value, field) {
96
+ const normalized = stringValue(value, field);
97
+ const date = new Date(normalized);
98
+ if (!Number.isFinite(date.getTime()) || date.toISOString() !== normalized) {
99
+ validationError(field, "must be a canonical UTC ISO-8601 timestamp");
100
+ }
101
+ return normalized;
102
+ }
103
+ function nullableHexValue(value, field, byteLength) {
104
+ return value === null ? null : hexValue(value, field, byteLength);
105
+ }
106
+ function nullableTimestampValue(value, field) {
107
+ return value === null ? null : timestampValue(value, field);
108
+ }
109
+ function voutValue(value, field) {
110
+ if (!Number.isSafeInteger(value) || value < 0 || value > 0xffff_ffff) {
111
+ validationError(field, "must be a uint32");
112
+ }
113
+ return value;
114
+ }
115
+ function normalizeStrictPolicySnapshotPayload(value) {
116
+ const record = recordValue(value, "payload");
117
+ const verifier = recordValue(record.verifier, "payload.verifier");
118
+ const outpoint = recordValue(verifier.outpoint, "payload.verifier.outpoint");
119
+ const effectiveAt = timestampValue(record.effectiveAt, "payload.effectiveAt");
120
+ const validUntil = nullableTimestampValue(record.validUntil, "payload.validUntil");
121
+ if (validUntil !== null && Date.parse(validUntil) <= Date.parse(effectiveAt)) {
122
+ validationError("payload.validUntil", "must be later than effectiveAt");
123
+ }
124
+ const epoch = decimalValue(record.epoch, "payload.epoch");
125
+ const previousSnapshotHash = nullableHexValue(record.previousSnapshotHash, "payload.previousSnapshotHash", 32);
126
+ if (epoch === "0" && previousSnapshotHash !== null) {
127
+ validationError("payload.previousSnapshotHash", "must be null for epoch 0");
128
+ }
129
+ if (epoch !== "0" && previousSnapshotHash === null) {
130
+ validationError("payload.previousSnapshotHash", "is required after epoch 0");
131
+ }
132
+ return {
133
+ schema: literalValue(record.schema, "payload.schema", [exports.STRICT_POLICY_SNAPSHOT_SCHEMA]),
134
+ network: literalValue(record.network, "payload.network", [
135
+ "liquid-regtest",
136
+ "liquid-testnet",
137
+ "liquid-mainnet",
138
+ ]),
139
+ liquidGenesisHash: hexValue(record.liquidGenesisHash, "payload.liquidGenesisHash", 32),
140
+ assetBindingId: stringValue(record.assetBindingId, "payload.assetBindingId"),
141
+ liquidAssetId: hexValue(record.liquidAssetId, "payload.liquidAssetId", 32),
142
+ epoch,
143
+ previousSnapshotHash,
144
+ policyRoot: hexValue(record.policyRoot, "payload.policyRoot", 32),
145
+ whitelistRoot: hexValue(record.whitelistRoot, "payload.whitelistRoot", 32),
146
+ verifier: {
147
+ outpoint: {
148
+ txid: hexValue(outpoint.txid, "payload.verifier.outpoint.txid", 32),
149
+ vout: voutValue(outpoint.vout, "payload.verifier.outpoint.vout"),
150
+ },
151
+ assetId: hexValue(verifier.assetId, "payload.verifier.assetId", 32),
152
+ amountAtomic: decimalValue(verifier.amountAtomic, "payload.verifier.amountAtomic", false),
153
+ cmr: hexValue(verifier.cmr, "payload.verifier.cmr", 32),
154
+ scriptHash: hexValue(verifier.scriptHash, "payload.verifier.scriptHash", 32),
155
+ },
156
+ effectiveAt,
157
+ validUntil,
158
+ };
159
+ }
160
+ function normalizeStrictPolicySnapshotAuthority(value) {
161
+ const record = recordValue(value, "authority");
162
+ const threshold = record.approvalThreshold;
163
+ if (threshold !== 1) {
164
+ validationError("authority.approvalThreshold", "must equal 1 for issuer_single");
165
+ }
166
+ const signerXonly = hexValue(record.signerXonly, "authority.signerXonly", 32);
167
+ const authoritySetHash = hexValue(record.authoritySetHash, "authority.authoritySetHash", 32);
168
+ const expectedAuthoritySetHash = computeStrictPolicyAuthoritySetHash(signerXonly);
169
+ if (authoritySetHash !== expectedAuthoritySetHash) {
170
+ validationError("authority.authoritySetHash", "does not match signerXonly and threshold");
171
+ }
172
+ return {
173
+ authorityType: literalValue(record.authorityType, "authority.authorityType", ["issuer_single"]),
174
+ authorityId: stringValue(record.authorityId, "authority.authorityId"),
175
+ authorityVersion: stringValue(record.authorityVersion, "authority.authorityVersion"),
176
+ authoritySetHash,
177
+ approvalScheme: literalValue(record.approvalScheme, "authority.approvalScheme", ["bip340-sha256"]),
178
+ approvalThreshold: threshold,
179
+ signerXonly,
180
+ approvedAt: timestampValue(record.approvedAt, "authority.approvedAt"),
181
+ };
182
+ }
183
+ function normalizeStrictPolicySnapshotApprovalProof(value) {
184
+ const record = recordValue(value, "approvalProof");
185
+ return {
186
+ type: literalValue(record.type, "approvalProof.type", ["issuer_single_v1"]),
187
+ signerXonly: hexValue(record.signerXonly, "approvalProof.signerXonly", 32),
188
+ signatureHex: hexValue(record.signatureHex, "approvalProof.signatureHex", 64),
189
+ };
190
+ }
191
+ function normalizeStrictPolicyHolderCovenantParams(value) {
192
+ const record = recordValue(value, "holderCovenant");
193
+ return {
194
+ strictAssetIdJetHex: hexValue(record.strictAssetIdJetHex, "holderCovenant.strictAssetIdJetHex", 32),
195
+ verifierAssetIdJetHex: hexValue(record.verifierAssetIdJetHex, "holderCovenant.verifierAssetIdJetHex", 32),
196
+ verifierAmountAtomic: u64DecimalValue(record.verifierAmountAtomic, "holderCovenant.verifierAmountAtomic", false),
197
+ numsInternalKey: hexValue(record.numsInternalKey, "holderCovenant.numsInternalKey", 32),
198
+ };
199
+ }
200
+ function normalizeStrictPolicyVerifierCovenantParams(value) {
201
+ const record = recordValue(value, "verifierCovenant");
202
+ const strictAssetIdJetHex = hexValue(record.strictAssetIdJetHex, "verifierCovenant.strictAssetIdJetHex", 32);
203
+ const verifierAssetIdJetHex = hexValue(record.verifierAssetIdJetHex, "verifierCovenant.verifierAssetIdJetHex", 32);
204
+ if (strictAssetIdJetHex === verifierAssetIdJetHex) {
205
+ validationError("verifierCovenant", "strict and verifier assets must be distinct");
206
+ }
207
+ return {
208
+ strictAssetIdJetHex,
209
+ verifierAssetIdJetHex,
210
+ verifierAmountAtomic: u64DecimalValue(record.verifierAmountAtomic, "verifierCovenant.verifierAmountAtomic", false),
211
+ holderProgramCmr: hexValue(record.holderProgramCmr, "verifierCovenant.holderProgramCmr", 32),
212
+ holderNumsInternalKey: hexValue(record.holderNumsInternalKey, "verifierCovenant.holderNumsInternalKey", 32),
213
+ whitelistRoot: hexValue(record.whitelistRoot, "verifierCovenant.whitelistRoot", 32),
214
+ redemptionScriptHash: hexValue(record.redemptionScriptHash, "verifierCovenant.redemptionScriptHash", 32),
215
+ authorityXonly: hexValue(record.authorityXonly, "verifierCovenant.authorityXonly", 32),
216
+ };
217
+ }
218
+ function strictPolicyHolderCovenantTemplatePath() {
219
+ return `${__dirname}/../docs/definitions/strict-holder-input-v1.simf`;
220
+ }
221
+ async function renderStrictPolicyHolderCovenantSource(value) {
222
+ const params = normalizeStrictPolicyHolderCovenantParams(value);
223
+ const source = await readTemplateFile(strictPolicyHolderCovenantTemplatePath());
224
+ return (0, templating_1.renderTemplate)(source, {
225
+ STRICT_ASSET_ID_JET_HEX: params.strictAssetIdJetHex,
226
+ VERIFIER_ASSET_ID_JET_HEX: params.verifierAssetIdJetHex,
227
+ VERIFIER_AMOUNT_ATOMIC: params.verifierAmountAtomic,
228
+ NUMS_INTERNAL_KEY: params.numsInternalKey,
229
+ });
230
+ }
231
+ async function compileStrictPolicyHolderCovenant(config, value) {
232
+ const params = normalizeStrictPolicyHolderCovenantParams(value);
233
+ const { compileFromFile } = await loadCompiler();
234
+ return compileFromFile(config, {
235
+ simfPath: strictPolicyHolderCovenantTemplatePath(),
236
+ templateVars: {
237
+ STRICT_ASSET_ID_JET_HEX: params.strictAssetIdJetHex,
238
+ VERIFIER_ASSET_ID_JET_HEX: params.verifierAssetIdJetHex,
239
+ VERIFIER_AMOUNT_ATOMIC: params.verifierAmountAtomic,
240
+ NUMS_INTERNAL_KEY: params.numsInternalKey,
241
+ },
242
+ });
243
+ }
244
+ function strictPolicyVerifierCovenantTemplatePath() {
245
+ return `${__dirname}/../docs/definitions/strict-verifier-ordinary-v1.simf`;
246
+ }
247
+ function strictPolicyVerifierTemplateVars(value) {
248
+ const params = normalizeStrictPolicyVerifierCovenantParams(value);
249
+ const tagHashes = (0, dampPolicy_1.strictPolicyWhitelistTagHashes)();
250
+ return {
251
+ STRICT_ASSET_ID_JET_HEX: params.strictAssetIdJetHex,
252
+ VERIFIER_ASSET_ID_JET_HEX: params.verifierAssetIdJetHex,
253
+ VERIFIER_AMOUNT_ATOMIC: params.verifierAmountAtomic,
254
+ HOLDER_PROGRAM_CMR: params.holderProgramCmr,
255
+ HOLDER_NUMS_INTERNAL_KEY: params.holderNumsInternalKey,
256
+ WHITELIST_ROOT: params.whitelistRoot,
257
+ REDEMPTION_SCRIPT_HASH: params.redemptionScriptHash,
258
+ AUTHORITY_XONLY: params.authorityXonly,
259
+ WHITELIST_OWNER_TAG_HASH: tagHashes.owner,
260
+ WHITELIST_NODE_TAG_HASH: tagHashes.node,
261
+ WHITELIST_DEPTH: dampPolicy_1.STRICT_POLICY_WHITELIST_DEPTH,
262
+ MAX_INPUTS: exports.STRICT_POLICY_VERIFIER_MAX_INPUTS,
263
+ MAX_OUTPUTS: exports.STRICT_POLICY_VERIFIER_MAX_OUTPUTS,
264
+ INPUT_UNIT_SLOTS: Array.from({ length: exports.STRICT_POLICY_VERIFIER_MAX_INPUTS }, () => "()").join(", "),
265
+ OUTPUT_UNIT_SLOTS: Array.from({ length: exports.STRICT_POLICY_VERIFIER_MAX_OUTPUTS }, () => "()").join(", "),
266
+ };
267
+ }
268
+ async function renderStrictPolicyVerifierCovenantSource(value) {
269
+ const source = await readTemplateFile(strictPolicyVerifierCovenantTemplatePath());
270
+ return (0, templating_1.renderTemplate)(source, strictPolicyVerifierTemplateVars(value));
271
+ }
272
+ async function compileStrictPolicyVerifierCovenant(config, value) {
273
+ const { compileFromFile } = await loadCompiler();
274
+ return compileFromFile(config, {
275
+ simfPath: strictPolicyVerifierCovenantTemplatePath(),
276
+ templateVars: strictPolicyVerifierTemplateVars(value),
277
+ });
278
+ }
279
+ function taggedHashHexUtf8(tag, canonicalPayload) {
280
+ const tagHash = (0, node_crypto_1.createHash)("sha256").update(tag, "utf8").digest();
281
+ return (0, node_crypto_1.createHash)("sha256")
282
+ .update(tagHash)
283
+ .update(tagHash)
284
+ .update(canonicalPayload, "utf8")
285
+ .digest("hex");
286
+ }
287
+ function computeStrictPolicyAuthoritySetHash(signerXonlyValue) {
288
+ const signerXonly = hexValue(signerXonlyValue, "signerXonly", 32);
289
+ return taggedHashHexUtf8(exports.STRICT_POLICY_AUTHORITY_SET_DOMAIN, (0, summary_1.stableStringify)({
290
+ authorityType: "issuer_single",
291
+ approvalThreshold: 1,
292
+ members: [signerXonly],
293
+ }));
294
+ }
295
+ function prepareStrictPolicySnapshot(input) {
296
+ const payload = normalizeStrictPolicySnapshotPayload(input.payload);
297
+ const authority = normalizeStrictPolicySnapshotAuthority(input.authority);
298
+ if (Date.parse(authority.approvedAt) > Date.parse(payload.effectiveAt)) {
299
+ validationError("authority.approvedAt", "must not be later than payload.effectiveAt");
300
+ }
301
+ const canonicalPayload = (0, summary_1.stableStringify)(payload);
302
+ const snapshotHash = (0, summary_1.sha256HexUtf8)(canonicalPayload);
303
+ const canonicalApprovalMessage = (0, summary_1.stableStringify)({
304
+ schema: exports.STRICT_POLICY_SNAPSHOT_APPROVAL_SCHEMA,
305
+ network: payload.network,
306
+ liquidGenesisHash: payload.liquidGenesisHash,
307
+ assetBindingId: payload.assetBindingId,
308
+ liquidAssetId: payload.liquidAssetId,
309
+ epoch: payload.epoch,
310
+ previousSnapshotHash: payload.previousSnapshotHash,
311
+ snapshotHash,
312
+ verifierCmr: payload.verifier.cmr,
313
+ verifierScriptHash: payload.verifier.scriptHash,
314
+ authorityType: authority.authorityType,
315
+ authorityId: authority.authorityId,
316
+ authorityVersion: authority.authorityVersion,
317
+ authoritySetHash: authority.authoritySetHash,
318
+ approvalScheme: authority.approvalScheme,
319
+ approvalThreshold: authority.approvalThreshold,
320
+ signerXonly: authority.signerXonly,
321
+ approvedAt: authority.approvedAt,
322
+ });
323
+ const approvalMessageHash = taggedHashHexUtf8(exports.STRICT_POLICY_SNAPSHOT_APPROVAL_DOMAIN, canonicalApprovalMessage);
324
+ return {
325
+ payload,
326
+ authority,
327
+ canonicalPayload,
328
+ snapshotHash,
329
+ canonicalApprovalMessage,
330
+ approvalMessageHash,
331
+ };
332
+ }
333
+ function finalizeStrictPolicySnapshot(prepared, proofValue) {
334
+ const approvalProof = normalizeStrictPolicySnapshotApprovalProof(proofValue);
335
+ if (approvalProof.signerXonly !== prepared.authority.signerXonly) {
336
+ validationError("approvalProof.signerXonly", "must match authority.signerXonly");
337
+ }
338
+ const approvalProofHash = (0, summary_1.sha256HexUtf8)((0, summary_1.stableStringify)(approvalProof));
339
+ return {
340
+ ...prepared,
341
+ approvalProof,
342
+ approvalProofHash,
343
+ };
344
+ }
345
+ async function verifyStrictPolicySnapshot(envelopeValue) {
346
+ const prepared = prepareStrictPolicySnapshot({
347
+ payload: envelopeValue.payload,
348
+ authority: envelopeValue.authority,
349
+ });
350
+ const envelope = finalizeStrictPolicySnapshot(prepared, envelopeValue.approvalProof);
351
+ if (envelopeValue.canonicalPayload !== prepared.canonicalPayload) {
352
+ validationError("canonicalPayload", "does not match the normalized snapshot payload");
353
+ }
354
+ if (envelopeValue.snapshotHash !== prepared.snapshotHash) {
355
+ validationError("snapshotHash", "does not match the canonical payload");
356
+ }
357
+ if (envelopeValue.canonicalApprovalMessage !== prepared.canonicalApprovalMessage) {
358
+ validationError("canonicalApprovalMessage", "does not match the normalized snapshot approval message");
359
+ }
360
+ if (envelopeValue.approvalMessageHash !== prepared.approvalMessageHash) {
361
+ validationError("approvalMessageHash", "does not match the canonical approval message");
362
+ }
363
+ if (envelopeValue.approvalProofHash !== envelope.approvalProofHash) {
364
+ validationError("approvalProofHash", "does not match the canonical approval proof");
365
+ }
366
+ const valid = await (0, schnorr_1.schnorrVerifyHex)(envelope.approvalProof.signatureHex, prepared.approvalMessageHash, prepared.authority.signerXonly);
367
+ if (!valid) {
368
+ validationError("approvalProof.signatureHex", "is not valid for the snapshot authority");
369
+ }
370
+ return prepared;
371
+ }
@@ -0,0 +1,61 @@
1
+ export declare const STRICT_POLICY_WHITELIST_DEPTH: 8;
2
+ export declare const STRICT_POLICY_MAX_WHITELIST_OWNERS: number;
3
+ export declare const STRICT_POLICY_WHITELIST_OWNER_DOMAIN: "HAZBASE-RWA-STRICT-WHITELIST-OWNER-V1";
4
+ export declare const STRICT_POLICY_WHITELIST_EMPTY_DOMAIN: "HAZBASE-RWA-STRICT-WHITELIST-EMPTY-V1";
5
+ export declare const STRICT_POLICY_WHITELIST_NODE_DOMAIN: "HAZBASE-RWA-STRICT-WHITELIST-NODE-V1";
6
+ export interface StrictPolicyHolderPositionParams {
7
+ holderProgramCmr: string;
8
+ numsInternalKey: string;
9
+ holderXonly: string;
10
+ }
11
+ export interface StrictPolicyHolderPosition {
12
+ holderProgramCmr: string;
13
+ holderTapleafHash: string;
14
+ holderXonly: string;
15
+ ownerTapDataHash: string;
16
+ taprootRoot: string;
17
+ numsInternalKey: string;
18
+ outputKey: string;
19
+ scriptPubKey: string;
20
+ scriptHash: string;
21
+ }
22
+ export interface StrictPolicyWhitelistProofStep {
23
+ siblingPosition: "left" | "right";
24
+ siblingHash: string;
25
+ }
26
+ export interface StrictPolicyWhitelistEntry {
27
+ ownerXonly: string;
28
+ leafHash: string;
29
+ proof: StrictPolicyWhitelistProofStep[];
30
+ }
31
+ export interface StrictPolicyWhitelist {
32
+ depth: typeof STRICT_POLICY_WHITELIST_DEPTH;
33
+ root: string;
34
+ entries: StrictPolicyWhitelistEntry[];
35
+ }
36
+ export interface StrictPolicyWhitelistProof {
37
+ ownerXonly: string;
38
+ proof: StrictPolicyWhitelistProofStep[];
39
+ }
40
+ export declare function computeStrictPolicyHolderTapleafHash(holderProgramCmrValue: string): string;
41
+ export declare function computeStrictPolicyOwnerTapDataHash(holderXonlyValue: string): string;
42
+ export declare function computeElementsTapBranch(leftValue: string, rightValue: string): string;
43
+ export declare function deriveStrictPolicyHolderPosition(value: StrictPolicyHolderPositionParams): Promise<StrictPolicyHolderPosition>;
44
+ export declare function computeStrictPolicyWhitelistOwnerLeaf(ownerXonlyValue: string): string;
45
+ export declare function computeStrictPolicyWhitelistEmptyLeaf(): string;
46
+ export declare function computeStrictPolicyWhitelistNode(leftValue: string, rightValue: string): string;
47
+ export declare function buildStrictPolicyWhitelist(ownerXonlyValues: readonly string[]): StrictPolicyWhitelist;
48
+ export declare function verifyStrictPolicyWhitelistProof(input: {
49
+ ownerXonly: string;
50
+ proof: readonly StrictPolicyWhitelistProofStep[];
51
+ expectedRoot: string;
52
+ }): boolean;
53
+ export declare function normalizeStrictPolicyWhitelistProof(input: {
54
+ ownerXonly: string;
55
+ proof: readonly StrictPolicyWhitelistProofStep[];
56
+ }): StrictPolicyWhitelistProof;
57
+ export declare function strictPolicyWhitelistTagHashes(): {
58
+ owner: string;
59
+ empty: string;
60
+ node: string;
61
+ };
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STRICT_POLICY_WHITELIST_NODE_DOMAIN = exports.STRICT_POLICY_WHITELIST_EMPTY_DOMAIN = exports.STRICT_POLICY_WHITELIST_OWNER_DOMAIN = exports.STRICT_POLICY_MAX_WHITELIST_OWNERS = exports.STRICT_POLICY_WHITELIST_DEPTH = void 0;
4
+ exports.computeStrictPolicyHolderTapleafHash = computeStrictPolicyHolderTapleafHash;
5
+ exports.computeStrictPolicyOwnerTapDataHash = computeStrictPolicyOwnerTapDataHash;
6
+ exports.computeElementsTapBranch = computeElementsTapBranch;
7
+ exports.deriveStrictPolicyHolderPosition = deriveStrictPolicyHolderPosition;
8
+ exports.computeStrictPolicyWhitelistOwnerLeaf = computeStrictPolicyWhitelistOwnerLeaf;
9
+ exports.computeStrictPolicyWhitelistEmptyLeaf = computeStrictPolicyWhitelistEmptyLeaf;
10
+ exports.computeStrictPolicyWhitelistNode = computeStrictPolicyWhitelistNode;
11
+ exports.buildStrictPolicyWhitelist = buildStrictPolicyWhitelist;
12
+ exports.verifyStrictPolicyWhitelistProof = verifyStrictPolicyWhitelistProof;
13
+ exports.normalizeStrictPolicyWhitelistProof = normalizeStrictPolicyWhitelistProof;
14
+ exports.strictPolicyWhitelistTagHashes = strictPolicyWhitelistTagHashes;
15
+ const node_crypto_1 = require("node:crypto");
16
+ const errors_1 = require("../core/errors");
17
+ const strictPolicyCrypto_1 = require("../core/strictPolicyCrypto");
18
+ exports.STRICT_POLICY_WHITELIST_DEPTH = 8;
19
+ exports.STRICT_POLICY_MAX_WHITELIST_OWNERS = 1 << exports.STRICT_POLICY_WHITELIST_DEPTH;
20
+ exports.STRICT_POLICY_WHITELIST_OWNER_DOMAIN = "HAZBASE-RWA-STRICT-WHITELIST-OWNER-V1";
21
+ exports.STRICT_POLICY_WHITELIST_EMPTY_DOMAIN = "HAZBASE-RWA-STRICT-WHITELIST-EMPTY-V1";
22
+ exports.STRICT_POLICY_WHITELIST_NODE_DOMAIN = "HAZBASE-RWA-STRICT-WHITELIST-NODE-V1";
23
+ function validationError(field, message) {
24
+ throw new errors_1.ValidationError(`${field} ${message}`, {
25
+ code: "STRICT_POLICY_COVENANT_INVALID",
26
+ field,
27
+ });
28
+ }
29
+ function hex32(value, field) {
30
+ if (typeof value !== "string")
31
+ validationError(field, "must be 32 bytes of hex");
32
+ const normalized = value.trim().toLowerCase().replace(/^0x/u, "");
33
+ if (!/^[0-9a-f]{64}$/u.test(normalized)) {
34
+ validationError(field, "must be 32 bytes of hex");
35
+ }
36
+ return normalized;
37
+ }
38
+ function bytes(hex) {
39
+ return Buffer.from(hex, "hex");
40
+ }
41
+ function sha256(value) {
42
+ return (0, node_crypto_1.createHash)("sha256").update(value).digest();
43
+ }
44
+ function concat(...values) {
45
+ return Buffer.concat(values.map((value) => Buffer.from(value)));
46
+ }
47
+ function taggedHash(tag, payload) {
48
+ const tagHash = sha256(Buffer.from(tag, "utf8"));
49
+ return sha256(concat(tagHash, tagHash, payload));
50
+ }
51
+ function taggedHashHex(tag, payload) {
52
+ return taggedHash(tag, payload).toString("hex");
53
+ }
54
+ async function loadSecp256k1() {
55
+ return (await (0, strictPolicyCrypto_1.loadStrictPolicyCrypto)()).secp256k1;
56
+ }
57
+ function computeStrictPolicyHolderTapleafHash(holderProgramCmrValue) {
58
+ const holderProgramCmr = hex32(holderProgramCmrValue, "holderProgramCmr");
59
+ return taggedHashHex("TapLeaf/elements", concat(Uint8Array.of(0xbe, 0x20), bytes(holderProgramCmr)));
60
+ }
61
+ function computeStrictPolicyOwnerTapDataHash(holderXonlyValue) {
62
+ const holderXonly = hex32(holderXonlyValue, "holderXonly");
63
+ return taggedHashHex("TapData", bytes(holderXonly));
64
+ }
65
+ function computeElementsTapBranch(leftValue, rightValue) {
66
+ const left = hex32(leftValue, "left");
67
+ const right = hex32(rightValue, "right");
68
+ const ordered = left < right ? [left, right] : [right, left];
69
+ return taggedHashHex("TapBranch/elements", concat(bytes(ordered[0]), bytes(ordered[1])));
70
+ }
71
+ async function deriveStrictPolicyHolderPosition(value) {
72
+ const holderProgramCmr = hex32(value.holderProgramCmr, "holderProgramCmr");
73
+ const numsInternalKey = hex32(value.numsInternalKey, "numsInternalKey");
74
+ const holderXonly = hex32(value.holderXonly, "holderXonly");
75
+ const holderTapleafHash = computeStrictPolicyHolderTapleafHash(holderProgramCmr);
76
+ const ownerTapDataHash = computeStrictPolicyOwnerTapDataHash(holderXonly);
77
+ const taprootRoot = computeElementsTapBranch(holderTapleafHash, ownerTapDataHash);
78
+ const tweak = taggedHash("TapTweak/elements", concat(bytes(numsInternalKey), bytes(taprootRoot)));
79
+ const secp256k1 = await loadSecp256k1();
80
+ const tweakScalar = BigInt(`0x${tweak.toString("hex")}`);
81
+ if (tweakScalar >= secp256k1.Point.Fn.ORDER) {
82
+ validationError("taprootTweak", "must be below the secp256k1 group order");
83
+ }
84
+ let outputPoint;
85
+ try {
86
+ const internalPoint = secp256k1.Point.fromBytes(concat(Uint8Array.of(0x02), bytes(numsInternalKey)));
87
+ outputPoint = internalPoint.add(secp256k1.Point.BASE.multiply(tweakScalar));
88
+ if (outputPoint.equals(secp256k1.Point.ZERO)) {
89
+ validationError("taprootTweak", "must not produce the point at infinity");
90
+ }
91
+ }
92
+ catch (error) {
93
+ if (error instanceof errors_1.ValidationError)
94
+ throw error;
95
+ validationError("numsInternalKey", "must be a valid x-only secp256k1 public key");
96
+ }
97
+ const outputKey = Buffer.from(outputPoint.toBytes(true)).subarray(1).toString("hex");
98
+ const scriptPubKey = `5120${outputKey}`;
99
+ const scriptHash = sha256(bytes(scriptPubKey)).toString("hex");
100
+ return {
101
+ holderProgramCmr,
102
+ holderTapleafHash,
103
+ holderXonly,
104
+ ownerTapDataHash,
105
+ taprootRoot,
106
+ numsInternalKey,
107
+ outputKey,
108
+ scriptPubKey,
109
+ scriptHash,
110
+ };
111
+ }
112
+ function computeStrictPolicyWhitelistOwnerLeaf(ownerXonlyValue) {
113
+ const ownerXonly = hex32(ownerXonlyValue, "ownerXonly");
114
+ return taggedHashHex(exports.STRICT_POLICY_WHITELIST_OWNER_DOMAIN, bytes(ownerXonly));
115
+ }
116
+ function computeStrictPolicyWhitelistEmptyLeaf() {
117
+ return taggedHashHex(exports.STRICT_POLICY_WHITELIST_EMPTY_DOMAIN, new Uint8Array());
118
+ }
119
+ function computeStrictPolicyWhitelistNode(leftValue, rightValue) {
120
+ const left = hex32(leftValue, "left");
121
+ const right = hex32(rightValue, "right");
122
+ return taggedHashHex(exports.STRICT_POLICY_WHITELIST_NODE_DOMAIN, concat(bytes(left), bytes(right)));
123
+ }
124
+ function buildStrictPolicyWhitelist(ownerXonlyValues) {
125
+ if (!Array.isArray(ownerXonlyValues) || ownerXonlyValues.length === 0) {
126
+ validationError("ownerXonlyValues", "must contain at least one owner");
127
+ }
128
+ if (ownerXonlyValues.length > exports.STRICT_POLICY_MAX_WHITELIST_OWNERS) {
129
+ validationError("ownerXonlyValues", `must contain at most ${exports.STRICT_POLICY_MAX_WHITELIST_OWNERS} owners`);
130
+ }
131
+ const owners = ownerXonlyValues
132
+ .map((owner, index) => hex32(owner, `ownerXonlyValues[${index}]`))
133
+ .sort();
134
+ if (new Set(owners).size !== owners.length) {
135
+ validationError("ownerXonlyValues", "must not contain duplicates");
136
+ }
137
+ const emptyLeaf = computeStrictPolicyWhitelistEmptyLeaf();
138
+ const leaves = Array.from({ length: exports.STRICT_POLICY_MAX_WHITELIST_OWNERS }, (_, index) => (index < owners.length ? computeStrictPolicyWhitelistOwnerLeaf(owners[index]) : emptyLeaf));
139
+ const levels = [leaves];
140
+ for (let depth = 0; depth < exports.STRICT_POLICY_WHITELIST_DEPTH; depth += 1) {
141
+ const current = levels[depth];
142
+ const next = [];
143
+ for (let index = 0; index < current.length; index += 2) {
144
+ next.push(computeStrictPolicyWhitelistNode(current[index], current[index + 1]));
145
+ }
146
+ levels.push(next);
147
+ }
148
+ const entries = owners.map((ownerXonly, ownerIndex) => {
149
+ const proof = [];
150
+ let index = ownerIndex;
151
+ for (let depth = 0; depth < exports.STRICT_POLICY_WHITELIST_DEPTH; depth += 1) {
152
+ const siblingIndex = index ^ 1;
153
+ proof.push({
154
+ siblingPosition: siblingIndex < index ? "left" : "right",
155
+ siblingHash: levels[depth][siblingIndex],
156
+ });
157
+ index = Math.floor(index / 2);
158
+ }
159
+ return {
160
+ ownerXonly,
161
+ leafHash: levels[0][ownerIndex],
162
+ proof,
163
+ };
164
+ });
165
+ return {
166
+ depth: exports.STRICT_POLICY_WHITELIST_DEPTH,
167
+ root: levels[exports.STRICT_POLICY_WHITELIST_DEPTH][0],
168
+ entries,
169
+ };
170
+ }
171
+ function verifyStrictPolicyWhitelistProof(input) {
172
+ const normalized = normalizeStrictPolicyWhitelistProof(input);
173
+ const ownerXonly = normalized.ownerXonly;
174
+ const expectedRoot = hex32(input.expectedRoot, "expectedRoot");
175
+ let node = computeStrictPolicyWhitelistOwnerLeaf(ownerXonly);
176
+ for (const step of normalized.proof) {
177
+ node = step.siblingPosition === "left"
178
+ ? computeStrictPolicyWhitelistNode(step.siblingHash, node)
179
+ : computeStrictPolicyWhitelistNode(node, step.siblingHash);
180
+ }
181
+ return node === expectedRoot;
182
+ }
183
+ function normalizeStrictPolicyWhitelistProof(input) {
184
+ const ownerXonly = hex32(input.ownerXonly, "ownerXonly");
185
+ if (!Array.isArray(input.proof) || input.proof.length !== exports.STRICT_POLICY_WHITELIST_DEPTH) {
186
+ validationError("proof", `must contain exactly ${exports.STRICT_POLICY_WHITELIST_DEPTH} sibling hashes`);
187
+ }
188
+ const proof = input.proof.map((step, index) => {
189
+ if (!step || typeof step !== "object") {
190
+ validationError(`proof[${index}]`, "must be an object");
191
+ }
192
+ if (step.siblingPosition !== "left" && step.siblingPosition !== "right") {
193
+ validationError(`proof[${index}].siblingPosition`, "must be left or right");
194
+ }
195
+ return {
196
+ siblingPosition: step.siblingPosition,
197
+ siblingHash: hex32(step.siblingHash, `proof[${index}].siblingHash`),
198
+ };
199
+ });
200
+ return { ownerXonly, proof };
201
+ }
202
+ function strictPolicyWhitelistTagHashes() {
203
+ return {
204
+ owner: sha256(Buffer.from(exports.STRICT_POLICY_WHITELIST_OWNER_DOMAIN, "utf8")).toString("hex"),
205
+ empty: sha256(Buffer.from(exports.STRICT_POLICY_WHITELIST_EMPTY_DOMAIN, "utf8")).toString("hex"),
206
+ node: sha256(Buffer.from(exports.STRICT_POLICY_WHITELIST_NODE_DOMAIN, "utf8")).toString("hex"),
207
+ };
208
+ }