@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,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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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 (
|
|
141
|
+
getAddress: async (_derivationPath) => ({
|
|
142
142
|
address: `canton_test_${keyPair.fingerprint.slice(-8)}`,
|
|
143
143
|
publicKey: keyPair.publicKeyHex,
|
|
144
144
|
}),
|
|
145
|
-
signTransaction: async (
|
|
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
|
-
|
|
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
|