Files
brk/website_next/wallets/derive/index.js
T
2026-06-17 11:25:42 +02:00

135 lines
3.4 KiB
JavaScript

import { encodePublicKeyAddressData } from "./address.js";
import { derivePublicKeys, parseXpub } from "./bip32.js";
import {
generateAddressesFromDescriptor,
getOutputDescriptorBranchIds,
isOutputDescriptor,
selectOutputDescriptor,
} from "./descriptor.js";
const DEFAULT_START_INDEX = 0;
const DEFAULT_ADDRESS_COUNT = 20;
const MAX_ADDRESS_COUNT = 100;
const addrTypeByScript = /** @type {const} */ ({
p2pkh: "p2pkh",
p2sh_p2wpkh: "p2sh",
v0_p2wpkh: "v0_p2wpkh",
v1_p2tr: "v1_p2tr",
v0_p2wsh_sortedmulti: "v0_p2wsh",
});
/**
* @typedef {import("./address.js").AddressScript} AddressScript
* @typedef {import("./address.js").BitcoinNetwork} BitcoinNetwork
* @typedef {(typeof addrTypeByScript)[keyof typeof addrTypeByScript]} AddressType
*/
/**
* @typedef {Object} GeneratedAddress
* @property {number} index
* @property {string} address
* @property {Uint8Array} payload
* @property {Uint8Array} [publicKey]
* @property {AddressScript} script
* @property {BitcoinNetwork} network
* @property {AddressType} addrType
*/
/**
* @typedef {Object} GenerateAddressesOptions
* @property {number} [start]
* @property {number} [count]
* @property {AddressScript} [script]
* @property {readonly number[]} [path]
* @property {string} [branchId]
*/
/**
* @param {number | undefined} value
*/
function readStart(value) {
if (value === undefined) return DEFAULT_START_INDEX;
if (!Number.isSafeInteger(value) || value < 0) {
throw new Error("Expected a non-negative start index");
}
return value;
}
/**
* @param {number | undefined} value
*/
function readCount(value) {
const count = value ?? DEFAULT_ADDRESS_COUNT;
if (!Number.isSafeInteger(count) || count < 1 || count > MAX_ADDRESS_COUNT) {
throw new Error(`Expected an address count from 1 to ${MAX_ADDRESS_COUNT}`);
}
return count;
}
/**
* @param {string} source
* @param {GenerateAddressesOptions} [options]
* @returns {Promise<GeneratedAddress[]>}
*/
export async function generateAddressesFromKey(source, options = {}) {
const key = await parseXpub(source);
const start = readStart(options.start);
const count = readCount(options.count);
const script = options.script ?? key.version.script;
const addrType = addrTypeByScript[script];
const children = await derivePublicKeys(key, start, count, options.path);
const addresses = /** @type {GeneratedAddress[]} */ ([]);
for (const child of children) {
const addressData = await encodePublicKeyAddressData(
child.publicKey,
script,
key.version.network,
);
addresses.push({
index: child.index,
address: addressData.address,
payload: addressData.payload,
publicKey: child.publicKey,
script,
network: key.version.network,
addrType,
});
}
return addresses;
}
/**
* @param {string} source
* @param {GenerateAddressesOptions} [options]
* @returns {Promise<GeneratedAddress[]>}
*/
export async function generateAddressesFromWalletSource(source, options = {}) {
const start = readStart(options.start);
const count = readCount(options.count);
if (isOutputDescriptor(source)) {
return generateAddressesFromDescriptor(
selectOutputDescriptor(source, options.branchId),
{ start, count },
);
}
return generateAddressesFromKey(source, {
...options,
start,
count,
});
}
export {
getOutputDescriptorBranchIds,
isOutputDescriptor,
};