@ledgerhq/coin-canton 0.8.1-nightly.0 → 0.9.0-nightly.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.
Files changed (104) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +75 -0
  4. package/lib/api/index.d.ts.map +1 -1
  5. package/lib/api/index.js +3 -0
  6. package/lib/api/index.js.map +1 -1
  7. package/lib/bridge/deviceTransactionConfig.d.ts +1 -1
  8. package/lib/bridge/deviceTransactionConfig.d.ts.map +1 -1
  9. package/lib/bridge/deviceTransactionConfig.js +1 -1
  10. package/lib/bridge/deviceTransactionConfig.js.map +1 -1
  11. package/lib/bridge/onboard.d.ts.map +1 -1
  12. package/lib/bridge/onboard.js +48 -22
  13. package/lib/bridge/onboard.js.map +1 -1
  14. package/lib/bridge/signOperation.d.ts.map +1 -1
  15. package/lib/bridge/signOperation.js +10 -3
  16. package/lib/bridge/signOperation.js.map +1 -1
  17. package/lib/common-logic/index.d.ts +1 -0
  18. package/lib/common-logic/index.d.ts.map +1 -1
  19. package/lib/common-logic/index.js +3 -1
  20. package/lib/common-logic/index.js.map +1 -1
  21. package/lib/common-logic/transaction/sign.d.ts +8 -0
  22. package/lib/common-logic/transaction/sign.d.ts.map +1 -0
  23. package/lib/common-logic/transaction/sign.js +27 -0
  24. package/lib/common-logic/transaction/sign.js.map +1 -0
  25. package/lib/common-logic/transaction/split.d.ts +9 -0
  26. package/lib/common-logic/transaction/split.d.ts.map +1 -0
  27. package/lib/common-logic/transaction/split.js +119 -0
  28. package/lib/common-logic/transaction/split.js.map +1 -0
  29. package/lib/common-logic/utils.js +2 -2
  30. package/lib/common-logic/utils.js.map +1 -1
  31. package/lib/network/gateway.d.ts +1 -0
  32. package/lib/network/gateway.d.ts.map +1 -1
  33. package/lib/network/gateway.js +2 -7
  34. package/lib/network/gateway.js.map +1 -1
  35. package/lib/test/cantonTestUtils.d.ts +4 -9
  36. package/lib/test/cantonTestUtils.d.ts.map +1 -1
  37. package/lib/test/cantonTestUtils.js +736 -27
  38. package/lib/test/cantonTestUtils.js.map +1 -1
  39. package/lib/types/signer.d.ts +10 -1
  40. package/lib/types/signer.d.ts.map +1 -1
  41. package/lib/types/transaction-proto.json +1238 -0
  42. package/lib-es/api/index.d.ts.map +1 -1
  43. package/lib-es/api/index.js +3 -0
  44. package/lib-es/api/index.js.map +1 -1
  45. package/lib-es/bridge/deviceTransactionConfig.d.ts +1 -1
  46. package/lib-es/bridge/deviceTransactionConfig.d.ts.map +1 -1
  47. package/lib-es/bridge/deviceTransactionConfig.js +1 -1
  48. package/lib-es/bridge/deviceTransactionConfig.js.map +1 -1
  49. package/lib-es/bridge/onboard.d.ts.map +1 -1
  50. package/lib-es/bridge/onboard.js +49 -23
  51. package/lib-es/bridge/onboard.js.map +1 -1
  52. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  53. package/lib-es/bridge/signOperation.js +10 -3
  54. package/lib-es/bridge/signOperation.js.map +1 -1
  55. package/lib-es/common-logic/index.d.ts +1 -0
  56. package/lib-es/common-logic/index.d.ts.map +1 -1
  57. package/lib-es/common-logic/index.js +1 -0
  58. package/lib-es/common-logic/index.js.map +1 -1
  59. package/lib-es/common-logic/transaction/sign.d.ts +8 -0
  60. package/lib-es/common-logic/transaction/sign.d.ts.map +1 -0
  61. package/lib-es/common-logic/transaction/sign.js +24 -0
  62. package/lib-es/common-logic/transaction/sign.js.map +1 -0
  63. package/lib-es/common-logic/transaction/split.d.ts +9 -0
  64. package/lib-es/common-logic/transaction/split.d.ts.map +1 -0
  65. package/lib-es/common-logic/transaction/split.js +83 -0
  66. package/lib-es/common-logic/transaction/split.js.map +1 -0
  67. package/lib-es/common-logic/utils.js +2 -2
  68. package/lib-es/common-logic/utils.js.map +1 -1
  69. package/lib-es/network/gateway.d.ts +1 -0
  70. package/lib-es/network/gateway.d.ts.map +1 -1
  71. package/lib-es/network/gateway.js +1 -8
  72. package/lib-es/network/gateway.js.map +1 -1
  73. package/lib-es/test/cantonTestUtils.d.ts +4 -9
  74. package/lib-es/test/cantonTestUtils.d.ts.map +1 -1
  75. package/lib-es/test/cantonTestUtils.js +697 -21
  76. package/lib-es/test/cantonTestUtils.js.map +1 -1
  77. package/lib-es/types/signer.d.ts +10 -1
  78. package/lib-es/types/signer.d.ts.map +1 -1
  79. package/lib-es/types/transaction-proto.json +1238 -0
  80. package/package.json +10 -7
  81. package/scripts/generate.js +261 -0
  82. package/src/api/index.ts +4 -0
  83. package/src/bridge/deviceTransactionConfig.test.ts +14 -10
  84. package/src/bridge/deviceTransactionConfig.ts +2 -2
  85. package/src/bridge/getTransactionStatus.test.ts +19 -19
  86. package/src/bridge/onboard.integ.test.ts +0 -20
  87. package/src/bridge/onboard.ts +57 -33
  88. package/src/bridge/signOperation.test.ts +114 -0
  89. package/src/bridge/signOperation.ts +12 -5
  90. package/src/bridge/sync.integ.test.ts +157 -132
  91. package/src/common-logic/index.ts +1 -0
  92. package/src/common-logic/transaction/sign.test.ts +317 -0
  93. package/src/common-logic/transaction/sign.ts +33 -0
  94. package/src/common-logic/transaction/split.test.ts +50 -0
  95. package/src/common-logic/transaction/split.ts +101 -0
  96. package/src/common-logic/utils.test.ts +22 -30
  97. package/src/common-logic/utils.ts +2 -2
  98. package/src/network/gateway.integ.test.ts +2 -0
  99. package/src/network/gateway.ts +3 -8
  100. package/src/test/cantonTestUtils.ts +789 -24
  101. package/src/test/prepare-transfer-serialized.json +26 -0
  102. package/src/test/prepare-transfer.json +3298 -0
  103. package/src/types/signer.ts +17 -3
  104. package/src/types/transaction-proto.json +1238 -0
@@ -1,10 +1,38 @@
1
1
  "use strict";
2
- /**
3
- * Canton Testing Utilities for Ed25519 Key Generation
4
- *
5
- * Provides utilities to generate proper Ed25519 keypairs for testing Canton
6
- * onboarding without requiring physical Ledger devices.
7
- */
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
8
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
9
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
10
38
  };
@@ -12,7 +40,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
40
  exports.generateMockKeyPair = generateMockKeyPair;
13
41
  exports.verifySignature = verifySignature;
14
42
  exports.createMockSigner = createMockSigner;
43
+ /**
44
+ * Canton Testing Utilities for Ed25519 Key Generation
45
+ *
46
+ * Provides utilities to generate proper Ed25519 keypairs for testing Canton
47
+ * onboarding without requiring physical Ledger devices.
48
+ * The mock signer implements canonical hash algorithm used by the Canton app.
49
+ */
15
50
  const crypto_1 = __importDefault(require("crypto"));
51
+ const protobuf = __importStar(require("protobufjs"));
52
+ const transactionProto = __importStar(require("../types/transaction-proto.json"));
53
+ const root = protobuf.Root.fromJSON(transactionProto) || {};
54
+ // Constants from app-canton
55
+ const PREPARED_TRANSACTION_HASH_PURPOSE = Buffer.from([0x00, 0x00, 0x00, 0x30]);
56
+ const HASHING_SCHEME_VERSION = 0x02;
57
+ const NODE_ENCODING_VERSION = 0x01;
58
+ const PURPOSE_PUBLIC_KEY_FINGERPRINT = 12;
59
+ const PURPOSE_TOPOLOGY_TRANSACTION_SIGNATURE = 11;
60
+ const PURPOSE_MULTI_TOPOLOGY_TRANSACTION_SIGNATURE = 55;
61
+ const MULTIHASH_SHA256_PREFIX = Buffer.from([0x12, 0x20]);
16
62
  /**
17
63
  * Generate fresh Ed25519 keypair
18
64
  */
@@ -26,8 +72,6 @@ function generateMockKeyPair() {
26
72
  const privateKeyDer = privateKey.export({ type: "pkcs8", format: "der" });
27
73
  const privateKeyHex = privateKeyDer.toString("hex");
28
74
  // Generate fingerprint: Canton computes SHA256(purpose_bytes + public_key_bytes)
29
- // where purpose_bytes is 4-byte big-endian representation of PURPOSE_PUBLIC_KEY_FINGERPRINT (12)
30
- const PURPOSE_PUBLIC_KEY_FINGERPRINT = 12;
31
75
  const purposeBytes = Buffer.allocUnsafe(4);
32
76
  purposeBytes.writeInt32BE(PURPOSE_PUBLIC_KEY_FINGERPRINT, 0);
33
77
  const hash = crypto_1.default.createHash("sha256");
@@ -35,8 +79,7 @@ function generateMockKeyPair() {
35
79
  hash.update(rawPublicKey);
36
80
  const hashedContent = hash.digest();
37
81
  // Multihash encoding: 0x12 (SHA256) + 0x20 (32 bytes) + hash
38
- const multihashPrefix = Buffer.from([0x12, 0x20]);
39
- const fingerprintBuffer = Buffer.concat([multihashPrefix, hashedContent]);
82
+ const fingerprintBuffer = Buffer.concat([MULTIHASH_SHA256_PREFIX, hashedContent]);
40
83
  const fingerprint = fingerprintBuffer.toString("hex");
41
84
  return {
42
85
  publicKeyHex, // 64-char hex string (no 0x prefix)
@@ -53,8 +96,7 @@ function generateMockKeyPair() {
53
96
  format: "pem",
54
97
  type: "pkcs8",
55
98
  });
56
- const signature = crypto_1.default.sign(null, hashBuffer, privateKeyObj);
57
- return signature.toString("hex");
99
+ return crypto_1.default.sign(null, hashBuffer, privateKeyObj).toString("hex");
58
100
  },
59
101
  };
60
102
  }
@@ -73,9 +115,6 @@ function verifySignature(publicKeyHex, signatureHex, messageHashHex) {
73
115
  publicKeyLength: cleanPublicKey.length,
74
116
  signatureLength: cleanSignature.length,
75
117
  messageHashLength: cleanMessageHash.length,
76
- publicKeyBytes: cleanPublicKey.length / 2,
77
- signatureBytes: cleanSignature.length / 2,
78
- messageHashBytes: cleanMessageHash.length / 2,
79
118
  };
80
119
  // Validate input lengths
81
120
  if (cleanPublicKey.length !== 64) {
@@ -85,17 +124,15 @@ function verifySignature(publicKeyHex, signatureHex, messageHashHex) {
85
124
  details,
86
125
  };
87
126
  }
88
- // Ed25519 signatures should be 64 bytes, but we might receive 65 bytes with recovery ID
127
+ // Handle 65-byte signatures (remove recovery ID)
89
128
  let processedSignature = cleanSignature;
90
129
  if (cleanSignature.length === 130) {
91
130
  processedSignature = cleanSignature.slice(2, -2);
92
- details.originalSignatureLength = cleanSignature.length;
93
- details.processedSignatureLength = processedSignature.length;
94
131
  }
95
132
  else if (cleanSignature.length !== 128) {
96
133
  return {
97
134
  isValid: false,
98
- error: `Invalid signature length: expected 128 hex chars (64 bytes) or 130 hex chars (65 bytes), got ${cleanSignature.length}`,
135
+ error: `Invalid signature length: expected 128 or 130 hex chars, got ${cleanSignature.length}`,
99
136
  details,
100
137
  };
101
138
  }
@@ -129,11 +166,7 @@ function verifySignature(publicKeyHex, signatureHex, messageHashHex) {
129
166
  const isValid = crypto_1.default.verify(null, messageBuffer, publicKeyObj, signatureBuffer);
130
167
  return {
131
168
  isValid,
132
- details: {
133
- ...details,
134
- processedSignatureLength: processedSignature.length,
135
- verificationMethod: "Node.js crypto.verify with Ed25519",
136
- },
169
+ details: { ...details, processedSignatureLength: processedSignature.length },
137
170
  };
138
171
  }
139
172
  catch (error) {
@@ -146,14 +179,690 @@ function verifySignature(publicKeyHex, signatureHex, messageHashHex) {
146
179
  }
147
180
  function createMockSigner(keyPair) {
148
181
  return {
149
- getAddress: async (derivationPath) => ({
182
+ getAddress: async (_derivationPath) => ({
150
183
  address: `canton_test_${keyPair.fingerprint.slice(-8)}`,
151
184
  publicKey: keyPair.publicKeyHex,
152
185
  }),
153
- signTransaction: async (derivationPath, hashToSign) => {
186
+ signTransaction: async (_derivationPath, data) => {
187
+ let hashToSign;
188
+ if (typeof data === "object" && "transactions" in data) {
189
+ // CantonUntypedVersionedMessage - process multiple topology transactions
190
+ const hashes = data.transactions.map(tx => {
191
+ const txBytes = typeof tx === "string" ? Buffer.from(tx, "hex") : Buffer.from(tx);
192
+ return computeCantonHash(PURPOSE_TOPOLOGY_TRANSACTION_SIGNATURE, txBytes);
193
+ });
194
+ // Sort hashes lexicographically in hex format
195
+ const sortedHashes = hashes.sort((a, b) => a.toString("hex").localeCompare(b.toString("hex")));
196
+ // Concatenate with length prefixes: 4-byte count + for each hash: 4-byte length + 34-byte hash
197
+ const concatBuffer = Buffer.alloc(4 + sortedHashes.length * (4 + 34));
198
+ let offset = 0;
199
+ // Write count of hashes (4 bytes big-endian)
200
+ concatBuffer.writeUInt32BE(sortedHashes.length, offset);
201
+ offset += 4;
202
+ // Write each hash with length prefix
203
+ for (const hash of sortedHashes) {
204
+ concatBuffer.writeUInt32BE(34, offset); // Length is always 34 bytes
205
+ offset += 4;
206
+ hash.copy(concatBuffer, offset);
207
+ offset += 34;
208
+ }
209
+ // Compute final multi-hash
210
+ const finalHash = computeCantonHash(PURPOSE_MULTI_TOPOLOGY_TRANSACTION_SIGNATURE, concatBuffer);
211
+ hashToSign = finalHash.toString("hex");
212
+ }
213
+ else if (typeof data === "object") {
214
+ const canonicalHash = computeCanonicalHash(data);
215
+ hashToSign = canonicalHash.toString("hex");
216
+ }
217
+ else {
218
+ hashToSign = data;
219
+ }
154
220
  const cleanHash = hashToSign.startsWith("0x") ? hashToSign.slice(2) : hashToSign;
155
- return keyPair.sign(cleanHash);
221
+ const signature = keyPair.sign(cleanHash);
222
+ return signature;
156
223
  },
157
224
  };
158
225
  }
226
+ /**
227
+ * Compute Canton hash with purpose and multihash encoding
228
+ */
229
+ function computeCantonHash(purpose, data) {
230
+ const purposeBytes = Buffer.allocUnsafe(4);
231
+ purposeBytes.writeUInt32BE(purpose, 0);
232
+ const hash = crypto_1.default.createHash("sha256");
233
+ hash.update(purposeBytes);
234
+ hash.update(data);
235
+ const hashedContent = hash.digest();
236
+ // Multihash encoding: 0x12 (SHA256) + 0x20 (32 bytes) + hash
237
+ return Buffer.concat([MULTIHASH_SHA256_PREFIX, hashedContent]);
238
+ }
239
+ /**
240
+ * Compute canonical hash for CantonPreparedTransaction
241
+ * Implements canonical_hash.c
242
+ */
243
+ function computeCanonicalHash(data) {
244
+ // Step 1: Parse DAML transaction
245
+ const DeviceDamlTransaction = root.lookupType("com.daml.ledger.api.v2.interactive.DeviceDamlTransaction");
246
+ const damlTx = DeviceDamlTransaction.decode(data.damlTransaction);
247
+ // Step 2: Hash transaction (hash_transaction)
248
+ const txHash = hashTransaction(damlTx);
249
+ // Step 3: Hash nodes (hash_node)
250
+ const nodesHash = hashNodes(damlTx, data.nodes);
251
+ // Step 4: Combine transaction and nodes hash
252
+ const combinedTxHash = crypto_1.default.createHash("sha256");
253
+ combinedTxHash.update(txHash);
254
+ combinedTxHash.update(nodesHash);
255
+ const finalTxHash = combinedTxHash.digest();
256
+ // Step 5: Hash metadata (hash_metadata)
257
+ const DeviceMetadata = root.lookupType("com.daml.ledger.api.v2.interactive.DeviceMetadata");
258
+ const metadata = DeviceMetadata.decode(data.metadata);
259
+ const metadataHash = hashMetadata(metadata, data.inputContracts);
260
+ // Step 6: Finalize hash (finalize_hash)
261
+ return finalizeHash(finalTxHash, metadataHash);
262
+ }
263
+ /**
264
+ * Hash DAML transaction
265
+ * Implements hash_transaction from canonical_hash.c
266
+ */
267
+ function hashTransaction(damlTx) {
268
+ const hash = crypto_1.default.createHash("sha256");
269
+ // Add purpose (PREPARED_TRANSACTION_HASH_PURPOSE = {0x00, 0x00, 0x00, 0x30})
270
+ hash.update(PREPARED_TRANSACTION_HASH_PURPOSE);
271
+ // Encode version string
272
+ encodeString(hash, damlTx.version || "");
273
+ // Encode roots count
274
+ encodeInt32(hash, damlTx.rootsCount || 0);
275
+ return hash.digest();
276
+ }
277
+ /**
278
+ * Hash nodes
279
+ * Implements hash_node from canonical_hash.c
280
+ */
281
+ function hashNodes(damlTx, nodes) {
282
+ const hash = crypto_1.default.createHash("sha256");
283
+ // Process each node
284
+ for (const nodeBytes of nodes) {
285
+ const Node = root.lookupType("com.daml.ledger.api.v2.interactive.DeviceDamlTransaction.Node");
286
+ const node = Node.decode(nodeBytes);
287
+ // Check if this is a root node
288
+ const isRootNode = damlTx.roots?.includes(node.nodeId);
289
+ // Hash the node (encode_node_id_hash)
290
+ const nodeHash = hashNodeId(node, isRootNode, damlTx.nodeSeeds || []);
291
+ if (isRootNode) {
292
+ // For root nodes, add directly to the hash
293
+ hash.update(nodeHash);
294
+ }
295
+ // Non-root nodes are ignored in this implementation
296
+ }
297
+ return hash.digest();
298
+ }
299
+ /**
300
+ * Hash a single node
301
+ * Implements encode_node_id_hash from canonical_hash.c
302
+ */
303
+ function hashNodeId(node, isRootNode, nodeSeeds) {
304
+ // Create separate hash writer for the node
305
+ const nodeHash = crypto_1.default.createHash("sha256");
306
+ // Encode the node based on its type (encode_node)
307
+ if (node.v1) {
308
+ if (node.v1.create) {
309
+ encodeCreateNode(nodeHash, node.v1.create, node.nodeId, nodeSeeds);
310
+ }
311
+ else if (node.v1.exercise) {
312
+ encodeExerciseNode(nodeHash, node.v1.exercise, node.nodeId, nodeSeeds);
313
+ }
314
+ else if (node.v1.fetch) {
315
+ encodeFetchNode(nodeHash, node.v1.fetch);
316
+ }
317
+ else if (node.v1.rollback) {
318
+ encodeRollbackNode(nodeHash, node.v1.rollback);
319
+ }
320
+ }
321
+ return nodeHash.digest();
322
+ }
323
+ /**
324
+ * Encode create node
325
+ * Implements encode_create from canonical_hash.c
326
+ */
327
+ function encodeCreateNode(hash, create, nodeId, nodeSeeds) {
328
+ // NODE_ENCODING_VERSION = 0x01
329
+ hash.update(Buffer.from([NODE_ENCODING_VERSION]));
330
+ // lf_version string
331
+ encodeString(hash, create.lfVersion || "");
332
+ // 0x00 byte
333
+ hash.update(Buffer.from([0x00]));
334
+ // Optional seed (find_seed)
335
+ const seed = findNodeSeed(nodeId, nodeSeeds);
336
+ if (seed) {
337
+ // Seed is present - encode as hash
338
+ hash.update(seed);
339
+ }
340
+ else {
341
+ // No seed - encode as optional false
342
+ hash.update(Buffer.from([0x00]));
343
+ }
344
+ // contract_id as hex string
345
+ encodeHexString(hash, create.contractId || "");
346
+ // package_name string
347
+ encodeString(hash, create.packageName || "");
348
+ // template_id identifier
349
+ if (create.templateId) {
350
+ encodeIdentifier(hash, create.templateId);
351
+ }
352
+ // argument value (this needs proper DAML value encoding)
353
+ if (create.argument) {
354
+ encodeValue(hash, create.argument);
355
+ }
356
+ // signatories repeated strings
357
+ if (create.signatories) {
358
+ encodeRepeatedStrings(hash, create.signatories);
359
+ }
360
+ // stakeholders repeated strings
361
+ if (create.stakeholders) {
362
+ encodeRepeatedStrings(hash, create.stakeholders);
363
+ }
364
+ }
365
+ /**
366
+ * Encode exercise node
367
+ * Implements encode_exercise from canonical_hash.c
368
+ */
369
+ function encodeExerciseNode(hash, exercise, nodeId, nodeSeeds) {
370
+ // NODE_ENCODING_VERSION = 0x01
371
+ hash.update(Buffer.from([NODE_ENCODING_VERSION]));
372
+ // lf_version string
373
+ encodeString(hash, exercise.lfVersion || "");
374
+ // 0x01 byte
375
+ hash.update(Buffer.from([0x01]));
376
+ // Seed is always present for exercise nodes
377
+ const seed = findNodeSeed(nodeId, nodeSeeds);
378
+ if (seed) {
379
+ encodeHash(hash, seed);
380
+ }
381
+ else {
382
+ // This should not happen, but handle gracefully
383
+ hash.update(Buffer.alloc(32, 0));
384
+ }
385
+ // contract_id as hex string
386
+ encodeHexString(hash, exercise.contractId || "");
387
+ // package_name string
388
+ encodeString(hash, exercise.packageName || "");
389
+ // template_id identifier (required for exercise nodes)
390
+ if (exercise.templateId) {
391
+ encodeIdentifier(hash, exercise.templateId);
392
+ }
393
+ // choice string
394
+ encodeString(hash, exercise.choice || "");
395
+ // argument value
396
+ if (exercise.argument) {
397
+ encodeValue(hash, exercise.argument);
398
+ }
399
+ // acting_parties repeated strings
400
+ if (exercise.actingParties) {
401
+ encodeRepeatedStrings(hash, exercise.actingParties);
402
+ }
403
+ // children repeated strings
404
+ if (exercise.children) {
405
+ encodeRepeatedStrings(hash, exercise.children);
406
+ }
407
+ // exercise_result optional value
408
+ if (exercise.exerciseResult) {
409
+ // Optional present
410
+ hash.update(Buffer.from([0x01]));
411
+ encodeValue(hash, exercise.exerciseResult);
412
+ }
413
+ else {
414
+ // Optional not present
415
+ hash.update(Buffer.from([0x00]));
416
+ }
417
+ }
418
+ /**
419
+ * Encode fetch node
420
+ * Implements encode_fetch from canonical_hash.c
421
+ */
422
+ function encodeFetchNode(hash, fetch) {
423
+ // NODE_ENCODING_VERSION = 0x01
424
+ hash.update(Buffer.from([NODE_ENCODING_VERSION]));
425
+ // lf_version string
426
+ encodeString(hash, fetch.lfVersion || "");
427
+ // 0x02 byte
428
+ hash.update(Buffer.from([0x02]));
429
+ // contract_id as hex string
430
+ encodeHexString(hash, fetch.contractId || "");
431
+ // package_name string
432
+ encodeString(hash, fetch.packageName || "");
433
+ // template_id identifier
434
+ if (fetch.templateId) {
435
+ encodeIdentifier(hash, fetch.templateId);
436
+ }
437
+ }
438
+ /**
439
+ * Encode rollback node
440
+ * Implements encode_rollback from canonical_hash.c
441
+ */
442
+ function encodeRollbackNode(hash, rollback) {
443
+ // NODE_ENCODING_VERSION = 0x01
444
+ hash.update(Buffer.from([NODE_ENCODING_VERSION]));
445
+ // lf_version string
446
+ encodeString(hash, rollback.lfVersion || "");
447
+ // 0x03 byte
448
+ hash.update(Buffer.from([0x03]));
449
+ // children repeated strings
450
+ if (rollback.children) {
451
+ encodeRepeatedStrings(hash, rollback.children);
452
+ }
453
+ }
454
+ /**
455
+ * Find node seed for a given node ID
456
+ */
457
+ function findNodeSeed(nodeId, nodeSeeds) {
458
+ const nodeIdNum = Number.parseInt(nodeId, 10);
459
+ for (const seed of nodeSeeds) {
460
+ if (seed.nodeId === nodeIdNum) {
461
+ return Buffer.from(seed.seed);
462
+ }
463
+ }
464
+ return null;
465
+ }
466
+ /**
467
+ * Encode identifier
468
+ */
469
+ function encodeIdentifier(hash, identifier) {
470
+ if (identifier.packageId) {
471
+ encodeString(hash, identifier.packageId);
472
+ }
473
+ if (identifier.moduleName) {
474
+ splitDotAndEncode(hash, identifier.moduleName);
475
+ }
476
+ if (identifier.entityName) {
477
+ splitDotAndEncode(hash, identifier.entityName);
478
+ }
479
+ }
480
+ /**
481
+ * Split dot-separated string and encode
482
+ */
483
+ function splitDotAndEncode(hash, dotStr) {
484
+ const parts = dotStr.split(".");
485
+ encodeInt32(hash, parts.length);
486
+ for (const part of parts) {
487
+ encodeInt32(hash, part.length);
488
+ hash.update(Buffer.from(part, "utf8"));
489
+ }
490
+ }
491
+ /**
492
+ * Hash metadata
493
+ * Implements hash_metadata from canonical_hash.c
494
+ */
495
+ function hashMetadata(metadata, inputContracts) {
496
+ const hash = crypto_1.default.createHash("sha256");
497
+ // Add purpose (PREPARED_TRANSACTION_HASH_PURPOSE = {0x00, 0x00, 0x00, 0x30})
498
+ hash.update(PREPARED_TRANSACTION_HASH_PURPOSE);
499
+ // Encode metadata (encode_metadata)
500
+ // Version marker (0x01)
501
+ hash.update(Buffer.from([0x01]));
502
+ // Encode submitter info
503
+ if (metadata.submitterInfo) {
504
+ // act_as repeated strings
505
+ if (metadata.submitterInfo.actAs) {
506
+ encodeRepeatedStrings(hash, metadata.submitterInfo.actAs);
507
+ }
508
+ else {
509
+ encodeInt32(hash, 0);
510
+ }
511
+ // command_id string
512
+ encodeString(hash, metadata.submitterInfo.commandId || "");
513
+ }
514
+ else {
515
+ // No submitter info - encode empty
516
+ encodeInt32(hash, 0);
517
+ encodeString(hash, "");
518
+ }
519
+ // Encode other metadata fields
520
+ encodeString(hash, metadata.transactionUuid || "");
521
+ encodeInt32(hash, metadata.mediatorGroup || 0);
522
+ encodeString(hash, metadata.synchronizerId || "");
523
+ // Encode optional time fields
524
+ if (metadata.minLedgerEffectiveTime === undefined) {
525
+ // Optional not present
526
+ hash.update(Buffer.from([0x00]));
527
+ }
528
+ else {
529
+ // Optional present
530
+ hash.update(Buffer.from([0x01]));
531
+ encodeInt64(hash, metadata.minLedgerEffectiveTime);
532
+ }
533
+ if (metadata.maxLedgerEffectiveTime === undefined) {
534
+ // Optional not present
535
+ hash.update(Buffer.from([0x00]));
536
+ }
537
+ else {
538
+ // Optional present
539
+ hash.update(Buffer.from([0x01]));
540
+ encodeInt64(hash, metadata.maxLedgerEffectiveTime);
541
+ }
542
+ // Encode submission time and input contracts count
543
+ encodeInt64(hash, metadata.submissionTime || 0);
544
+ encodeInt32(hash, metadata.inputContractsCount || 0);
545
+ // Hash input contracts
546
+ for (const contract of inputContracts) {
547
+ const contractHash = hashInputContract(contract);
548
+ encodeHash(hash, contractHash);
549
+ }
550
+ return hash.digest();
551
+ }
552
+ /**
553
+ * Hash input contract
554
+ * Implements hash_input_contract from canonical_hash.c
555
+ */
556
+ function hashInputContract(contract) {
557
+ const InputContract = root.lookupType("com.daml.ledger.api.v2.interactive.DeviceMetadata.InputContract");
558
+ const contractData = InputContract.decode(contract);
559
+ const hash = crypto_1.default.createHash("sha256");
560
+ // Encode created_at (int64)
561
+ encodeInt64(hash, contractData.createdAt || 0);
562
+ // Hash the contract create node in separate buffer
563
+ const nodeHash = crypto_1.default.createHash("sha256");
564
+ // Encode the create node
565
+ if (contractData.v1) {
566
+ // Simplified encode_create approach
567
+ nodeHash.update(Buffer.from([NODE_ENCODING_VERSION])); // NODE_ENCODING_VERSION = 0x01
568
+ encodeString(nodeHash, contractData.v1.lfVersion || "");
569
+ nodeHash.update(Buffer.from([0x00])); // 0x00 byte
570
+ nodeHash.update(Buffer.from([0x00])); // No seed for input contracts
571
+ encodeHexString(nodeHash, contractData.v1.contractId || "");
572
+ encodeString(nodeHash, contractData.v1.packageName || "");
573
+ if (contractData.v1.templateId) {
574
+ encodeIdentifier(nodeHash, contractData.v1.templateId);
575
+ }
576
+ if (contractData.v1.argument) {
577
+ encodeValue(nodeHash, contractData.v1.argument);
578
+ }
579
+ if (contractData.v1.signatories) {
580
+ encodeRepeatedStrings(nodeHash, contractData.v1.signatories);
581
+ }
582
+ if (contractData.v1.stakeholders) {
583
+ encodeRepeatedStrings(nodeHash, contractData.v1.stakeholders);
584
+ }
585
+ }
586
+ const contractNodeHash = nodeHash.digest();
587
+ encodeHash(hash, contractNodeHash);
588
+ return hash.digest();
589
+ }
590
+ /**
591
+ * Finalize hash
592
+ * Implements finalize_hash from canonical_hash.c
593
+ */
594
+ function finalizeHash(txHash, metadataHash) {
595
+ const hash = crypto_1.default.createHash("sha256");
596
+ // Add purpose (PREPARED_TRANSACTION_HASH_PURPOSE = {0x00, 0x00, 0x00, 0x30})
597
+ hash.update(PREPARED_TRANSACTION_HASH_PURPOSE);
598
+ // Add hashing scheme version (HASHING_SCHEME_VERSION = 0x02)
599
+ hash.update(Buffer.from([HASHING_SCHEME_VERSION]));
600
+ // Add transaction hash (32 bytes)
601
+ hash.update(txHash);
602
+ // Add metadata hash (32 bytes)
603
+ hash.update(metadataHash);
604
+ return hash.digest();
605
+ }
606
+ // Helper functions for canonical encoding
607
+ function encodeString(hash, value) {
608
+ const bytes = Buffer.from(value, "utf8");
609
+ encodeBytes(hash, bytes);
610
+ }
611
+ function encodeInt32(hash, value) {
612
+ const buffer = Buffer.allocUnsafe(4);
613
+ const safeValue = value ?? 0;
614
+ buffer.writeUInt32BE(safeValue, 0);
615
+ hash.update(buffer);
616
+ }
617
+ function encodeInt64(hash, value) {
618
+ const buffer = Buffer.allocUnsafe(8);
619
+ const safeValue = value ?? 0;
620
+ buffer.writeBigUInt64BE(BigInt(safeValue), 0);
621
+ hash.update(buffer);
622
+ }
623
+ function encodeBytes(hash, bytes) {
624
+ encodeInt32(hash, bytes.length);
625
+ hash.update(bytes);
626
+ }
627
+ function encodeRepeatedStrings(hash, strings) {
628
+ encodeInt32(hash, strings.length);
629
+ for (const str of strings) {
630
+ encodeString(hash, str);
631
+ }
632
+ }
633
+ function encodeHash(hash, hashValue) {
634
+ hash.update(hashValue);
635
+ }
636
+ /**
637
+ * Encode DAML value
638
+ * Implements encode_value from canonical_hash.c
639
+ */
640
+ function encodeValue(hash, value) {
641
+ if (!value || typeof value !== "object") {
642
+ // Handle primitive values
643
+ if (value === null || value === undefined) {
644
+ // VALUE_UNIT_TAG = 0x00
645
+ hash.update(Buffer.from([0x00]));
646
+ }
647
+ else if (typeof value === "boolean") {
648
+ // VALUE_BOOL_TAG = 0x01
649
+ hash.update(Buffer.from([0x01]));
650
+ encodeBool(hash, value);
651
+ }
652
+ else if (typeof value === "number" && Number.isInteger(value)) {
653
+ // VALUE_INT64_TAG = 0x02
654
+ hash.update(Buffer.from([0x02]));
655
+ encodeInt64(hash, value);
656
+ }
657
+ else if (typeof value === "string") {
658
+ // VALUE_TEXT_TAG = 0x07
659
+ hash.update(Buffer.from([0x07]));
660
+ encodeString(hash, value);
661
+ }
662
+ return;
663
+ }
664
+ // Handle complex values based on their structure
665
+ if (value.unit !== undefined) {
666
+ // VALUE_UNIT_TAG = 0x00
667
+ hash.update(Buffer.from([0x00]));
668
+ }
669
+ else if (value.bool !== undefined) {
670
+ // VALUE_BOOL_TAG = 0x01
671
+ hash.update(Buffer.from([0x01]));
672
+ encodeBool(hash, value.bool);
673
+ }
674
+ else if (value.int64 !== undefined) {
675
+ // VALUE_INT64_TAG = 0x02
676
+ hash.update(Buffer.from([0x02]));
677
+ encodeInt64(hash, value.int64);
678
+ }
679
+ else if (value.numeric !== undefined) {
680
+ // VALUE_NUMERIC_TAG = 0x03
681
+ hash.update(Buffer.from([0x03]));
682
+ encodeString(hash, value.numeric);
683
+ }
684
+ else if (value.timestamp !== undefined) {
685
+ // VALUE_TIMESTAMP_TAG = 0x04
686
+ hash.update(Buffer.from([0x04]));
687
+ encodeInt64(hash, value.timestamp);
688
+ }
689
+ else if (value.date !== undefined) {
690
+ // VALUE_DATE_TAG = 0x05
691
+ hash.update(Buffer.from([0x05]));
692
+ encodeInt32(hash, value.date);
693
+ }
694
+ else if (value.party !== undefined) {
695
+ // VALUE_PARTY_TAG = 0x06
696
+ hash.update(Buffer.from([0x06]));
697
+ encodeString(hash, value.party);
698
+ }
699
+ else if (value.text !== undefined) {
700
+ // VALUE_TEXT_TAG = 0x07
701
+ hash.update(Buffer.from([0x07]));
702
+ encodeString(hash, value.text);
703
+ }
704
+ else if (value.contractId !== undefined) {
705
+ // VALUE_CONTRACT_ID_TAG = 0x08
706
+ hash.update(Buffer.from([0x08]));
707
+ encodeHexString(hash, value.contractId);
708
+ }
709
+ else if (value.optional !== undefined) {
710
+ // VALUE_OPTIONAL_TAG = 0x09
711
+ hash.update(Buffer.from([0x09]));
712
+ if (value.optional.value === undefined) {
713
+ // Optional not present
714
+ hash.update(Buffer.from([0x00]));
715
+ }
716
+ else {
717
+ // Optional present
718
+ hash.update(Buffer.from([0x01]));
719
+ encodeValue(hash, value.optional.value);
720
+ }
721
+ }
722
+ else if (value.list !== undefined) {
723
+ // VALUE_LIST_TAG = 0x0A
724
+ hash.update(Buffer.from([0x0a]));
725
+ if (value.list.elements) {
726
+ encodeRepeatedValues(hash, value.list.elements);
727
+ }
728
+ else {
729
+ encodeInt32(hash, 0);
730
+ }
731
+ }
732
+ else if (value.textMap !== undefined) {
733
+ // VALUE_TEXT_MAP_TAG = 0x0B
734
+ hash.update(Buffer.from([0x0b]));
735
+ if (value.textMap.entries) {
736
+ encodeRepeatedTextMapEntries(hash, value.textMap.entries);
737
+ }
738
+ else {
739
+ encodeInt32(hash, 0);
740
+ }
741
+ }
742
+ else if (value.record !== undefined) {
743
+ // VALUE_RECORD_TAG = 0x0C
744
+ hash.update(Buffer.from([0x0c]));
745
+ if (value.record.recordId) {
746
+ // Optional present
747
+ hash.update(Buffer.from([0x01]));
748
+ encodeIdentifier(hash, value.record.recordId);
749
+ }
750
+ else {
751
+ // Optional not present
752
+ hash.update(Buffer.from([0x00]));
753
+ }
754
+ if (value.record.fields) {
755
+ encodeRepeatedRecordFields(hash, value.record.fields);
756
+ }
757
+ else {
758
+ encodeInt32(hash, 0);
759
+ }
760
+ }
761
+ else if (value.variant !== undefined) {
762
+ // VALUE_VARIANT_TAG = 0x0D
763
+ hash.update(Buffer.from([0x0d]));
764
+ if (value.variant.variantId) {
765
+ // Optional present
766
+ hash.update(Buffer.from([0x01]));
767
+ encodeIdentifier(hash, value.variant.variantId);
768
+ }
769
+ else {
770
+ // Optional not present
771
+ hash.update(Buffer.from([0x00]));
772
+ }
773
+ encodeString(hash, value.variant.constructor || "");
774
+ if (value.variant.value) {
775
+ encodeValue(hash, value.variant.value);
776
+ }
777
+ }
778
+ else if (value.enum !== undefined) {
779
+ // VALUE_ENUM_TAG = 0x0E
780
+ hash.update(Buffer.from([0x0e]));
781
+ if (value.enum.enumId) {
782
+ // Optional present
783
+ hash.update(Buffer.from([0x01]));
784
+ encodeIdentifier(hash, value.enum.enumId);
785
+ }
786
+ else {
787
+ // Optional not present
788
+ hash.update(Buffer.from([0x00]));
789
+ }
790
+ encodeString(hash, value.enum.constructor || "");
791
+ }
792
+ else if (value.genMap === undefined) {
793
+ // Fallback for unknown value types
794
+ hash.update(Buffer.from([0x00]));
795
+ }
796
+ else {
797
+ // VALUE_GEN_MAP_TAG = 0x0F
798
+ hash.update(Buffer.from([0x0f]));
799
+ if (value.genMap.entries) {
800
+ encodeRepeatedGenMapEntries(hash, value.genMap.entries);
801
+ }
802
+ else {
803
+ encodeInt32(hash, 0);
804
+ }
805
+ }
806
+ }
807
+ /**
808
+ * Encode boolean value
809
+ */
810
+ function encodeBool(hash, value) {
811
+ hash.update(Buffer.from([value ? 0x01 : 0x00]));
812
+ }
813
+ /**
814
+ * Encode hex string (for contract IDs)
815
+ */
816
+ function encodeHexString(hash, hexStr) {
817
+ const cleanHex = hexStr.startsWith("0x") ? hexStr.slice(2) : hexStr;
818
+ const bytes = Buffer.from(cleanHex, "hex");
819
+ encodeBytes(hash, bytes);
820
+ }
821
+ /**
822
+ * Encode repeated values
823
+ */
824
+ function encodeRepeatedValues(hash, values) {
825
+ encodeInt32(hash, values.length);
826
+ for (const value of values) {
827
+ encodeValue(hash, value);
828
+ }
829
+ }
830
+ /**
831
+ * Encode repeated text map entries
832
+ */
833
+ function encodeRepeatedTextMapEntries(hash, entries) {
834
+ encodeInt32(hash, entries.length);
835
+ for (const entry of entries) {
836
+ encodeString(hash, entry.key || "");
837
+ encodeValue(hash, entry.value);
838
+ }
839
+ }
840
+ /**
841
+ * Encode repeated record fields
842
+ */
843
+ function encodeRepeatedRecordFields(hash, fields) {
844
+ encodeInt32(hash, fields.length);
845
+ for (const field of fields) {
846
+ if (field.label) {
847
+ // Optional present
848
+ hash.update(Buffer.from([0x01]));
849
+ encodeString(hash, field.label);
850
+ }
851
+ else {
852
+ // Optional not present
853
+ hash.update(Buffer.from([0x00]));
854
+ }
855
+ encodeValue(hash, field.value);
856
+ }
857
+ }
858
+ /**
859
+ * Encode repeated gen map entries
860
+ */
861
+ function encodeRepeatedGenMapEntries(hash, entries) {
862
+ encodeInt32(hash, entries.length);
863
+ for (const entry of entries) {
864
+ encodeValue(hash, entry.key);
865
+ encodeValue(hash, entry.value);
866
+ }
867
+ }
159
868
  //# sourceMappingURL=cantonTestUtils.js.map