PHP WebShell

Текущая директория: /opt/BitGoJS/modules/utxo-lib/src/bitgo

Просмотр файла: outputScripts.ts

import * as assert from 'assert';
import * as bitcoinjs from 'bitcoinjs-lib';

import { Network, supportsSegwit, supportsTaproot } from '../networks';
import * as p2trPayments from '../payments';
import * as taproot from '../taproot';

import { isTriple, Triple, Tuple } from './types';

import { ecc as eccLib } from '../noble_ecc';
import { getDepthFirstTaptree, getTweakedOutputKey } from '../taproot';

export { scriptTypeForChain } from './wallet/chains';

export const scriptTypeP2shP2pk = 'p2shP2pk';
export type ScriptTypeP2shP2pk = typeof scriptTypeP2shP2pk;

export const scriptTypes2Of3 = ['p2sh', 'p2shP2wsh', 'p2wsh', 'p2tr', 'p2trMusig2'] as const;
export type ScriptType2Of3 = (typeof scriptTypes2Of3)[number];

export function isScriptType2Of3(t: string): t is ScriptType2Of3 {
  return scriptTypes2Of3.includes(t as ScriptType2Of3);
}

export type ScriptType = ScriptTypeP2shP2pk | ScriptType2Of3;

/**
 * @return true iff scriptType requires witness data
 */
export function hasWitnessData(scriptType: ScriptType): scriptType is 'p2shP2wsh' | 'p2wsh' | 'p2tr' | 'p2trMusig2' {
  return ['p2shP2wsh', 'p2wsh', 'p2tr', 'p2trMusig2'].includes(scriptType);
}

/**
 * @param network
 * @param scriptType
 * @return true iff script type is supported for network
 */
export function isSupportedScriptType(network: Network, scriptType: ScriptType): boolean {
  switch (scriptType) {
    case 'p2sh':
    case 'p2shP2pk':
      return true;
    case 'p2shP2wsh':
    case 'p2wsh':
      return supportsSegwit(network);
    case 'p2tr':
    case 'p2trMusig2':
      return supportsTaproot(network);
  }

  /* istanbul ignore next */
  throw new Error(`unexpected script type ${scriptType}`);
}

/**
 * @param t
 * @return string prevOut as defined in PREVOUT_TYPES (bitcoinjs-lib/.../transaction_builder.js)
 */
export function scriptType2Of3AsPrevOutType(t: ScriptType2Of3): string {
  switch (t) {
    case 'p2sh':
      return 'p2sh-p2ms';
    case 'p2shP2wsh':
      return 'p2sh-p2wsh-p2ms';
    case 'p2wsh':
      return 'p2wsh-p2ms';
    case 'p2tr':
      return 'p2tr-p2ns';
    case 'p2trMusig2':
      return 'p2tr';
  }

  /* istanbul ignore next */
  throw new Error(`unsupported script type ${t}`);
}

export type SpendableScript = {
  scriptPubKey: Buffer;
  redeemScript?: Buffer;
  witnessScript?: Buffer;
};

export type SpendScriptP2tr = {
  controlBlock: Buffer;
  witnessScript: Buffer;
  leafVersion: number;
  leafHash: Buffer;
};

/**
 * Tweak data holder for P2tr Musig2 key path.
 */
export type KeyPathP2trMusig2 = {
  internalPubkey: Buffer;
  outputPubkey: Buffer;
  taptreeRoot: Buffer;
};

/**
 * Return scripts for p2sh-p2pk (used for BCH/BSV replay protection)
 * @param pubkey
 */
export function createOutputScriptP2shP2pk(pubkey: Buffer): SpendableScript {
  const p2pk = bitcoinjs.payments.p2pk({ pubkey });
  const p2sh = bitcoinjs.payments.p2sh({ redeem: p2pk });
  if (!p2sh.output || !p2pk.output) {
    throw new Error(`invalid state`);
  }
  return {
    scriptPubKey: p2sh.output,
    redeemScript: p2pk.output,
  };
}

export function getOutputScript(scriptType: 'p2sh' | 'p2shP2wsh' | 'p2wsh', conditionScript: Buffer): Buffer {
  let output;
  switch (scriptType) {
    case 'p2sh':
      ({ output } = bitcoinjs.payments.p2sh({ redeem: { output: conditionScript } }));
      break;
    case 'p2shP2wsh':
      ({ output } = bitcoinjs.payments.p2sh({
        redeem: { output: getOutputScript('p2wsh', conditionScript) },
      }));
      break;
    case 'p2wsh':
      ({ output } = bitcoinjs.payments.p2wsh({ redeem: { output: conditionScript } }));
      break;
  }
  if (output === undefined) {
    throw new Error(`output undefined`);
  }
  return output;
}

/**
 * Return scripts for 2-of-3 multisig output
 * @param pubkeys - the key triple for multisig
 * @param scriptType
 * @param network - if set, performs sanity check for scriptType support
 * @returns {{redeemScript, witnessScript, scriptPubKey}}
 */
export function createOutputScript2of3(
  pubkeys: Buffer[],
  scriptType: ScriptType2Of3,
  network?: Network
): SpendableScript {
  if (network) {
    if (!isSupportedScriptType(network, scriptType)) {
      throw new Error(`unsupported script type ${scriptType} for network`);
    }
  }

  if (!isTriple(pubkeys)) {
    throw new Error(`must provide pubkey triple`);
  }

  pubkeys.forEach((key) => {
    if (key.length !== 33) {
      throw new Error(`Unexpected key length ${key.length}. Must use compressed keys.`);
    }
  });

  if (scriptType === 'p2tr' || scriptType === 'p2trMusig2') {
    // p2tr/p2trMusig2 addresses use a combination of 2 of 2 multisig scripts distinct from
    // the 2 of 3 multisig used for other script types
    return createTaprootScript2of3(scriptType, pubkeys);
  }

  const script2of3 = bitcoinjs.payments.p2ms({ m: 2, pubkeys });
  assert(script2of3.output);

  let redeemScript: bitcoinjs.Payment | undefined;
  let witnessScript: bitcoinjs.Payment | undefined;
  switch (scriptType) {
    case 'p2sh':
      redeemScript = script2of3;
      break;
    case 'p2shP2wsh':
      witnessScript = script2of3;
      redeemScript = bitcoinjs.payments.p2wsh({ redeem: script2of3 });
      break;
    case 'p2wsh':
      witnessScript = script2of3;
      break;
    default:
      throw new Error(`unknown multisig script type ${scriptType}`);
  }

  return {
    scriptPubKey: getOutputScript(scriptType, script2of3.output),
    redeemScript: redeemScript?.output,
    witnessScript: witnessScript?.output,
  };
}

export function toXOnlyPublicKey(b: Buffer): Buffer {
  if (b.length === 33) {
    return b.slice(1);
  }
  if (b.length === 32) {
    return b;
  }
  throw new Error(`invalid key size ${b.length}`);
}

function checkSize(b: Buffer, targetSize: number, name: string): Buffer {
  if (b.length === targetSize) {
    return b;
  }
  throw new Error(`invalid size ${b.length}. Must use ${name}.`);
}

/**
 * Validates size of the pub key for 32 bytes and returns the same iff true.
 */
export function checkXOnlyPublicKey(b: Buffer): Buffer {
  return checkSize(b, 32, 'x-only key');
}

/**
 * Validates size of the pub key for 32 bytes and returns the same iff true.
 */
export function checkPlainPublicKey(b: Buffer): Buffer {
  return checkSize(b, 33, 'plain key');
}

export function checkTapMerkleRoot(b: Buffer): Buffer {
  return checkSize(b, 32, 'tap merkle root');
}

export function checkTxHash(b: Buffer): Buffer {
  return checkSize(b, 32, 'tx hash');
}

function getTaptreeKeyCombinations(scriptType: 'p2tr' | 'p2trMusig2', keys: Triple<Buffer>): Tuple<Buffer>[] {
  const [userKey, backupKey, bitGoKey] = keys.map((k) => toXOnlyPublicKey(k));
  return scriptType === 'p2tr'
    ? [
        [userKey, bitGoKey],
        [userKey, backupKey],
        [backupKey, bitGoKey],
      ]
    : [
        [userKey, backupKey],
        [backupKey, bitGoKey],
      ];
}

function getKeyPathCombination(scriptType: 'p2tr' | 'p2trMusig2', keys: Triple<Buffer>): Tuple<Buffer> {
  const sanitizePublicKey = scriptType === 'p2tr' ? toXOnlyPublicKey : checkPlainPublicKey;
  return [sanitizePublicKey(keys[0]), sanitizePublicKey(keys[2])];
}

function getRedeemIndex(keyCombinations: [Buffer, Buffer][], signer: Buffer, cosigner: Buffer): number {
  signer = toXOnlyPublicKey(signer);
  cosigner = toXOnlyPublicKey(cosigner);
  const i = keyCombinations.findIndex(([a, b]) => {
    if (a.length !== signer.length || b.length !== cosigner.length) {
      throw new Error(`invalid comparison`);
    }
    return (a.equals(signer) && b.equals(cosigner)) || (a.equals(cosigner) && b.equals(signer));
  });
  if (0 <= i) {
    return i;
  }
  throw new Error(`could not find singer/cosigner combination`);
}

function createPaymentP2trCommon(
  scriptType: 'p2tr' | 'p2trMusig2',
  pubkeys: Triple<Buffer>,
  redeemIndex?: number | { signer: Buffer; cosigner: Buffer }
): bitcoinjs.Payment {
  const keyCombinations2of2 = getTaptreeKeyCombinations(scriptType, pubkeys);
  if (typeof redeemIndex === 'object') {
    redeemIndex = getRedeemIndex(keyCombinations2of2, redeemIndex.signer, redeemIndex.cosigner);
  }
  const redeems = keyCombinations2of2.map((pubkeys, index) =>
    p2trPayments.p2tr_ns(
      {
        pubkeys,
        depth: scriptType === 'p2trMusig2' || index === 0 ? 1 : 2,
      },
      { eccLib }
    )
  );

  return p2trPayments.p2tr(
    {
      pubkeys: getKeyPathCombination(scriptType, pubkeys),
      redeems,
      redeemIndex,
    },
    { eccLib }
  );
}

export function createPaymentP2tr(
  pubkeys: Triple<Buffer>,
  redeemIndex?: number | { signer: Buffer; cosigner: Buffer }
): bitcoinjs.Payment {
  return createPaymentP2trCommon('p2tr', pubkeys, redeemIndex);
}

export function createPaymentP2trMusig2(
  pubkeys: Triple<Buffer>,
  redeemIndex?: number | { signer: Buffer; cosigner: Buffer }
): bitcoinjs.Payment {
  return createPaymentP2trCommon('p2trMusig2', pubkeys, redeemIndex);
}

function getLeafHashCommon(
  scriptType: 'p2tr' | 'p2trMusig2',
  params: bitcoinjs.Payment | { publicKeys: Triple<Buffer>; signer: Buffer; cosigner: Buffer }
): Buffer {
  if ('publicKeys' in params) {
    params = createPaymentP2trCommon(scriptType, params.publicKeys, params);
  }
  const { output, controlBlock, redeem } = params;
  if (!output || !controlBlock || !redeem || !redeem.output) {
    throw new Error(`invalid state`);
  }
  return taproot.getTapleafHash(eccLib, controlBlock, redeem.output);
}

export function getLeafHash(
  params: bitcoinjs.Payment | { publicKeys: Triple<Buffer>; signer: Buffer; cosigner: Buffer }
): Buffer {
  return getLeafHashCommon('p2tr', params);
}

export function createKeyPathP2trMusig2(pubkeys: Triple<Buffer>): KeyPathP2trMusig2 {
  const payment = createPaymentP2trCommon('p2trMusig2', pubkeys);
  assert(payment.internalPubkey);
  assert(payment.tapTree);
  return {
    internalPubkey: payment.internalPubkey,
    outputPubkey: getTweakedOutputKey(payment),
    taptreeRoot: getDepthFirstTaptree(payment.tapTree).root,
  };
}

function createSpendScriptP2trCommon(
  scriptType: 'p2tr' | 'p2trMusig2',
  pubkeys: Triple<Buffer>,
  keyCombination: Tuple<Buffer>
): SpendScriptP2tr {
  const keyCombinations = getTaptreeKeyCombinations(scriptType, pubkeys);
  const [a, b] = keyCombination.map((k) => toXOnlyPublicKey(k));
  const redeemIndex = keyCombinations.findIndex(
    ([c, d]) => (a.equals(c) && b.equals(d)) || (a.equals(d) && b.equals(c))
  );

  if (redeemIndex < 0) {
    throw new Error(`could not find redeemIndex for key combination`);
  }

  const payment = createPaymentP2trCommon(scriptType, pubkeys, redeemIndex);
  const { controlBlock } = payment;
  assert(Buffer.isBuffer(controlBlock));

  assert(payment.redeem);
  const leafScript = payment.redeem.output;
  assert(Buffer.isBuffer(leafScript));

  const parsedControlBlock = taproot.parseControlBlock(eccLib, controlBlock);
  const { leafVersion } = parsedControlBlock;
  const leafHash = taproot.getTapleafHash(eccLib, parsedControlBlock, leafScript);

  return {
    controlBlock,
    witnessScript: leafScript,
    leafVersion,
    leafHash,
  };
}

export function createSpendScriptP2tr(pubkeys: Triple<Buffer>, keyCombination: Tuple<Buffer>): SpendScriptP2tr {
  return createSpendScriptP2trCommon('p2tr', pubkeys, keyCombination);
}

export function createSpendScriptP2trMusig2(pubkeys: Triple<Buffer>, keyCombination: Tuple<Buffer>): SpendScriptP2tr {
  return createSpendScriptP2trCommon('p2trMusig2', pubkeys, keyCombination);
}

/**
 * Creates and returns a taproot output script using the user+bitgo keys for the aggregate
 * public key using MuSig2 and a taptree containing either of the following depends on scriptType.
 * p2tr type: a user+bitgo 2-of-2 script at the first depth level of the tree and user+backup
 * and bitgo+backup 2-of-2 scripts one level deeper.
 * p2trMusig2 type: user+backup and bitgo+backup 2-of-2 scripts at the first depth level of the
 * tree.
 * @param pubkeys - a pubkey array containing the user key, backup key, and bitgo key in that order
 * @returns {{scriptPubKey}}
 */
function createTaprootScript2of3(scriptType: 'p2tr' | 'p2trMusig2', pubkeys: Triple<Buffer>): SpendableScript {
  const { output } = createPaymentP2trCommon(scriptType, pubkeys);
  assert(Buffer.isBuffer(output));
  return {
    scriptPubKey: output,
  };
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!