UNPKG

@didtools/pkh-ethereum

Version:

Implements support to authenticate, authorize and verify with Ethereum accounts as a did:pkh with SIWE(X) and CACAO. Primarly used with `did-session` and `@didtools/cacao`.

47 lines (46 loc) 2.41 kB
import { SiweMessage, asLegacyChainIdString, verifyTimeChecks, assertSigned } from '@didtools/cacao'; import { AccountId } from 'caip'; import { bytesToHex, concatBytes, hexToBytes, utf8ToBytes } from '@noble/hashes/utils'; import { keccak_256 } from '@noble/hashes/sha3'; import { secp256k1 } from '@noble/curves/secp256k1'; /** * Get a configured CACAO EIP191Verifier map for Ethereum EOA accounts */ export function getEIP191Verifier() { return { // eslint-disable-next-line @typescript-eslint/require-await eip191: async (cacao, opts)=>{ verifyEIP191Signature(cacao, opts); } }; } // CACAOs issued after that day must be of new format export const LEGACY_CHAIN_ID_REORG_DATE = new Date('2022-09-20').valueOf(); const MESSAGE_PREFIX = '\x19Ethereum Signed Message:\n'; function verifyMessage(message, signature) { const effectiveMessage = typeof message === 'string' ? utf8ToBytes(message) : message; const digest = keccak_256(concatBytes(utf8ToBytes(MESSAGE_PREFIX), utf8ToBytes(String(message.length)), effectiveMessage)); const signatureBytes = hexToBytes(signature.replace(/^0x/, '')); let v = signatureBytes[64]; if (v === 0 || v === 1) v += 27; const publicKey = secp256k1.Signature.fromCompact(signatureBytes.slice(0, 64)).addRecoveryBit(v - 27).recoverPublicKey(digest).toRawBytes(false); const recoveredAddress = keccak_256(publicKey.subarray(1)).subarray(-20); return `0x${bytesToHex(recoveredAddress)}`; } export function verifyEIP191Signature(cacao, options) { assertSigned(cacao); verifyTimeChecks(cacao, options); const issuer = AccountId.parse(cacao.p.iss.replace('did:pkh:', '')).address.toLowerCase(); // assume the message doesn't use eip55 for the ethereum address let recovered = verifyMessage(SiweMessage.fromCacao(cacao).toMessage(), cacao.s.s); if (recovered !== issuer) { // try to verify signature using eip55 address recovered = verifyMessage(SiweMessage.fromCacao(cacao).toMessageEip55(), cacao.s.s); } if (recovered !== issuer && Date.parse(cacao.p.iat) <= LEGACY_CHAIN_ID_REORG_DATE) { // might be an old CACAOv1 format recovered = verifyMessage(asLegacyChainIdString(SiweMessage.fromCacao(cacao), 'Ethereum'), cacao.s.s); } if (recovered !== issuer) { throw new Error(`Signature does not belong to issuer`); } }