@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
JavaScript
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`);
}
}