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: 1,
})

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 keyVersion = 1
const { address: expectedAddress } = await evm.deriveAddressAndPublicKey(
  account.address,
  path,
  keyVersion
)
 
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.