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