@hazbase/simplicity 0.4.6 → 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,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
+ }
@@ -0,0 +1,51 @@
1
+ import { type StrictPolicyNetwork } from "./damp";
2
+ import { type StrictPolicyDecodedTransaction, type StrictPolicyProofBackedTransactionExpectation, type StrictPolicyProofBackedTransactionInspection } from "./dampTransaction";
3
+ export declare const STRICT_POLICY_PSET_INSPECTION_SCHEMA: "hazbase_strict_policy_pset_inspection_v1";
4
+ export interface StrictPolicySimplicityInputRequest {
5
+ inputIndex: number;
6
+ cmr: string;
7
+ genesisHash: string;
8
+ }
9
+ export interface StrictPolicySimplicityInputInspection extends StrictPolicySimplicityInputRequest {
10
+ sigAllHash: string;
11
+ }
12
+ export interface StrictPolicyPsetDecoderRequest {
13
+ psetBase64: string;
14
+ network: StrictPolicyNetwork;
15
+ simplicityInputs: StrictPolicySimplicityInputRequest[];
16
+ }
17
+ export type StrictPolicyPsetDecoder = (request: StrictPolicyPsetDecoderRequest) => Promise<unknown> | unknown;
18
+ export interface StrictPolicyPsetInspection {
19
+ schema: typeof STRICT_POLICY_PSET_INSPECTION_SCHEMA;
20
+ psetVersion: 2;
21
+ inputCount: number;
22
+ outputCount: number;
23
+ inputSummaries: unknown[];
24
+ extractable: true;
25
+ canonicalPsetBase64: string;
26
+ psetHash: string;
27
+ canonicalPsetHash: string;
28
+ canonicalEncodingMatches: true;
29
+ unknownFieldCount: number;
30
+ proprietaryFieldCount: number;
31
+ decodedPset: Record<string, unknown>;
32
+ transaction: StrictPolicyDecodedTransaction;
33
+ simplicityInputs: StrictPolicySimplicityInputInspection[];
34
+ }
35
+ export interface StrictPolicyPsetSigningInspectionRequest {
36
+ psetBase64: string;
37
+ network: StrictPolicyNetwork;
38
+ simplicityInputs: StrictPolicySimplicityInputRequest[];
39
+ signingInputIndex: number;
40
+ serviceSighashHex?: string;
41
+ transactionExpectation: StrictPolicyProofBackedTransactionExpectation;
42
+ decodePset: StrictPolicyPsetDecoder;
43
+ }
44
+ export interface StrictPolicyPsetSigningInspection {
45
+ pset: StrictPolicyPsetInspection;
46
+ transaction: StrictPolicyProofBackedTransactionInspection;
47
+ signingInputIndex: number;
48
+ signingSighashHex: string;
49
+ }
50
+ export declare function normalizeStrictPolicyPsetInspection(value: unknown, expectedPsetBase64?: string): StrictPolicyPsetInspection;
51
+ export declare function inspectStrictPolicyPsetForSigning(request: StrictPolicyPsetSigningInspectionRequest): Promise<StrictPolicyPsetSigningInspection>;
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STRICT_POLICY_PSET_INSPECTION_SCHEMA = void 0;
4
+ exports.normalizeStrictPolicyPsetInspection = normalizeStrictPolicyPsetInspection;
5
+ exports.inspectStrictPolicyPsetForSigning = inspectStrictPolicyPsetForSigning;
6
+ const node_crypto_1 = require("node:crypto");
7
+ const errors_1 = require("../core/errors");
8
+ const damp_1 = require("./damp");
9
+ const dampTransaction_1 = require("./dampTransaction");
10
+ exports.STRICT_POLICY_PSET_INSPECTION_SCHEMA = "hazbase_strict_policy_pset_inspection_v1";
11
+ function invalid(field, message) {
12
+ throw new errors_1.ValidationError(`${field} ${message}`, {
13
+ code: "STRICT_POLICY_PSET_INVALID",
14
+ field,
15
+ });
16
+ }
17
+ function recordValue(value, field) {
18
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
19
+ invalid(field, "must be an object");
20
+ }
21
+ return value;
22
+ }
23
+ function stringValue(value, field) {
24
+ if (typeof value !== "string" || value.trim().length === 0) {
25
+ invalid(field, "must be a non-empty string");
26
+ }
27
+ return value.trim();
28
+ }
29
+ function hex32(value, field) {
30
+ const normalized = stringValue(value, field).toLowerCase().replace(/^0x/u, "");
31
+ if (!/^[0-9a-f]{64}$/u.test(normalized)) {
32
+ invalid(field, "must be 32 bytes of hex");
33
+ }
34
+ return normalized;
35
+ }
36
+ function uint(value, field) {
37
+ if (!Number.isSafeInteger(value) || value < 0) {
38
+ invalid(field, "must be a non-negative safe integer");
39
+ }
40
+ return value;
41
+ }
42
+ function uint32(value, field) {
43
+ const normalized = uint(value, field);
44
+ if (normalized > 0xffff_ffff)
45
+ invalid(field, "must be a uint32");
46
+ return normalized;
47
+ }
48
+ function trueValue(value, field) {
49
+ if (value !== true)
50
+ invalid(field, "must be true");
51
+ return true;
52
+ }
53
+ function strictBase64Bytes(value, field) {
54
+ const normalized = stringValue(value, field);
55
+ if (normalized.length % 4 !== 0
56
+ || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(normalized)) {
57
+ invalid(field, "must be canonical standard base64");
58
+ }
59
+ const decoded = Buffer.from(normalized, "base64");
60
+ if (decoded.toString("base64") !== normalized) {
61
+ invalid(field, "must use canonical base64 encoding");
62
+ }
63
+ return decoded;
64
+ }
65
+ function psetHash(value, field) {
66
+ return (0, node_crypto_1.createHash)("sha256").update(strictBase64Bytes(value, field)).digest("hex");
67
+ }
68
+ function normalizeSimplicityInputRequest(value, field) {
69
+ const record = recordValue(value, field);
70
+ return {
71
+ inputIndex: uint32(record.inputIndex, `${field}.inputIndex`),
72
+ cmr: hex32(record.cmr, `${field}.cmr`),
73
+ genesisHash: hex32(record.genesisHash, `${field}.genesisHash`),
74
+ };
75
+ }
76
+ function normalizeSimplicityInputInspection(value, index) {
77
+ const field = `inspection.simplicityInputs[${index}]`;
78
+ const request = normalizeSimplicityInputRequest(value, field);
79
+ const record = value;
80
+ return {
81
+ ...request,
82
+ sigAllHash: hex32(record.sigAllHash, `${field}.sigAllHash`),
83
+ };
84
+ }
85
+ function normalizeNetwork(value, field) {
86
+ const network = stringValue(value, field);
87
+ if (!("liquid-regtest liquid-testnet liquid-mainnet".split(" ")).includes(network)) {
88
+ invalid(field, "is unsupported");
89
+ }
90
+ return network;
91
+ }
92
+ function normalizeStrictPolicyPsetInspection(value, expectedPsetBase64) {
93
+ const record = recordValue(value, "inspection");
94
+ if (typeof record.error === "string" && record.error.trim()) {
95
+ invalid("inspection.error", `reported ${record.error.trim()}`);
96
+ }
97
+ if (record.schema !== exports.STRICT_POLICY_PSET_INSPECTION_SCHEMA) {
98
+ invalid("inspection.schema", `must be ${exports.STRICT_POLICY_PSET_INSPECTION_SCHEMA}`);
99
+ }
100
+ if (record.psetVersion !== 2) {
101
+ invalid("inspection.psetVersion", "must be PSET v2");
102
+ }
103
+ const inputCount = uint(record.inputCount, "inspection.inputCount");
104
+ const outputCount = uint(record.outputCount, "inspection.outputCount");
105
+ if (!Array.isArray(record.inputs) || record.inputs.length !== inputCount) {
106
+ invalid("inspection.inputs", "must describe every PSET input exactly once");
107
+ }
108
+ if (record.extractable !== true) {
109
+ invalid("inspection.extractable", "must be true");
110
+ }
111
+ const canonicalPsetBase64 = stringValue(record.canonicalPsetBase64, "inspection.canonicalPsetBase64");
112
+ const canonicalPsetHash = hex32(record.canonicalPsetHash, "inspection.canonicalPsetHash");
113
+ const calculatedCanonicalHash = psetHash(canonicalPsetBase64, "inspection.canonicalPsetBase64");
114
+ if (canonicalPsetHash !== calculatedCanonicalHash) {
115
+ invalid("inspection.canonicalPsetHash", "does not match canonicalPsetBase64");
116
+ }
117
+ const inspectedPsetHash = hex32(record.psetHash, "inspection.psetHash");
118
+ trueValue(record.canonicalEncodingMatches, "inspection.canonicalEncodingMatches");
119
+ if (inspectedPsetHash !== canonicalPsetHash) {
120
+ invalid("inspection.psetHash", "does not match the canonical PSET hash");
121
+ }
122
+ if (expectedPsetBase64 !== undefined) {
123
+ const expectedHash = psetHash(expectedPsetBase64, "psetBase64");
124
+ if (inspectedPsetHash !== expectedHash) {
125
+ invalid("inspection.psetHash", "does not match the requested PSET");
126
+ }
127
+ }
128
+ const decodedPset = recordValue(record.decodedPset, "inspection.decodedPset");
129
+ const transaction = (0, dampTransaction_1.normalizeStrictPolicyDecodedTransaction)(record.transaction);
130
+ if (transaction.inputs.length !== inputCount) {
131
+ invalid("inspection.transaction.inputs", "count does not match the complete PSET");
132
+ }
133
+ if (transaction.outputs.length !== outputCount) {
134
+ invalid("inspection.transaction.outputs", "count does not match the complete PSET");
135
+ }
136
+ if (!Array.isArray(record.simplicityInputs)) {
137
+ invalid("inspection.simplicityInputs", "must be an array");
138
+ }
139
+ const simplicityInputs = record.simplicityInputs.map(normalizeSimplicityInputInspection);
140
+ const seenInputs = new Set();
141
+ for (const input of simplicityInputs) {
142
+ if (input.inputIndex >= inputCount) {
143
+ invalid("inspection.simplicityInputs", "contains an out-of-range input index");
144
+ }
145
+ if (seenInputs.has(input.inputIndex)) {
146
+ invalid("inspection.simplicityInputs", "must not contain duplicate input indexes");
147
+ }
148
+ seenInputs.add(input.inputIndex);
149
+ }
150
+ return {
151
+ schema: exports.STRICT_POLICY_PSET_INSPECTION_SCHEMA,
152
+ psetVersion: 2,
153
+ inputCount,
154
+ outputCount,
155
+ inputSummaries: record.inputs,
156
+ extractable: true,
157
+ canonicalPsetBase64,
158
+ psetHash: inspectedPsetHash,
159
+ canonicalPsetHash,
160
+ canonicalEncodingMatches: true,
161
+ unknownFieldCount: uint(record.unknownFieldCount, "inspection.unknownFieldCount"),
162
+ proprietaryFieldCount: uint(record.proprietaryFieldCount, "inspection.proprietaryFieldCount"),
163
+ decodedPset,
164
+ transaction,
165
+ simplicityInputs,
166
+ };
167
+ }
168
+ async function inspectStrictPolicyPsetForSigning(request) {
169
+ const psetBase64 = stringValue(request.psetBase64, "psetBase64");
170
+ const network = normalizeNetwork(request.network, "network");
171
+ if (typeof request.decodePset !== "function") {
172
+ invalid("decodePset", "must be a local complete-PSET decoder");
173
+ }
174
+ if (!Array.isArray(request.simplicityInputs) || request.simplicityInputs.length === 0) {
175
+ invalid("simplicityInputs", "must not be empty");
176
+ }
177
+ const simplicityInputs = request.simplicityInputs.map((input, index) => normalizeSimplicityInputRequest(input, `simplicityInputs[${index}]`));
178
+ const requestedIndexes = new Set();
179
+ for (const input of simplicityInputs) {
180
+ if (requestedIndexes.has(input.inputIndex)) {
181
+ invalid("simplicityInputs", "must not contain duplicate input indexes");
182
+ }
183
+ requestedIndexes.add(input.inputIndex);
184
+ }
185
+ const signingInputIndex = uint32(request.signingInputIndex, "signingInputIndex");
186
+ if (!requestedIndexes.has(signingInputIndex)) {
187
+ invalid("signingInputIndex", "must identify a requested Simplicity input");
188
+ }
189
+ const snapshot = (0, damp_1.normalizeStrictPolicySnapshotPayload)(request.transactionExpectation.snapshot);
190
+ if (network !== snapshot.network) {
191
+ invalid("network", "does not match the policy snapshot");
192
+ }
193
+ for (const input of simplicityInputs) {
194
+ if (input.genesisHash !== snapshot.liquidGenesisHash) {
195
+ invalid(`simplicityInputs[${input.inputIndex}].genesisHash`, "does not match the policy snapshot");
196
+ }
197
+ }
198
+ const signingRequest = simplicityInputs.find((input) => input.inputIndex === signingInputIndex);
199
+ if (signingRequest.cmr !== request.transactionExpectation.holderProgramCmr.toLowerCase()) {
200
+ invalid("signingInputIndex", "does not use the expected holder covenant CMR");
201
+ }
202
+ const decoded = await request.decodePset({
203
+ psetBase64,
204
+ network,
205
+ simplicityInputs,
206
+ });
207
+ const pset = normalizeStrictPolicyPsetInspection(decoded, psetBase64);
208
+ if (pset.transaction.network !== network) {
209
+ invalid("inspection.transaction.network", "does not match the signing request");
210
+ }
211
+ if (pset.simplicityInputs.length !== simplicityInputs.length) {
212
+ invalid("inspection.simplicityInputs", "must contain every requested input exactly once");
213
+ }
214
+ for (const expected of simplicityInputs) {
215
+ const inspected = pset.simplicityInputs.find((candidate) => candidate.inputIndex === expected.inputIndex);
216
+ if (!inspected || inspected.cmr !== expected.cmr || inspected.genesisHash !== expected.genesisHash) {
217
+ invalid(`inspection.simplicityInputs[${expected.inputIndex}]`, "does not match the requested input, CMR, and genesis hash");
218
+ }
219
+ }
220
+ const transaction = await (0, dampTransaction_1.inspectStrictPolicyTransactionWithProofs)(pset.transaction, request.transactionExpectation);
221
+ if (!transaction.summary.strictInputIndexes.includes(signingInputIndex)) {
222
+ invalid("signingInputIndex", "must identify a strict-asset input");
223
+ }
224
+ const signingInspection = pset.simplicityInputs.find((input) => input.inputIndex === signingInputIndex);
225
+ if (request.serviceSighashHex !== undefined) {
226
+ const serviceHint = hex32(request.serviceSighashHex, "serviceSighashHex");
227
+ if (serviceHint !== signingInspection.sigAllHash) {
228
+ invalid("serviceSighashHex", "does not match the locally recomputed sig_all_hash");
229
+ }
230
+ }
231
+ return {
232
+ pset,
233
+ transaction,
234
+ signingInputIndex,
235
+ signingSighashHex: signingInspection.sigAllHash,
236
+ };
237
+ }
@@ -0,0 +1,96 @@
1
+ import { type StrictPolicyNetwork, type StrictPolicySnapshotPayload } from "./damp";
2
+ import { type StrictPolicyWhitelistProofStep } from "./dampPolicy";
3
+ export declare const STRICT_POLICY_TRANSACTION_SUMMARY_SCHEMA: "hazbase_strict_policy_transaction_summary_v1";
4
+ export type StrictPolicyOperation = "purchase" | "transfer" | "redemption";
5
+ export interface StrictPolicyTransactionOutpoint {
6
+ txid: string;
7
+ vout: number;
8
+ }
9
+ export interface StrictPolicyDecodedInput {
10
+ outpoint: StrictPolicyTransactionOutpoint;
11
+ assetId: string;
12
+ amountAtomic: string;
13
+ scriptHash: string;
14
+ hasIssuance: boolean;
15
+ }
16
+ export interface StrictPolicyDecodedOutput {
17
+ assetId: string;
18
+ amountAtomic: string;
19
+ scriptHash: string;
20
+ isFee: boolean;
21
+ }
22
+ export interface StrictPolicyDecodedTransaction {
23
+ network: StrictPolicyNetwork;
24
+ inputs: StrictPolicyDecodedInput[];
25
+ outputs: StrictPolicyDecodedOutput[];
26
+ feeAmountAtomic: string;
27
+ }
28
+ export interface StrictPolicyTransactionExpectation {
29
+ operation: StrictPolicyOperation;
30
+ snapshot: StrictPolicySnapshotPayload | unknown;
31
+ feeAssetId: string;
32
+ maxFeeAmountAtomic: string;
33
+ /** Derived locally from a verified policy proof; never trust a service hint here. */
34
+ allowedStrictOutputScriptHashes: string[];
35
+ }
36
+ export interface StrictPolicyOutputAuthorization {
37
+ outputIndex: number;
38
+ ownerXonly: string;
39
+ proof: StrictPolicyWhitelistProofStep[];
40
+ }
41
+ export interface StrictPolicyProofBackedTransactionExpectation {
42
+ operation: StrictPolicyOperation;
43
+ snapshot: StrictPolicySnapshotPayload | unknown;
44
+ feeAssetId: string;
45
+ maxFeeAmountAtomic: string;
46
+ holderProgramCmr: string;
47
+ holderNumsInternalKey: string;
48
+ strictOutputAuthorizations: StrictPolicyOutputAuthorization[];
49
+ }
50
+ export interface StrictPolicyVerifiedOutputAuthorization extends StrictPolicyOutputAuthorization {
51
+ derivedScriptHash: string;
52
+ }
53
+ export interface StrictPolicyTransactionSummary {
54
+ schema: typeof STRICT_POLICY_TRANSACTION_SUMMARY_SCHEMA;
55
+ operation: StrictPolicyOperation;
56
+ network: StrictPolicyNetwork;
57
+ snapshotHash: string;
58
+ transactionViewHash: string;
59
+ allowedStrictOutputSetHash: string;
60
+ verifierOutpoint: StrictPolicyTransactionOutpoint;
61
+ verifierAssetId: string;
62
+ verifierAmountAtomic: string;
63
+ strictAssetId: string;
64
+ strictInputIndexes: number[];
65
+ strictOutputIndexes: number[];
66
+ strictAmountAtomic: string;
67
+ feeAssetId: string;
68
+ feeAmountAtomic: string;
69
+ feeOutputIndex: number;
70
+ inputCount: number;
71
+ outputCount: number;
72
+ }
73
+ export interface StrictPolicyTransactionInspection {
74
+ transaction: StrictPolicyDecodedTransaction;
75
+ snapshot: StrictPolicySnapshotPayload;
76
+ summary: StrictPolicyTransactionSummary;
77
+ canonicalSummary: string;
78
+ summaryHash: string;
79
+ }
80
+ export interface StrictPolicyProofBackedTransactionInspection extends StrictPolicyTransactionInspection {
81
+ strictOutputAuthorizations: StrictPolicyVerifiedOutputAuthorization[];
82
+ }
83
+ export declare function normalizeStrictPolicyDecodedTransaction(value: StrictPolicyDecodedTransaction | unknown): StrictPolicyDecodedTransaction;
84
+ /**
85
+ * Validates a complete, locally decoded transaction view. Callers must first
86
+ * verify the snapshot approval and must derive the allowed scripts from the
87
+ * snapshot's policy proof. This function does not decode a PSET or compute its
88
+ * Simplicity sighash.
89
+ */
90
+ export declare function inspectStrictPolicyTransaction(transactionValue: StrictPolicyDecodedTransaction | unknown, expectation: StrictPolicyTransactionExpectation): StrictPolicyTransactionInspection;
91
+ /**
92
+ * High-level wallet inspection. Every strict-asset output must carry a proof
93
+ * for its output index. The destination script is derived locally from the
94
+ * verified snapshot root, fixed holder CMR, NUMS key, and owner key.
95
+ */
96
+ export declare function inspectStrictPolicyTransactionWithProofs(transactionValue: StrictPolicyDecodedTransaction | unknown, expectation: StrictPolicyProofBackedTransactionExpectation): Promise<StrictPolicyProofBackedTransactionInspection>;