Skip to main content

Signing transactions

Advanced
Ethereum
Tutorial

Before a transaction can be sent to the Ethereum network, it must be signed and formatted into a raw ETH transaction. Transactions are signed with threshold ECDSA. For this example, the transaction standard EIP1559 will be used.

Build a transaction

First, a raw EIP1559 ETH transaction must be built containing the transaction's metadata, such as gas fee, sender, receiver, and transaction data. Below is a programmatic example of how to build a transaction using Rust:

packages/ic-evm-utils/src/eth_send_raw_transaction.rs
loading...

Format, hash, and sign a transaction

Ethereum EIP1559 transactions are first hashed with the Keccak256 algorithm and then signed using the private key. Below is an example written in Rust demonstrating how to format a raw ETH transaction, hash it using Keccak256 and sign the hash using threshold ECDSA. This code snippet accomplishes the following:

  • Formats the transaction.

  • Hashes the transaction using Keccak256.

  • Signs the Keccak hash.

  • Rebuilds the transaction using the VRS signature.

packages/ic-evm-utils/src/evm_signer.rs
pub async fn sign_eip1559_transaction(
tx: Eip1559TransactionRequest,
key_id: EcdsaKeyId,
derivation_path: Vec<Vec<u8>>,
) -> SignedTransaction {
const EIP1559_TX_ID: u8 = 2;

let ecdsa_pub_key =
get_canister_public_key(key_id.clone(), None, derivation_path.clone()).await;

let mut unsigned_tx_bytes = tx.rlp().to_vec();
unsigned_tx_bytes.insert(0, EIP1559_TX_ID);

let txhash = keccak256(&unsigned_tx_bytes);

let signature = sign_with_ecdsa(SignWithEcdsaArgument {
message_hash: txhash.to_vec(),
derivation_path,
key_id,
})
.await
.expect("failed to sign the transaction")
.0
.signature;

let signature = Signature {
v: y_parity(&txhash, &signature, &ecdsa_pub_key),
r: U256::from_big_endian(&signature[0..32]),
s: U256::from_big_endian(&signature[32..64]),
};

let mut signed_tx_bytes = tx.rlp_signed(&signature).to_vec();
signed_tx_bytes.insert(0, EIP1559_TX_ID);

SignedTransaction {
tx_hex: format!("0x{}", hex::encode(&signed_tx_bytes)),
tx_hash: format!("0x{}", hex::encode(keccak256(&signed_tx_bytes))),
}
}

Additional examples of signing transactions with threshold ECDSA can be found in the threshold ECDSA documentation.

Submit transaction

Now that your transaction is signed, it can be submitted to Ethereum to be executed.

Learn how to submit Ethereum transactions.