The Raw Signing feature in the Fireblocks Recovery Tool lets you securely sign raw messages or transaction hashes with your recovered private keys without ever exposing your keys to an online environment.
This feature provides flexibility for advanced recovery scenarios, such as manually broadcasting transactions or interacting with smart contracts outside the usual Fireblocks workflow.
Before you begin
- Recover your private keys using the Utility app.
- Choose whether you want to:
- Generate a signature for a hashed message, or-
- Sign a QR code generated by the Relay app
Option 1: Generate a signature
- In the Recovery Tool, select a wallet from your restored vault accounts or select a derivation path and signing algorithm (ECDSA or EDDSA).
- Paste the message or transaction hash you want to sign.
- Select Sign Message. A signature will appear in a modal window.
- EDDSA (e.g. Solana): Paste your Solana message into the tool and generate a signature. Import the signature into an online machine, add it to the transaction, and broadcast to the blockchain.
- ECDSA (e.g. Ethereum): Paste the Ethereum transaction hash, generate the (r+s+v) signature, and apply it to your unsigned transaction before broadcasting.
Option 2: Sign via QR code
- In the Relay app, go to Raw Sign.
- Select a wallet or derivation path and enter the message you want to sign.
- A QR code will be generated.
- In the Utility app (offline), select Sign via QR and scan the QR code. Your private key stays secure and never leaves the offline device.
- A new QR code containing the signature is then generated.
- Scan this signature QR code back in the Relay app to complete the process.
- The signed message will appear, ready for broadcasting.
Using unsigned messages
If you need to prepare unsigned transactions for signing:
- For Solana, you can create an unsigned transfer transaction (or other Solana transactions) using the Solana SDK. Once done, sign it offline with the Recovery Tool and broadcast it once signed.
- For Ethereum, you can prepare role-change or contract transactions. Use the Recovery Tool to sign the transaction hash and then attach the r+s+v signature components before broadcasting.
Code examples
Solana
This is a simple example of a Solana transfer transaction, provided to demonstrate how to generate an unsigned message that can be signed using the new Raw Signing feature. While this example uses a basic SOL transfer, it serves only as a generic reference; users can create any other type of Solana transaction or custom signature request and use the Raw Signing process to securely sign it offline.
Install the necessary SDK package
npm install @solana/web3.jsGenerate an unsigned message
import * as web3 from "@solana/web3.js";
const generateMessage = async () => {
const connection = new web3.Connection(
web3.clusterApiUrl("mainnet-beta"),
"confirmed"
);
const tx = new web3.Transaction();
const from = new web3.PublicKey(
"H682Lm8MgXVNz5u9hP7Wh6JM257uKd5sn3CGiHa7eJBD" // the solana wallet address from the recovery tool
);
tx.add(
web3.SystemProgram.transfer({
fromPubkey: from,
toPubkey: new web3.PublicKey(
"5Q1CJ9ERHt4hj3bSymkg8Dd924891Ky4mRDyUENVddE9"// the solana address of the recipient
),
lamports: web3.LAMPORTS_PER_SOL, // 1 SOL
})
);
const recentBlockhash = await connection.getLatestBlockhash();
tx.recentBlockhash = recentBlockhash.blockhash;
tx.feePayer = from;
const messageToSign = tx.serializeMessage();
console.log("Message to sign:", messageToSign.toString("hex"));
console.log(
"Transaction: ",
tx.serialize({ verifySignatures: false }).toString("hex")
);
console.log("Signer Address", from.toBase58());
return {
transaction: tx,
connection: connection,
serializedTx: tx.serialize({ verifySignatures: false }).toString("hex"),
};
};Add a signature to the message
const addSignature = async (
tx: String,
signatureHex: string,
signerAddress: string
) => {
const newTx = web3.Transaction.from(Buffer.from(tx, "hex"));
const pubkey = new web3.PublicKey(signerAddress);
console.log("New Transaction: ", newTx.serializeMessage().toString("hex"));
newTx.addSignature(pubkey, Buffer.from(signatureHex, "hex"));
console.log("Verified Transaction: ", newTx.verifySignatures(true));
return newTx;
};Broadcast the message to the blockchain
const broadcastTransaction = async (
connectionUrl: string,
signedTransactionHex: string
) => {
try {
const connection = new web3.Connection(connectionUrl, "confirmed");
const signedTransaction = web3.Transaction.from(
Buffer.from(signedTransactionHex, "hex")
);
console.log("Broadcasting transaction to Solana blockchain...");
// Send the transaction
const signature = await connection.sendRawTransaction(
signedTransaction.serialize(),
{
skipPreflight: false, // Set to true to skip preflight checks
preflightCommitment: "confirmed",
maxRetries: 3,
}
);
console.log("Transaction sent! Signature:", signature);
console.log(
`View on Solana Explorer: https://explorer.solana.com/tx/${signature}`
);
// Wait for confirmation
console.log("Waiting for confirmation...");
const confirmation = await connection.confirmTransaction({
signature: signature,
blockhash: signedTransaction.recentBlockhash!,
lastValidBlockHeight: (
await connection.getLatestBlockhash()
).lastValidBlockHeight,
});
if (confirmation.value.err) {
throw new Error(
`Transaction failed: ${JSON.stringify(confirmation.value.err)}`
);
}
console.log("Transaction confirmed!");
return {
signature: signature,
confirmed: true,
explorerUrl: `https://explorer.solana.com/tx/${signature}`,
confirmation: confirmation,
};
} catch (error) {
console.error("Error broadcasting transaction:", error);
throw error;
}
};Ethereum
This section demonstrates how to use the Raw Signing feature to authorize a role change on a smart contract using an ECDSA signature. Fireblocks' Tokenization Engine provides a customizable smart contract template for ERC-20 tokens, known as ERC20F. This template supports enterprise-grade controls such as granular role management and permission delegation. To learn more about the ERC20F contract see this article.
To test a disaster recovery (DR) scenario, you can use the grantRole method of the ERC20F contract. This method allows you, as administrator, to add, remove, or change the roles assigned to specific addresses, which enables or restricts their ability to perform certain actions, such as minting, burning, or pausing tokens.
Below is a list of common role IDs used in the ERC20F contract:
- DEFAULT_ADMIN_ROLE: 0x0000000000000000000000000000000000000000000000000000000000000000
- MINTER_ROLE: 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6
- BURNER_ROLE: 0x3c11d16cbaffd01df69ce1c404f6340ee057498f5f00246190ea54220576a848
- PAUSER_ROLE: 0x65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a
- RECOVERY_ROLE: 0x0acf805600123ef007091da3b3ffb39474074c656c127aa68cb0ffec232a8ff8
This example guides you through preparing a raw transaction for a role change, generating the hash for offline signing, and then using the Recovery Tool to sign and broadcast the transaction securely.
- To sign and broadcast an Ethereum transaction, it is necessary to use the following functions and the recovery tool simultaneously, due to nonce changes affecting the transaction hash. Therefore, the process will obtain the signature components (r, s, and v) via CLI (Console).
- Gather your contract address, the previous admin address, and the new admin address.
Run the code example as explained in the Github repository instructions file:
Enter your ERC20F contract address
Enter the sender’s address
Enter the recipient’s address
Select the role to grant
Get the hash from the console and paste it into the recovery tool
Add signature from the Recovery Utility, serialize the transaction, and broadcast it