Skip to content

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.