Signing
Signing Messages
Signing messages with a wallet is a fundamental security practice in a blockchain environment. It verifies ownership and ensures the integrity of data. Here's how to use the wallet.signMessage
method to sign messages:
import { hashMessage, Provider, Signer, WalletUnlocked } from 'fuels';
import { LOCAL_NETWORK_URL } from '../../env';
const provider = await Provider.create(LOCAL_NETWORK_URL);
const wallet = WalletUnlocked.generate({ provider });
const message = 'my-message';
const signedMessage = await wallet.signMessage(message);
// Example output: 0x277e1461cbb2e6a3250fa8c490221595efb3f4d66d43a4618d1013ca61ca56ba
const hashedMessage = hashMessage(message);
// Example output: 0x40436501b686546b7c660bb18791ac2ae35e77fbe2ac977fc061922b9ec83766
const recoveredAddress = Signer.recoverAddress(hashedMessage, signedMessage);
// Example output: Address {
// bech32Address: 'fuel1za0wl90u09c6v88faqkvczu9r927kewvvr0asejv5xmdwtm98w0st7m2s3'
// }
The wallet.signMessage
method internally hashes the message using the SHA-256 algorithm, then signs the hashed message, returning the signature as a hex string.
The hashMessage
helper gives us the hash of the original message. This is crucial to ensure that the hash used during signing matches the one used during the address recovery process.
The recoverAddress
method from the Signer
class takes the hashed message and the signature to recover the signer's address. This confirms that the signature was created by the holder of the private key associated with that address, ensuring the authenticity and integrity of the signed message.
Signing Transactions
Signing a transaction involves using your wallet to sign the transaction ID (also known as transaction hash) to authorize the use of your resources. Here's how it works:
Generate a Signature
: Using the wallet to create a signature based on the transaction ID.Using the Signature on the transaction
: Place the signature in the transaction'switnesses
array. Each Coin / Message input should have a matchingwitnessIndex
. This index indicates your signature's location within thewitnesses
array.Security Mechanism
: The transaction ID is derived from the transaction bytes (excluding thewitnesses
). If the transaction changes, the ID changes, making any previous signatures invalid. This ensures no unauthorized changes can be made after signing.
The following code snippet exemplifies how a Transaction can be signed:
import {
Address,
Provider,
ScriptTransactionRequest,
Signer,
Wallet,
} from 'fuels';
import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../env';
const provider = await Provider.create(LOCAL_NETWORK_URL);
const sender = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);
const receiverAddress = Address.fromRandom();
const request = new ScriptTransactionRequest({
gasLimit: 10000,
});
request.addCoinOutput(receiverAddress, 1000, provider.getBaseAssetId());
const txCost = await sender.getTransactionCost(request);
request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;
await sender.fund(request, txCost);
const signedTransaction = await sender.signTransaction(request);
const transactionId = request.getTransactionId(provider.getChainId());
const recoveredAddress = Signer.recoverAddress(
transactionId,
signedTransaction
);
request.updateWitnessByOwner(recoveredAddress, signedTransaction);
const tx = await provider.sendTransaction(request);
await tx.waitForResult();
Similar to the sign message example, the previous code used Signer.recoverAddress
to get the wallet's address from the transaction ID and the signed data.
When using your wallet to submit a transaction with wallet.sendTransaction()
, the SDK already handles these steps related to signing the transaction and adding the signature to the witnesses
array. Because of that, you can skip this in most cases:
import { Address, Provider, ScriptTransactionRequest, Wallet } from 'fuels';
import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../env';
const provider = await Provider.create(LOCAL_NETWORK_URL);
const sender = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);
const receiverAddress = Address.fromRandom();
const request = new ScriptTransactionRequest({
gasLimit: 10000,
});
request.addCoinOutput(receiverAddress, 1000, provider.getBaseAssetId());
const txCost = await sender.getTransactionCost(request);
request.gasLimit = txCost.gasUsed;
request.maxFee = txCost.maxFee;
await sender.fund(request, txCost);
const tx = await sender.sendTransaction(request);
await tx.waitForResult();