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