JSON Hash
This Deno1 package contains several utilities to deal JSON data with cryptography. See the below sections for details.
canon.ts: JSON normalizer compliant with JCS
Cryptographic operations like hashing and signing need the data to be expressed in an invariant format so that the operations are reliably repeatable. Even if two messages are similar, only a single byte of difference causes totally different digests or signatures.
However, JSON allows stylistic freedom on their representations. For example, the following representations all encodes the same JSON entity:
{ "foo": "bar", "baz": [1, 2, 3] }
{ "baz": [1, 2, 3], "foo": "bar" }
{"foo":"bar","baz":[1,2,3]}
{ "\u0066\u006f\u006f": "\u0062\u0061\u0072", "\u0062\u0061\u007a": [1, 2, 3] }
In order to hash or sign JSON data, they should be normalized first. That’s why JSON Canonicalization Scheme (JCS) was proposed in RFC 8785. JCS allows only a single representation for each possible JSON entity. For example, the JSON entity the above multiple representations encode can be represented into only the below single form:
{"baz":[1,2,3],"foo":"bar"}
The canon.ts module implements JCS, which completely complies with RFC 8785. Here’s some example:
import { canonicalize } from "https://deno.land/x/json_hash/canon.ts";
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
assertEquals(
canonicalize({ foo: "bar", baz: [1, 2, 3] }),
canonicalize({
"\x62\x61\x7a": [1, 2, 3],
"\x66\x6f\x6f": "\x62\x61\x72",
}),
);
digest.ts: Hashing JSON entities
This module is a thin wrapper around the above canonicalize()
function and
the curated collection of cryptographic hash algorithms:
import { digest } from "https://deno.land/x/json_hash/digest.ts";
import { toHex } from "https://deno.land/x/json_hash/hex.ts";
const data = { foo: "bar", baz: [ 1, 2, 3 ]};
const hash: Uint8Array = await digest("BLAKE3", data);
console.log(toHex(hash));
// dec9c1a89be824103c812b7ace381263335cd6f421a4d0f4dd407a4d3335189c
The digest()
function guarantees that it will return the equivalent hash
digest (in Uint8Array
) for the same hash algorithm and the equivalent JSON
entity. It means you don’t have to care about the order of how object keys
occurs or how characters in string are encoded.
Also note that the above example uses BLAKE3, which is unsupported by Web Crypto API (as of January 2022). Powered by Deno’s std/crypto module, which is baked into Web Assembly, it provides much wider range of hash algorithms than Web Crypto API.
merkle.ts: Dealing JSON with Merkle tree
If you need to track changes of large JSON trees this module could help you.
It provides merkle()
function and MerkleHash<T>
class which enables you to
build Merkle trees.
Unlike digest()
function from digest.ts module, merkle()
function doesn’t
digest the entire JSON data at once, but digests each small entity of the tree
and digests them recursively. For example:
import { MerkleHash, merkle } from "https://deno.land/x/json_hash/merkle.ts";
import { assert } from "https://deno.land/std/testing/asserts.ts";
const a: MerkleHash<"BLAKE3"> = await merkle("BLAKE3", [1, 2, 3]);
const b: MerkleHash<"BLAKE3"> = await merkle("BLAKE3", [
await merkle("BLAKE3", 1),
await merkle("BLAKE3", 2),
await merkle("BLAKE3", 3),
]);
assert(a.equals(b));
const c: MerkleHash<"BLAKE3"> = await merkle(
"BLAKE3",
{ foo: "bar", baz: [1, 2, 3] }
);
const d: MerkleHash<"BLAKE3"> = await merkle(
"BLAKE3",
{ foo: await merkle("BLAKE3", "bar"), baz: a }
);
assert(c.equals(d));
const e = await merkle(c);
assert(e.equals(c));
Note that merkle()
returns MerkleHash<T>
(instead of Uint8Array
),
and also takes a JSON tree mixed with MerkleHash<T>
values. In this module,
such mixed trees are called MerkleTree<T>
.2 The merkle()
function
behaves like below:
merkle()
derivesMerkleHash<T>
fromMerkleTree<T>
.merkle()
makes no distinction between aMerkleTree<T>
andMerkleHash<T>
derived from the same tree.
In other words, the idea this module implements is very simple: hierarchical hash verification of JSON trees. If you still don’t get it well I recommend you to read Diving into Merkle Trees by Pedro Tavares.
mod.ts: Façade
This module re-exports everything in the above files.
See also Deno Doc for the complete API references.
Changelog
See CHANGES.md file.
License
Distributed under LGPL 3.0 or later.
Footnotes
-
It is open to expand its target runtimes including web browsers in particular. ↩
-
It is a compiled-time abstract type, and the type parameter
T
represents the hash algorithm, which corresponds toMerkleHash<T>
’s type parameter. ↩