@unikvs/checksum
Explains how to use the transformer group that verifies hash values of byte arrays.
Overview
@unikvs/checksum provides transformers that compute the hash of a byte array and compare it against the expected value in variables. Data is left unchanged, and on success the input is returned as is.
The six supported algorithms are as follows.
| Algorithm | Class | Variable key | Hash function |
|---|---|---|---|
| MD5 | ChecksumMd5 |
@unikvs/checksum:md5 |
md5 from @noble/hashes/legacy.js. |
| SHA-1 | ChecksumSha1 |
@unikvs/checksum:sha1 |
sha1 from @noble/hashes/legacy.js. |
| SHA-224 | ChecksumSha224 |
@unikvs/checksum:sha224 |
sha224 from @noble/hashes/sha2.js. |
| SHA-256 | ChecksumSha256 |
@unikvs/checksum:sha256 |
sha256 from @noble/hashes/sha2.js. |
| SHA-384 | ChecksumSha384 |
@unikvs/checksum:sha384 |
sha384 from @noble/hashes/sha2.js. |
| SHA-512 | ChecksumSha512 |
@unikvs/checksum:sha512 |
sha512 from @noble/hashes/sha2.js. |
Install it as follows. The actual hash computation is provided by the peer dependency @noble/hashes.
npm install @unikvs/checksum @noble/hashes@2.0.0pnpm add @unikvs/checksum @noble/hashes@2.0.0yarn add @unikvs/checksum @noble/hashes@2.0.0bun add @unikvs/checksum @noble/hashes@2.0.0nub add @unikvs/checksum @noble/hashes@2.0.0aube add @unikvs/checksum @noble/hashes@2.0.0package.json lists 2.0.0 of @noble/hashes and 2.0.0 of @logtape/logtape as peerDependencies. Install both to suit your environment.
Class List
The base class is the abstract class Checksum. Each subclass is a preset with a fixed name, hash function, and variable key. Direct new is not intended.
| Class | Value passed to the constructor |
|---|---|
Checksum |
Direct instantiation is not intended. |
ChecksumMd5 |
Only options?: ChecksumMd5Options. |
ChecksumSha1 |
Only options?: ChecksumSha1Options. |
ChecksumSha224 |
Only options?: ChecksumSha224Options. |
ChecksumSha256 |
Only options?: ChecksumSha256Options. |
ChecksumSha384 |
Only options?: ChecksumSha384Options. |
ChecksumSha512 |
Only options?: ChecksumSha512Options. |
For example, ChecksumSha256 fixes the name "ChecksumSha256", the sha256 function, and "@unikvs/checksum:sha256" as CHECKSUM_VAR_NAME.
The options type is based on ChecksumOptions. Each subclass type has the same shape.
type ChecksumOptions = {
readonly required?: boolean | undefined;
};
When required is omitted, it is treated as false. The name property holds the fixed name of each subclass. isOpen always returns true.
Usage
Subclasses can be created with options only. Normally use a subclass.
import { ChecksumSha256 } from "@unikvs/checksum";
const optional = new ChecksumSha256();
const required = new ChecksumSha256({ required: true });
Set the expected hash as a hex string under the per-class key in vars. The key is fixed per class. For ChecksumSha256, use ChecksumSha256.CHECKSUM_VAR_NAME, that is, "@unikvs/checksum:sha256".
const vars = {
"@unikvs/checksum:sha256":
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
};
const result = optional.encode({ vars, data });
To embed it as a transformer, register it with the builder.
import { ChecksumSha256 } from "@unikvs/checksum";
const transformer = new ChecksumSha256({ required: true });
builder.appendTransformer(transformer);
The builder above is the config builder from UniKvs.config(). Pass verification variables at runtime as vars.
Verification Mechanism
Both encode and decode perform the same bulk verification. Both getEncodable and getDecodable perform the same stream verification. Writes and reads are not distinguished.
The bulk verification flow is as follows.
- Get the value for the subclass
CHECKSUM_VAR_NAMEfromvars. - If the value is a string, compute the hash, convert it to hex, and compare. On mismatch, throw
ChecksumMismatchError. - If the value is not a string, throw
ChecksumRequiredErrorwhenrequiredistrue. Whenrequiredisfalse, skip computation and return the data as is. - Even on success, return the data as is.
The stream verification flow is as follows.
- Take the expected value from
vars. If it is not a string, throwChecksumRequiredErrorwhenrequiredistrue. Whenrequiredisfalse, return a pass-throughTransformStream. - If it is a string, create an
IHasherwiththis.hash.create(). - On each chunk, call
hasher.updatewhile splitting into 4 GB units, passing split chunks downstream as is. - On completion in
flush, converthasher.digest()to hex and compare. On mismatch, throwChecksumMismatchError.
Errors
This package throws the following three errors.
| Error | Meaning | Handling |
|---|---|---|
ChecksumMismatchError |
The computed hash does not match the expected value. Includes actual and expected in meta. |
Check the expected value or data corruption. |
ChecksumRequiredError |
required is true, yet vars has no string checksum. |
Set a hex string under the key, or set required to false. |
ChecksumInvalidVarNameError |
CHECKSUM_VAR_NAME is not a string. This does not occur with the normal subclasses. |
If you extended the base class, check the static property. |
ChecksumMismatchError is thrown on encode/decode for bulk verification, and in flush for stream verification.
import {
ChecksumMismatchError,
ChecksumRequiredError,
} from "@unikvs/checksum";
try {
transformer.encode({ vars, data });
} catch (error) {
if (error instanceof ChecksumMismatchError) {
console.log(error.meta.actual);
console.log(error.meta.expected);
} else if (error instanceof ChecksumRequiredError) {
console.log("No checksum specified.");
} else {
throw error;
}
}
Subpath Exports
You can use the subpaths in package.json exports.
| Subpath | Contents |
|---|---|
. |
Re-exports all classes and all errors. |
./checksum |
Outputs the base class Checksum and related types. |
./errors |
Outputs ChecksumMismatchError, ChecksumRequiredError, and ChecksumInvalidVarNameError. |
./md5 |
Outputs ChecksumMd5. |
./sha1 |
Outputs ChecksumSha1. |
./sha224 |
Outputs ChecksumSha224. |
./sha256 |
Outputs ChecksumSha256. |
./sha384 |
Outputs ChecksumSha384. |
./sha512 |
Outputs ChecksumSha512. |
import Checksum from "@unikvs/checksum/checksum";import ChecksumMd5 from "@unikvs/checksum/md5";import ChecksumSha1 from "@unikvs/checksum/sha1";import ChecksumSha224 from "@unikvs/checksum/sha224";import ChecksumSha256 from "@unikvs/checksum/sha256";import ChecksumSha384 from "@unikvs/checksum/sha384";import ChecksumSha512 from "@unikvs/checksum/sha512";import {
ChecksumMismatchError,
ChecksumRequiredError,
ChecksumInvalidVarNameError,
} from "@unikvs/checksum/errors";The default setup uses the root specifier.
import { ChecksumSha256 } from "@unikvs/checksum";
Examples
A minimal bulk verification with ChecksumSha256. It uses the SHA-256 hash of "test".
import { ChecksumSha256, ChecksumMismatchError } from "@unikvs/checksum";
const transformer = new ChecksumSha256();
const data = new TextEncoder().encode("test");
const vars = {
[ChecksumSha256.CHECKSUM_VAR_NAME]:
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
};
const verified = transformer.encode({ vars, data });
console.log(verified);
try {
transformer.encode({
vars: { [ChecksumSha256.CHECKSUM_VAR_NAME]: "0".repeat(64) },
data,
});
} catch (error) {
if (error instanceof ChecksumMismatchError) {
console.log(`expected: ${error.meta.expected}`);
console.log(`actual: ${error.meta.actual}`);
} else {
throw error;
}
}