@kaspacom/kcc20-tx-builder 0.6.3 → 0.7.0

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 (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/RELEASING.md +1 -1
  3. package/artifacts/KCC20SnapshotBootstrap.placeholder.json +14577 -7985
  4. package/artifacts/KCC20SnapshotController.placeholder.json +978 -813
  5. package/dist/abi.d.ts.map +1 -1
  6. package/dist/abi.js +32 -1
  7. package/dist/abi.js.map +1 -1
  8. package/dist/artifacts.d.ts +7 -7
  9. package/dist/artifacts.js +7 -7
  10. package/dist/cjs/abi.js +32 -1
  11. package/dist/cjs/abi.js.map +1 -1
  12. package/dist/cjs/artifacts.d.ts +7 -7
  13. package/dist/cjs/artifacts.js +7 -7
  14. package/dist/cjs/snapshot-deployment.js +2 -1
  15. package/dist/cjs/snapshot-deployment.js.map +1 -1
  16. package/dist/cjs/snapshot-operation.js +17 -1
  17. package/dist/cjs/snapshot-operation.js.map +1 -1
  18. package/dist/cjs/snapshot-signerless.js +26 -6
  19. package/dist/cjs/snapshot-signerless.js.map +1 -1
  20. package/dist/snapshot-deployment.d.ts.map +1 -1
  21. package/dist/snapshot-deployment.js +2 -1
  22. package/dist/snapshot-deployment.js.map +1 -1
  23. package/dist/snapshot-operation.d.ts.map +1 -1
  24. package/dist/snapshot-operation.js +17 -1
  25. package/dist/snapshot-operation.js.map +1 -1
  26. package/dist/snapshot-signerless.d.ts.map +1 -1
  27. package/dist/snapshot-signerless.js +26 -6
  28. package/dist/snapshot-signerless.js.map +1 -1
  29. package/docs/COMPATIBILITY.json +26 -14
  30. package/package.json +3 -1
  31. package/src/abi.ts +46 -6
  32. package/src/artifacts.ts +7 -7
  33. package/src/snapshot-deployment.ts +2 -1
  34. package/src/snapshot-operation.ts +17 -1
  35. package/src/snapshot-signerless.ts +30 -6
  36. package/artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json +0 -101678
  37. package/artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json +0 -46548
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "schema": "kcc20-example-compatibility/v1",
3
- "packageVersion": "0.5.0-lastcall.0",
3
+ "packageVersion": "0.7.0",
4
4
  "contractsRepository": "https://github.com/KASPACOM/kaspa-covenants",
5
- "contractsCommit": null,
5
+ "contractsCommit": "5deb1769e69142595576ef7c573af9f433a48a3c",
6
6
  "compiler": {
7
7
  "repository": "https://github.com/kaspanet/silverscript",
8
8
  "tag": "v1.0.0",
9
9
  "commit": "3ed973335b59269293564805cc2c58a14595ec03",
10
- "tree": "2a30f522768d9b4cae6fe30014326c689a25bd41",
11
- "build": "verified-source"
10
+ "tree": "2a30f522768d9b4cae6fe30014326c689a25bd41"
12
11
  },
13
12
  "wasm": {
14
13
  "version": "1.1.1-toc.1",
@@ -31,45 +30,58 @@
31
30
  "scriptSha256": "ef9fe81ff7aa2dc2df118d8d7cc126aca6f2f9c181b9c2d15727a538b23c6f71",
32
31
  "sourceSha256": "b4216aa3db10f486b2032fa52eb93ec1768c65ea841415bd2fffb85bdc652bbb",
33
32
  "constructorArgsSha256": "a3f32dc91fee760df98d0238d60f65a6d1324bce5ab0740e31f035144a90cc61",
34
- "abiSha256": "bc5c24dc0e7ccfdd734981bfa81c806672ae90059fd075e93c9254671b25f155"
33
+ "abiSha256": "bc5c24dc0e7ccfdd734981bfa81c806672ae90059fd075e93c9254671b25f155",
34
+ "compilerBuild": "verified-source"
35
35
  },
36
36
  "KCC20Wrapper.placeholder.json": {
37
37
  "fileSha256": "2461e39a285d1f135f4891659cb78b3b6e5d2b28b32e153b61e2e336eab02115",
38
38
  "scriptSha256": "23cbd5473860cab4e63890d24ae1bd6732fce5beaa34f594dad01e845e3e9b9f",
39
39
  "sourceSha256": "030708f5b9c9e5cf7d1c4c9b9d699a681d11fa197635c3225f321dadb92ae37a",
40
40
  "constructorArgsSha256": "085f7457aa6456ef49ea3b3181739943d17b498d82fc2c8b6ab61a6f51f95573",
41
- "abiSha256": "d4f2fdfd16cd0cd6b42e863de90a8bae90e5b44d9495b85a32f287f88750df2c"
41
+ "abiSha256": "d4f2fdfd16cd0cd6b42e863de90a8bae90e5b44d9495b85a32f287f88750df2c",
42
+ "compilerBuild": "verified-source"
42
43
  },
43
44
  "KCC20.placeholder.json": {
44
45
  "fileSha256": "a45d06e8351421dac1ef73f3595c6f34068e4fa1e13d3f8fa1c88be518c6e56f",
45
46
  "scriptSha256": "dd9cb334cd9e9220b15644782f271ef3ecdba34fd0762010e3d6bc18176837ae",
46
47
  "sourceSha256": "9ae9b13ebc56cd85f072ea6cc78d3a5e5f050b66dda62677e223bc9c29ec231e",
47
48
  "constructorArgsSha256": "c86edb17c8e7a338bf35b519caa3c4f955bcf5ab73476f9c5a3baa34bb73926a",
48
- "abiSha256": "c2a169d67682b49ce3501f288e73f18ca96c815fd47db8e614d75b20f3678802"
49
+ "abiSha256": "c2a169d67682b49ce3501f288e73f18ca96c815fd47db8e614d75b20f3678802",
50
+ "compilerBuild": "verified-source"
49
51
  },
50
52
  "KCC20FeeTicket.placeholder.json": {
51
53
  "fileSha256": "b104ff07adcfe58a1db134bb44782fa59453dadb7716e0a831447f910efb9526",
52
54
  "scriptSha256": "c0e95cb8e5226462674647f86d5760626b5c38b7bd97a6e2ec9ba61fdef8941f",
53
55
  "sourceSha256": "980294c10d9af5d79218a5d81100df30da7ca80e3bf5c284adac3c8803b51d49",
54
56
  "constructorArgsSha256": "66b2ca443a59de6854f29bf2e8aa9c11a3074707070d653310432ac1c31ec81f",
55
- "abiSha256": "8bb5f66f91bc296380eaa6b5bcc7d5045b59c2b4783e8b1bcf394a7e1395a420"
57
+ "abiSha256": "8bb5f66f91bc296380eaa6b5bcc7d5045b59c2b4783e8b1bcf394a7e1395a420",
58
+ "compilerBuild": "verified-source"
56
59
  },
57
60
  "KCC20Vesting.placeholder.json": {
58
61
  "fileSha256": "b917c9dcd05237fdedf76ac6b0df5791d5953aca1d057909c5fac075fb17ca43",
59
62
  "scriptSha256": "e32d9f5bde124526475a61075ba46eef41955d4662b1422713860cfbd7a59661",
60
63
  "sourceSha256": "6f32a53c6212bd985c2fad48b3dc66ee0daffd5ac411e5676268d22eccbfa5b3",
61
64
  "constructorArgsSha256": "b1adf7afaf5daa150e7bb54e8ddfdc63fae5a23a9b30ad528435ce6346205b40",
62
- "abiSha256": "b58ee502376aaf59e0b2d663012b2964a3ae7b30025b2d830e46ea18993e7dc7"
65
+ "abiSha256": "b58ee502376aaf59e0b2d663012b2964a3ae7b30025b2d830e46ea18993e7dc7",
66
+ "compilerBuild": "verified-source"
63
67
  },
64
68
  "KCC20SnapshotBootstrap.placeholder.json": {
65
- "fileSha256": "5f0caea9818f5bd6110023a7f089a0d26f87d3fd0901cb52e24a32bd1853b302",
66
- "scriptSha256": "3d64211a2b3c5ac24009b9ce59d4e8ecb8f2114dbf0c43b5a060b2ed91704e02"
69
+ "fileSha256": "daddabe50454e1c6c187c0b027cc8887f79f0f0086a14f26c6a2a56054991ebb",
70
+ "scriptSha256": "a22b93b0d8cd04096d83d25bbb6c38f0c07ac025b70226677db484fa93fe09ef",
71
+ "sourceSha256": "6e6e6a8a5d12b68d0bf5a67c4e9a727dbed4605c62896f869c73f1f3e66c0fa8",
72
+ "constructorArgsSha256": "ccfd363002a8efb2ad8d8c7a3e44be9ad54078c292d294098ec9262326d040ce",
73
+ "abiSha256": "7cf08a871d29a3fddc988000c6dd0bb01140bcacdf931254c341248e0baa8a3b",
74
+ "compilerBuild": "verified-release"
67
75
  },
68
76
  "KCC20SnapshotController.placeholder.json": {
69
- "fileSha256": "8c5d2056bb0a445ffc1a6967238fac46378372b5f357524c6795201699bf2b2f",
70
- "scriptSha256": "fdc2e396a66a1f708522d68f879ec67d71bd0070c7417b1e7f5dd3a6c8cfda88"
77
+ "fileSha256": "a73727cfe03108097e1226566cff9dbbbf42a5dc8526aac3196817ffc642212f",
78
+ "scriptSha256": "8a73c57e309860ff799a71ca9352afa84f71638061cfa427cc5e8d36d1b8b025",
79
+ "sourceSha256": "1d8697fd967af3278e9fceae5a98ef442eaf29037822c963baf4c9d252288edd",
80
+ "constructorArgsSha256": "1d6b2ac27fa5fea7e6c419543d4968f5b623b3139e880984d2fa5a116a453635",
81
+ "abiSha256": "f4e503b480fd8c63194fcbe987e3818998bf4c387c360cb0371bebf542c60050",
82
+ "compilerBuild": "verified-release"
71
83
  }
72
84
  },
73
- "contractsSourceStatus": "unreviewed Last Call candidate; source commit must be pinned before publication",
85
+ "contractsSourceStatus": "reviewed source and generated inputs pinned to contractsCommit",
74
86
  "standardRevision": "3fbec524abfbc20e87652eb938db218f8c17db17"
75
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaspacom/kcc20-tx-builder",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Runtime-independent KCC20 covenant transaction and PSKT construction primitives.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "KaspaCom",
@@ -42,6 +42,8 @@
42
42
  "default": "./dist/cjs/index.js"
43
43
  }
44
44
  },
45
+ "./artifacts/kcc20-snapshot-bootstrap/KCC20SnapshotBootstrap.placeholder.json": "./artifacts/KCC20SnapshotBootstrap.placeholder.json",
46
+ "./artifacts/kcc20-snapshot-controller/KCC20SnapshotController.placeholder.json": "./artifacts/KCC20SnapshotController.placeholder.json",
45
47
  "./artifacts/*": "./artifacts/*",
46
48
  "./package.json": "./package.json",
47
49
  "./*": {
package/src/abi.ts CHANGED
@@ -207,15 +207,41 @@ export function mintExtensionCommitment(extension) {
207
207
 
208
208
  export function holderExtensionCommitment(extension) {
209
209
  const { kind = 1 } = extension;
210
+ const kindValue = requireByte(kind, "kind");
211
+ const maxSupply = BigInt(extension.maxSupply);
212
+ const fixedPrefix = [
213
+ Uint8Array.of(kindValue),
214
+ requireBytes(extension.creator, 32, "creator"),
215
+ requireBytes(extension.ticker, 32, "ticker"),
216
+ requireBytes(extension.name, 32, "name"),
217
+ signedI64Payload(BigInt(extension.displayScale)),
218
+ ];
219
+ if (kindValue === 2) {
220
+ if (Number(extension.mintPolicy) !== 0) {
221
+ throw new Error("wide holder extension is fixed-supply only");
222
+ }
223
+ const { high, low } = unsignedWideAmountLimbs(maxSupply, "maxSupply");
224
+ return blake3(
225
+ concatBytes(
226
+ new TextEncoder().encode("KASPACOM/KCC20/HOLDER/V2"),
227
+ ...fixedPrefix,
228
+ signedI64Payload(high),
229
+ signedI64Payload(low),
230
+ signedI64Payload(BigInt(extension.mintLaneCount ?? 1)),
231
+ Uint8Array.of(requireByte(extension.mintPolicy, "mintPolicy")),
232
+ signedI64Payload(BigInt(extension.mintPriceSompi)),
233
+ requireBytes(extension.treasury, 32, "treasury"),
234
+ requireBytes(extension.protocolFeeRecipient, 32, "protocolFeeRecipient"),
235
+ signedI64Payload(BigInt(extension.protocolFeeBps)),
236
+ ),
237
+ );
238
+ }
239
+ if (kindValue !== 1) throw new Error("unsupported holder extension kind");
210
240
  return blake3(
211
241
  concatBytes(
212
242
  new TextEncoder().encode("KASPACOM/KCC20/HOLDER/V1"),
213
- Uint8Array.of(requireByte(kind, "kind")),
214
- requireBytes(extension.creator, 32, "creator"),
215
- requireBytes(extension.ticker, 32, "ticker"),
216
- requireBytes(extension.name, 32, "name"),
217
- signedI64Payload(BigInt(extension.displayScale)),
218
- signedI64Payload(BigInt(extension.maxSupply)),
243
+ ...fixedPrefix,
244
+ signedI64Payload(maxSupply),
219
245
  signedI64Payload(BigInt(extension.mintLaneCount ?? 1)),
220
246
  Uint8Array.of(requireByte(extension.mintPolicy, "mintPolicy")),
221
247
  signedI64Payload(BigInt(extension.mintPriceSompi)),
@@ -226,6 +252,20 @@ export function holderExtensionCommitment(extension) {
226
252
  );
227
253
  }
228
254
 
255
+ function unsignedWideAmountLimbs(value, label) {
256
+ if (typeof value === "number" && !Number.isSafeInteger(value)) {
257
+ throw new Error(`${label} must not be an unsafe JavaScript number`);
258
+ }
259
+ const amount = BigInt(value);
260
+ const base = 1n << 63n;
261
+ const high = amount / base;
262
+ const low = amount % base;
263
+ if (amount <= 0n || high > base - 1n) {
264
+ throw new Error(`${label} is outside the unsigned two-limb range`);
265
+ }
266
+ return { high, low };
267
+ }
268
+
229
269
  export function buildKcc20ScriptForState(artifact, state) {
230
270
  const normalized = normalizeSilverScriptArtifact(artifact);
231
271
  const script = Uint8Array.from(normalized.script);
package/src/artifacts.ts CHANGED
@@ -8,9 +8,9 @@ export const KCC20_CURRENT_NATIVE_ARTIFACT_KEY =
8
8
 
9
9
  export const KCC20_ARTIFACT_SCRIPT_SHA256 = Object.freeze({
10
10
  "KCC20SnapshotBootstrap.placeholder.json":
11
- "3d64211a2b3c5ac24009b9ce59d4e8ecb8f2114dbf0c43b5a060b2ed91704e02",
11
+ "a22b93b0d8cd04096d83d25bbb6c38f0c07ac025b70226677db484fa93fe09ef",
12
12
  "KCC20SnapshotController.placeholder.json":
13
- "fdc2e396a66a1f708522d68f879ec67d71bd0070c7417b1e7f5dd3a6c8cfda88",
13
+ "8a73c57e309860ff799a71ca9352afa84f71638061cfa427cc5e8d36d1b8b025",
14
14
  [KCC20_CURRENT_NATIVE_ARTIFACT_KEY]:
15
15
  "dd9cb334cd9e9220b15644782f271ef3ecdba34fd0762010e3d6bc18176837ae",
16
16
  "KCC20FeeTicket.placeholder.json":
@@ -33,17 +33,17 @@ export const KCC20_SNAPSHOT_ARTIFACT_PINS = Object.freeze({
33
33
  key: "KCC20SnapshotBootstrap.placeholder.json",
34
34
  contractName: "KCC20SnapshotBootstrap",
35
35
  artifactSha256:
36
- "5f0caea9818f5bd6110023a7f089a0d26f87d3fd0901cb52e24a32bd1853b302",
37
- scriptLength: 33718,
36
+ "daddabe50454e1c6c187c0b027cc8887f79f0f0086a14f26c6a2a56054991ebb",
37
+ scriptLength: 35913,
38
38
  stateStart: 1,
39
- stateLength: 405,
39
+ stateLength: 414,
40
40
  }),
41
41
  controller: Object.freeze({
42
42
  key: "KCC20SnapshotController.placeholder.json",
43
43
  contractName: "KCC20SnapshotController",
44
44
  artifactSha256:
45
- "8c5d2056bb0a445ffc1a6967238fac46378372b5f357524c6795201699bf2b2f",
46
- scriptLength: 15297,
45
+ "a73727cfe03108097e1226566cff9dbbbf42a5dc8526aac3196817ffc642212f",
46
+ scriptLength: 15350,
47
47
  stateStart: 1,
48
48
  stateLength: 432,
49
49
  }),
@@ -49,7 +49,8 @@ export function prepareSnapshotDeployment(kw: any, definition: SnapshotDeploymen
49
49
  throw new Error("shard allocations do not equal fixed token supply");
50
50
  }
51
51
  const extension = {
52
- kind: 1, creator: definition.creator, ticker: bytesToHex(asciiToBytes32(definition.ticker)),
52
+ kind: BigInt(definition.totalSupply) > 9_223_372_036_854_775_807n ? 2 : 1,
53
+ creator: definition.creator, ticker: bytesToHex(asciiToBytes32(definition.ticker)),
53
54
  name: bytesToHex(asciiToBytes32(definition.ticker)), displayScale: definition.displayScale,
54
55
  maxSupply: definition.totalSupply, mintLaneCount: 1, mintPolicy: 0, mintPriceSompi: "0",
55
56
  treasury: definition.creator, protocolFeeRecipient: definition.creator, protocolFeeBps: "0",
@@ -618,7 +618,7 @@ export function createKcc20SnapshotPsktBuilder(
618
618
  params.holderExtensionCommitment,
619
619
  "holder extension commitment",
620
620
  ),
621
- totalSupply: positiveI64(params.totalSupply, "total supply"),
621
+ totalSupply: positiveWideAmount(params.totalSupply, "total supply"),
622
622
  claimNotBeforeDaaScore: positiveI64(
623
623
  params.claimNotBeforeDaaScore,
624
624
  "claim-not-before DAA score",
@@ -1830,6 +1830,22 @@ export function createKcc20SnapshotPsktBuilder(
1830
1830
  return amount;
1831
1831
  }
1832
1832
 
1833
+ function positiveWideAmount(value, label) {
1834
+ let amount;
1835
+ try {
1836
+ if (typeof value === "number" && !Number.isSafeInteger(value)) {
1837
+ throw new Error("unsafe integer");
1838
+ }
1839
+ amount = BigInt(value);
1840
+ } catch {
1841
+ throw new Error(`${label} must be a positive unsigned two-limb integer`);
1842
+ }
1843
+ if (amount <= 0n || amount > (1n << 126n) - 1n) {
1844
+ throw new Error(`${label} must be a positive unsigned two-limb integer`);
1845
+ }
1846
+ return amount;
1847
+ }
1848
+
1833
1849
  function nonnegativeI64(value, label) {
1834
1850
  let amount;
1835
1851
  try {
@@ -37,9 +37,9 @@ export function buildSnapshotTokenMetadata({
37
37
  protocolFeeRecipient,
38
38
  protocolFeeBps = 0n,
39
39
  }) {
40
- const amount = positiveI64(totalAllocation, "total allocation");
40
+ const amount = positiveWideAmount(totalAllocation, "total allocation");
41
41
  const extension = {
42
- kind: 1,
42
+ kind: amount > MAX_I64 ? 2 : 1,
43
43
  creator: bytes32(creator, "creator"),
44
44
  ticker: bytes32(ticker, "ticker"),
45
45
  name: bytes32(name, "name"),
@@ -164,13 +164,15 @@ export function snapshotStartPlanCommitment({
164
164
  shards,
165
165
  }) {
166
166
  const normalized = normalizeStartShards(shards, false);
167
+ const supply = wideAmountLimbs(totalSupply, "total supply");
167
168
  return hash256(
168
169
  Uint8Array.of(0x22),
169
170
  bytes32(migrationCommitment, "migration commitment"),
170
171
  bytes32(manifestCommitment, "manifest commitment"),
171
172
  bytes32(extensionCommitment, "holder extension commitment"),
172
173
  nonzeroBytes32(tokenId, "token id"),
173
- leI64(positiveI64(totalSupply, "total supply")),
174
+ leI64(supply.high),
175
+ leI64(supply.low),
174
176
  leI64(claimActivation(claimNotBeforeDaaScore).lockTime),
175
177
  leI64(normalized.length),
176
178
  ...normalized.flatMap(shardPlanParts),
@@ -202,7 +204,7 @@ export function buildBootstrapGenesisPlan({
202
204
  "holder extension commitment",
203
205
  );
204
206
  const token = nonzeroBytes32(expectedTokenId, "expected token id");
205
- const supply = positiveI64(totalSupply, "total supply");
207
+ const supply = positiveWideAmount(totalSupply, "total supply");
206
208
  const activation = claimActivation(claimNotBeforeDaaScore).lockTime;
207
209
  const bootstrapTemplate = templateParts(bootstrapArtifact);
208
210
  validateStartShards(normalized, {
@@ -816,7 +818,8 @@ export function encodeSnapshotBootstrapState(state) {
816
818
  push(value.migrationId),
817
819
  push(value.manifestCommitment),
818
820
  push(value.holderExtensionCommitment),
819
- push(leI64(value.totalSupply)),
821
+ push(leI64(value.totalSupplyHigh)),
822
+ push(leI64(value.totalSupplyLow)),
820
823
  push(leI64(value.claimNotBeforeDaaScore)),
821
824
  push(value.expectedTokenId),
822
825
  push(value.kcc20TemplateHash),
@@ -1159,7 +1162,7 @@ function normalizeBootstrapState(state) {
1159
1162
  state.holderExtensionCommitment,
1160
1163
  "holder extension commitment",
1161
1164
  ),
1162
- totalSupply: positiveI64(state.totalSupply, "total supply"),
1165
+ totalSupply: positiveWideAmount(state.totalSupply, "total supply"),
1163
1166
  claimNotBeforeDaaScore: positiveI64(
1164
1167
  state.claimNotBeforeDaaScore,
1165
1168
  "claim-not-before DAA score",
@@ -1217,6 +1220,9 @@ function normalizeBootstrapState(state) {
1217
1220
  "start plan commitment",
1218
1221
  ),
1219
1222
  };
1223
+ const supplyLimbs = wideAmountLimbs(value.totalSupply, "total supply");
1224
+ value.totalSupplyHigh = supplyLimbs.high;
1225
+ value.totalSupplyLow = supplyLimbs.low;
1220
1226
  if (
1221
1227
  value.claimNotBeforeDaaScore >= LOCK_TIME_THRESHOLD ||
1222
1228
  value.shardCount > BigInt(MAX_SNAPSHOT_SHARDS) ||
@@ -1503,6 +1509,24 @@ function positiveI64(value, label) {
1503
1509
  return number;
1504
1510
  }
1505
1511
 
1512
+ function positiveWideAmount(value, label) {
1513
+ if (typeof value === "number" && !Number.isSafeInteger(value)) {
1514
+ throw new Error(`${label} must not be an unsafe JavaScript number`);
1515
+ }
1516
+ const number = BigInt(value);
1517
+ const maximum = (1n << 126n) - 1n;
1518
+ if (number <= 0n || number > maximum) {
1519
+ throw new Error(`${label} must be a positive unsigned two-limb integer`);
1520
+ }
1521
+ return number;
1522
+ }
1523
+
1524
+ function wideAmountLimbs(value, label) {
1525
+ const amount = positiveWideAmount(value, label);
1526
+ const base = 1n << 63n;
1527
+ return { high: amount / base, low: amount % base };
1528
+ }
1529
+
1506
1530
  function equalBytes(a, b) {
1507
1531
  const left = Uint8Array.from(a ?? []);
1508
1532
  const right = Uint8Array.from(b ?? []);