@iacobus/hd
Version:
Hierarchical Deterministic Symmetric Keys.
87 lines (86 loc) • 3.52 kB
JavaScript
;
/**
* @fileoverview Provides functionality for deriving hierarchical deterministic symmetric keys.
* @module
* @author Jacob V. B. Haap <iacobus.xyz>
* @license MIT
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.deriveMaster = deriveMaster;
exports.deriveChild = deriveChild;
exports.deriveNode = deriveNode;
exports.lineage = lineage;
const hkdf_1 = require("@noble/hashes/hkdf");
const utils_js_1 = require("./utils.js");
/**
* deriveMaster derives a new master key from a given hash and secret.
* @example
* const master = deriveMaster(h, secret);
*/
function deriveMaster(h, secret) {
const salt = (0, utils_js_1.calcSalt)(h, secret); // Derive salt from the secret
const ikm = (0, hkdf_1.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 = (0, utils_js_1.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);
*/
function deriveChild(h, master, index) {
index = index >>> 0; // Emulate 32 bit integer
const info1 = (0, utils_js_1.encodeInt)(index); // Context info from encoded index
const salt = (0, utils_js_1.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 = (0, hkdf_1.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 = (0, utils_js_1.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);
*/
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);
*/
function lineage(h, child, master) {
const fp1 = child.fingerprint; // Extract the child fingerprint as fp1
const fp2 = (0, utils_js_1.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
}