bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.68k stars 2.1k forks source link

Error when send tx from a taproot wallet #1765

Closed code4trade closed 5 months ago

code4trade commented 2 years ago

I sent a transaction to a new taproot address (bc1p...) and when i send back the amount from this new address, i received the error "Witness program was passed an empty witness" from node.

I'm using the simple keyspend example to sign and build the transaction. I think is something wrong with the vout and/or scriptPubkeys...

Here's the code:

// Run this whole file as async
// Catch any errors at the bottom of the file
// and exit the process with 1 error code
(async () => {
// Order of the curve (N) - 1
const N_LESS_1 = Buffer.from(
  'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
  'hex'
);
// 1 represented as 32 bytes BE
const ONE = Buffer.from(
  '0000000000000000000000000000000000000000000000000000000000000001',
  'hex'
);
const crypto = require('crypto');
const bitcoin = require('bitcoinjs-lib');
const ecc = await import('tiny-secp256k1');
const ecpair = require('ecpair');

const tinysecp = require('tiny-secp256k1');
const ECPair = ecpair.ECPairFactory(tinysecp);

var myKey = ECPair.fromWIF('my_priv');

const output = createKeySpendOutput(Buffer.from('pubkey'));
const amount = 100000;

const sendAmount = 95000;

const tx = createSigned(
  myKey,
  'input_tx_id_hex',
  0,
  sendAmount,
  [output],
  [amount]
);

const hex = tx.toHex();
console.log('tx hex:');
console.log(hex);
// Function for creating a tweaked p2tr key-spend only address
// (This is recommended by BIP341)
function createKeySpendOutput(publicKey) {
  // x-only pubkey (remove 1 byte y parity)
  const myXOnlyPubkey = publicKey.slice(1, 33);
  const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey);
  const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash);
  if (tweakResult === null) throw new Error('Invalid Tweak');
  const { xOnlyPubkey: tweaked } = tweakResult;
  // scriptPubkey
  return Buffer.concat([
    // witness v1, PUSH_DATA 32 bytes
    Buffer.from([0x51, 0x20]),
    // x-only tweaked pubkey
    tweaked,
  ]);
}
// Function for signing for a tweaked p2tr key-spend only address
// (Required for the above address)
function signTweaked(messageHash, key) {
  const privateKey =
    key.publicKey[0] === 2
      ? key.privateKey
      : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey), ONE);
  const tweakHash = bitcoin.crypto.taggedHash(
    'TapTweak',
    key.publicKey.slice(1, 33)
  );
  const newPrivateKey = ecc.privateAdd(privateKey, tweakHash);
  if (newPrivateKey === null) throw new Error('Invalid Tweak');
  return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32));
}
// Function for creating signed tx
function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
  const tx = new bitcoin.Transaction();
  tx.version = 2;
  // Add input
  tx.addInput(Buffer.from(txid, 'hex').reverse(), vout);
  // Add output
  tx.addOutput(scriptPubkeys[0], amountToSend);
  const sighash = tx.hashForWitnessV1(
    0, // which input
    scriptPubkeys, // All previous outputs of all inputs
    values, // All previous values of all inputs
    bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
  );
  const signature = Buffer.from(signTweaked(sighash, key));
  // witness stack for keypath spend is just the signature.
  // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value
  tx.ins[0].witness = [signature];
  return tx;
}
})().catch((err) => {
  console.error(err);
  process.exit(1);
});
junderw commented 2 years ago

Everything looks fine.

is the first output script of the tx at input_tx_id_hex the same script as the output script you calculated?

junderw commented 2 years ago

Also, this bears repeating, do not use real bitcoins when testing out your wallet.

That is what testnet is for.

jasonandjay commented 5 months ago

It seems to have been solved