PHP WebShell

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

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

/*
Functions for dealing with inscriptions.

See https://docs.ordinals.com/inscriptions.html
*/

import * as assert from 'assert';
import {
  p2trPayments as payments,
  ecc as eccLib,
  script as bscript,
  Payment,
  Network,
  bitgo,
  address,
  taproot,
  ECPair,
} from '@bitgo/utxo-lib';
import * as utxolib from '@bitgo/utxo-lib';
import { PreparedInscriptionRevealData } from '@bitgo/sdk-core';

const OPS = bscript.OPS;
const MAX_LENGTH_TAP_DATA_PUSH = 520;

/**
 * The max size of an individual OP_PUSH in a Taproot script is 520 bytes. This
 * function splits inscriptionData into an array buffer of 520 bytes length.
 * https://docs.ordinals.com/inscriptions.html
 * @param inscriptionData
 * @param chunkSize
 */
function splitBuffer(inscriptionData: Buffer, chunkSize: number) {
  const pushDataBuffers: Buffer[] = [];
  for (let i = 0; i < inscriptionData.length; i += chunkSize) {
    pushDataBuffers.push(inscriptionData.slice(i, i + chunkSize));
  }

  return pushDataBuffers;
}

/**
 *
 * @returns inscription payment object
 * @param pubkey
 * @param contentType
 * @param inscriptionData
 */
function createPaymentForInscription(pubkey: Buffer, contentType: string, inscriptionData: Buffer): Payment {
  const dataPushBuffers = splitBuffer(inscriptionData, MAX_LENGTH_TAP_DATA_PUSH);

  const uncompiledScript = [
    pubkey,
    OPS.OP_CHECKSIG,
    OPS.OP_FALSE,
    OPS.OP_IF,
    Buffer.from('ord', 'ascii'),
    1, // these two lines should be combined as a single OPS.OP_1,
    1, // but `ord`'s decoder has a bug so it has to be like this
    Buffer.from(contentType, 'ascii'),
    OPS.OP_0,
    ...dataPushBuffers,
    OPS.OP_ENDIF,
  ];

  const compiledScript = bscript.compile(uncompiledScript);
  const redeem: Payment = {
    output: compiledScript,
    depth: 0,
  };

  return payments.p2tr({ redeems: [redeem], redeemIndex: 0 }, { eccLib });
}

/**
 * @param payment
 * @param controlBlock
 * @param commitOutput
 * @param network
 * @return virtual size of a transaction with a single inscription reveal input and a single commitOutput
 */
function getInscriptionRevealSize(
  payment: Payment,
  controlBlock: Buffer,
  commitOutput: Buffer,
  network: Network
): number {
  const psbt = bitgo.createPsbtForNetwork({ network });
  const parsedControlBlock = taproot.parseControlBlock(eccLib, controlBlock);
  const leafHash = taproot.getTapleafHash(eccLib, parsedControlBlock, payment.redeem?.output as Buffer);

  psbt.addInput({
    hash: Buffer.alloc(32),
    index: 0,
    witnessUtxo: { script: commitOutput, value: BigInt(100_000) },
    tapLeafScript: [
      {
        controlBlock,
        script: payment.redeem?.output as Buffer,
        leafVersion: taproot.INITIAL_TAPSCRIPT_VERSION,
      },
    ],
  });
  psbt.addOutput({ script: commitOutput, value: BigInt(10_000) });

  psbt.signTaprootInput(
    0,
    {
      publicKey: Buffer.alloc(32),
      signSchnorr(hash: Buffer): Buffer {
        // dummy schnorr-sized signature
        return Buffer.alloc(64);
      },
    },
    [leafHash]
  );

  psbt.finalizeTapInputWithSingleLeafScriptAndSignature(0);
  return psbt.extractTransaction(/* disableFeeCheck */ true).virtualSize();
}

/**
 * @param pubkey
 * @param contentType
 * @param inscriptionData
 * @param network
 * @returns PreparedInscriptionRevealData
 */
export function createInscriptionRevealData(
  pubkey: Buffer,
  contentType: string,
  inscriptionData: Buffer,
  network: Network
): PreparedInscriptionRevealData {
  const payment = createPaymentForInscription(pubkey, contentType, inscriptionData);

  const { output: commitOutput, controlBlock } = payment;
  assert(commitOutput);
  assert(controlBlock);
  assert(payment.redeem?.output);
  const commitAddress = address.fromOutputScript(commitOutput, network);

  const tapLeafScript: utxolib.bitgo.TapLeafScript[] = [
    {
      controlBlock,
      script: payment.redeem?.output,
      leafVersion: taproot.INITIAL_TAPSCRIPT_VERSION,
    },
  ];
  const revealTransactionVSize = getInscriptionRevealSize(payment, controlBlock, commitOutput, network);

  return {
    address: commitAddress,
    revealTransactionVSize,
    tapLeafScript: tapLeafScript[0],
  };
}

/**
 * @param pubkey
 * @param contentType
 * @param inscriptionData
 * @returns inscription address
 */
export function createOutputScriptForInscription(pubkey: Buffer, contentType: string, inscriptionData: Buffer): Buffer {
  const payment = createPaymentForInscription(pubkey, contentType, inscriptionData);

  assert(payment.output, 'Failed to create inscription output script');
  return payment.output;
}

/**
 *
 * @param privateKey
 * @param tapLeafScript
 * @param commitAddress
 * @param recipientAddress
 * @param unsignedCommitTx
 * @param network
 *
 * @return a fully signed reveal transaction
 */
export function signRevealTransaction(
  privateKey: Buffer,
  tapLeafScript: utxolib.bitgo.TapLeafScript,
  commitAddress: string,
  recipientAddress: string,
  unsignedCommitTx: Buffer,
  network: Network
): utxolib.bitgo.UtxoPsbt {
  const unserCommitTxn = utxolib.bitgo.createTransactionFromBuffer(unsignedCommitTx, network);
  const hash = unserCommitTxn.getHash();
  const commitOutput = utxolib.address.toOutputScript(commitAddress, network);
  const vout = unserCommitTxn.outs.findIndex((out) => out.script.equals(commitOutput));

  if (vout === -1) {
    throw new Error('Invalid commit transaction');
  }

  const psbt = bitgo.createPsbtForNetwork({ network });
  psbt.addInput({
    hash,
    index: vout,
    witnessUtxo: { script: commitOutput, value: BigInt(unserCommitTxn.outs[vout].value) },
    tapLeafScript: [tapLeafScript],
  });

  const recipientOutput = address.toOutputScript(recipientAddress, network);
  psbt.addOutput({ script: recipientOutput, value: BigInt(10_000) });

  const signer = ECPair.fromPrivateKey(privateKey);
  const parsedControlBlock = taproot.parseControlBlock(eccLib, tapLeafScript.controlBlock);
  const leafHash = taproot.getTapleafHash(eccLib, parsedControlBlock, tapLeafScript.script as Buffer);
  psbt.signTaprootInput(0, signer, [leafHash]);

  return psbt;
}

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


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