@holochain/hc-seed-bundle

@holochain/hc-seed-bundle

License: Apache-2.0

TypeScript SeedBundle parsing and generation library.

Links

Rationale

  • Applications like Holochain have different requirements than classic blockchain system in terms of key management. Namely there is no need for read-only or hardened wallets (Holochain handles these concepts through capabilities and membranes).
  • Applications like Holochain still have need of hierarchy and determinism in key (or in this case seed) derivation.
  • Since we're using libsodium for hashing, signature, and encryption algorithms, let's use it for derivation as well.
  • To be psychologically compatible with the Bitcoin "HD Wallet" spec, we will do away with the "context" part of sodium KDF by always setting it to b"SeedBndl" and focusing on the subkey_id and can declare a chain of subsequent derivations of a 32 byte seed in the form m/68/1/65/8 where we apply subkey_ids 68, 1, 65, then 8 in turn.

Derivation Usage

import { UnlockedSeedBundle, seedBundleReady } from "@holochain/hc-seed-bundle";

// await library functions ready to call
await seedBundleReady;

// generate a new pure entropy master seed
const master = UnlockedSeedBundle.newRandom({
bundleType: "master",
});

// derive a device root seed from the master
const deviceRoot = master.derive(68, {
bundleType: "deviceRoot",
});

// clear our secrets
master.zero();
deviceRoot.zero();

Locking (encrypting) a SeedBundle

import { UnlockedSeedBundle, SeedCipherPwHash, seedBundleReady, parseSecret } from "@holochain/hc-seed-bundle";

// await library functions ready to call
await seedBundleReady;

// generate a new pure entropy master seed
const master = UnlockedSeedBundle.newRandom({
bundleType: "master",
});

// we need the passphrase as a Uint8Array
const pw = new TextEncoder().encode("test-passphrase");
const encodedBytes = master.lock([new SeedCipherPwHash(parseSecret(pw), "minimum")]);

// -- if you want to regenerate for (decrypting) below:
// console.log(Buffer.from(encodedBytes).toString('base64'))

// clear our secrets
master.zero();

Locking (encrypting) a SeedBundle with Security Questions

import {
UnlockedSeedBundle,
SeedCipherSecurityQuestions,
seedBundleReady,
parseSecret,
} from "@holochain/hc-seed-bundle";

// await library functions ready to call
await seedBundleReady;

// generate a new pure entropy master seed
const master = UnlockedSeedBundle.newRandom({
bundleType: "master",
});

// we need the answers as a Uint8Arrays
const pw = (pw: string) => parseSecret(new TextEncoder().encode(pw));

const encodedBytes = master.lock([
new SeedCipherSecurityQuestions(
["Favorite Color?", "Favorite Hair?", "Favorite Food?"],
[pw("blue"), pw("big"), pw("begal")],
"minimum",
),
]);

// clear our secrets
master.zero();

Unlocking (decrypting) a SeedBundle

// await library functions ready to call
import { UnlockedSeedBundle, LockedSeedCipherPwHash, seedBundleReady, parseSecret } from "@holochain/hc-seed-bundle";

await seedBundleReady;

const encodedBytes = Buffer.from(
"k6VoY3NiMJGWonB3xBD5Ov1Vas4XnV1XPsf8ddCqzSAAAcQYkO36tg8NHoec02I7KtxfX+ZnmBzIz+SoxDFDNfr4/9811ugf18FiRSywOyVagFHIRTyrfV3jZLRt6W0r7WuepaQLjlFu4jgVMrd2xBOBqmJ1bmRsZVR5cGWmbWFzdGVy",
"base64",
);
// decode the SeedCiphers that will let us unlock this bundle
const cipherList = UnlockedSeedBundle.fromLocked(encodedBytes);
// the demo is encrypted with PwHash
if (!(cipherList[0] instanceof LockedSeedCipherPwHash)) {
throw new Error("Expecting PwHash");
}

// unlock with the passphrase
const pw = new TextEncoder().encode("test-passphrase");
const master = cipherList[0].unlock(parseSecret(pw));

// clear our secrets
master.zero();

Development

To install dependencies

bun install

To run:

bun run index.ts

This project was created using bun init in bun v1.1.8. Bun is a fast all-in-one JavaScript runtime.