@pulsadev/signature-utils
v0.1.1
Published
EIP-712 typed data signing, personal_sign, signature verification, ecrecover — zero dependencies, pure JS.
Readme
@pulsadev/signature-utils
EIP-712 typed data hashing, personal_sign, signature verification, ecrecover — zero dependencies, pure JS secp256k1.
Hash, sign, verify, and recover Ethereum signatures. No ethers, no viem, no native bindings.
Features
- EIP-712 typed data hashing — full struct encoding, domain separator, dependency resolution
- EIP-191 personal message hashing —
\x19Ethereum Signed Message:\nprefix - ecrecover — recover signer address from signature + message hash
- Signature verification — verify personal_sign and typed data signatures
- Address derivation — compute address from private key
- Pure JS secp256k1 — elliptic curve point multiplication, modular arithmetic, no native deps
- Signature parsing — split/join r, s, v components, EIP-2098 support
- Zero dependencies — ~27 KB bundled, ESM + CJS, pure TypeScript
Install
npm install @pulsadev/signature-utilsQuick Start
Verify a personal_sign signature
import { verifyMessage } from '@pulsadev/signature-utils'
const isValid = verifyMessage(
'Hello, world!',
'0x..signature...',
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
)Recover signer from signature
import { recoverPersonalSignAddress, ecrecover, hashPersonalMessage } from '@pulsadev/signature-utils'
// From personal_sign
const signer = recoverPersonalSignAddress('Hello', '0x..signature...')
// From raw hash
const hash = hashPersonalMessage('Hello')
const signer2 = ecrecover(hash, '0x..signature...')Hash EIP-712 typed data
import { hashTypedData } from '@pulsadev/signature-utils'
const hash = hashTypedData({
domain: {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
},
primaryType: 'Mail',
message: {
from: { name: 'Cow', wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' },
to: { name: 'Bob', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' },
contents: 'Hello, Bob!',
},
})Compute address from private key
import { computeAddress } from '@pulsadev/signature-utils'
const address = computeAddress('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')
// '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'Parse and join signatures
import { parseSignature, joinSignature } from '@pulsadev/signature-utils'
const { r, s, v, yParity } = parseSignature('0x...')
const sig = joinSignature({ r, s, v, yParity })API
Signature Operations
| Function | Description |
|----------|-------------|
| verifyMessage(message, signature, address) | Verify personal_sign signature |
| verifyTypedData(hash, signature, address) | Verify typed data signature |
| ecrecover(msgHash, signature) | Recover signer address from hash + sig |
| recoverPersonalSignAddress(message, signature) | Recover signer from personal_sign |
| recoverAddress(msgHash, signature) | Alias for ecrecover |
| computeAddress(privateKey) | Derive address from private key |
Hashing
| Function | Description |
|----------|-------------|
| hashPersonalMessage(message) | EIP-191 personal message hash |
| hashTypedData(typedData) | EIP-712 typed data hash |
| hashDomain(domain, types) | Hash domain separator |
| hashStruct(typeName, data, types) | Hash a struct |
| encodeType(typeName, types) | Encode type string |
Signature Parsing
| Function | Description |
|----------|-------------|
| parseSignature(sig) | Split into r, s, v, yParity |
| joinSignature(components) | Join r, s, v into 65 bytes |
| isValidSignature(sig) | Check if signature format is valid |
License
MIT © Yuto Nakamura
