Signing an Arbitrary Hash
This page demonstrates how to request and retrieve a signature for an arbitrary 32-byte hash using the ChainSignatures
contract deployed on Sepolia.
Set Up Your Clients and Contract
Ensure your environment is configured (e.g., .env
containing your PRIVATE_KEY
).
import { chainAdapters, constants, contracts } from "signet.js";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import dotenv from 'dotenv';
dotenv.config();
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const publicClient = createPublicClient({ chain: sepolia, transport: http() });
const walletClient = createWalletClient({ account, chain: sepolia, transport: http() });
const evmChainSigContract = new contracts.evm.ChainSignatureContract({
publicClient,
walletClient,
contractAddress: constants.CONTRACT_ADDRESSES.ETHEREUM.TESTNET_DEV as `0x${string}`,
});
Request Signature for an Arbitrary Hash
Prepare your 32-byte payload (hash of your message) and choose an arbitrary path
:
import { keccak256, stringToBytes, toBytes } from "viem";
const message = "dontcare";
const payload = Array.from(toBytes(keccak256(stringToBytes(message))));
const path = "randomPath";
const rsvSignature = await evmChainSigContract.sign({
payload,
path,
key_version: 0,
});
At this stage, the MPC network processes your signature request off-chain, subsequently calling the respond()
method on-chain.
Retrieve and Validate the Signature
After the MPC responds, retrieve your signature components:
console.log('r:', rsvSignature.r);
console.log('s:', rsvSignature.s);
console.log('v:', rsvSignature.v);
Verify the Signature
You can verify the signature to ensure it was correctly signed:
import { concat, padHex, recoverAddress } from "viem";
const messageHash = keccak256(stringToBytes(message));
const signature = concat([
padHex(`0x${rsvSignature.r}`, { size: 32 }),
padHex(`0x${rsvSignature.s}`, { size: 32 }),
`0x${rsvSignature.v.toString(16)}`,
]);
const recoveredAddress = await recoverAddress({
hash: messageHash,
signature,
});
const evm = new chainAdapters.evm.EVM({ publicClient, contract: evmChainSigContract });
const { address: expectedAddress } = await evm.deriveAddressAndPublicKey(account.address, path);
console.log("Recovered address:", recoveredAddress);
console.log("Original address:", expectedAddress);
console.log("Signature is valid:", recoveredAddress.toLowerCase() === expectedAddress.toLowerCase());
Complete Code Example
For a complete working example, see the full implementation here.
This approach ensures secure, verifiable signatures for arbitrary payloads through the ChainSignatures smart contract.