bitcoinjs / bitcoinjs-lib

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

Error: Mandatory-script-verify-flag-failed (Invalid Schnorr signature) #2140

Closed iashishanand closed 3 months ago

iashishanand commented 4 months ago

I am trying to implement MuSig transaction using @cmdcode/musig2 and bitcoinjs-lib library. But I am getting this error constantly.

import * as musig from "@cmdcode/musig2";
import bitcoin from "bitcoinjs-lib";
import * as ecc from "tiny-secp256k1";
import { Buff } from "@cmdcode/buff";
import { schnorr } from "@noble/curves/secp256k1";
import { Buffer } from "node:buffer";
import { Transaction } from "bitcoinjs-lib";

bitcoin.initEccLib(ecc);
const TESTNET = bitcoin.networks.testnet;

// Convert the public key to x-only format for Taproot
const toXOnly = (pubKey) =>
    pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);

const wallets = [
    {
        name: "alice",
        sec_key: "6ccc11c46751edab5d9ba2acf68d0133fb67b68fa701c2ab8eddd3d98efe5595",
        pub_key: "633d066237862db2292981e8b1e191c15b6a853a8083160076a24168f83a9d57",
        sec_nonce:
            "cab3a3a5bf918e75e6835a37e8988a99b90e15d32efa67a31c5992a1d449754462a3d3468cb0d14c1c3eb4c3c3743c29c821ea2e969fcb847cff6ab137f4555f",
        pub_nonce:
            "33fce8b07af2a03c9ea546c2c231cc8a5668cb28dc95290b3650fdaeaa1ef571e1cc83c729b3838fadc9dd3d6fecf58115e890974e0187d3c75f1ca52cb3759a",
    },
    {
        name: "bob",
        sec_key: "126086e85273e375ec6a059266c92f30ba070696334633624ace6a3bf5777f4b",
        pub_key: "d6be41e01c23fb2c57c1664c58998710f88174a41f1c69fd27be91518a5a84dd",
        sec_nonce:
            "28b75dc04532f69c64ac974eae8d43bd5db60b10410fc4838ae63f80a255108baafbcc53735b698dfce5f57bbbcce233fd9bc220470be343ea2b2b77e17c471f",
        pub_nonce:
            "95f5b0ebb638e5f0d374c17c7fb3d63126a20ffb2ef1b81c2c0afeee04c5e8dc016e291f06ebc7748c7f5e084fd8c12b655b602ebe5001e627334a225fa34350",
    },
];

// Collect public keys and nonces from all signers.
const group_keys = wallets.map((e) => e.pub_key);
const group_nonces = wallets.map((e) => e.pub_nonce);

// Get the combined public key
const { group_pubkey } = musig.get_key_ctx(group_keys);
const internalPubkey = toXOnly(Buffer.from(group_pubkey, "hex"));

// Create a Taproot address from the x-only public key
const p2pktr = bitcoin.payments.p2tr({
    internalPubkey,
    network: TESTNET,
});

console.log("Taproot address : ", p2pktr.address);

const utxo = {
    txid: "c4ce128062c3d0165cbf58be8d02d771a578bb97864cc217a528d8b72a6554af",
    vout: 1,
    value: 500000,
};

const reciever = {
    value: 10000,
    address: p2pktr.address,
};

// Building a new transaction
let transaction = new Transaction();
transaction.version = 2;
transaction.addInput(Buffer.from(utxo.txid, "hex").reverse(), utxo.vout);
transaction.addOutput(bitcoin.address.toOutputScript(reciever.address, TESTNET), reciever.value);

const signatureHash = transaction.hashForWitnessV1(
    0, // Input index
    [p2pktr.output], // Prevouts
    [utxo.value], // Amounts
    Transaction.SIGHASH_DEFAULT // Sighash type
);

let message = signatureHash;
console.log("Message : ", message.toString('hex'));

// Combine all your collected keys into a signing session.
const ctx = musig.get_ctx(group_keys, group_nonces, Buff.from(message));

// Each member creates their own partial signature,
// using their own computed signing session.
const group_sigs = wallets.map((wallet) => {
    return musig.musign(ctx, wallet.sec_key, wallet.sec_nonce);
});

// Combine all the partial signatures into our final signature.
const signature = musig.combine_psigs(ctx, group_sigs);
console.log("Signature : ", signature);

let tapKeySig = Buffer.from(signature, 'hex');

// Check if the signature is valid.
const isValid = schnorr.verify(tapKeySig, message, internalPubkey);
if (isValid) {
    console.log("The signature is valid.");
} else {
    console.log("The signature is NOT valid.");
}

transaction.ins[0].witness = [tapKeySig];

// Broadcasting the transaction
const txHex = transaction.toHex();
console.log(`Broadcasting Transaction Hex: ${txHex}`);

I have also looked into this thread #2066 for help, but even trying the same thing is giving me error. Any help would be appreciated.

jasonandjay commented 3 months ago

Obviously, for taproot type addresses, we need to tweak the private key and sign it, and the same is true for verification.

// for sign
import * as btc from '@scure/btc-signer';
const tweakedPrivateKey = btc.taprootTweakPrivKey(fromInternalKey.privateKey);

// for validate
import {tweakKey } from 'bitcoinjs-lib/src/payments/bip341.js';
const isValid = schnorr.verify(tapKeySig, message, tweakKey(internalPubkey));