EIPs
EIP-1271 — Standard signature validation for contracts
EOAs sign with their private key; smart contracts can’t. EIP-1271 specifies how a contract validates a signature on its own behalf — the contract implements isValidSignature(hash, signature) which returns the magic value 0x1626ba7e for valid signatures.
import { parse } from "valibot";
import { AddressSchema, BytesSchema, Hash32Schema } from "@ethernauta/core";
import { verify_hash } from "@ethernauta/eip/1271";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
{ chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);
const address = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const hash = parse(Hash32Schema, "0x0000000000000000000000000000000000000000000000000000000000000001");
const signature = parse(BytesSchema, "0x");
const ok = await verify_hash({ address, hash, signature })(
reader({ chain_id: CHAIN_ID }),
);
// ok === true iff isValidSignature returned MAGIC_VALUE
void ok; Surface
| Export | Type | Purpose |
|---|---|---|
MAGIC_VALUE | Bytes4 | 0x1626ba7e — the success sentinel. |
verify_hash | Readable<boolean> | Call isValidSignature and compare. |
How it fits
verify_hash is the EIP-1271 verification primitive. For the common “verify a signed message” or “verify a signed typed data” flow, use the higher-level @ethernauta/crypto verifiers — they call verify_hash automatically when the address has deployed code, and fall through to EOA recovery when it doesn’t.
import { parse } from "valibot";
import { AddressSchema, BytesSchema } from "@ethernauta/core";
import { verify_message } from "@ethernauta/crypto";
import { create_reader, encode_chain_id, http } from "@ethernauta/transport";
import { eip155_1 } from "@ethernauta/chain/eip155-1";
const CHAIN_ID = encode_chain_id({ namespace: "eip155", reference: eip155_1.chainId });
const reader = create_reader([
{ chainId: CHAIN_ID, transports: [http("https://ethereum-rpc.publicnode.com")] },
]);
const address = parse(AddressSchema, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
const message = "hello";
const signature = parse(BytesSchema, "0x");
// works whether `address` is an EOA or a smart account
const ok = await verify_message({ address, message, signature })(
reader({ chain_id: CHAIN_ID }),
);
void ok; Counterfactual addresses
If the contract isn’t deployed yet, EIP-1271 by itself can’t validate — there’s no code to call. EIP-6492 wraps the signature with deployment instructions and a validator contract that handles the not-yet-deployed case. Use verify_message_universal / verify_typed_data_universal from @ethernauta/crypto when you need that.
See also
- EIP-191 — what a personal-sign signature looks like.
- EIP-712 — typed-data signatures (the more common 1271 input).
- EIP-6492 — counterfactual signatures.
- @ethernauta/crypto —
verify_message,verify_typed_data.