Bitcoin Transaction

Examples showing how to do Bitcoin transactions with the TSM

To carry out a Bitcoin transaction using the TSM, ensure you've installed the necessary Bitcoin NPM dependencies. Here's an example of Bitcoin NPM dependencies:

{
  "dependencies": {
    "@sepior/tsm": "^latest",
    "asn1.js": "^5.4.1",
    "bitcoinjs-lib": "^6.1.3",
    "ecpair": "^2.1.0",
    "tiny-secp256k1": "^2.2.3"
  }
}

After successfully installing the Bitcoin NPM dependencies, you can now proceed with a Bitcoin transaction using TSM. Here's an example of how to execute a Bitcoin transaction with TSM:

const fs = require("fs");
const bitcoin = require('bitcoinjs-lib');
const testnet = bitcoin.networks.testnet;

const ECPairFactory = require('ecpair');
const ecc = require('tiny-secp256k1');
const asn = require("asn1.js");
const {TSMClient, algorithms} = require("@sepior/tsm");
const ECPair = ECPairFactory.ECPairFactory(ecc);
const credsRaw = fs.readFileSync("creds.json");
const creds = JSON.parse(credsRaw);


async function example() {
    // this is the info from the transaction that created the UTXO we want to spend
    const utxo_transactionId = '072ca1827060d9867e82842ae6cca6f56de02c1db09838410713750d13e04b1b';
    const utxo_index = 1;
    const utxo_value = 1102453;
    const from_testnet_address = 'mhmRiCf3ZjzpGVzvy8HQDkLhFSCsJy52fp';

    // this is the to-address
    const to_testnet_address = 'mjWbTCFBGbbvPR2eRk5JkoLrSNLe4xoQwS';

    const send_amount = 20000; // send 0.0002 btc which is 20000 satoshi;
    let TRANSACTION_FEE = 1470;
    const change_amount = utxo_value - send_amount - TRANSACTION_FEE;

    const rawTransaction = new bitcoin.Psbt({network: testnet});
    let rawTx =
        "02000000012e53b492c7e90ba70723ae4fcf0dfada206f0b6ab4c0686c253641554142e374000000006a4730440220213912ba7ec26d18290b4dfa9c1b719916284bdd4a81fad0d34a2ea7faba9232022065af566333f357ba3d5dcd2eb55248be3e61c42141683f3d3e020f3c3877d3b30121032fde6e073384cf87761b4274b2d5096f64a71ae328218514c436cc97b7f8d292fdffffff02eb6fd192030000001976a91499be3ab045896f8fcce4e3e46b3786cded164f8988ac75d21000000000001976a91418acf606cd2b44f02270bcf468e467b5abca5e7f88ac74c62500";
//above  From https://blockstream.info/testnet/api/tx/072ca1827060d9867e82842ae6cca6f56de02c1db09838410713750d13e04b1b/hex


    rawTransaction.addInput({
        hash: utxo_transactionId,
        index: utxo_index,
        nonWitnessUtxo: Buffer.from(rawTx, "hex"),
    });

    rawTransaction.addOutput({
        address: to_testnet_address,
        value: send_amount,
    });

    rawTransaction.addOutput({
        address: from_testnet_address,
        value: change_amount,
    });


    let playerCount = 3;
    let threshold = 1;
    const tsmClient1 = await TSMClient.init(playerCount, threshold, creds.creds1);
    const tsmClient2 = await TSMClient.init(playerCount, threshold, creds.creds2);
    const tsmClient3 = await TSMClient.init(playerCount, threshold, creds.creds3);

    // Use an existing key
    const keyID = "SOMEID";

    const addressChainPath = new Uint32Array([49, 1, 0, 0, 0]);

    let [pk, pkDER] = await tsmClient1.publicKey(algorithms.ECDSA, keyID, addressChainPath);
    const pkCompressed = await pk2Sec1Compressed(tsmClient1, pkDER);
    const pkRaw = pk.export({format: "der", type: "spki"});
    pkBytes = pkRaw.slice(23); // Remove RFC 5280 prefix

    sessionNum = generateRandomNumber(43);
    sessionID = "b"+sessionNum; // sessionID should not be reused, reusing it on subsequent retries can cause errors
    presigCount = 1; // In production, many presignatures should be generated at once
    results = await Promise.all([
        tsmClient1.presigGenWithSessionID(algorithms.ECDSA, sessionID, keyID, presigCount),
        tsmClient2.presigGenWithSessionID(algorithms.ECDSA, sessionID, keyID, presigCount),
        tsmClient3.presigGenWithSessionID(algorithms.ECDSA, sessionID, keyID, presigCount),
    ]);

    presigIDs = results[0]; // In production, validate that all players return the same IDs

    const signer = {
        network: testnet,
        publicKey: pkCompressed,
        sign: async ($hash) => {
            const [partialSignature1, ,] = await tsmClient1.partialSignWithPresig(algorithms.ECDSA, keyID, presigIDs[0], addressChainPath, $hash);
            const [partialSignature2, ,] = await tsmClient2.partialSignWithPresig(algorithms.ECDSA, keyID, presigIDs[0], addressChainPath, $hash);
            const [partialSignature3, ,] = await tsmClient3.partialSignWithPresig(algorithms.ECDSA, keyID, presigIDs[0], addressChainPath, $hash);

            let [signature,] = await tsmClient1.finalize(algorithms.ECDSA, [partialSignature1, partialSignature2, partialSignature3]);
            console.log("Signature :", Buffer.from(signature).toString('hex'))

            var ECSignature = asn.define('ECSignature', function () {
                this.seq().obj(
                    this.key('R').int(),
                    this.key('S').int()
                );
            });

            var ecSignature = ECSignature.decode(Buffer.from(signature), 'der');
            srSignature = ecSignature.R.toString('hex').padStart(64, '0') + ecSignature.S.toString('hex').padStart(64, '0');
            console.log(srSignature.length);

            console.log(Buffer.from(srSignature, 'hex').toString("hex"));
            console.log(Buffer.from(srSignature, 'hex').length);
            return Buffer.from(srSignature, 'hex');
        }
    };

    await rawTransaction.signInputAsync(0, signer);

    const validator = (pubkey, msghash, signature) => ECPair.fromPublicKey(pubkey).verify(msghash, signature);

    var retval = rawTransaction.validateSignaturesOfInput(0, validator);
    console.log('validation check = ' + retval);

    rawTransaction.finalizeAllInputs();

    const signed_tx = rawTransaction.extractTransaction().toHex();
    console.log('Signed Raw Transaction:', signed_tx);
}

async function pk2Sec1Compressed(tsmClient, pk) {
// The public key pk returned from the TSM is in DER format.
// We can convert to compressed format like this.
// See, e.g., https://matthewdowney.github.io/compress-bitcoin-public-key.html.
    let [curveName, pubX, pubY] = await tsmClient.parsePublicKey(algorithms.ECDSA, pk);
    let xBytes = Buffer.from(pubX.buffer);
    let prefix = (pubY[31] & 0x01) === 0x00 ? Buffer.from([0x02]) : Buffer.from([0x03]);
    return Buffer.concat([prefix, xBytes]);
}

function generateRandomNumber(n) {
    var add = 1, max = 12 - add; // 12 is the min safe number Math.random() can generate without it starting to pad the end with zeros.
    if ( n > max ) {
        return generateRandomNumber(max) + generateRandomNumber(n - max);
    }
    max = Math.pow(10, n+add);
    var min = max/10; // Math.pow(10, n) basically
    var number = Math.floor( Math.random() * (max - min + 1) ) + min;
    return ("" + number).substring(add);
}

example();