@arcanetech/privacy-sdk-stellar 0.5.1 → 0.5.2

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.
@@ -155,14 +155,15 @@ interface StellarTransactEnvironment {
155
155
  network: StellarNetworkConfig;
156
156
  /**
157
157
  * Nonce (persistent storage key) of the pool's `ZkConfig` entry this
158
- * environment's proofs/circuit shape target. Defaults to `2n` (Commitment
159
- * V2, 95 public signals) when omitted. Nonce `0` is the retired 93-signal
160
- * layout and must be passed explicitly.
158
+ * environment's proofs/circuit shape target. Defaults to `3n` (bundled 2×2
159
+ * binding layout) when omitted. Also bundled: `7n` (6×6). The value must
160
+ * match the on-chain pool config or bootstrap/execute fails with
161
+ * `no zk circuit configured for nonce …`.
161
162
  */
162
163
  zkConfigNonce?: bigint;
163
164
  /**
164
- * Optional nonce-keyed circuit map. Defaults to the SDK bundled 2×2 (nonce 2)
165
- * and 6×6 (nonce 6) schemes. URL fields are only for a custom/dev circuit.
165
+ * Optional nonce-keyed circuit map. Defaults to the SDK bundled 2×2 (nonce 3)
166
+ * and 6×6 (nonce 7) schemes. URL fields are only for a custom/dev circuit.
166
167
  */
167
168
  zkCircuits?: Record<string, StellarZkCircuitDefinition>;
168
169
  /**
@@ -552,6 +553,12 @@ interface FetchContractMerkleResult {
552
553
  merkleRootHex: string;
553
554
  updatedAt: number;
554
555
  }
556
+ /**
557
+ * Loads pool Merkle leaves without `get_commitments()`. That helper walks the
558
+ * whole tree in one simulate and exceeds the Soroban host budget on a live
559
+ * stand. Completes the cached prefix from persistent `TreeDataKey::Leaf`
560
+ * ledger entries instead.
561
+ */
555
562
  declare function fetchAndMergeMerkleState(parameters: {
556
563
  poolContractId: string;
557
564
  walletPublicKey: string;
@@ -356,6 +356,7 @@ function buildPoolTransactionAuditParameters(input) {
356
356
  input.nAuditSlots
357
357
  );
358
358
  }
359
+ var DECIMAL_APPLICATION_ID = /^\d+$/u;
359
360
  function resolvePoolApplicationId(applicationId) {
360
361
  const trimmed = applicationId?.trim();
361
362
  if (!trimmed) {
@@ -363,6 +364,11 @@ function resolvePoolApplicationId(applicationId) {
363
364
  "Privacy pool applicationId is required. Pass it in client factory config."
364
365
  );
365
366
  }
367
+ if (!DECIMAL_APPLICATION_ID.test(trimmed)) {
368
+ throw new Error(
369
+ "Privacy pool applicationId must be association.audit_id (decimal Fr), not a UUID or foreignId."
370
+ );
371
+ }
366
372
  return trimmed;
367
373
  }
368
374
 
@@ -2700,92 +2706,167 @@ async function prepareConfidentialTransferProof(parameters) {
2700
2706
  }
2701
2707
 
2702
2708
  // src/transact/merkle/fetch-contract.ts
2709
+ import { Buffer as Buffer14 } from "buffer";
2710
+
2711
+ // src/transact/merkle/state-port.ts
2712
+ function poolMerkleStateToCachedView(state) {
2713
+ return {
2714
+ commitments: state.commitments,
2715
+ updatedAt: state.updatedAt,
2716
+ merkleRootHex: state.merkleRootHex
2717
+ };
2718
+ }
2719
+
2720
+ // src/transact/merkle/read-tree-leaves.ts
2721
+ import { Address as Address5, xdr as xdr6 } from "@stellar/stellar-sdk";
2703
2722
  import { Buffer as Buffer13 } from "buffer";
2704
2723
 
2705
2724
  // src/transact/merkle/encoding.ts
2706
2725
  import "buffer";
2707
2726
  var FR_ORDER = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001n;
2708
- function bufferToDecimal(buf) {
2727
+ function commitmentBufferToDecimal(buf) {
2709
2728
  const hex = buf.toString("hex");
2710
2729
  const bigintFromHex = BigInt(`0x${hex}`);
2711
2730
  const reduced = bigintFromHex % FR_ORDER;
2712
2731
  return reduced.toString();
2713
2732
  }
2714
- function commitmentsBuffersToDecimal(buffers) {
2715
- return buffers.map((bufferRow) => bufferToDecimal(bufferRow));
2716
- }
2717
2733
 
2718
- // src/transact/merkle/state-port.ts
2719
- function poolMerkleStateToCachedView(state) {
2720
- return {
2721
- commitments: state.commitments,
2722
- updatedAt: state.updatedAt,
2723
- merkleRootHex: state.merkleRootHex
2724
- };
2725
- }
2726
-
2727
- // src/transact/merkle/fetch-contract.ts
2728
- function normalizeCommitmentBuffers(rawBuffers) {
2729
- return rawBuffers.map(
2730
- (bufferRow) => Buffer13.isBuffer(bufferRow) ? bufferRow : Buffer13.from(bufferRow)
2734
+ // src/transact/merkle/read-tree-leaves.ts
2735
+ var TREE_LEAF_LEDGER_BATCH_SIZE = 100;
2736
+ function treeLeafLedgerKey(contractId, leafIndex) {
2737
+ return xdr6.LedgerKey.contractData(
2738
+ new xdr6.LedgerKeyContractData({
2739
+ contract: new Address5(contractId).toScAddress(),
2740
+ key: treeLeafKeyScValue(leafIndex),
2741
+ durability: xdr6.ContractDataDurability.persistent()
2742
+ })
2731
2743
  );
2732
2744
  }
2733
- function mergeContractMerkleWithCache(parameters) {
2734
- const cachedCount = parameters.cachedState?.commitments.length ?? 0;
2735
- const shouldPreferContract = parameters.contractCount > cachedCount || !parameters.cachedState || cachedCount === 0;
2736
- if (shouldPreferContract) {
2737
- const updatedAt = Date.now();
2738
- void parameters.poolMerkleState?.set({
2739
- poolContract: parameters.poolContractId,
2740
- commitments: parameters.contractCommitments,
2741
- commitmentCount: parameters.contractCount,
2742
- merkleRootHex: parameters.merkleRootHex,
2743
- updatedAt
2744
- });
2745
- return {
2746
- commitments: parameters.contractCommitments,
2747
- updatedAt,
2748
- merkleRootHex: parameters.merkleRootHex,
2749
- commitmentCount: parameters.contractCount
2750
- };
2745
+ function treeLeafKeyScValue(leafIndex) {
2746
+ return xdr6.ScVal.scvVec([xdr6.ScVal.scvSymbol("Leaf"), xdr6.ScVal.scvU32(leafIndex)]);
2747
+ }
2748
+ function leafIndexFromContractDataKey(key) {
2749
+ if (key.switch() !== xdr6.ScValType.scvVec()) {
2750
+ return void 0;
2751
2751
  }
2752
- return {
2753
- commitments: parameters.cachedState.commitments,
2754
- updatedAt: parameters.cachedState.updatedAt,
2755
- merkleRootHex: parameters.cachedState.merkleRootHex ?? parameters.merkleRootHex,
2756
- commitmentCount: parameters.cachedState.commitments.length
2757
- };
2752
+ const fields = key.vec();
2753
+ if (!fields || fields.length !== 2) {
2754
+ return void 0;
2755
+ }
2756
+ const [tag, indexValue] = fields;
2757
+ if (!tag || tag.switch() !== xdr6.ScValType.scvSymbol()) {
2758
+ return void 0;
2759
+ }
2760
+ if (tag.sym().toString() !== "Leaf") {
2761
+ return void 0;
2762
+ }
2763
+ if (!indexValue || indexValue.switch() !== xdr6.ScValType.scvU32()) {
2764
+ return void 0;
2765
+ }
2766
+ return indexValue.u32();
2758
2767
  }
2759
- async function readCachedPoolMerkleView(poolMerkleState, poolContractId) {
2760
- if (!poolMerkleState) {
2768
+ function commitmentBytesFromContractDataValue(value) {
2769
+ if (value.switch() !== xdr6.ScValType.scvBytes()) {
2761
2770
  return void 0;
2762
2771
  }
2763
- const cached = await poolMerkleState.get(poolContractId);
2764
- return cached ? poolMerkleStateToCachedView(cached) : void 0;
2772
+ return Buffer13.from(value.bytes());
2773
+ }
2774
+ async function readTreeLeafCommitments(input) {
2775
+ if (input.startIndex >= input.endIndex) {
2776
+ return [];
2777
+ }
2778
+ const byIndex = await readLeafBuffersByIndex(input);
2779
+ return decimalCommitmentsInRange(byIndex, input.startIndex, input.endIndex);
2780
+ }
2781
+ async function readLeafBuffersByIndex(input) {
2782
+ const keys = ledgerKeysForRange(input.contractId, input.startIndex, input.endIndex);
2783
+ const byIndex = /* @__PURE__ */ new Map();
2784
+ for (const batch of chunkLedgerKeys(keys, TREE_LEAF_LEDGER_BATCH_SIZE)) {
2785
+ const response = await input.reader.getLedgerEntries(...batch);
2786
+ collectLeafBuffers(response.entries, byIndex);
2787
+ }
2788
+ return byIndex;
2789
+ }
2790
+ function ledgerKeysForRange(contractId, startIndex, endIndex) {
2791
+ const keys = [];
2792
+ for (let leafIndex = startIndex; leafIndex < endIndex; leafIndex += 1) {
2793
+ keys.push(treeLeafLedgerKey(contractId, leafIndex));
2794
+ }
2795
+ return keys;
2796
+ }
2797
+ function chunkLedgerKeys(keys, batchSize) {
2798
+ const batches = [];
2799
+ for (let offset = 0; offset < keys.length; offset += batchSize) {
2800
+ batches.push(keys.slice(offset, offset + batchSize));
2801
+ }
2802
+ return batches;
2803
+ }
2804
+ function collectLeafBuffers(entries, byIndex) {
2805
+ for (const entry of entries) {
2806
+ const contractData = entry.val.contractData();
2807
+ const leafIndex = leafIndexFromContractDataKey(contractData.key());
2808
+ const bytes = commitmentBytesFromContractDataValue(contractData.val());
2809
+ if (leafIndex === void 0 || !bytes) {
2810
+ continue;
2811
+ }
2812
+ byIndex.set(leafIndex, bytes);
2813
+ }
2814
+ }
2815
+ function decimalCommitmentsInRange(byIndex, startIndex, endIndex) {
2816
+ const commitments = [];
2817
+ for (let leafIndex = startIndex; leafIndex < endIndex; leafIndex += 1) {
2818
+ const bytes = byIndex.get(leafIndex);
2819
+ if (!bytes) {
2820
+ throw new Error(`Missing Merkle leaf ${String(leafIndex)} in pool storage.`);
2821
+ }
2822
+ commitments.push(commitmentBufferToDecimal(bytes));
2823
+ }
2824
+ return commitments;
2765
2825
  }
2826
+
2827
+ // src/transact/merkle/reuse-cached-commitments.ts
2828
+ function completeCacheRootMatches(cachedState, merkleRootHex) {
2829
+ const cachedRoot = cachedState?.merkleRootHex;
2830
+ if (cachedRoot === void 0 || cachedRoot === "") {
2831
+ return true;
2832
+ }
2833
+ return cachedRoot === merkleRootHex;
2834
+ }
2835
+ function reuseCachedCommitments(input) {
2836
+ const cachedCommitments = input.cachedState?.commitments ?? [];
2837
+ if (cachedCommitments.length === 0) {
2838
+ return [];
2839
+ }
2840
+ if (cachedCommitments.length > input.onChainCount) {
2841
+ return [];
2842
+ }
2843
+ if (cachedCommitments.length === input.onChainCount) {
2844
+ return completeCacheRootMatches(input.cachedState, input.merkleRootHex) ? cachedCommitments : [];
2845
+ }
2846
+ return cachedCommitments;
2847
+ }
2848
+
2849
+ // src/transact/merkle/fetch-contract.ts
2766
2850
  async function fetchAndMergeMerkleState(parameters) {
2767
- const client = readPoolClientFactory(parameters.transactEnvironment)({
2768
- contractId: parameters.poolContractId,
2769
- walletPublicKey: parameters.walletPublicKey,
2770
- networkPassphrase: parameters.transactEnvironment.network.networkPassphrase,
2771
- sorobanRpcUrl: parameters.transactEnvironment.network.rpcUrl
2772
- });
2773
- const commitmentsRead = await client.get_commitments();
2774
- const rawBuffers = commitmentsRead.result;
2775
- const buffers = normalizeCommitmentBuffers(rawBuffers);
2776
- const contractCommitments = commitmentsBuffersToDecimal(buffers);
2777
- const contractCount = contractCommitments.length;
2851
+ const client = createPoolClient(parameters);
2852
+ const countRead = await client.get_commitment_count();
2853
+ const onChainCount = requireCommitmentCount(countRead.result);
2778
2854
  const rootRead = await client.get_merkle_root();
2779
- const merkleRootBuffer = rootRead.result;
2780
- const merkleRootHex = Buffer13.from(merkleRootBuffer).toString("hex");
2855
+ const merkleRootHex = merkleRootToHex(rootRead.result);
2781
2856
  const cachedState = await readCachedPoolMerkleView(
2782
2857
  parameters.poolMerkleState,
2783
2858
  parameters.poolContractId
2784
2859
  );
2785
- return mergeContractMerkleWithCache({
2860
+ const commitments = await loadCommitments({
2861
+ cachedState,
2862
+ onChainCount,
2863
+ merkleRootHex,
2864
+ rpcUrl: parameters.transactEnvironment.network.rpcUrl,
2865
+ contractId: parameters.poolContractId
2866
+ });
2867
+ return persistFetchedMerkleState({
2786
2868
  poolContractId: parameters.poolContractId,
2787
- contractCommitments,
2788
- contractCount,
2869
+ commitments,
2789
2870
  merkleRootHex,
2790
2871
  cachedState,
2791
2872
  ...parameters.poolMerkleState ? { poolMerkleState: parameters.poolMerkleState } : {}
@@ -2799,19 +2880,14 @@ async function readLeafEphemeralHex(parameters) {
2799
2880
  if (cached?.xHex && cached.yHex) {
2800
2881
  return { xHex: cached.xHex, yHex: cached.yHex };
2801
2882
  }
2802
- const client = readPoolClientFactory(parameters.transactEnvironment)({
2803
- contractId: parameters.poolContractId,
2804
- walletPublicKey: parameters.walletPublicKey,
2805
- networkPassphrase: parameters.transactEnvironment.network.networkPassphrase,
2806
- sorobanRpcUrl: parameters.transactEnvironment.network.rpcUrl
2807
- });
2883
+ const client = createPoolClient(parameters);
2808
2884
  const read = await client.get_leaf_ephemeral({ leaf_index: parameters.leafIndex });
2809
2885
  const coords = read.result;
2810
2886
  if (!coords?.x || !coords?.y) {
2811
2887
  throw new Error("Missing leaf ephemeral key on contract");
2812
2888
  }
2813
- const xHex = Buffer13.from(coords.x).toString("hex");
2814
- const yHex = Buffer13.from(coords.y).toString("hex");
2889
+ const xHex = Buffer14.from(coords.x).toString("hex");
2890
+ const yHex = Buffer14.from(coords.y).toString("hex");
2815
2891
  await parameters.leafEphemeral?.set({
2816
2892
  poolContract: parameters.poolContractId,
2817
2893
  leafIndex: parameters.leafIndex,
@@ -2821,6 +2897,84 @@ async function readLeafEphemeralHex(parameters) {
2821
2897
  });
2822
2898
  return { xHex, yHex };
2823
2899
  }
2900
+ function createPoolClient(parameters) {
2901
+ return readPoolClientFactory(parameters.transactEnvironment)({
2902
+ contractId: parameters.poolContractId,
2903
+ walletPublicKey: parameters.walletPublicKey,
2904
+ networkPassphrase: parameters.transactEnvironment.network.networkPassphrase,
2905
+ sorobanRpcUrl: parameters.transactEnvironment.network.rpcUrl
2906
+ });
2907
+ }
2908
+ function requireCommitmentCount(result) {
2909
+ if (typeof result === "number" && Number.isInteger(result) && result >= 0) {
2910
+ return result;
2911
+ }
2912
+ throw new Error("Pool commitment count is not a non-negative integer.");
2913
+ }
2914
+ function merkleRootToHex(root) {
2915
+ return Buffer14.from(root).toString("hex");
2916
+ }
2917
+ async function readCachedPoolMerkleView(poolMerkleState, poolContractId) {
2918
+ if (!poolMerkleState) {
2919
+ return void 0;
2920
+ }
2921
+ const cached = await poolMerkleState.get(poolContractId);
2922
+ return cached ? poolMerkleStateToCachedView(cached) : void 0;
2923
+ }
2924
+ async function loadCommitments(input) {
2925
+ const reused = reuseCachedCommitments({
2926
+ cachedState: input.cachedState,
2927
+ onChainCount: input.onChainCount,
2928
+ merkleRootHex: input.merkleRootHex
2929
+ });
2930
+ if (reused.length === input.onChainCount) {
2931
+ return reused;
2932
+ }
2933
+ const fetched = await readTreeLeafCommitments({
2934
+ reader: createStellarRpcServer(input.rpcUrl),
2935
+ contractId: input.contractId,
2936
+ startIndex: reused.length,
2937
+ endIndex: input.onChainCount
2938
+ });
2939
+ return [...reused, ...fetched];
2940
+ }
2941
+ async function persistFetchedMerkleState(input) {
2942
+ const cachedState = input.cachedState;
2943
+ if (cachedState && cachedMerkleUnchanged(cachedState, input.commitments, input.merkleRootHex)) {
2944
+ return viewFromCachedMerkle(cachedState, input.merkleRootHex);
2945
+ }
2946
+ const updatedAt = Date.now();
2947
+ await input.poolMerkleState?.set({
2948
+ poolContract: input.poolContractId,
2949
+ commitments: input.commitments,
2950
+ commitmentCount: input.commitments.length,
2951
+ merkleRootHex: input.merkleRootHex,
2952
+ updatedAt
2953
+ });
2954
+ return {
2955
+ commitments: input.commitments,
2956
+ updatedAt,
2957
+ merkleRootHex: input.merkleRootHex,
2958
+ commitmentCount: input.commitments.length
2959
+ };
2960
+ }
2961
+ function viewFromCachedMerkle(cachedState, merkleRootHex) {
2962
+ return {
2963
+ commitments: cachedState.commitments,
2964
+ updatedAt: cachedState.updatedAt,
2965
+ merkleRootHex: cachedState.merkleRootHex ?? merkleRootHex,
2966
+ commitmentCount: cachedState.commitments.length
2967
+ };
2968
+ }
2969
+ function cachedMerkleUnchanged(cachedState, commitments, merkleRootHex) {
2970
+ if (!cachedState) {
2971
+ return false;
2972
+ }
2973
+ if ((cachedState.merkleRootHex ?? merkleRootHex) !== merkleRootHex) {
2974
+ return false;
2975
+ }
2976
+ return cachedState.commitments.join(",") === commitments.join(",");
2977
+ }
2824
2978
 
2825
2979
  // src/transact/encoding/fr-decimal-to-bytes.ts
2826
2980
  var BN254_SCALAR_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
@@ -4921,10 +5075,10 @@ function readTransferFromPrivateAddress(from) {
4921
5075
  import { executionError, insufficientStateError as insufficientStateError2 } from "@arcanetech/privacy-sdk-core";
4922
5076
 
4923
5077
  // src/contracts/pool/pool-domain-service.ts
4924
- import { Buffer as Buffer14 } from "buffer";
5078
+ import { Buffer as Buffer15 } from "buffer";
4925
5079
  function hexToBuffer(hex) {
4926
5080
  const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
4927
- return Buffer14.from(clean, "hex");
5081
+ return Buffer15.from(clean, "hex");
4928
5082
  }
4929
5083
  async function readNullifierConsumedOnChain(input) {
4930
5084
  const client = input.contractContext.createPoolClient({
@@ -5471,7 +5625,7 @@ var RELAY_SIGNAL_INDEX_PUBLIC_DEPOSIT = 91;
5471
5625
  var RELAY_SIGNAL_INDEX_PUBLIC_WITHDRAWAL = 92;
5472
5626
 
5473
5627
  // src/transact/relay/signals.ts
5474
- import { Buffer as Buffer15 } from "buffer";
5628
+ import { Buffer as Buffer16 } from "buffer";
5475
5629
 
5476
5630
  // src/transact/relay/layout-profile.ts
5477
5631
  var FIELD_BYTES = 32;
@@ -5551,7 +5705,7 @@ function relayLayoutProfileForNonce(nonce) {
5551
5705
 
5552
5706
  // src/transact/relay/signals.ts
5553
5707
  function decodePublicSignalBytes(publicSignals) {
5554
- return Buffer15.from(publicSignals.replace(/^0x/iu, ""), "hex");
5708
+ return Buffer16.from(publicSignals.replace(/^0x/iu, ""), "hex");
5555
5709
  }
5556
5710
  function hasSupportedLengthPrefix(bytes, signalCount) {
5557
5711
  const expected = RELAY_TRANSACT_SIGNAL_PREFIX_BYTES + signalCount * RELAY_TRANSACT_FIELD_BYTES;
@@ -5578,7 +5732,7 @@ function sliceSupportedPublicSignalFields(publicSignals, zkConfigNonce = DEFAULT
5578
5732
  const body = hasSupportedLengthPrefix(bytes, profile.signalCount) ? bytes.subarray(RELAY_TRANSACT_SIGNAL_PREFIX_BYTES) : bytes;
5579
5733
  return Array.from(
5580
5734
  { length: profile.signalCount },
5581
- (_unused, index) => Buffer15.from(
5735
+ (_unused, index) => Buffer16.from(
5582
5736
  body.subarray(
5583
5737
  index * RELAY_TRANSACT_FIELD_BYTES,
5584
5738
  (index + 1) * RELAY_TRANSACT_FIELD_BYTES