Cardano Transaction Signing

To sign and broadcast the transaction to the Cardano network, follow these steps:

  1. Prepare unsigned transaction in Base64 encrypted format.

  2. Sign the transaction using the following code:

import {
    Bip32PrivateKey,
    Transaction,
    TransactionHash,
    TransactionWitnessSet,
    Vkey,
    Vkeywitness,
    Vkeywitnesses,
} from "@emurgo/cardano-serialization-lib-nodejs";
import { mnemonicToEntropy } from "bip39";
import { blake2b } from "blakejs";

const MNEMONIC = "<MNEMONIC>";
const RAW_TX_HEX = "<RAW_TX_HEX>";

function deriveKey(mnemonic: string, role: number, index = 0): Bip32PrivateKey {
    const entropy = Buffer.from(mnemonicToEntropy(mnemonic), "hex");
    const rootKey = Bip32PrivateKey.from_bip39_entropy(entropy, Buffer.from(""));

    return rootKey
        .derive(1852 | 0x80000000) // purpose
        .derive(1815 | 0x80000000) // coin type
        .derive(0 | 0x80000000)    // account 0
        .derive(role)              // role: 0=payment, 2=stake
        .derive(index);            // index
}

function signTransactionHex(
    unsignedHex: string,
    paymentKey: Bip32PrivateKey,
    stakeKey: Bip32PrivateKey
): string {
    const tx = Transaction.from_bytes(Buffer.from(unsignedHex, "hex"));
    const txHashBytes = blake2b(tx.body().to_bytes(), undefined, 32);
    const txHash = TransactionHash.from_bytes(txHashBytes);

    const witnesses = Vkeywitnesses.new();

    // Payment key witness
    const rawPaymentKey = paymentKey.to_raw_key();
    witnesses.add(
        Vkeywitness.new(
            Vkey.new(rawPaymentKey.to_public()),
            rawPaymentKey.sign(txHash.to_bytes())
        )
    );

    // Stake key witness
    const rawStakeKey = stakeKey.to_raw_key();
    witnesses.add(
        Vkeywitness.new(
            Vkey.new(rawStakeKey.to_public()),
            rawStakeKey.sign(txHash.to_bytes())
        )
    );

    const witnessSet = TransactionWitnessSet.new();
    witnessSet.set_vkeys(witnesses);

    const signedTx = Transaction.new(tx.body(), witnessSet, tx.auxiliary_data());
    return Buffer.from(signedTx.to_bytes()).toString("hex");
}

async function main() {
    if (!RAW_TX_HEX || RAW_TX_HEX.length < 10) {
        throw new Error("❌ RAW_TX_HEX is missing or invalid.");
    }

    const paymentKey = deriveKey(MNEMONIC, 0); // m/1852'/1815'/0'/0/0
    const stakeKey = deriveKey(MNEMONIC, 2);   // m/1852'/1815'/0'/2/0

    const signedHex = signTransactionHex(RAW_TX_HEX, paymentKey, stakeKey);

    console.log("✅ Signed Cardano Tx (hex):");
    console.log(signedHex);
}

main().catch(console.error);

Upon successful execution, the script prints the signed transaction in the hexadecimal format, ready to be broadcasted:

✅ Signed Cardano Tx (hex): ...
  1. Broadcast the transaction to the Cardano network by sending a POST request to /api/v1/unified/transaction/broadcast.

What's Next?