PHP WebShell

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

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

// SegWit version 1 P2TR output type for Taproot defined in
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki

import { networks } from '../networks';
import { script as bscript, Payment, PaymentOpts, lazy } from 'bitcoinjs-lib';
import * as taproot from '../taproot';
import { musig } from '../noble_ecc';
import { secp256k1 as necc } from '@noble/curves/secp256k1';

const typef = require('typeforce');
const OPS = bscript.OPS;

const { bech32m } = require('bech32');

const BITCOIN_NETWORK = networks.bitcoin;

/**
 * A secp256k1 x coordinate with unknown discrete logarithm used for eliminating
 * keypath spends, equal to SHA256(uncompressedDER(SECP256K1_GENERATOR_POINT)).
 */
const H = Buffer.from('50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0', 'hex');
const EMPTY_BUFFER = Buffer.alloc(0);

function isPlainPubkey(pubKey: Uint8Array): boolean {
  if (pubKey.length !== 33) return false;
  try {
    return !!necc.ProjectivePoint.fromHex(pubKey);
  } catch (e) {
    return false;
  }
}

function isPlainPubkeys(pubkeys: Buffer[]) {
  return pubkeys.every(isPlainPubkey);
}

// output: OP_1 {witnessProgram}
export function p2tr(a: Payment, opts?: PaymentOpts): Payment {
  if (!a.address && !a.pubkey && !a.pubkeys && !(a.redeems && a.redeems.length) && !a.output && !a.witness) {
    throw new TypeError('Not enough data');
  }
  opts = Object.assign({ validate: true }, opts || {});

  if (!opts.eccLib) throw new Error('ECC Library is required for p2tr.');
  const ecc = opts.eccLib;

  typef(
    {
      network: typef.maybe(typef.Object),

      address: typef.maybe(typef.String),
      // the output script should be a fixed 34 bytes.
      // 1 byte for OP_1 indicating segwit version 1, one byte for 0x20 to push
      // the next 32 bytes, followed by the 32 byte witness program
      output: typef.maybe(typef.BufferN(34)),
      // a single pubkey
      pubkey: typef.maybe(ecc.isXOnlyPoint),
      // the pub key(s) used for keypath signing.
      // aggregated with MuSig2* if > 1
      pubkeys: typef.maybe(typef.anyOf(typef.arrayOf(ecc.isXOnlyPoint), typef.arrayOf(isPlainPubkey))),

      redeems: typef.maybe(
        typef.arrayOf({
          network: typef.maybe(typef.Object),
          output: typef.maybe(typef.Buffer),
          weight: typef.maybe(typef.Number),
          depth: typef.maybe(typef.Number),
          witness: typef.maybe(typef.arrayOf(typef.Buffer)),
        })
      ),
      redeemIndex: typef.maybe(typef.Number), // Selects the redeem to spend

      signature: typef.maybe(bscript.isCanonicalSchnorrSignature),
      controlBlock: typef.maybe(typef.Buffer),
      annex: typef.maybe(typef.Buffer),
    },
    a
  );

  const _address = lazy.value(() => {
    if (!a.address) return undefined;

    const result = bech32m.decode(a.address);
    const version = result.words.shift();
    const data = bech32m.fromWords(result.words);
    return {
      version,
      prefix: result.prefix,
      data: Buffer.from(data),
    };
  });
  const _outputPubkey = lazy.value(() => {
    // we remove the first two bytes (OP_1 0x20) from the output script to
    // extract the 32 byte taproot pubkey (aka witness program)
    return a.output && a.output.slice(2);
  });

  const network = a.network || BITCOIN_NETWORK;

  const o: Payment = { network };

  const _taprootPaths = lazy.value(() => {
    if (!a.redeems) return;
    if (o.tapTree) {
      return taproot.getDepthFirstTaptree(o.tapTree);
    }
    const outputs: Array<Buffer | undefined> = a.redeems.map(({ output }) => output);
    if (!outputs.every((output) => output)) return;
    return taproot.getHuffmanTaptree(
      outputs as Buffer[],
      a.redeems.map(({ weight }) => weight)
    );
  });
  const _parsedWitness = lazy.value(() => {
    if (!a.witness) return;
    return taproot.parseTaprootWitness(a.witness);
  });
  const _parsedControlBlock = lazy.value(() => {
    // Can't use o.controlBlock, because it could be circular
    if (a.controlBlock) return taproot.parseControlBlock(ecc, a.controlBlock);
    const parsedWitness = _parsedWitness();
    if (parsedWitness && parsedWitness.spendType === 'Script') {
      return taproot.parseControlBlock(ecc, parsedWitness.controlBlock);
    }
  });

  lazy.prop(o, 'internalPubkey', () => {
    if (a.pubkey) {
      // single pubkey
      return a.pubkey;
    } else if (a.pubkeys && a.pubkeys.length === 1) {
      return a.pubkeys[0];
    } else if (a.pubkeys && a.pubkeys.length > 1) {
      // multiple pubkeys
      if (isPlainPubkeys(a.pubkeys)) {
        return Buffer.from(musig.getXOnlyPubkey(musig.keyAgg(a.pubkeys)));
      }

      return Buffer.from(taproot.aggregateMuSigPubkeys(ecc, a.pubkeys));
    } else if (_parsedControlBlock()) {
      return _parsedControlBlock()?.internalPubkey;
    } else {
      // If there is no key path spending condition, we use an internal key with unknown secret key.
      // TODO: In order to avoid leaking the information that key path spending is not possible it
      // is recommended to pick a fresh integer r in the range 0...n-1 uniformly at random and use
      // H + rG as internal key. It is possible to prove that this internal key does not have a
      // known discrete logarithm with respect to G by revealing r to a verifier who can then
      // reconstruct how the internal key was created.
      return H;
    }
  });

  lazy.prop(o, 'taptreeRoot', () => {
    const parsedControlBlock = _parsedControlBlock();
    const parsedWitness = _parsedWitness();
    let taptreeRoot;
    // Prefer to get the root via the control block because not all redeems may
    // be available
    if (parsedControlBlock) {
      let tapscript;
      if (parsedWitness && parsedWitness.spendType === 'Script') {
        tapscript = parsedWitness.tapscript;
      } else if (o.redeem && o.redeem.output) {
        tapscript = o.redeem.output;
      }
      if (tapscript) taptreeRoot = taproot.getTaptreeRoot(ecc, parsedControlBlock, tapscript);
    }
    if (!taptreeRoot && _taprootPaths()) taptreeRoot = _taprootPaths()?.root;

    return taptreeRoot;
  });

  const _taprootPubkey = lazy.value(() => {
    const taptreeRoot = o.taptreeRoot;
    // Refuse to create an unspendable key
    if (!a.pubkey && !(a.pubkeys && a.pubkeys.length) && !a.redeems && !taptreeRoot) {
      return;
    }
    return taproot.tapTweakPubkey(ecc, o?.internalPubkey as Uint8Array, taptreeRoot);
  });

  lazy.prop(o, 'tapTree', () => {
    if (!a.redeems) return;
    if (a.redeems.find(({ depth }) => depth === undefined)) {
      console.warn(
        'Deprecation Warning: Weight-based tap tree construction will be removed in the future. ' +
          'Please use depth-first coding as specified in BIP-0371.'
      );
      return;
    }
    if (!a.redeems.every(({ output }) => output)) return;
    return {
      leaves: a.redeems.map(({ output, depth }) => {
        return {
          script: output,
          leafVersion: taproot.INITIAL_TAPSCRIPT_VERSION,
          depth,
        };
      }),
    };
  });
  lazy.prop(o, 'address', () => {
    const pubkey = _outputPubkey() || (_taprootPubkey() && _taprootPubkey()?.xOnlyPubkey);
    // only encode the 32 byte witness program as bech32m
    const words = bech32m.toWords(pubkey);
    words.unshift(0x01);
    return bech32m.encode(network.bech32, words);
  });
  lazy.prop(o, 'controlBlock', () => {
    const parsedWitness = _parsedWitness();
    if (parsedWitness && parsedWitness.spendType === 'Script') {
      return parsedWitness.controlBlock;
    }
    const taprootPubkey = _taprootPubkey();
    const taprootPaths = _taprootPaths();
    if (!taprootPaths || !taprootPubkey || a.redeemIndex === undefined) return;
    return taproot.getControlBlock(taprootPubkey.parity, o.internalPubkey!, taprootPaths.paths[a.redeemIndex]);
  });
  lazy.prop(o, 'signature', () => {
    const parsedWitness = _parsedWitness();
    if (parsedWitness && parsedWitness.spendType === 'Key') {
      return parsedWitness.signature;
    }
  });
  lazy.prop(o, 'annex', () => {
    if (!_parsedWitness()) return;
    return _parsedWitness()!.annex;
  });
  lazy.prop(o, 'output', () => {
    if (a.address) {
      const { data } = _address()!;
      return bscript.compile([OPS.OP_1, data]);
    }

    const taprootPubkey = _taprootPubkey();
    if (!taprootPubkey) return;

    // OP_1 indicates segwit version 1
    return bscript.compile([OPS.OP_1, Buffer.from(taprootPubkey.xOnlyPubkey)]);
  });
  lazy.prop(o, 'witness', () => {
    if (!a.redeems) {
      if (a.signature) return [a.signature]; // Keypath spend
      return;
    } else if (!o.redeem) {
      return; // No chosen redeem script, can't make witness
    } else if (!o.controlBlock) {
      return;
    }

    let redeemWitness;
    // some callers may provide witness elements in the input script
    if (o.redeem.input && o.redeem.input.length > 0 && o.redeem.output && o.redeem.output.length > 0) {
      // transform redeem input to witness stack
      redeemWitness = bscript.toStack(bscript.decompile(o.redeem.input)!);

      // assigns a new object to o.redeem
      o.redeems![a.redeemIndex!] = Object.assign({ witness: redeemWitness }, o.redeem);
      o.redeem.input = EMPTY_BUFFER;
    } else if (o.redeem.output && o.redeem.output.length > 0 && o.redeem.witness && o.redeem.witness.length > 0) {
      redeemWitness = o.redeem.witness;
    } else {
      return;
    }

    const witness = [...redeemWitness, o.redeem.output, o.controlBlock];

    if (a.annex) {
      witness.push(a.annex);
    }

    return witness;
  });
  lazy.prop(o, 'name', () => {
    const nameParts = ['p2tr'];
    return nameParts.join('-');
  });
  lazy.prop(o, 'redeem', () => {
    if (a.redeems) {
      if (a.redeemIndex === undefined) return;
      return a.redeems[a.redeemIndex];
    }
    const parsedWitness = _parsedWitness();
    if (parsedWitness && parsedWitness.spendType === 'Script') {
      return {
        witness: parsedWitness.scriptSig,
        output: parsedWitness.tapscript,
      };
    }
  });

  // extended validation
  if (opts.validate) {
    const taprootPubkey = _taprootPubkey();

    if (a.output) {
      if (a.output[0] !== OPS.OP_1 || a.output[1] !== 0x20) {
        throw new TypeError('Output is invalid');
      }

      // if we're passed both an output script and an address, ensure they match
      if (a.address && _outputPubkey && !_outputPubkey()?.equals(_address()?.data as Buffer)) {
        throw new TypeError('mismatch between address & output');
      }

      // Wrapping `taprootPubkey.xOnlyPubkey` in Buffer because of a peculiar issue in the frontend
      // where a polyfill for Buffer is used. Refer: https://bitgoinc.atlassian.net/browse/BG-61420
      if (taprootPubkey && _outputPubkey && !_outputPubkey()?.equals(Buffer.from(taprootPubkey.xOnlyPubkey))) {
        throw new TypeError('mismatch between output and taproot pubkey');
      }
    }

    if (a.address) {
      if (taprootPubkey && !_address()?.data.equals(Buffer.from(taprootPubkey.xOnlyPubkey))) {
        throw new TypeError('mismatch between address and taproot pubkey');
      }
    }

    const parsedControlBlock = _parsedControlBlock();
    if (parsedControlBlock) {
      if (!parsedControlBlock.internalPubkey.equals(o?.internalPubkey as Uint8Array)) {
        throw new TypeError('Internal pubkey mismatch');
      }
      if (taprootPubkey && parsedControlBlock.parity !== taprootPubkey.parity) {
        throw new TypeError('Parity mismatch');
      }
    }

    if (a.redeems) {
      if (!a.redeems.length) throw new TypeError('Empty redeems');
      if (a.redeemIndex !== undefined && (a.redeemIndex < 0 || a.redeemIndex >= a.redeems.length)) {
        throw new TypeError('invalid redeem index');
      }
      a.redeems.forEach((redeem) => {
        if (redeem.network && redeem.network !== network) {
          throw new TypeError('Network mismatch');
        }
      });
    }

    const chosenRedeem = a.redeems && a.redeemIndex !== undefined && a.redeems[a.redeemIndex];

    const parsedWitness = _parsedWitness();
    if (parsedWitness && parsedWitness.spendType === 'Key') {
      if (a.controlBlock) {
        throw new TypeError('unexpected control block for key path');
      }

      if (a.signature && !a.signature.equals(parsedWitness.signature)) {
        throw new TypeError('mismatch between witness & signature');
      }
    }
    if (parsedWitness && parsedWitness.spendType === 'Script') {
      if (a.signature) {
        throw new TypeError('unexpected signature with script path witness');
      }

      if (a.controlBlock && !a.controlBlock.equals(parsedWitness.controlBlock)) {
        throw new TypeError('control block mismatch');
      }

      if (a.annex && parsedWitness.annex && !a.annex.equals(parsedWitness.annex)) {
        throw new TypeError('annex mismatch');
      }

      if (chosenRedeem && chosenRedeem.output && !chosenRedeem.output.equals(parsedWitness.tapscript)) {
        throw new TypeError('tapscript mismatch');
      }
    }
  }

  return Object.assign(o, a);
}

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


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