@imikailoby/sats
Version:
Tiny non-custodial Bitcoin SDK (TS) — keys, addresses, PSBT, provider chain
78 lines (77 loc) • 2.93 kB
JavaScript
import * as bip39 from "bip39";
import { BIP32Factory } from "bip32";
import { ECPairFactory } from "ecpair";
import * as ecc from "tiny-secp256k1";
import * as bitcoin from "bitcoinjs-lib";
import { network } from "../net/index";
import { DerivationError, AddressError } from "../core/errors";
import { toBuffer } from "../utils/bytes";
const bip32 = BIP32Factory(ecc);
const ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);
/**
* Generate BIP39 mnemonic.
*/
/**
* Generate BIP39 mnemonic phrase.
* @param strength - Entropy bits (128..256)
* @returns mnemonic words string
* @example
* const m = generateMnemonic();
*/
export function generateMnemonic(strength = 128) {
return bip39.generateMnemonic(strength);
}
/**
* Derive BIP84 keypair for path m/84'/coin'/account'/change/index.
* coin: 0 mainnet, 1 testnet
*/
/**
* Derive a keypair by BIP84 path m/84'/coin'/account'/change/index.
* @param mnemonic - BIP39 mnemonic
* @param d - derivation params
* @param testnet - if true, coin=1; else coin=0
* @returns private/public key buffers and path
*/
export function deriveKeypair(mnemonic, d, testnet = false) {
const seedU8 = bip39.mnemonicToSeedSync(mnemonic);
const seed = toBuffer(seedU8);
const root = bip32.fromSeed(seed, network(testnet));
const coin = testnet ? 1 : 0;
const path = `m/84'/${coin}'/${d.account}'/${d.change}/${d.index}`;
const child = root.derivePath(path);
if (!child.privateKey || !child.publicKey)
throw new DerivationError("no keys derived");
const priv = toBuffer(child.privateKey);
const pub = toBuffer(child.publicKey);
return { priv, pub, path };
}
/**
* Construct wallet helper from mnemonic with BIP84 derivation (P2WPKH, bech32).
* @param opts.testnet When true, uses coin=1 and bitcoin testnet network
*/
/**
* Create a simple wallet helper using BIP84 (bech32 P2WPKH).
* @param opts.testnet - use testnet derivation and network
* @param opts.account - BIP84 account index (default 0)
* @returns Wallet helper with `nextReceive()` and `toWIF()`
*/
export function fromMnemonic(mnemonic, opts) {
const testnet = !!opts?.testnet;
const account = opts?.account ?? 0;
const getKeyAt = (index, change = 0) => deriveKeypair(mnemonic, { account, change, index }, testnet);
let nextIndex = 0;
const nextReceive = () => {
const k = getKeyAt(nextIndex, 0);
const pay = bitcoin.payments.p2wpkh({ pubkey: k.pub, network: network(testnet) });
if (!pay.address)
throw new AddressError("failed to build receive address");
nextIndex++;
return { address: pay.address, path: k.path };
};
const toWIF = (index, change = 0) => {
const k = getKeyAt(index, change);
return ECPair.fromPrivateKey(k.priv, { network: network(testnet) }).toWIF();
};
return { testnet, account, nextIndex, mnemonic, getKeyAt, nextReceive, toWIF };
}