@vocdoni/davinci-sdk 0.1.1 → 0.1.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.
package/dist/index.umd.js CHANGED
@@ -1,8 +1,27 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('axios'), require('@vocdoni/davinci-contracts'), require('snarkjs'), require('ethers')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'axios', '@vocdoni/davinci-contracts', 'snarkjs', 'ethers'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VocdoniSDK = {}, global.axios, global.davinciContracts, global.snarkjs, global.ethers));
5
- })(this, (function (exports, axios, davinciContracts, snarkjs, ethers) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('axios'), require('@vocdoni/davinci-contracts'), require('ethers'), require('snarkjs'), require('circomlibjs')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'axios', '@vocdoni/davinci-contracts', 'ethers', 'snarkjs', 'circomlibjs'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VocdoniSDK = {}, global.axios, global.davinciContracts, global.ethers, global.snarkjs, global.circomlibjs));
5
+ })(this, (function (exports, axios, davinciContracts, ethers, snarkjs, circomlibjs) { 'use strict';
6
+
7
+ function _interopNamespaceDefault(e) {
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var snarkjs__namespace = /*#__PURE__*/_interopNamespaceDefault(snarkjs);
6
25
 
7
26
  var ElectionResultsTypeNames = /* @__PURE__ */ ((ElectionResultsTypeNames2) => {
8
27
  ElectionResultsTypeNames2["SINGLE_CHOICE_MULTIQUESTION"] = "single-choice-multiquestion";
@@ -1881,97 +1900,6 @@
1881
1900
  }
1882
1901
  }
1883
1902
 
1884
- class CircomProof {
1885
- constructor(opts = {}) {
1886
- // simple in-memory cache keyed by URL
1887
- this.wasmCache = /* @__PURE__ */ new Map();
1888
- this.zkeyCache = /* @__PURE__ */ new Map();
1889
- this.vkeyCache = /* @__PURE__ */ new Map();
1890
- this.wasmUrl = opts.wasmUrl;
1891
- this.zkeyUrl = opts.zkeyUrl;
1892
- this.vkeyUrl = opts.vkeyUrl;
1893
- this.wasmHash = opts.wasmHash;
1894
- this.zkeyHash = opts.zkeyHash;
1895
- this.vkeyHash = opts.vkeyHash;
1896
- }
1897
- /**
1898
- * Computes SHA-256 hash of the given data and compares it with the expected hash.
1899
- * @param data - The data to hash (string or ArrayBuffer or Uint8Array)
1900
- * @param expectedHash - The expected SHA-256 hash in hexadecimal format
1901
- * @param filename - The filename for error reporting
1902
- * @throws Error if the computed hash doesn't match the expected hash
1903
- */
1904
- verifyHash(data, expectedHash, filename) {
1905
- let bytes;
1906
- if (typeof data === "string") {
1907
- bytes = new TextEncoder().encode(data);
1908
- } else if (data instanceof ArrayBuffer) {
1909
- bytes = new Uint8Array(data);
1910
- } else {
1911
- bytes = data;
1912
- }
1913
- const computedHash = ethers.sha256(bytes).slice(2);
1914
- if (computedHash.toLowerCase() !== expectedHash.toLowerCase()) {
1915
- throw new Error(
1916
- `Hash verification failed for ${filename}. Expected: ${expectedHash.toLowerCase()}, Computed: ${computedHash.toLowerCase()}`
1917
- );
1918
- }
1919
- }
1920
- /**
1921
- * Generate a zk‐SNARK proof.
1922
- * If you didn't pass wasmUrl/zkeyUrl in the constructor you must supply them here.
1923
- */
1924
- async generate(inputs, urls = {}) {
1925
- const wasmUrl = urls.wasmUrl ?? this.wasmUrl;
1926
- const zkeyUrl = urls.zkeyUrl ?? this.zkeyUrl;
1927
- if (!wasmUrl) throw new Error("`wasmUrl` is required to generate a proof");
1928
- if (!zkeyUrl) throw new Error("`zkeyUrl` is required to generate a proof");
1929
- let wasmBin = this.wasmCache.get(wasmUrl);
1930
- if (!wasmBin) {
1931
- const r = await fetch(wasmUrl);
1932
- if (!r.ok) throw new Error(`Failed to fetch wasm at ${wasmUrl}: ${r.status}`);
1933
- const buf = await r.arrayBuffer();
1934
- wasmBin = new Uint8Array(buf);
1935
- if (this.wasmHash) {
1936
- this.verifyHash(wasmBin, this.wasmHash, "circuit.wasm");
1937
- }
1938
- this.wasmCache.set(wasmUrl, wasmBin);
1939
- }
1940
- let zkeyBin = this.zkeyCache.get(zkeyUrl);
1941
- if (!zkeyBin) {
1942
- const r = await fetch(zkeyUrl);
1943
- if (!r.ok) throw new Error(`Failed to fetch zkey at ${zkeyUrl}: ${r.status}`);
1944
- const buf = await r.arrayBuffer();
1945
- zkeyBin = new Uint8Array(buf);
1946
- if (this.zkeyHash) {
1947
- this.verifyHash(zkeyBin, this.zkeyHash, "proving_key.zkey");
1948
- }
1949
- this.zkeyCache.set(zkeyUrl, zkeyBin);
1950
- }
1951
- const { proof, publicSignals } = await snarkjs.groth16.fullProve(inputs, wasmBin, zkeyBin);
1952
- return {
1953
- proof,
1954
- publicSignals
1955
- };
1956
- }
1957
- async verify(proof, publicSignals, urlOverride) {
1958
- const vkeyUrl = urlOverride ?? this.vkeyUrl;
1959
- if (!vkeyUrl) throw new Error("`vkeyUrl` is required to verify a proof");
1960
- let vk = this.vkeyCache.get(vkeyUrl);
1961
- if (!vk) {
1962
- const r = await fetch(vkeyUrl);
1963
- if (!r.ok) throw new Error(`Failed to fetch vkey at ${vkeyUrl}: ${r.status}`);
1964
- const vkeyText = await r.text();
1965
- if (this.vkeyHash) {
1966
- this.verifyHash(vkeyText, this.vkeyHash, "verification_key.json");
1967
- }
1968
- vk = JSON.parse(vkeyText);
1969
- this.vkeyCache.set(vkeyUrl, vk);
1970
- }
1971
- return snarkjs.groth16.verify(vk, publicSignals, proof);
1972
- }
1973
- }
1974
-
1975
1903
  var VoteStatus = /* @__PURE__ */ ((VoteStatus2) => {
1976
1904
  VoteStatus2["Pending"] = "pending";
1977
1905
  VoteStatus2["Verified"] = "verified";
@@ -1983,11 +1911,15 @@
1983
1911
  })(VoteStatus || {});
1984
1912
 
1985
1913
  class VoteOrchestrationService {
1986
- constructor(apiService, getCrypto, signer, censusProviders = {}, config = {}) {
1914
+ constructor(apiService, getBallotInputGenerator, signer, censusProviders = {}, config = {}) {
1987
1915
  this.apiService = apiService;
1988
- this.getCrypto = getCrypto;
1916
+ this.getBallotInputGenerator = getBallotInputGenerator;
1989
1917
  this.signer = signer;
1990
1918
  this.censusProviders = censusProviders;
1919
+ // Cache for circuit files
1920
+ this.wasmCache = /* @__PURE__ */ new Map();
1921
+ this.zkeyCache = /* @__PURE__ */ new Map();
1922
+ this.vkeyCache = /* @__PURE__ */ new Map();
1991
1923
  this.verifyCircuitFiles = config.verifyCircuitFiles ?? true;
1992
1924
  this.verifyProof = config.verifyProof ?? true;
1993
1925
  }
@@ -2037,7 +1969,7 @@
2037
1969
  if (process.census.censusOrigin === CensusOrigin.CSP) {
2038
1970
  voteRequest.censusProof = censusProof;
2039
1971
  }
2040
- await this.submitVoteRequest(voteRequest);
1972
+ await this.apiService.sequencer.submitVote(voteRequest);
2041
1973
  const status = await this.apiService.sequencer.getVoteStatus(config.processId, voteId);
2042
1974
  return {
2043
1975
  voteId,
@@ -2186,30 +2118,29 @@
2186
2118
  throw new Error(`Unsupported census origin: ${censusOrigin}`);
2187
2119
  }
2188
2120
  /**
2189
- * Generate vote proof inputs using DavinciCrypto
2121
+ * Generate vote proof inputs using BallotInputGenerator
2190
2122
  */
2191
2123
  async generateVoteProofInputs(processId, voterAddress, encryptionKey, ballotMode, choices, weight, customRandomness) {
2192
- const crypto = await this.getCrypto();
2124
+ const generator = await this.getBallotInputGenerator();
2193
2125
  this.validateChoices(choices, ballotMode);
2194
- const fieldValues = choices.map((choice) => choice.toString());
2195
- const inputs = {
2196
- address: voterAddress.replace(/^0x/, ""),
2197
- processID: processId.replace(/^0x/, ""),
2198
- encryptionKey: [encryptionKey.x, encryptionKey.y],
2199
- ballotMode,
2200
- weight,
2201
- fieldValues
2202
- };
2126
+ let k;
2203
2127
  if (customRandomness) {
2204
2128
  const hexRandomness = customRandomness.startsWith("0x") ? customRandomness : "0x" + customRandomness;
2205
- const k = BigInt(hexRandomness).toString();
2206
- inputs.k = k;
2129
+ k = BigInt(hexRandomness).toString();
2207
2130
  }
2208
- const cryptoOutput = await crypto.proofInputs(inputs);
2131
+ const result = await generator.generateInputs(
2132
+ processId.replace(/^0x/, ""),
2133
+ voterAddress.replace(/^0x/, ""),
2134
+ encryptionKey,
2135
+ ballotMode,
2136
+ choices,
2137
+ weight,
2138
+ k
2139
+ );
2209
2140
  return {
2210
- voteId: cryptoOutput.voteId,
2211
- cryptoOutput,
2212
- circomInputs: cryptoOutput.circomInputs
2141
+ voteId: result.voteId,
2142
+ cryptoOutput: result,
2143
+ circomInputs: result.circomInputs
2213
2144
  };
2214
2145
  }
2215
2146
  /**
@@ -2226,29 +2157,79 @@
2226
2157
  }
2227
2158
  }
2228
2159
  /**
2229
- * Generate zk-SNARK proof using CircomProof
2160
+ * Verify hash of downloaded file
2161
+ */
2162
+ verifyHash(data, expectedHash, filename) {
2163
+ const computedHash = ethers.sha256(data).slice(2);
2164
+ if (computedHash.toLowerCase() !== expectedHash.toLowerCase()) {
2165
+ throw new Error(
2166
+ `Hash verification failed for ${filename}. Expected: ${expectedHash.toLowerCase()}, Computed: ${computedHash.toLowerCase()}`
2167
+ );
2168
+ }
2169
+ }
2170
+ /**
2171
+ * Generate zk-SNARK proof using snarkjs directly (no CircomProof wrapper)
2230
2172
  */
2231
2173
  async generateZkProof(circomInputs) {
2232
2174
  const info = await this.apiService.sequencer.getInfo();
2233
- const circomProof = new CircomProof({
2234
- wasmUrl: info.circuitUrl,
2235
- zkeyUrl: info.provingKeyUrl,
2236
- vkeyUrl: info.verificationKeyUrl,
2237
- // Only pass hashes if verifyCircuitFiles is enabled
2238
- ...this.verifyCircuitFiles && {
2239
- wasmHash: info.circuitHash,
2240
- zkeyHash: info.provingKeyHash,
2241
- vkeyHash: info.verificationKeyHash
2175
+ let wasmBytes = this.wasmCache.get(info.circuitUrl);
2176
+ if (!wasmBytes) {
2177
+ const response = await fetch(info.circuitUrl);
2178
+ if (!response.ok) {
2179
+ throw new Error(`Failed to fetch WASM at ${info.circuitUrl}: ${response.status}`);
2242
2180
  }
2243
- });
2244
- const { proof, publicSignals } = await circomProof.generate(circomInputs);
2181
+ const buffer = await response.arrayBuffer();
2182
+ wasmBytes = new Uint8Array(buffer);
2183
+ if (this.verifyCircuitFiles) {
2184
+ this.verifyHash(wasmBytes, info.circuitHash, "circuit.wasm");
2185
+ }
2186
+ this.wasmCache.set(info.circuitUrl, wasmBytes);
2187
+ }
2188
+ let zkeyBytes = this.zkeyCache.get(info.provingKeyUrl);
2189
+ if (!zkeyBytes) {
2190
+ const response = await fetch(info.provingKeyUrl);
2191
+ if (!response.ok) {
2192
+ throw new Error(`Failed to fetch zkey at ${info.provingKeyUrl}: ${response.status}`);
2193
+ }
2194
+ const buffer = await response.arrayBuffer();
2195
+ zkeyBytes = new Uint8Array(buffer);
2196
+ if (this.verifyCircuitFiles) {
2197
+ this.verifyHash(zkeyBytes, info.provingKeyHash, "proving_key.zkey");
2198
+ }
2199
+ this.zkeyCache.set(info.provingKeyUrl, zkeyBytes);
2200
+ }
2201
+ const { proof, publicSignals } = await snarkjs__namespace.groth16.fullProve(
2202
+ circomInputs,
2203
+ wasmBytes,
2204
+ zkeyBytes
2205
+ );
2245
2206
  if (this.verifyProof) {
2246
- const isValid = await circomProof.verify(proof, publicSignals);
2207
+ let vkey = this.vkeyCache.get(info.verificationKeyUrl);
2208
+ if (!vkey) {
2209
+ const response = await fetch(info.verificationKeyUrl);
2210
+ if (!response.ok) {
2211
+ throw new Error(`Failed to fetch vkey at ${info.verificationKeyUrl}: ${response.status}`);
2212
+ }
2213
+ const vkeyText = await response.text();
2214
+ if (this.verifyCircuitFiles) {
2215
+ const vkeyBytes = new TextEncoder().encode(vkeyText);
2216
+ this.verifyHash(vkeyBytes, info.verificationKeyHash, "verification_key.json");
2217
+ }
2218
+ vkey = JSON.parse(vkeyText);
2219
+ this.vkeyCache.set(info.verificationKeyUrl, vkey);
2220
+ }
2221
+ const isValid = await snarkjs__namespace.groth16.verify(vkey, publicSignals, proof);
2247
2222
  if (!isValid) {
2248
2223
  throw new Error("Generated proof is invalid");
2249
2224
  }
2250
2225
  }
2251
- return { proof, publicSignals };
2226
+ const voteProof = {
2227
+ pi_a: proof.pi_a,
2228
+ pi_b: proof.pi_b,
2229
+ pi_c: proof.pi_c,
2230
+ protocol: proof.protocol
2231
+ };
2232
+ return { proof: voteProof, publicSignals };
2252
2233
  }
2253
2234
  hexToBytes(hex) {
2254
2235
  const clean = hex.replace(/^0x/, "");
@@ -2263,22 +2244,6 @@
2263
2244
  async signVote(voteId) {
2264
2245
  return this.signer.signMessage(this.hexToBytes(voteId));
2265
2246
  }
2266
- /**
2267
- * Submit the vote request to the sequencer
2268
- */
2269
- async submitVoteRequest(voteRequest) {
2270
- const ballotProof = {
2271
- pi_a: voteRequest.ballotProof.pi_a,
2272
- pi_b: voteRequest.ballotProof.pi_b,
2273
- pi_c: voteRequest.ballotProof.pi_c,
2274
- protocol: voteRequest.ballotProof.protocol
2275
- };
2276
- const request = {
2277
- ...voteRequest,
2278
- ballotProof
2279
- };
2280
- await this.apiService.sequencer.submitVote(request);
2281
- }
2282
2247
  }
2283
2248
 
2284
2249
  class OrganizationRegistryService extends SmartContractService {
@@ -2377,6 +2342,97 @@
2377
2342
  }
2378
2343
  }
2379
2344
 
2345
+ class CircomProof {
2346
+ constructor(opts = {}) {
2347
+ // simple in-memory cache keyed by URL
2348
+ this.wasmCache = /* @__PURE__ */ new Map();
2349
+ this.zkeyCache = /* @__PURE__ */ new Map();
2350
+ this.vkeyCache = /* @__PURE__ */ new Map();
2351
+ this.wasmUrl = opts.wasmUrl;
2352
+ this.zkeyUrl = opts.zkeyUrl;
2353
+ this.vkeyUrl = opts.vkeyUrl;
2354
+ this.wasmHash = opts.wasmHash;
2355
+ this.zkeyHash = opts.zkeyHash;
2356
+ this.vkeyHash = opts.vkeyHash;
2357
+ }
2358
+ /**
2359
+ * Computes SHA-256 hash of the given data and compares it with the expected hash.
2360
+ * @param data - The data to hash (string or ArrayBuffer or Uint8Array)
2361
+ * @param expectedHash - The expected SHA-256 hash in hexadecimal format
2362
+ * @param filename - The filename for error reporting
2363
+ * @throws Error if the computed hash doesn't match the expected hash
2364
+ */
2365
+ verifyHash(data, expectedHash, filename) {
2366
+ let bytes;
2367
+ if (typeof data === "string") {
2368
+ bytes = new TextEncoder().encode(data);
2369
+ } else if (data instanceof ArrayBuffer) {
2370
+ bytes = new Uint8Array(data);
2371
+ } else {
2372
+ bytes = data;
2373
+ }
2374
+ const computedHash = ethers.sha256(bytes).slice(2);
2375
+ if (computedHash.toLowerCase() !== expectedHash.toLowerCase()) {
2376
+ throw new Error(
2377
+ `Hash verification failed for ${filename}. Expected: ${expectedHash.toLowerCase()}, Computed: ${computedHash.toLowerCase()}`
2378
+ );
2379
+ }
2380
+ }
2381
+ /**
2382
+ * Generate a zk‐SNARK proof.
2383
+ * If you didn't pass wasmUrl/zkeyUrl in the constructor you must supply them here.
2384
+ */
2385
+ async generate(inputs, urls = {}) {
2386
+ const wasmUrl = urls.wasmUrl ?? this.wasmUrl;
2387
+ const zkeyUrl = urls.zkeyUrl ?? this.zkeyUrl;
2388
+ if (!wasmUrl) throw new Error("`wasmUrl` is required to generate a proof");
2389
+ if (!zkeyUrl) throw new Error("`zkeyUrl` is required to generate a proof");
2390
+ let wasmBin = this.wasmCache.get(wasmUrl);
2391
+ if (!wasmBin) {
2392
+ const r = await fetch(wasmUrl);
2393
+ if (!r.ok) throw new Error(`Failed to fetch wasm at ${wasmUrl}: ${r.status}`);
2394
+ const buf = await r.arrayBuffer();
2395
+ wasmBin = new Uint8Array(buf);
2396
+ if (this.wasmHash) {
2397
+ this.verifyHash(wasmBin, this.wasmHash, "circuit.wasm");
2398
+ }
2399
+ this.wasmCache.set(wasmUrl, wasmBin);
2400
+ }
2401
+ let zkeyBin = this.zkeyCache.get(zkeyUrl);
2402
+ if (!zkeyBin) {
2403
+ const r = await fetch(zkeyUrl);
2404
+ if (!r.ok) throw new Error(`Failed to fetch zkey at ${zkeyUrl}: ${r.status}`);
2405
+ const buf = await r.arrayBuffer();
2406
+ zkeyBin = new Uint8Array(buf);
2407
+ if (this.zkeyHash) {
2408
+ this.verifyHash(zkeyBin, this.zkeyHash, "proving_key.zkey");
2409
+ }
2410
+ this.zkeyCache.set(zkeyUrl, zkeyBin);
2411
+ }
2412
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(inputs, wasmBin, zkeyBin);
2413
+ return {
2414
+ proof,
2415
+ publicSignals
2416
+ };
2417
+ }
2418
+ async verify(proof, publicSignals, urlOverride) {
2419
+ const vkeyUrl = urlOverride ?? this.vkeyUrl;
2420
+ if (!vkeyUrl) throw new Error("`vkeyUrl` is required to verify a proof");
2421
+ let vk = this.vkeyCache.get(vkeyUrl);
2422
+ if (!vk) {
2423
+ const r = await fetch(vkeyUrl);
2424
+ if (!r.ok) throw new Error(`Failed to fetch vkey at ${vkeyUrl}: ${r.status}`);
2425
+ const vkeyText = await r.text();
2426
+ if (this.vkeyHash) {
2427
+ this.verifyHash(vkeyText, this.vkeyHash, "verification_key.json");
2428
+ }
2429
+ vk = JSON.parse(vkeyText);
2430
+ this.vkeyCache.set(vkeyUrl, vk);
2431
+ }
2432
+ return snarkjs.groth16.verify(vk, publicSignals, proof);
2433
+ }
2434
+ }
2435
+
2380
2436
  class DavinciCrypto {
2381
2437
  constructor(opts) {
2382
2438
  this.initialized = false;
@@ -2560,6 +2616,518 @@
2560
2616
  }
2561
2617
  }
2562
2618
 
2619
+ class DavinciCSP {
2620
+ constructor(opts) {
2621
+ this.initialized = false;
2622
+ const { wasmExecUrl, wasmUrl, initTimeoutMs, wasmExecHash, wasmHash } = opts;
2623
+ if (!wasmExecUrl) throw new Error("`wasmExecUrl` is required");
2624
+ if (!wasmUrl) throw new Error("`wasmUrl` is required");
2625
+ this.wasmExecUrl = wasmExecUrl;
2626
+ this.wasmUrl = wasmUrl;
2627
+ this.initTimeoutMs = initTimeoutMs ?? 5e3;
2628
+ this.wasmExecHash = wasmExecHash;
2629
+ this.wasmHash = wasmHash;
2630
+ }
2631
+ static {
2632
+ // Cache for wasm files
2633
+ this.wasmExecCache = /* @__PURE__ */ new Map();
2634
+ }
2635
+ static {
2636
+ this.wasmBinaryCache = /* @__PURE__ */ new Map();
2637
+ }
2638
+ /**
2639
+ * Computes SHA-256 hash of the given data and compares it with the expected hash.
2640
+ * @param data - The data to hash (string or ArrayBuffer)
2641
+ * @param expectedHash - The expected SHA-256 hash in hexadecimal format
2642
+ * @param filename - The filename for error reporting
2643
+ * @throws Error if the computed hash doesn't match the expected hash
2644
+ */
2645
+ verifyHash(data, expectedHash, filename) {
2646
+ let bytes;
2647
+ if (typeof data === "string") {
2648
+ bytes = new TextEncoder().encode(data);
2649
+ } else {
2650
+ bytes = new Uint8Array(data);
2651
+ }
2652
+ const computedHash = ethers.sha256(bytes).slice(2);
2653
+ if (computedHash.toLowerCase() !== expectedHash.toLowerCase()) {
2654
+ throw new Error(
2655
+ `Hash verification failed for ${filename}. Expected: ${expectedHash.toLowerCase()}, Computed: ${computedHash.toLowerCase()}`
2656
+ );
2657
+ }
2658
+ }
2659
+ /**
2660
+ * Must be awaited before calling CSP functions.
2661
+ * Safe to call multiple times.
2662
+ */
2663
+ async init() {
2664
+ if (this.initialized) return;
2665
+ let shimCode = DavinciCSP.wasmExecCache.get(this.wasmExecUrl);
2666
+ if (!shimCode) {
2667
+ const shim = await fetch(this.wasmExecUrl);
2668
+ if (!shim.ok) {
2669
+ throw new Error(`Failed to fetch wasm_exec.js from ${this.wasmExecUrl}`);
2670
+ }
2671
+ shimCode = await shim.text();
2672
+ if (this.wasmExecHash) {
2673
+ this.verifyHash(shimCode, this.wasmExecHash, "wasm_exec.js");
2674
+ }
2675
+ DavinciCSP.wasmExecCache.set(this.wasmExecUrl, shimCode);
2676
+ }
2677
+ new Function(shimCode)();
2678
+ if (typeof globalThis.Go !== "function") {
2679
+ throw new Error("Global `Go` constructor not found after loading wasm_exec.js");
2680
+ }
2681
+ this.go = new globalThis.Go();
2682
+ let bytes = DavinciCSP.wasmBinaryCache.get(this.wasmUrl);
2683
+ if (!bytes) {
2684
+ const resp = await fetch(this.wasmUrl);
2685
+ if (!resp.ok) {
2686
+ throw new Error(`Failed to fetch davinci_crypto.wasm from ${this.wasmUrl}`);
2687
+ }
2688
+ bytes = await resp.arrayBuffer();
2689
+ if (this.wasmHash) {
2690
+ this.verifyHash(bytes, this.wasmHash, "davinci_crypto.wasm");
2691
+ }
2692
+ DavinciCSP.wasmBinaryCache.set(this.wasmUrl, bytes);
2693
+ }
2694
+ const { instance } = await WebAssembly.instantiate(bytes, this.go.importObject);
2695
+ this.go.run(instance).catch(() => {
2696
+ });
2697
+ const deadline = Date.now() + this.initTimeoutMs;
2698
+ while (Date.now() < deadline && !globalThis.DavinciCrypto) {
2699
+ await new Promise((r) => setTimeout(r, 50));
2700
+ }
2701
+ if (!globalThis.DavinciCrypto) {
2702
+ throw new Error("`DavinciCrypto` not initialized within timeout");
2703
+ }
2704
+ this.initialized = true;
2705
+ }
2706
+ /**
2707
+ * Generate a CSP (Credential Service Provider) signature for census proof.
2708
+ * @param censusOrigin - The census origin type (e.g., CensusOrigin.CSP)
2709
+ * @param privKey - The private key in hex format
2710
+ * @param processId - The process ID in hex format
2711
+ * @param address - The address in hex format
2712
+ * @param weight - The vote weight as a decimal string
2713
+ * @returns The CSP proof as a parsed JSON object
2714
+ * @throws if called before `await init()`, or if Go returns an error
2715
+ */
2716
+ async cspSign(censusOrigin, privKey, processId, address, weight) {
2717
+ if (!this.initialized) {
2718
+ throw new Error("DavinciCSP not initialized \u2014 call `await init()` first");
2719
+ }
2720
+ const raw = globalThis.DavinciCrypto.cspSign(censusOrigin, privKey, processId, address, weight);
2721
+ if (raw.error) {
2722
+ throw new Error(`Go/WASM cspSign error: ${raw.error}`);
2723
+ }
2724
+ if (!raw.data) {
2725
+ throw new Error("Go/WASM cspSign returned no data");
2726
+ }
2727
+ return raw.data;
2728
+ }
2729
+ /**
2730
+ * Verify a CSP (Credential Service Provider) proof.
2731
+ * @param censusOrigin - The census origin type (e.g., CensusOrigin.CSP)
2732
+ * @param root - The census root
2733
+ * @param address - The address
2734
+ * @param weight - The vote weight as a decimal string
2735
+ * @param processId - The process ID
2736
+ * @param publicKey - The public key
2737
+ * @param signature - The signature
2738
+ * @returns The verification result
2739
+ * @throws if called before `await init()`, or if Go returns an error
2740
+ */
2741
+ async cspVerify(censusOrigin, root, address, weight, processId, publicKey, signature) {
2742
+ if (!this.initialized) {
2743
+ throw new Error("DavinciCSP not initialized \u2014 call `await init()` first");
2744
+ }
2745
+ const cspProof = {
2746
+ censusOrigin,
2747
+ root,
2748
+ address,
2749
+ weight,
2750
+ processId,
2751
+ publicKey,
2752
+ signature
2753
+ };
2754
+ const raw = globalThis.DavinciCrypto.cspVerify(JSON.stringify(cspProof));
2755
+ if (raw.error) {
2756
+ throw new Error(`Go/WASM cspVerify error: ${raw.error}`);
2757
+ }
2758
+ if (!raw.data) {
2759
+ throw new Error("Go/WASM cspVerify returned no data");
2760
+ }
2761
+ return raw.data;
2762
+ }
2763
+ /**
2764
+ * Generate a CSP (Credential Service Provider) census root.
2765
+ * @param censusOrigin - The census origin type (e.g., CensusOrigin.CSP)
2766
+ * @param privKey - The private key in hex format
2767
+ * @returns The census root as a hexadecimal string
2768
+ * @throws if called before `await init()`, or if Go returns an error
2769
+ */
2770
+ async cspCensusRoot(censusOrigin, privKey) {
2771
+ if (!this.initialized) {
2772
+ throw new Error("DavinciCSP not initialized \u2014 call `await init()` first");
2773
+ }
2774
+ const raw = globalThis.DavinciCrypto.cspCensusRoot(censusOrigin, privKey);
2775
+ if (raw.error) {
2776
+ throw new Error(`Go/WASM cspCensusRoot error: ${raw.error}`);
2777
+ }
2778
+ if (!raw.data) {
2779
+ throw new Error("Go/WASM cspCensusRoot returned no data");
2780
+ }
2781
+ return raw.data.root;
2782
+ }
2783
+ }
2784
+
2785
+ function getRandomBytes(n) {
2786
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
2787
+ return globalThis.crypto.getRandomValues(new Uint8Array(n));
2788
+ }
2789
+ throw new Error("Crypto not available");
2790
+ }
2791
+ async function buildElGamal() {
2792
+ const babyjub = await circomlibjs.buildBabyjub();
2793
+ const F = babyjub.F;
2794
+ function randomScalar() {
2795
+ const bytes = getRandomBytes(32);
2796
+ let bi = 0n;
2797
+ for (let i = 0; i < bytes.length; i++) {
2798
+ bi += BigInt(bytes[i]) << BigInt(8 * i);
2799
+ }
2800
+ return bi % babyjub.order;
2801
+ }
2802
+ function generateKeyPair() {
2803
+ const privKey = randomScalar();
2804
+ const pubKey = babyjub.mulPointEscalar(babyjub.Base8, privKey);
2805
+ return { privKey, pubKey };
2806
+ }
2807
+ function encrypt(msg, pubKey, k) {
2808
+ const kVal = BigInt(k);
2809
+ const mVal = BigInt(msg);
2810
+ const c1 = babyjub.mulPointEscalar(babyjub.Base8, kVal);
2811
+ const s = babyjub.mulPointEscalar(pubKey, kVal);
2812
+ const mPoint = babyjub.mulPointEscalar(babyjub.Base8, mVal);
2813
+ const c2 = babyjub.addPoint(mPoint, s);
2814
+ return { c1, c2 };
2815
+ }
2816
+ return {
2817
+ babyjub,
2818
+ F,
2819
+ encrypt,
2820
+ generateKeyPair,
2821
+ randomScalar,
2822
+ packPoint: babyjub.packPoint,
2823
+ unpackPoint: babyjub.unpackPoint
2824
+ };
2825
+ }
2826
+
2827
+ const FIELD_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
2828
+ const SCALING_FACTOR = 6360561867910373094066688120553762416144456282423235903351243436111059670888n;
2829
+ function modInverse(a, m) {
2830
+ let [old_r, r] = [a, m];
2831
+ let [old_s, s] = [1n, 0n];
2832
+ while (r !== 0n) {
2833
+ const quotient = old_r / r;
2834
+ [old_r, r] = [r, old_r - quotient * r];
2835
+ [old_s, s] = [s, old_s - quotient * s];
2836
+ }
2837
+ return (old_s % m + m) % m;
2838
+ }
2839
+ function mod(n, m) {
2840
+ return (n % m + m) % m;
2841
+ }
2842
+ function fromRTEtoTE(x, y) {
2843
+ const negF = mod(-SCALING_FACTOR, FIELD_MODULUS);
2844
+ const negFInv = modInverse(negF, FIELD_MODULUS);
2845
+ const xTE = mod(x * negFInv, FIELD_MODULUS);
2846
+ return [xTE, y];
2847
+ }
2848
+ function hexToDecimal(hex) {
2849
+ const cleanHex = hex.startsWith("0x") || hex.startsWith("0X") ? hex : "0x" + hex;
2850
+ return BigInt(cleanHex).toString();
2851
+ }
2852
+ function parseBallotMode(ballotMode) {
2853
+ return {
2854
+ numFields: ballotMode.numFields,
2855
+ uniqueValues: ballotMode.uniqueValues ? 1 : 0,
2856
+ maxValue: parseInt(ballotMode.maxValue),
2857
+ minValue: parseInt(ballotMode.minValue),
2858
+ maxValueSum: parseInt(ballotMode.maxValueSum),
2859
+ minValueSum: parseInt(ballotMode.minValueSum),
2860
+ costExponent: ballotMode.costExponent,
2861
+ costFromWeight: ballotMode.costFromWeight ? 1 : 0
2862
+ };
2863
+ }
2864
+ class BallotBuilder {
2865
+ constructor(elgamal, poseidon) {
2866
+ this.elgamal = elgamal;
2867
+ this.poseidon = poseidon;
2868
+ this.F = poseidon.F;
2869
+ }
2870
+ static async build() {
2871
+ const elgamal = await buildElGamal();
2872
+ const poseidon = await circomlibjs.buildPoseidon();
2873
+ return new BallotBuilder(elgamal, poseidon);
2874
+ }
2875
+ randomK() {
2876
+ return this.elgamal.randomScalar().toString();
2877
+ }
2878
+ derivePoseidonChain(seedK, n) {
2879
+ let current = BigInt(seedK);
2880
+ const out = [current.toString()];
2881
+ for (let i = 0; i < n; i++) {
2882
+ const h = this.poseidon([current]);
2883
+ const hBig = BigInt(this.F.toString(h, 10));
2884
+ out.push(hBig.toString());
2885
+ current = hBig;
2886
+ }
2887
+ return out;
2888
+ }
2889
+ // Matches circuits/lib/multiposeidon.circom logic
2890
+ multiHash(inputs) {
2891
+ const nInputs = inputs.length;
2892
+ if (nInputs <= 16) {
2893
+ return this.F.toObject(this.poseidon(inputs));
2894
+ }
2895
+ const chunks = [];
2896
+ for (let i = 0; i < nInputs; i += 16) {
2897
+ chunks.push(inputs.slice(i, i + 16));
2898
+ }
2899
+ const intermediateHashes = [];
2900
+ for (const chunk of chunks) {
2901
+ intermediateHashes.push(this.F.toObject(this.poseidon(chunk)));
2902
+ }
2903
+ return this.F.toObject(this.poseidon(intermediateHashes));
2904
+ }
2905
+ encryptFields(fields, pubKey, seedK, nFields) {
2906
+ const paddedFields = [...fields];
2907
+ while (paddedFields.length < nFields) {
2908
+ paddedFields.push(0);
2909
+ }
2910
+ const ks = this.derivePoseidonChain(seedK, nFields);
2911
+ const cipherfields = [];
2912
+ for (let i = 0; i < nFields; i++) {
2913
+ const k = ks[i + 1];
2914
+ const msg = BigInt(paddedFields[i]);
2915
+ const enc = this.elgamal.encrypt(msg, pubKey, k);
2916
+ cipherfields.push([
2917
+ [this.elgamal.F.toString(enc.c1[0], 10), this.elgamal.F.toString(enc.c1[1], 10)],
2918
+ [this.elgamal.F.toString(enc.c2[0], 10), this.elgamal.F.toString(enc.c2[1], 10)]
2919
+ ]);
2920
+ }
2921
+ return { cipherfields, paddedFields };
2922
+ }
2923
+ computeVoteID(processId, address, k) {
2924
+ const h = this.poseidon([BigInt(processId), BigInt(address), BigInt(k)]);
2925
+ const hBig = BigInt(this.F.toString(h, 10));
2926
+ const mask = (1n << 160n) - 1n;
2927
+ return (hBig & mask).toString();
2928
+ }
2929
+ computeInputsHash(inputs) {
2930
+ return this.multiHash(inputs).toString();
2931
+ }
2932
+ /**
2933
+ * Creates a public key point from TE coordinates (as strings or bigints).
2934
+ * Use this when you already have coordinates in TE format.
2935
+ */
2936
+ createPubKeyFromTE(x, y) {
2937
+ return [this.elgamal.F.e(BigInt(x)), this.elgamal.F.e(BigInt(y))];
2938
+ }
2939
+ /**
2940
+ * Creates a public key point from RTE coordinates (as strings or bigints).
2941
+ * Use this when you have coordinates from a Gnark-based system (like the DAVINCI sequencer).
2942
+ * This automatically converts from RTE to TE format.
2943
+ */
2944
+ createPubKeyFromRTE(x, y) {
2945
+ const [xTE, yTE] = fromRTEtoTE(BigInt(x), BigInt(y));
2946
+ return [this.elgamal.F.e(xTE), this.elgamal.F.e(yTE)];
2947
+ }
2948
+ /**
2949
+ * Generates ballot inputs for the circuit.
2950
+ *
2951
+ * IMPORTANT: The pubKey must be in TE (Twisted Edwards) format as used by circomlibjs.
2952
+ * If you have RTE coordinates from a Gnark-based system (like DAVINCI sequencer),
2953
+ * use `createPubKeyFromRTE()` or `generateInputsFromSequencer()` instead.
2954
+ *
2955
+ * @param fields - The vote field values
2956
+ * @param weight - The voter's weight
2957
+ * @param pubKey - Public key as field elements [x, y] in TE format (use createPubKeyFromTE/RTE)
2958
+ * @param processId - Process ID as decimal string
2959
+ * @param address - Voter address as decimal string
2960
+ * @param k - Random k value for encryption
2961
+ * @param config - Ballot configuration
2962
+ * @param circuitCapacity - Number of fields the circuit supports (default: 8)
2963
+ */
2964
+ generateInputs(fields, weight, pubKey, processId, address, k, config, circuitCapacity = 8) {
2965
+ const activeFields = fields.length;
2966
+ const { cipherfields, paddedFields } = this.encryptFields(fields, pubKey, k, circuitCapacity);
2967
+ const voteId = this.computeVoteID(processId, address, k);
2968
+ const inputsList = [];
2969
+ const ffProcessID = mod(BigInt(processId), FIELD_MODULUS);
2970
+ inputsList.push(ffProcessID);
2971
+ inputsList.push(BigInt(activeFields));
2972
+ inputsList.push(BigInt(config.uniqueValues));
2973
+ inputsList.push(BigInt(config.maxValue));
2974
+ inputsList.push(BigInt(config.minValue));
2975
+ inputsList.push(BigInt(config.maxValueSum));
2976
+ inputsList.push(BigInt(config.minValueSum));
2977
+ inputsList.push(BigInt(config.costExponent));
2978
+ inputsList.push(BigInt(config.costFromWeight));
2979
+ inputsList.push(BigInt(this.elgamal.F.toString(pubKey[0], 10)));
2980
+ inputsList.push(BigInt(this.elgamal.F.toString(pubKey[1], 10)));
2981
+ inputsList.push(BigInt(address));
2982
+ inputsList.push(BigInt(voteId));
2983
+ for (const cf of cipherfields) {
2984
+ inputsList.push(BigInt(cf[0][0]));
2985
+ inputsList.push(BigInt(cf[0][1]));
2986
+ inputsList.push(BigInt(cf[1][0]));
2987
+ inputsList.push(BigInt(cf[1][1]));
2988
+ }
2989
+ inputsList.push(BigInt(weight));
2990
+ const inputsHash = this.computeInputsHash(inputsList);
2991
+ return {
2992
+ fields: paddedFields,
2993
+ weight,
2994
+ encryption_pubkey: [
2995
+ this.elgamal.F.toString(pubKey[0], 10),
2996
+ this.elgamal.F.toString(pubKey[1], 10)
2997
+ ],
2998
+ cipherfields,
2999
+ process_id: processId,
3000
+ address,
3001
+ k,
3002
+ vote_id: voteId,
3003
+ inputs_hash: inputsHash,
3004
+ // Config
3005
+ num_fields: activeFields,
3006
+ unique_values: config.uniqueValues,
3007
+ max_value: config.maxValue,
3008
+ min_value: config.minValue,
3009
+ max_value_sum: config.maxValueSum,
3010
+ min_value_sum: config.minValueSum,
3011
+ cost_exponent: config.costExponent,
3012
+ cost_from_weight: config.costFromWeight
3013
+ };
3014
+ }
3015
+ /**
3016
+ * Generates ballot inputs from sequencer data.
3017
+ * This is a convenience method that handles the RTE to TE conversion
3018
+ * for the public key automatically.
3019
+ *
3020
+ * @param sequencerData - Data from the DAVINCI sequencer
3021
+ * @param fields - The vote field values
3022
+ * @param weight - The voter's weight
3023
+ * @param k - Optional random k value (generated if not provided)
3024
+ * @param circuitCapacity - The number of fields the circuit supports (default: 8)
3025
+ */
3026
+ generateInputsFromSequencer(sequencerData, fields, weight, k, circuitCapacity = 8) {
3027
+ const processId = hexToDecimal(sequencerData.processId);
3028
+ const address = hexToDecimal(sequencerData.address);
3029
+ const [pubKeyX_TE, pubKeyY_TE] = fromRTEtoTE(
3030
+ BigInt(sequencerData.pubKeyX),
3031
+ BigInt(sequencerData.pubKeyY)
3032
+ );
3033
+ const pubKey = [this.elgamal.F.e(pubKeyX_TE), this.elgamal.F.e(pubKeyY_TE)];
3034
+ const config = parseBallotMode(sequencerData.ballotMode);
3035
+ const kValue = k ?? this.randomK();
3036
+ return this.generateInputs(fields, weight, pubKey, processId, address, kValue, config, circuitCapacity);
3037
+ }
3038
+ }
3039
+
3040
+ class BallotInputGenerator {
3041
+ constructor() {
3042
+ this.initialized = false;
3043
+ }
3044
+ /**
3045
+ * Initialize the ballot input generator
3046
+ * Must be called before generating inputs
3047
+ */
3048
+ async init() {
3049
+ if (this.initialized) return;
3050
+ this.builder = await BallotBuilder.build();
3051
+ this.initialized = true;
3052
+ }
3053
+ /**
3054
+ * Generate ballot inputs for voting
3055
+ * @param processId - Process ID (hex string without 0x)
3056
+ * @param address - Voter address (hex string without 0x)
3057
+ * @param encryptionKey - Encryption public key [x, y]
3058
+ * @param ballotMode - Ballot mode configuration
3059
+ * @param choices - Array of voter choices
3060
+ * @param weight - Voter weight
3061
+ * @param customK - Optional custom randomness (will be generated if not provided)
3062
+ * @returns Ballot inputs ready for proof generation
3063
+ */
3064
+ async generateInputs(processId, address, encryptionKey, ballotMode, choices, weight, customK) {
3065
+ if (!this.initialized || !this.builder) {
3066
+ throw new Error("BallotInputGenerator not initialized \u2014 call `await init()` first");
3067
+ }
3068
+ const ballotInputs = this.builder.generateInputsFromSequencer(
3069
+ {
3070
+ processId,
3071
+ address: "0x" + address,
3072
+ pubKeyX: encryptionKey.x,
3073
+ pubKeyY: encryptionKey.y,
3074
+ ballotMode: {
3075
+ numFields: ballotMode.numFields,
3076
+ uniqueValues: ballotMode.uniqueValues,
3077
+ maxValue: ballotMode.maxValue,
3078
+ minValue: ballotMode.minValue,
3079
+ maxValueSum: ballotMode.maxValueSum,
3080
+ minValueSum: ballotMode.minValueSum,
3081
+ costExponent: ballotMode.costExponent,
3082
+ costFromWeight: ballotMode.costFromWeight
3083
+ }
3084
+ },
3085
+ choices,
3086
+ parseInt(weight),
3087
+ customK,
3088
+ 8
3089
+ );
3090
+ const circomInputs = {
3091
+ fields: ballotInputs.fields.map((f) => f.toString()),
3092
+ num_fields: ballotInputs.num_fields.toString(),
3093
+ unique_values: ballotInputs.unique_values.toString(),
3094
+ max_value: ballotInputs.max_value.toString(),
3095
+ min_value: ballotInputs.min_value.toString(),
3096
+ max_value_sum: ballotInputs.max_value_sum.toString(),
3097
+ min_value_sum: ballotInputs.min_value_sum.toString(),
3098
+ cost_exponent: ballotInputs.cost_exponent.toString(),
3099
+ cost_from_weight: ballotInputs.cost_from_weight.toString(),
3100
+ address: ballotInputs.address,
3101
+ weight: ballotInputs.weight.toString(),
3102
+ process_id: ballotInputs.process_id,
3103
+ vote_id: ballotInputs.vote_id,
3104
+ encryption_pubkey: [
3105
+ ballotInputs.encryption_pubkey[0],
3106
+ ballotInputs.encryption_pubkey[1]
3107
+ ],
3108
+ k: ballotInputs.k,
3109
+ cipherfields: ballotInputs.cipherfields.flat(2),
3110
+ inputs_hash: ballotInputs.inputs_hash
3111
+ };
3112
+ const ciphertexts = ballotInputs.cipherfields.map((cf) => ({
3113
+ c1: [cf[0][0], cf[0][1]],
3114
+ c2: [cf[1][0], cf[1][1]]
3115
+ }));
3116
+ const output = {
3117
+ processId: "0x" + BigInt(ballotInputs.process_id).toString(16).padStart(64, "0"),
3118
+ address: "0x" + BigInt(ballotInputs.address).toString(16).padStart(40, "0"),
3119
+ ballot: {
3120
+ curveType: "bjj_iden3",
3121
+ ciphertexts
3122
+ },
3123
+ ballotInputsHash: ballotInputs.inputs_hash,
3124
+ voteId: "0x" + BigInt(ballotInputs.vote_id).toString(16).padStart(64, "0"),
3125
+ circomInputs
3126
+ };
3127
+ return output;
3128
+ }
3129
+ }
3130
+
2563
3131
  class DavinciSDK {
2564
3132
  constructor(config) {
2565
3133
  this.initialized = false;
@@ -2649,6 +3217,30 @@
2649
3217
  }
2650
3218
  return this.davinciCrypto;
2651
3219
  }
3220
+ /**
3221
+ * Get or initialize the DavinciCSP service for CSP cryptographic operations
3222
+ */
3223
+ async getCSP() {
3224
+ if (!this.davinciCSP) {
3225
+ const info = await this.apiService.sequencer.getInfo();
3226
+ this.davinciCSP = new DavinciCSP({
3227
+ wasmExecUrl: info.ballotProofWasmHelperExecJsUrl,
3228
+ wasmUrl: info.ballotProofWasmHelperUrl
3229
+ });
3230
+ await this.davinciCSP.init();
3231
+ }
3232
+ return this.davinciCSP;
3233
+ }
3234
+ /**
3235
+ * Get or initialize the BallotInputGenerator service for ballot input generation
3236
+ */
3237
+ async getBallotInputGenerator() {
3238
+ if (!this.ballotInputGenerator) {
3239
+ this.ballotInputGenerator = new BallotInputGenerator();
3240
+ await this.ballotInputGenerator.init();
3241
+ }
3242
+ return this.ballotInputGenerator;
3243
+ }
2652
3244
  /**
2653
3245
  * Get the process orchestration service for simplified process creation.
2654
3246
  * Requires a signer with a provider for blockchain interactions.
@@ -2675,7 +3267,7 @@
2675
3267
  if (!this._voteOrchestrator) {
2676
3268
  this._voteOrchestrator = new VoteOrchestrationService(
2677
3269
  this.apiService,
2678
- () => this.getCrypto(),
3270
+ () => this.getBallotInputGenerator(),
2679
3271
  this.config.signer,
2680
3272
  this.censusProviders,
2681
3273
  {
@@ -3528,6 +4120,7 @@
3528
4120
  }
3529
4121
  }
3530
4122
 
4123
+ exports.BallotInputGenerator = BallotInputGenerator;
3531
4124
  exports.BaseService = BaseService;
3532
4125
  exports.Census = Census;
3533
4126
  exports.CensusNotUpdatable = CensusNotUpdatable;
@@ -3536,6 +4129,7 @@
3536
4129
  exports.CircomProof = CircomProof;
3537
4130
  exports.ContractServiceError = ContractServiceError;
3538
4131
  exports.CspCensus = CspCensus;
4132
+ exports.DavinciCSP = DavinciCSP;
3539
4133
  exports.DavinciCrypto = DavinciCrypto;
3540
4134
  exports.DavinciSDK = DavinciSDK;
3541
4135
  exports.ElectionMetadataTemplate = ElectionMetadataTemplate;