/**
* This module provides an API around the multisig capabilities of the
* bitcoinjs-lib library. The API is functional but requires you
* creating and passing around a [`Multisig`]{@link module:multisig.MULTISIG} object.
*
* This `Multisig` object represents the combination of:
*
* 1) a sequence of N public keys
* 2) the number of required signers (M)
* 3) the address type (P2SH, P2SH-P2WSH, P2WSH)
* 4) the bitcoin network
*
* This corresponds to a unique bitcoin multisig address. Note that
* since (3) & (4) can change without changing (1) & (2), different
* `Multisig` objects (and their corresponding bitcoin addresses) can
* have different representations but the same security rules as to
* who can sign.
*
* You can create `Multisig` objects yourself using the following
* functions:
*
* - `generateMultisigFromPublicKeys` which takes public keys as input
* - `generateMultisigFromHex` which takes a redeem/witness script as input
*
* Once you have a `Multisig` object you can pass it around in your
* code and then ask questions about it using the other functions
* defined in this module.
*
* You can manipulate `Multisig` objects directly but it's better to
* use the functions from API provided by this module.
*
* @module multisig
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2SH,
* multisigRequiredSigners, multisigTotalSigners,
* multisigAddressType,
* multisigPublicKeys,
* } from "unchained-bitcoin";
* const pubkey1 = "03a...";
* const pubkey2 = "03b...";
* // A mainnet 1-of-2 P2SH multisig
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2SH, 1, pubkey1, pubkey2);
*
* console.log(multisigRequiredSigners(multisig)); // 1
* console.log(multisigTotalSigners(multisig)); // 2
* console.log(multisigAddressType(multisig)); // "P2SH"
* console.log(multisigPublicKeys(multisig)); // ["03a...", "03b..."]
*
*/
import { networkData } from "./networks";
import { P2SH } from "./p2sh";
import { P2SH_P2WSH } from "./p2sh_p2wsh";
import { P2WSH } from "./p2wsh";
import { toHexString } from "./utils";
import { payments } from "bitcoinjs-lib";
/**
* Describes the return type of several functions in the
* `payments` module of bitcoinjs-lib.
*
* The following functions in this module will return objects of this
* type:
*
* - `generateMultisigFromPublicKeys` which takes public keys as input
* - `generateMultisigFromHex` which takes a redeem/witness script as input
*
* The remaining functions accept these objects as arguments.
*
* @typedef Multisig
* @type {Object}
* @property {string} address - The multisig address
* @property {Object} redeem - the redeem object from p2ms
* @property {Object} multisigBraidDetails - details about the braid (addressType, network, requiredSigners, xpubs, index)
* @property {Object[]} getBip32Derivation - Array of objects for every key in this multisig address
*
*/
/**
* Enumeration of possible multisig address types ([P2SH]{@link module:p2sh.P2SH}|[P2SH_P2WSH]{@link module:p2sh_p2wsh.P2SH_P2WSH}|[P2WSH]{@link module:p2wsh.P2WSH}).
*
* @constant
* @enum {string}
* @default
*/
export const MULTISIG_ADDRESS_TYPES = {
P2SH,
P2SH_P2WSH,
P2WSH,
};
/**
* Return an M-of-N [`Multisig`]{@link module:multisig.MULTISIG}
* object by specifying the total number of signers (M) and the public
* keys (N total).
*
* @param {module:networks.NETWORKS} network - bitcoin network
* @param {module:multisig.MULTISIG_ADDRESS_TYPES} addressType - address type
* @param {number} requiredSigners - number of signers required needed to spend funds (M)
* @param {...string} publicKeys - list of public keys, 1 per possible signer (N)
* @returns {Multisig} the corresponding `Multisig` object
* @example
* // A 2-of-3 P2SH mainnet multisig built from 3 public keys.
* import {
* generateMultisigFromPublicKeys, MAINNET, P2SH, P2WSH,
* } from "unchained-bitcoin";
* const multisigP2SH = generateMultisigFromPublicKeys(MAINNET, P2SH, 2, "03a...", "03b...", "03c...");
* const multisigP2WSH = generateMultisigFromPublicKeys(MAINNET, P2WSH, 2, "03a...", "03b...", "03c...");
*/
export function generateMultisigFromPublicKeys(
network,
addressType,
requiredSigners,
...publicKeys
) {
const multisig = payments.p2ms({
m: requiredSigners,
pubkeys: publicKeys.map((hex) => Buffer.from(hex, "hex")),
network: networkData(network),
});
return generateMultisigFromRaw(addressType, multisig);
}
/**
* Return an M-of-N [`Multisig`]{@link module.multisig:Multisig}
* object by passing a script in hex.
*
* If the `addressType` is `P2SH` then the script hex being passed is
* the redeem script. If the `addressType` is P2SH-wrapped SegWit
* (`P2SH_P2WSH`) or native SegWit (`P2WSH`) then the script hex being
* passed is the witness script.
*
* In practice, the same script hex can be thought of as any of
* several address types, depending on context.
*
* @param {module:networks.NETWORKS} network - bitcoin network
* @param {module:multisig.MULTISIG_ADDRESS_TYPES} addressType - address type
* @param {string} multisigScriptHex - hex representation of the redeem/witness script
* @returns {Multisig} object for further parsing
* @example
* import {
* generateMultisigFromHex, MAINNET, P2SH, P2WSH,
* } from "unchained-bitcoin";
* const multisigScript = "512103a90d10bf3794352bb1fa533dbd4ea75a0ffc98e0d05124938fcc3e10cdbe1a4321030d60e8d497fa8ce59a2b3203f0e597cd0182e1fe0cc3688f73497f2e99fbf64b52ae";
* const multisigP2SH = generateMultisigFromHex(MAINNET, P2SH, multisigScript);
* const multisigP2WSH = generateMultisigFromHex(MAINNET, P2WSH, multisigScript);
*/
export function generateMultisigFromHex(
network,
addressType,
multisigScriptHex
) {
const multisig = payments.p2ms({
output: Buffer.from(multisigScriptHex, "hex"),
network: networkData(network),
});
return generateMultisigFromRaw(addressType, multisig);
}
/**
* Return an M-of-N [`Multisig`]{@link module.multisig:Multisig}
* object by passing in a raw P2MS multisig object (from bitcoinjs-lib).
*
* This function is only used internally, do not call it directly.
*
* @param {module:multisig.MULTISIG_ADDRESS_TYPES} addressType - address type
* @param {object} multisig - P2MS multisig object
* @returns {Multisig} object for further parsing
* @ignore
*
*/
export function generateMultisigFromRaw(addressType, multisig) {
switch (addressType) {
case P2SH:
return payments.p2sh({ redeem: multisig });
case P2SH_P2WSH:
return payments.p2sh({
redeem: payments.p2wsh({ redeem: multisig }),
});
case P2WSH:
return payments.p2wsh({ redeem: multisig });
default:
return null;
}
}
/**
* Return the [address type]{@link module:multisig.MULTISIG_ADDRESS_TYPES} of the given `Multisig` object.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {module:multisig.MULTISIG_ADDRESS_TYPES|String} the address type
* @example
* import {
* multisigAddressType, P2SH, P2SH_P2WSH, P2WSH,
* } from "unchained-bitcoin";
* function doSomething(multisig) {
* switch (multisigAddressType(multisig)) {
* case P2SH:
* // handle P2SH here
* case P2SH_P2WSH:
* // handle P2SH-P2WSH here
* case P2WSH:
* // handle P2WSH here
* default:
* // shouldn't reach here
* }
*/
export function multisigAddressType(multisig) {
if (multisig.redeem.redeem) {
return P2SH_P2WSH;
} else {
// FIXME why is multisig.witness null?
// if (multisig.witness) {
if (multisig.address.match(/^(tb|bc)/)) {
return P2WSH;
} else {
return P2SH;
}
}
}
/**
* Return the number of required signers of the given `Multisig`
* object.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {number} number of required signers
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2SH,
* multisigRequiredSigners,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2SH, 2, "03a...", "03b...", "03c...");
* console.log(multisigRequiredSigners(multisig)); // 2
*/
export function multisigRequiredSigners(multisig) {
return multisigAddressType(multisig) === P2SH_P2WSH
? multisig.redeem.redeem.m
: multisig.redeem.m;
}
/**
* Return the number of total signers (public keys) of the given
* `Multisig` object.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {number} number of total signers
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2SH,
* multisigTotalSigners,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2SH, 2, "03a...", "03b...", "03c...");
* console.log(multisigTotalSigners(multisig)); // 3
*/
export function multisigTotalSigners(multisig) {
return multisigAddressType(multisig) === P2SH_P2WSH
? multisig.redeem.redeem.n
: multisig.redeem.n;
}
/**
* Return the multisig script for the given `Multisig` object.
*
* If the address type of the given multisig object is P2SH, the
* redeem script will be returned. Otherwise, the witness script will
* be returned.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {Multisig|null} the corresponding script
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2SH,
* multisigScript,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2SH, 2, "03a...", "03b...", "03c...");
* console.log(multisigScript(multisig));
*/
export function multisigScript(multisig) {
switch (multisigAddressType(multisig)) {
case P2SH:
return multisigRedeemScript(multisig);
case P2SH_P2WSH:
return multisigWitnessScript(multisig);
case P2WSH:
return multisigWitnessScript(multisig);
default:
/* istanbul ignore next */
// multisigAddressType only returns one of the 3 above choices
return null;
}
}
/**
* Return the redeem script for the given `Multisig` object.
*
* If the address type of the given multisig object is P2WSH, this
* will return null.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {Multisig|null} the redeem script
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2SH,
* multisigRedeemScript,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2SH, 2, "03a...", "03b...", "03c...");
* console.log(multisigRedeemScript(multisig));
*/
export function multisigRedeemScript(multisig) {
switch (multisigAddressType(multisig)) {
case P2SH:
return multisig.redeem;
case P2SH_P2WSH:
return multisig.redeem;
case P2WSH:
return null;
default:
/* istanbul ignore next */
// multisigAddressType only returns one of the 3 above choices
return null;
}
}
/**
* Return the witness script for the given `Multisig` object.
*
* If the address type of the given multisig object is P2SH, this will
* return null.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {Multisig|null} the witness script
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2WSH,
* multisigWitnessScript,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2WSH, 2, "03a...", "03b...", "03c...");
* console.log(multisigWitnessScript(multisig));
*/
export function multisigWitnessScript(multisig) {
switch (multisigAddressType(multisig)) {
case P2SH:
return null;
case P2SH_P2WSH:
return multisig.redeem.redeem;
case P2WSH:
return multisig.redeem;
default:
/* istanbul ignore next */
// multisigAddressType only returns one of the 3 above choices
return null;
}
}
/**
* Return the (compressed) public keys in hex for the given `Multisig`
* object.
*
* The public keys are in the order used in the corresponding
* redeem/witness script.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {string[]} (compressed) public keys in hex
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2WSH,
* multisigPublicKeys,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2WSH, 2, "03a...", "03b...", "03c...");
* console.log(multisigPublicKeys(multisig)); // ["03a...", "03b...", "03c..."]
*
*/
export function multisigPublicKeys(multisig) {
return (
multisigAddressType(multisig) === P2SH
? multisigRedeemScript(multisig)
: multisigWitnessScript(multisig)
).pubkeys.map(toHexString);
}
/**
* Return the address for a given `Multisig` object.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {string} the address
* @example
* import {
* generateMultisigFromPublicKeys, MAINNET, P2SH,
* multisigAddress,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2SH, 2, "03a...", "03b...", "03c...");
* console.log(multisigAddress(multisig)); // "3j..."
*
*/
export function multisigAddress(multisig) {
return multisig.address;
}
/**
* Return the braid details (if known) for a given `Multisig` object.
*
* @param {module:multisig.Multisig} multisig the `Multisig` object
* @returns {string} the braid details
* @example
* import {
* generateBraidFromExtendedPublicKeys,
* generateMultisigFromPublicKeys, MAINNET, P2SH,
* braidConfig,
* } from "unchained-bitcoin";
* const multisig = generateMultisigFromPublicKeys(MAINNET, P2SH, 2, "03a...", "03b...", "03c...");
* console.log(multisigBraidDetails(multisig)); // null, unknown
*
* const braid = generateBraidFromExtenedPublicKeys(MAINNET, P2SH, {{'xpub...', bip32path: "m/45'/0'/0'"}, {'xpub...', bip32path: "m/45'/0/0"}, {'xpub...', bip32path: "m/45'/0/0"}}, 2);
* const multisig = braid.deriveMultisigByPath("0/0");
* console.log(multisigBraidDetails(multisig)); // {network: mainnet, addressType: p2sh, extendedPublicKeys: {...}, requiredSigners: 2}}
*/
export function multisigBraidDetails(multisig) {
return multisig.braidDetails ? multisig.braidDetails : null;
}