Fireblocks allows you to verify ETH transactions before they are signed by the API Co-signer. You can configure your workspace to receive the raw data of the ETH transaction as part of the payload sent to the Callback Handler. In this article, we are going to cover how to verify Ethereum raw transactions.Documentation Index
Fetch the complete documentation index at: https://developers.fireblocks.com/llms.txt
Use this file to discover all available pages before exploring further.
ETH - Callback Handler payload structure
First, let’s take a look at the payload that is sent from the Co-signer to the Callback Handler (a detailed spec can be found here):-
Amount (
destinations[0].amountNative) -
Destination Address (
destinations[0].displayDstAddress) -
Raw Transaction array (
rawTx) RLP encoded payload (rawTx.rawTx) The hash of the raw transaction (rawTx.payload)
rawTx.rawTx) is the actual payload that you are signing on, or to be precise, the signature is done over the keccak256 hash of the RLP encoded payload, which is exactly the hash provided in the rawTx.payload property.
Creating the Callback Handler application
Before diving into the verification of the ETH transaction process, let’s start with spinning up our Callback Handler server. In this example I am going to use the Express.js framework: Install Express:npm i express
We will also need some additional packages to be installed:
npm i jsonwebtoken fs body-parser @ethereumjs/tx
Initiating the app:
/v2/tx_sign_request endpoint:
rawBody of the HTTP request is obtained, let’s break down the process:
Mutual Authentication via Signed JWTs:
- The Fireblocks API Co-signer does not transmit the payload in plain text. Instead, it employs an authentication mechanism using signed JSON Web Tokens (JWTs) for secure communication.
- Each request sent by the Co-signer to the Callback Handler includes a signed JWT in the request body. This JWT is signed using the Co-signer application’s private key.
- Similarly, any response from the Callback Handler to the Co-signer must also be signed using the Callback Handler’s private key.
- To enable this mutual authentication, both the Callback Handler and the Co-signer must exchange their respective public keys.
- The Callback Handler should have access to:
- Its own private key for signing responses.
- The Co-signer’s public key to verify incoming requests.
- The Co-signer requires:
- Its private key for signing requests.
- The Callback Handler’s public key to validate responses.
- Callback Handler’s private and public keys.
- Co-signer’s public key.
Note that the signature part of the JWT is obfuscated as a security best practice.If you take this JWT and parse it in jwt.io - you’ll get the same clear text JSON payload as at the beginning of this guide.
JWT verification
So what should we do with this JWT? We need to verify its signature by using the Co-Signer public key and we need to decode it to JSON:Building the ETH transaction object
So now we have set the authentication, and if the token is verified, we can actually move on to the transaction validation part.Let’s define a new function, validateETHTransaction. It will get the plain text payload and will build an ETH transaction object from the RLP encoded hex:FeeMarketEIP1559Transaction class from @ethereumjs/tx.
If we print the unsignedTx object, we’ll get:
Verifying the transaction parameters
Now we can actually check whether the destination and the amount in the decoded transaction match the JSON values of the payload:Note that the address in the payload is in checksum format hence we need to lower case itWe can actually add one more check - verify that the hash of the unsignedTx matches the provided hash in the payload:
unsignedTx.getMessageToSign(true). This method returns the hash of the unsigned transaction that is expected to be signed.
Callback Handler response
The response from the Callback Handler should be in the following format (signed withRS256 algorithm by using the Callback Handler’s private key):