All files / src/wallet-sdk/wallet derive.ts

87.09% Statements 27/31
0% Branches 0/4
100% Functions 4/4
96% Lines 24/25

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 702x 2x 2x 2x 2x                                 2x 3x 3x 3x                     2x   1x 1x 1x   1x                 2x 3x 3x 3x     2x 3x   3x 3x 3x   3x            
import { HDKey } from '@scure/bip32';
import { bytesToHex, utf8ToBytes } from 'micro-stacks/common';
import { hashSha256 } from 'micro-stacks/crypto-sha';
import { DATA_DERIVATION_PATH, WALLET_CONFIG_PATH } from '../constants';
import { isHardened } from '../utils';
import type { WalletKeys } from '../types';
 
/**
 * Derive the `configPrivateKey` for a wallet.
 *
 * This key is derived from the path `m/44/5757'/0'/1`, using `1` for change option,
 * following the bip44 recommendation for keys relating to non-public data.
 *
 * > m / purpose' / coin_type' / account' / change / address_index
 
 *
 * This key is used to encrypt configuration data related to a wallet, so the user's
 * configuration can be synced across wallets.
 *
 * @param rootNode A keychain that was created using the wallet mnemonic phrase
 */
export const deriveConfigPrivateKey = (rootNode: HDKey): string => {
  const derivedConfigKey = rootNode.derive(WALLET_CONFIG_PATH).privateKey;
  Iif (!derivedConfigKey) throw new TypeError('Unable to derive config key for wallet identities');
  return bytesToHex(derivedConfigKey);
};
 
/**
 * Before modern Stacks Wallets, previous authenticators used a different format
 * and path for the config file.
 *
 * The path for this key is `m/45'`
 * @param rootKey A base64 encoded HDKeychain (BIP32) that was created using the wallet mnemonic phrase
 * @return privateKey Hex encoded legacy private key
 */
export const deriveLegacyConfigPrivateKey = (rootKey: string): string => {
  const rootNode = HDKey.fromExtendedKey(rootKey);
  const legacyNode = rootNode.deriveChild(isHardened(45));
  const derivedLegacyPrivateKey = legacyNode.privateKey;
  Iif (!derivedLegacyPrivateKey)
    throw new TypeError('Unable to derive config key for wallet identities');
  return bytesToHex(derivedLegacyPrivateKey);
};
 
/**
 * Generate a salt, which is used for generating an app-specific private key
 * the salt is the hex encoded public key converted to bytes, SHA256 hashed and then hex encoded
 * @param rootNode A keychain that was created using the wallet mnemonic phrase
 * @return salt A hex encoded hash
 */
export function deriveSalt(rootNode: HDKey): string {
  const publicKey = rootNode.derive(DATA_DERIVATION_PATH).publicKey;
  Iif (!publicKey) throw new TypeError('Unable to derive public key from data derivation path');
  return bytesToHex(hashSha256(utf8ToBytes(bytesToHex(publicKey))));
}
 
export function deriveWalletKeys(rootNode: HDKey): WalletKeys {
  Iif (!rootNode.privateKey) throw Error('no private key');
 
  const salt = deriveSalt(rootNode);
  const rootKey = rootNode.privateExtendedKey;
  const configPrivateKey = deriveConfigPrivateKey(rootNode);
 
  return {
    salt,
    rootKey,
    configPrivateKey,
  };
}