@hazbase/simplicity 0.4.8 → 0.4.9

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 CHANGED
@@ -12,7 +12,7 @@ It is intentionally narrower than a full market stack. It does **not** try to be
12
12
 
13
13
  This SDK is designed to help Node developers get productive quickly, but it is still opinionated, early-stage, and best suited today to permissioned settlement pilots on Liquid.
14
14
 
15
- ## Version 0.4.7
15
+ ## Strict policy lifecycle
16
16
 
17
17
  This release extends strict-policy positions from ordinary transfers to a
18
18
  complete controlled lifecycle:
@@ -24,7 +24,7 @@ complete controlled lifecycle:
24
24
  - browser wallets can provide their own audited crypto implementation before
25
25
  any strict-policy helper loads, avoiding extension CSP violations.
26
26
 
27
- For policy-locked redemptions, Liquid x402 now binds a wallet-created holder
27
+ For policy-locked redemptions, Liquid x402 binds a wallet-created holder
28
28
  signature for the locally recomputed transaction hash into the payment payload.
29
29
 
30
30
  ## What You Can Build
@@ -185,6 +185,16 @@ enough for the bounded transaction shape. Do not use it for a secondary market,
185
185
  additional intermediaries, or transactions that need more inputs, outputs, or
186
186
  approved holders.
187
187
 
188
+ `strict_v2_confidential` is a separate verifier profile for Atomic DvP
189
+ purchases. It keeps the protected RWA inputs and outputs explicit, while the
190
+ single treasury-payment output at index 2 uses standard Liquid confidentiality.
191
+ The verifier still checks strict-asset conservation and rejects confidential
192
+ strict outputs. A wallet must inspect the complete explicit proposal first,
193
+ blind only that fixed payment output locally, and sign the resulting PSET. The
194
+ treasury then independently unblinds and checks the payment before service
195
+ finalization. Existing `strict_v2` and `strict_v2_compact` positions are not
196
+ changed or reinterpreted by this profile.
197
+
188
198
  ```ts
189
199
  const verifier = normalizeStrictPolicyVerifierCovenantParams({
190
200
  assetId,
@@ -0,0 +1,265 @@
1
+ /*
2
+ * Strict v2 confidential-value verifier covenant.
3
+ *
4
+ * This is a distinct profile from strict-verifier-ordinary-v1. It permits a
5
+ * single, designated confidential payment output at index 2. Every strict
6
+ * RWA input and output remains explicit, and the covenant requires their
7
+ * aggregate amounts to match. This prevents a strict RWA from being moved
8
+ * through the confidential payment output while allowing standard PSET
9
+ * value-and-asset blinding for the payment itself.
10
+ */
11
+ fn not(bit: bool) -> bool {
12
+ <u1>::into(jet::complement_1(<bool>::into(bit)))
13
+ }
14
+
15
+ fn explicit_input_asset_amount(index: u32) -> (u256, u64) {
16
+ let pair: (Asset1, Amount1) = unwrap(jet::input_amount(index));
17
+ let (asset, amount): (Asset1, Amount1) = pair;
18
+ let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset);
19
+ let amount_bits: u64 = unwrap_right::<(u1, u256)>(amount);
20
+ (asset_bits, amount_bits)
21
+ }
22
+
23
+ fn explicit_output_asset_amount(index: u32) -> (u256, u64) {
24
+ let pair: (Asset1, Amount1) = unwrap(jet::output_amount(index));
25
+ let (asset, amount): (Asset1, Amount1) = pair;
26
+ let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset);
27
+ let amount_bits: u64 = unwrap_right::<(u1, u256)>(amount);
28
+ (asset_bits, amount_bits)
29
+ }
30
+
31
+ fn add_amount(left: u64, right: u64) -> u64 {
32
+ let (carry, sum): (bool, u64) = jet::add_64(left, right);
33
+ assert!(not(carry));
34
+ sum
35
+ }
36
+
37
+ fn whitelist_owner_leaf(owner: Pubkey) -> u256 {
38
+ let tag_hash: u256 = 0x{{WHITELIST_OWNER_TAG_HASH}};
39
+ let ctx: Ctx8 = jet::sha_256_ctx_8_init();
40
+ let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, tag_hash);
41
+ let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, tag_hash);
42
+ let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, owner);
43
+ jet::sha_256_ctx_8_finalize(ctx)
44
+ }
45
+
46
+ fn whitelist_step(step: (bool, u256), node: u256) -> u256 {
47
+ let (sibling_on_left, sibling): (bool, u256) = step;
48
+ let tag_hash: u256 = 0x{{WHITELIST_NODE_TAG_HASH}};
49
+ let ctx: Ctx8 = jet::sha_256_ctx_8_init();
50
+ let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, tag_hash);
51
+ let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, tag_hash);
52
+ let ctx: Ctx8 = match sibling_on_left {
53
+ true => jet::sha_256_ctx_8_add_32(ctx, sibling),
54
+ false => jet::sha_256_ctx_8_add_32(ctx, node),
55
+ };
56
+ let ctx: Ctx8 = match sibling_on_left {
57
+ true => jet::sha_256_ctx_8_add_32(ctx, node),
58
+ false => jet::sha_256_ctx_8_add_32(ctx, sibling),
59
+ };
60
+ jet::sha_256_ctx_8_finalize(ctx)
61
+ }
62
+
63
+ fn require_whitelisted(owner: Pubkey, proof: [(bool, u256); {{WHITELIST_DEPTH}}]) {
64
+ let leaf: u256 = whitelist_owner_leaf(owner);
65
+ let root: u256 = array_fold::<whitelist_step, {{WHITELIST_DEPTH}}>(proof, leaf);
66
+ let expected_root: u256 = 0x{{WHITELIST_ROOT}};
67
+ assert!(jet::eq_256(root, expected_root));
68
+ }
69
+
70
+ fn canonical_holder_script_hash(owner: Pubkey) -> u256 {
71
+ let holder_cmr: u256 = 0x{{HOLDER_PROGRAM_CMR}};
72
+ let holder_leaf: u256 = jet::build_tapleaf_simplicity(holder_cmr);
73
+ let tapdata_ctx: Ctx8 = jet::tapdata_init();
74
+ let tapdata_ctx: Ctx8 = jet::sha_256_ctx_8_add_32(tapdata_ctx, owner);
75
+ let owner_tapdata: u256 = jet::sha_256_ctx_8_finalize(tapdata_ctx);
76
+ let holder_root: u256 = jet::build_tapbranch(holder_leaf, owner_tapdata);
77
+ let holder_internal_key: Pubkey = 0x{{HOLDER_NUMS_INTERNAL_KEY}};
78
+ let output_key: u256 = jet::build_taptweak(holder_internal_key, holder_root);
79
+
80
+ let script_ctx: Ctx8 = jet::sha_256_ctx_8_init();
81
+ let script_ctx: Ctx8 = jet::sha_256_ctx_8_add_1(script_ctx, 0x51);
82
+ let script_ctx: Ctx8 = jet::sha_256_ctx_8_add_1(script_ctx, 0x20);
83
+ let script_ctx: Ctx8 = jet::sha_256_ctx_8_add_32(script_ctx, output_key);
84
+ jet::sha_256_ctx_8_finalize(script_ctx)
85
+ }
86
+
87
+ fn next_index(index: u32) -> u32 {
88
+ let (carry, next): (bool, u32) = jet::add_32(index, 1);
89
+ assert!(not(carry));
90
+ next
91
+ }
92
+
93
+ fn scan_input(unused: (), state: (u32, u64)) -> (u32, u64) {
94
+ let (index, strict_total): (u32, u64) = state;
95
+ let past_end: bool = jet::le_32(jet::num_inputs(), index);
96
+ let strict_total: u64 = match past_end {
97
+ true => strict_total,
98
+ false => {
99
+ let (asset, amount): (u256, u64) = explicit_input_asset_amount(index);
100
+ let strict_asset: u256 = 0x{{STRICT_ASSET_ID_JET_HEX}};
101
+ let is_strict: bool = jet::eq_256(asset, strict_asset);
102
+ match is_strict {
103
+ true => add_amount(strict_total, amount),
104
+ false => strict_total,
105
+ }
106
+ },
107
+ };
108
+ (next_index(index), strict_total)
109
+ }
110
+
111
+ fn scan_output_ordinary(
112
+ owner_proof: (Pubkey, [(bool, u256); {{WHITELIST_DEPTH}}]),
113
+ state: (u32, u64)
114
+ ) -> (u32, u64) {
115
+ let (index, strict_total): (u32, u64) = state;
116
+ let past_end: bool = jet::le_32(jet::num_outputs(), index);
117
+ let strict_total: u64 = match past_end {
118
+ true => strict_total,
119
+ false => {
120
+ let pair: (Asset1, Amount1) = unwrap(jet::output_amount(index));
121
+ let (asset, amount): (Asset1, Amount1) = pair;
122
+ match asset {
123
+ // PSET supports standard confidential outputs only when both
124
+ // asset and value are blinded. This profile permits exactly
125
+ // the atomic purchase payment at output 2 to use that form.
126
+ Left(unused_confidential_asset: (u1, u256)) => {
127
+ assert!(jet::eq_32(index, 2));
128
+ strict_total
129
+ },
130
+ Right(asset_bits: u256) => {
131
+ let strict_asset: u256 = 0x{{STRICT_ASSET_ID_JET_HEX}};
132
+ match jet::eq_256(asset_bits, strict_asset) {
133
+ true => {
134
+ let amount_bits: u64 = unwrap_right::<(u1, u256)>(amount);
135
+ let (owner, proof): (Pubkey, [(bool, u256); {{WHITELIST_DEPTH}}]) = owner_proof;
136
+ require_whitelisted(owner, proof);
137
+ let actual_script_hash: u256 = unwrap(jet::output_script_hash(index));
138
+ let expected_script_hash: u256 = canonical_holder_script_hash(owner);
139
+ assert!(jet::eq_256(actual_script_hash, expected_script_hash));
140
+ add_amount(strict_total, amount_bits)
141
+ },
142
+ false => strict_total,
143
+ }
144
+ },
145
+ }
146
+ },
147
+ };
148
+ (next_index(index), strict_total)
149
+ }
150
+
151
+ fn require_verifier_successor(expected_script_hash: u256) {
152
+ let (verifier_asset, verifier_amount): (u256, u64) = explicit_input_asset_amount(0);
153
+ let expected_verifier_asset: u256 = 0x{{VERIFIER_ASSET_ID_JET_HEX}};
154
+ assert!(jet::eq_256(verifier_asset, expected_verifier_asset));
155
+ assert!(jet::eq_64(verifier_amount, {{VERIFIER_AMOUNT_ATOMIC}}));
156
+
157
+ let (successor_asset, successor_amount): (u256, u64) = explicit_output_asset_amount(0);
158
+ assert!(jet::eq_256(successor_asset, expected_verifier_asset));
159
+ assert!(jet::eq_64(successor_amount, {{VERIFIER_AMOUNT_ATOMIC}}));
160
+ let successor_script_hash: u256 = unwrap(jet::output_script_hash(0));
161
+ assert!(jet::eq_256(successor_script_hash, expected_script_hash))
162
+ }
163
+
164
+ fn scan_output_redemption(unused: (), state: (u32, u64)) -> (u32, u64) {
165
+ let (index, strict_total): (u32, u64) = state;
166
+ let past_end: bool = jet::le_32(jet::num_outputs(), index);
167
+ let strict_total: u64 = match past_end {
168
+ true => strict_total,
169
+ false => {
170
+ let pair: (Asset1, Amount1) = unwrap(jet::output_amount(index));
171
+ let (asset, amount): (Asset1, Amount1) = pair;
172
+ match asset {
173
+ Left(unused_confidential_asset: (u1, u256)) => {
174
+ assert!(false);
175
+ strict_total
176
+ },
177
+ Right(asset_bits: u256) => {
178
+ let strict_asset: u256 = 0x{{STRICT_ASSET_ID_JET_HEX}};
179
+ match jet::eq_256(asset_bits, strict_asset) {
180
+ true => {
181
+ let amount_bits: u64 = unwrap_right::<(u1, u256)>(amount);
182
+ let actual_script_hash: u256 = unwrap(jet::output_script_hash(index));
183
+ let redemption_script_hash: u256 = 0x{{REDEMPTION_SCRIPT_HASH}};
184
+ assert!(jet::eq_256(actual_script_hash, redemption_script_hash));
185
+ add_amount(strict_total, amount_bits)
186
+ },
187
+ false => strict_total,
188
+ }
189
+ },
190
+ }
191
+ },
192
+ };
193
+ (next_index(index), strict_total)
194
+ }
195
+
196
+ fn strict_input_total() -> u64 {
197
+ let input_slots: [(); {{MAX_INPUTS}}] = [{{INPUT_UNIT_SLOTS}}];
198
+ let input_state: (u32, u64) =
199
+ array_fold::<scan_input, {{MAX_INPUTS}}>(input_slots, (0, 0));
200
+ let (unused_input_index, strict_total): (u32, u64) = input_state;
201
+ strict_total
202
+ }
203
+
204
+ fn ordinary_transfer(
205
+ output_owner_proofs: [(Pubkey, [(bool, u256); {{WHITELIST_DEPTH}}]); {{MAX_OUTPUTS}}]
206
+ ) {
207
+ require_verifier_successor(jet::current_script_hash());
208
+ let strict_inputs: u64 = strict_input_total();
209
+ assert!(not(jet::eq_64(strict_inputs, 0)));
210
+
211
+ let output_state: (u32, u64) =
212
+ array_fold::<scan_output_ordinary, {{MAX_OUTPUTS}}>(output_owner_proofs, (0, 0));
213
+ let (unused_output_index, strict_outputs): (u32, u64) = output_state;
214
+ assert!(jet::eq_64(strict_inputs, strict_outputs))
215
+ }
216
+
217
+ fn redemption(authority_signature: Signature) {
218
+ require_verifier_successor(jet::current_script_hash());
219
+ let strict_inputs: u64 = strict_input_total();
220
+ assert!(not(jet::eq_64(strict_inputs, 0)));
221
+
222
+ let output_slots: [(); {{MAX_OUTPUTS}}] = [{{OUTPUT_UNIT_SLOTS}}];
223
+ let output_state: (u32, u64) =
224
+ array_fold::<scan_output_redemption, {{MAX_OUTPUTS}}>(output_slots, (0, 0));
225
+ let (unused_output_index, strict_outputs): (u32, u64) = output_state;
226
+ assert!(jet::eq_64(strict_inputs, strict_outputs));
227
+
228
+ let authority: Pubkey = 0x{{AUTHORITY_XONLY}};
229
+ jet::bip_0340_verify((authority, jet::sig_all_hash()), authority_signature)
230
+ }
231
+
232
+ fn governance(next: (u256, Signature)) {
233
+ let (next_verifier_script_hash, authority_signature): (u256, Signature) = next;
234
+ require_verifier_successor(next_verifier_script_hash);
235
+ assert!(jet::eq_64(strict_input_total(), 0));
236
+
237
+ let output_slots: [(); {{MAX_OUTPUTS}}] = [{{OUTPUT_UNIT_SLOTS}}];
238
+ let output_state: (u32, u64) =
239
+ array_fold::<scan_output_redemption, {{MAX_OUTPUTS}}>(output_slots, (0, 0));
240
+ let (unused_output_index, strict_outputs): (u32, u64) = output_state;
241
+ assert!(jet::eq_64(strict_outputs, 0));
242
+
243
+ let authority: Pubkey = 0x{{AUTHORITY_XONLY}};
244
+ jet::bip_0340_verify((authority, jet::sig_all_hash()), authority_signature)
245
+ }
246
+
247
+ fn main() {
248
+ assert!(jet::eq_32(jet::current_index(), 0));
249
+ assert!(jet::le_32(jet::num_inputs(), {{MAX_INPUTS}}));
250
+ assert!(jet::le_32(jet::num_outputs(), {{MAX_OUTPUTS}}));
251
+
252
+ let action:
253
+ Either<
254
+ [(Pubkey, [(bool, u256); {{WHITELIST_DEPTH}}]); {{MAX_OUTPUTS}}],
255
+ Either<Signature, (u256, Signature)>
256
+ > = witness::ACTION;
257
+ match action {
258
+ Left(output_owner_proofs : [(Pubkey, [(bool, u256); {{WHITELIST_DEPTH}}]); {{MAX_OUTPUTS}}]) =>
259
+ ordinary_transfer(output_owner_proofs),
260
+ Right(protected_action : Either<Signature, (u256, Signature)>) => match protected_action {
261
+ Left(authority_signature : Signature) => redemption(authority_signature),
262
+ Right(next : (u256, Signature)) => governance(next),
263
+ },
264
+ }
265
+ }
@@ -83,7 +83,7 @@ export declare function normalizeStrictPolicyVerifierCovenantParams(value: Stric
83
83
  export declare function strictPolicyHolderCovenantTemplatePath(): string;
84
84
  export declare function renderStrictPolicyHolderCovenantSource(value: StrictPolicyHolderCovenantParams | unknown): Promise<string>;
85
85
  export declare function compileStrictPolicyHolderCovenant(config: SimplicityClientConfig, value: StrictPolicyHolderCovenantParams | unknown): Promise<SimplicityArtifact>;
86
- export declare function strictPolicyVerifierCovenantTemplatePath(): string;
86
+ export declare function strictPolicyVerifierCovenantTemplatePath(resourceProfile?: StrictPolicyVerifierResourceProfileId): string;
87
87
  export declare function renderStrictPolicyVerifierCovenantSource(value: StrictPolicyVerifierCovenantParams | unknown): Promise<string>;
88
88
  export declare function compileStrictPolicyVerifierCovenant(config: SimplicityClientConfig, value: StrictPolicyVerifierCovenantParams | unknown): Promise<SimplicityArtifact>;
89
89
  export declare function taggedHashHexUtf8(tag: string, canonicalPayload: string): string;
@@ -242,7 +242,10 @@ async function compileStrictPolicyHolderCovenant(config, value) {
242
242
  },
243
243
  });
244
244
  }
245
- function strictPolicyVerifierCovenantTemplatePath() {
245
+ function strictPolicyVerifierCovenantTemplatePath(resourceProfile = "strict_v2") {
246
+ if (resourceProfile === "strict_v2_confidential") {
247
+ return `${__dirname}/../docs/definitions/strict-verifier-confidential-v1.simf`;
248
+ }
246
249
  return `${__dirname}/../docs/definitions/strict-verifier-ordinary-v1.simf`;
247
250
  }
248
251
  function strictPolicyVerifierTemplateVars(value) {
@@ -268,14 +271,16 @@ function strictPolicyVerifierTemplateVars(value) {
268
271
  };
269
272
  }
270
273
  async function renderStrictPolicyVerifierCovenantSource(value) {
271
- const source = await readTemplateFile(strictPolicyVerifierCovenantTemplatePath());
272
- return (0, templating_1.renderTemplate)(source, strictPolicyVerifierTemplateVars(value));
274
+ const params = normalizeStrictPolicyVerifierCovenantParams(value);
275
+ const source = await readTemplateFile(strictPolicyVerifierCovenantTemplatePath(params.resourceProfile));
276
+ return (0, templating_1.renderTemplate)(source, strictPolicyVerifierTemplateVars(params));
273
277
  }
274
278
  async function compileStrictPolicyVerifierCovenant(config, value) {
275
279
  const { compileFromFile } = await loadCompiler();
280
+ const params = normalizeStrictPolicyVerifierCovenantParams(value);
276
281
  return compileFromFile(config, {
277
- simfPath: strictPolicyVerifierCovenantTemplatePath(),
278
- templateVars: strictPolicyVerifierTemplateVars(value),
282
+ simfPath: strictPolicyVerifierCovenantTemplatePath(params.resourceProfile),
283
+ templateVars: strictPolicyVerifierTemplateVars(params),
279
284
  });
280
285
  }
281
286
  function taggedHashHexUtf8(tag, canonicalPayload) {
@@ -22,6 +22,13 @@ export declare const STRICT_POLICY_VERIFIER_RESOURCE_PROFILES: {
22
22
  readonly whitelistDepth: 1;
23
23
  readonly maxWhitelistOwners: number;
24
24
  };
25
+ readonly strict_v2_confidential: {
26
+ readonly id: "strict_v2_confidential";
27
+ readonly maxInputs: 16;
28
+ readonly maxOutputs: 8;
29
+ readonly whitelistDepth: 8;
30
+ readonly maxWhitelistOwners: number;
31
+ };
25
32
  };
26
33
  export type StrictPolicyVerifierResourceProfileId = keyof typeof STRICT_POLICY_VERIFIER_RESOURCE_PROFILES;
27
34
  export type StrictPolicyVerifierResourceProfile = (typeof STRICT_POLICY_VERIFIER_RESOURCE_PROFILES)[StrictPolicyVerifierResourceProfileId];
@@ -40,6 +40,15 @@ exports.STRICT_POLICY_VERIFIER_RESOURCE_PROFILES = {
40
40
  whitelistDepth: exports.STRICT_POLICY_COMPACT_WHITELIST_DEPTH,
41
41
  maxWhitelistOwners: exports.STRICT_POLICY_COMPACT_MAX_WHITELIST_OWNERS,
42
42
  },
43
+ // This profile permits one full confidential treasury payment output while
44
+ // strict RWA inputs and outputs remain explicit for deterministic checks.
45
+ strict_v2_confidential: {
46
+ id: "strict_v2_confidential",
47
+ maxInputs: 16,
48
+ maxOutputs: 8,
49
+ whitelistDepth: exports.STRICT_POLICY_WHITELIST_DEPTH,
50
+ maxWhitelistOwners: exports.STRICT_POLICY_MAX_WHITELIST_OWNERS,
51
+ },
43
52
  };
44
53
  exports.STRICT_POLICY_WHITELIST_OWNER_DOMAIN = "HAZBASE-RWA-STRICT-WHITELIST-OWNER-V1";
45
54
  exports.STRICT_POLICY_WHITELIST_EMPTY_DOMAIN = "HAZBASE-RWA-STRICT-WHITELIST-EMPTY-V1";
@@ -52,7 +61,7 @@ function validationError(field, message) {
52
61
  }
53
62
  function resolveStrictPolicyVerifierResourceProfile(value = "strict_v2") {
54
63
  if (typeof value !== "string" || !(value in exports.STRICT_POLICY_VERIFIER_RESOURCE_PROFILES)) {
55
- validationError("resourceProfile", "must be strict_v2 or strict_v2_compact");
64
+ validationError("resourceProfile", "must be strict_v2, strict_v2_compact, or strict_v2_confidential");
56
65
  }
57
66
  return exports.STRICT_POLICY_VERIFIER_RESOURCE_PROFILES[value];
58
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazbase/simplicity",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "An SDK for Simplicity on Liquid",
5
5
  "author": "IndieSquare Inc <info@hazbase.com>",
6
6
  "keywords": [