Merge branch 'wallet' into 'main'

Add an HD wallet

See merge request soapbox-pub/ditto!532
This commit is contained in:
Alex Gleason
2024-10-03 22:56:58 +00:00
4 changed files with 95 additions and 0 deletions

51
src/DittoWallet.ts Normal file
View File

@@ -0,0 +1,51 @@
import { HDKey } from '@scure/bip32';
import { Conf } from '@/config.ts';
/**
* HD wallet based on the `DITTO_NSEC`.
* The wallet is used to derive keys for various purposes.
* It is a singleton with static methods, and the keys are cached.
*/
export class DittoWallet {
static #root = HDKey.fromMasterSeed(Conf.seckey);
static #keys = new Map<string, HDKey>();
/** Derive the key cached. */
static derive(path: string): HDKey {
const existing = this.#keys.get(path);
if (existing) {
return existing;
} else {
const key = this.#root.derive(path);
this.#keys.set(path, key);
return key;
}
}
/** Derive the key and return the bytes. */
static deriveKey(path: string): Uint8Array {
const { privateKey } = this.derive(path);
if (!privateKey) {
throw new Error('Private key not available');
}
return privateKey;
}
/** Database encryption key for AES-GCM encryption of database columns. */
static get dbKey(): Uint8Array {
return this.deriveKey(Conf.wallet.dbKeyPath);
}
/** Captcha encryption key for encrypting answer data in AES-GCM. */
static get captchaKey(): Uint8Array {
return this.deriveKey(Conf.wallet.captchaKeyPath);
}
/** VAPID secret key, used for web push notifications. ES256. */
static get vapidKey(): Uint8Array {
return this.deriveKey(Conf.wallet.vapidKeyPath);
}
}

View File

@@ -99,6 +99,25 @@ class Conf {
},
},
};
/**
* BIP-32 derivation paths for different crypto use-cases.
* The `DITTO_NSEC` is used as the seed.
* Keys can be rotated by changing the derviation path.
*/
static wallet = {
/** Private key for AES-GCM encryption in the Postgres database. */
get dbKeyPath(): string {
return Deno.env.get('WALLET_DB_KEY_PATH') || "m/0'/1'";
},
/** Private key for AES-GCM encryption of captcha answer data. */
get captchaKeyPath(): string {
return Deno.env.get('WALLET_CAPTCHA_KEY_PATH') || "m/0'/2'";
},
/** VAPID private key path. */
get vapidKeyPath(): string {
return Deno.env.get('WALLET_VAPID_KEY_PATH') || "m/0'/3'";
},
};
/** Character limit to enforce for posts made through Mastodon API. */
static get postCharLimit(): number {
return Number(Deno.env.get('POST_CHAR_LIMIT') || 5000);