bitcoinjs-lib
bitcoinjs-lib copied to clipboard
Error when send tx from a taproot wallet
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);
});
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?
Also, this bears repeating, do not use real bitcoins when testing out your wallet.
That is what testnet is for.
It seems to have been solved