@iacobus/hd
Version:
Hierarchical Deterministic Symmetric Keys.
81 lines (80 loc) • 3.24 kB
JavaScript
/**
* @fileoverview Provides functionality for deriving hierarchical deterministic symmetric keys.
* @module
* @author Jacob V. B. Haap <iacobus.xyz>
* @license MIT
*/
import { hkdf } from "@noble/hashes/hkdf";
import { fingerprint, calcSalt, encodeInt } from "./utils.js";
/**
* deriveMaster derives a new master key from a given hash and secret.
* @example
* const master = deriveMaster(h, secret);
*/
export function deriveMaster(h, secret) {
const salt = calcSalt(h, secret); // Derive salt from the secret
const ikm = hkdf(h, secret, salt, "MASTER", 64); // Derive ikm from bytes
const master = ikm.slice(0, 32); // First 32 bytes as the key
const code = ikm.slice(32, 64); // Last 32 bytes as the chain code
const fp = fingerprint(h, secret, master); // Derive a fingerprint for the master key
return {
key: master,
code: code,
depth: 0,
fingerprint: fp
};
}
/**
* deriveChild derives a new child key from a given hash, master key, and index.
* @example
* const child = deriveChild(h, master, 42);
*/
export function deriveChild(h, master, index) {
index = index >>> 0; // Emulate 32 bit integer
const info1 = encodeInt(index); // Context info from encoded index
const salt = calcSalt(h, master.code, info1); // Derive salt from the master code
const info2 = "CHILD" + index.toString(); // Construct info for HKDF form CHILD + index string
const ikm = hkdf(h, master.code, salt, info2, 64); // Derive ikm from master chain code
const child = ikm.slice(0, 32); // First 32 bytes as the key
const code = ikm.slice(32, 64); // Last 32 bytes as the chain code
const fp = fingerprint(h, master.key, child); // Derive a fingerprint for the child key
return {
key: child,
code: code,
depth: master.depth + 1,
fingerprint: fp
};
}
/**
* deriveNode derives a new key at a node in a hierarchy descending from a master key, from
* a given hash, master key, and derivation path.
* @example
* const node = deriveNode(h, master, path);
*/
export function deriveNode(h, master, path) {
let key = deriveChild(h, master, path[0]); // Initialize key with first index from the path
for (let i = 1; i < path.length; i++) {
const index = path[i]; // Get the current index
key = deriveChild(h, key, index); // Derive a child of key for the current index
}
return key;
}
/**
* lineage checks if a key is the direct child of a master key, from a given hash,
* child key, and master key.
* @example
* const related = lineage(h, child, master);
*/
export function lineage(h, child, master) {
const fp1 = child.fingerprint; // Extract the child fingerprint as fp1
const fp2 = fingerprint(h, master.key, child.key); // Derive fp2 from the master and child keys
if (fp1.length !== 16 || fp2.length !== 16) {
throw new RangeError(`fingerprints for lineage verification must be 16 bytes each`);
}
// Complete a constant-time comparison between the 16 bytes of each fingerprint
let result = 0;
for (let i = 0; i < 16; i++) {
result |= fp1[i] ^ fp2[i];
}
return result === 0; // Return a boolean result of the byte comparison
}