Using @emurgo/cardano-serialization-lib-nodejs for creating and signing transaction

Hi guys,

I would like to figure out why signing key is not being used.
I’m getting ‘“transaction submit error ShelleyTxValidationError ShelleyBasedEraAlonzo (ApplyTxError [UtxowFailure (WrappedShelleyEraFailure (MissingVKeyWitnessesUTXOW (WitHashes (fromList [KeyHash “d0cb4b4c7e82380f2867ddd2a88c377aa04c4c70d6b373cca280f03e”]))))])”’

I have wallet manager class:


import { mnemonicToEntropy } from 'bip39';

import * as CardanoWasm from "@emurgo/cardano-serialization-lib-nodejs"

const entropy = mnemonicToEntropy(

    [ "fuel", "erode", "cotton", "pole", "unit", "grace", "forum", "shed", "solid", "corn", "nose", "play", "exit", "liquid", "case" ].join(' ')

  );

 

export const getPrivateKey = () => {

    const keyDetails = getKeyDetails();

    return keyDetails.privateKey;

}

export const getPublicKey = () => {

    const keyDetails = getKeyDetails();

    return keyDetails.publicKey;

}

export const getBaseAddress = () => {

    const keyDetails = getKeyDetails()

    const baseAddr = CardanoWasm.BaseAddress.new(

        CardanoWasm.NetworkInfo.testnet().network_id(),

        CardanoWasm.StakeCredential.from_keyhash(keyDetails.utxoPubKey.to_raw_key().hash()),

        CardanoWasm.StakeCredential.from_keyhash(keyDetails.stakeKey.to_raw_key().hash()),

      );

    return baseAddr;

}

const getKeyDetails = () => {

    const rootKey = CardanoWasm.Bip32PrivateKey.from_bip39_entropy(

        Buffer.from(entropy, 'hex'),

        Buffer.from(''),

        );

    const privateKey = rootKey.to_raw_key();

    const publicKey = rootKey.to_public().to_raw_key();

    const accountKey = rootKey

        .derive(harden(1852)) // purpose

        .derive(harden(1815)) // coin type

        .derive(harden(0)); // account #0

 

    const utxoPubKey = accountKey

        .derive(0) // external

        .derive(0)

        .to_public();

 

    const stakeKey = accountKey

        .derive(2) // chimeric

        .derive(0)

        .to_public();

    return { privateKey, publicKey, utxoPubKey, stakeKey }

}

function harden(num: number): number {

    return 0x80000000 + num;

}

and the code for sending funds from my wallet to some 3rd party address

import * as WASM_lib from "@emurgo/cardano-serialization-lib-nodejs"
import { fromHex, toHex } from "./helloworld";
import * as walletManager from "./walletManager"
import * as fs from "fs"
import fetch from "node-fetch";

const DESTINATION_ADDRESS = () =>
    WASM_lib.Address.from_bech32(
      "addr_test1wpwkkr2sh346h45lx852ffv8l64we0kkahdxe6l5zl70vnc9przaf"
    );

export async function lockFunds() {
    const fee = 600000;
    const amountToTransfer = 2000000; //2ADA
    const ownerAddress = walletManager.getBaseAddress().to_address().to_bech32();
    console.log(`owner address: ${ownerAddress}`);
    console.log(`hash ${walletManager.getPublicKey().hash()}`);
    console.log(`priv key bech32 ${walletManager.getPrivateKey().to_bech32()}`);
    const input = WASM_lib.TransactionInput.new(
        WASM_lib.TransactionHash.from_bytes(fromHex("364b50a7eda80f7a7dcca3d31c2bb13da25edf54e97374a8a6d0febeb8a11fb7")), 0
    );
    var output = WASM_lib.TransactionOutput.new(
      DESTINATION_ADDRESS(),
        WASM_lib.Value.new(WASM_lib.BigNum.from_str(amountToTransfer.toString()))
    )

    var outputOwner = WASM_lib.TransactionOutput.new(
      walletManager.getBaseAddress().to_address(),
        WASM_lib.Value.new(WASM_lib.BigNum.from_str((1000000000 - amountToTransfer - fee).toString()))
    )

    const transactionWitnessSet = WASM_lib.TransactionWitnessSet.new();
   const transactionInputs = WASM_lib.TransactionInputs.new();
   transactionInputs.add(input);
   const transactionOutputs = WASM_lib.TransactionOutputs.new();
   transactionOutputs.add(output);
   transactionOutputs.add(outputOwner);

   const requiredSigners = WASM_lib.Ed25519KeyHashes.new();
   requiredSigners.add(walletManager.getPublicKey().hash());
   const txBody = WASM_lib.TransactionBody.new(
       transactionInputs,
       transactionOutputs,
       WASM_lib.BigNum.from_str(fee.toString())
   );
  
   txBody.set_required_signers(requiredSigners);
   const transaction = WASM_lib.Transaction.new(
        WASM_lib.TransactionBody.from_bytes(txBody.to_bytes()),
        WASM_lib.TransactionWitnessSet.from_bytes(
            transactionWitnessSet.to_bytes()
        )
    );
  
   const serializedTx = toHex(transaction.to_bytes());
  
   const txVkeyWitnesses = await signTx(serializedTx);

   transactionWitnessSet.set_vkeys(txVkeyWitnesses.vkeys() as WASM_lib.Vkeywitnesses);
  
  const signedTx = WASM_lib.Transaction.new(
      WASM_lib.TransactionBody.from_bytes(txBody.to_bytes()),
      WASM_lib.TransactionWitnessSet.from_bytes(
          transactionWitnessSet.to_bytes()
      )
  );
  
  var transactionInBytes = signedTx.to_bytes();
   console.log("Full Tx Size", transactionInBytes.length);
   await fs.promises.writeFile('c:/s/cardano-nft-minter/tokens/AdamNFT/tx_body.bin', transactionInBytes);
   let result = await submitTx(toHex(transactionInBytes));
   console.log("tx submitted", result);
}

const signTx = async (
    tx,
  ) => {
    const rawTx = WASM_lib.Transaction.from_bytes(Buffer.from(tx, 'hex'));
  
    const txWitnessSet = WASM_lib.TransactionWitnessSet.new();
    const vkeyWitnesses = WASM_lib.Vkeywitnesses.new();
    const txHash = WASM_lib.hash_transaction(rawTx.body());
    const vkey = WASM_lib.make_vkey_witness(txHash, walletManager.getPrivateKey());
    vkeyWitnesses.add(vkey);
  
    txWitnessSet.set_vkeys(vkeyWitnesses);
    return txWitnessSet;
  };

  const submitTx = async (tx) => {
    const result = await blockfrostRequest(
      `/tx/submit`,
      { 'Content-Type': 'application/cbor' },
      Buffer.from(tx, 'hex')
    );
    return result;
  };

  const blockfrostRequest = async (endpoint, headers = null, body = null) => {
    return await fetch('https://cardano-testnet.blockfrost.io/api/v0' + endpoint, {
      headers: {
        project_id: "some_project_id",
        ...headers,
        "User-Agent": "some-marketplace",
      },
      method: body ? "POST" : "GET",
      body,
    }).then((res) => res.json())
    .catch(r => {
        console.log(r);

    })
  }

You need to sign the txBody with the input’s (UTxO’s) address’ private key and not with the root priv key.

So, the witness vkey is the exposed UTxO’s address public key and the
signature is the txBody signed by the same UTxO’s private key.

1 Like

Thanks a lot, it worked.

2 Likes

Hi @Adamuso this post is very helpful. Thanks for posting this.
Can you be kind enough to send the code and how to call it in nodejs.
Actually i am very new to this and would love to have some help with this.
Thanks a lot in advance

@Adamuso Can you pls share ./HelloWorld as well, it would be a great help

Hi,

I didn’t have time to clean the mess in the code.
However once you download it from here:

you can open it with vscode and run in terminal yarn install to install missing node_modules.
Once it is done you can press F5 to run it.
Currently code is just checking hashes.
If you want to play around with Martify market place you would have to use smartcontract.ts code.

Thanks @Adamuso
This helps a lot.