Validate a KEL
Verify SAID integrity, structural fields, key-chain continuity, delegation approval, and full chain validity.
KERI's security model requires verifiers to independently validate every event in a KEL before trusting any key state derived from it. There is no trusted third party: validity is a property of the event log itself, derivable from cryptographic rules alone.
@kerits/core exposes granular validation helpers, pure predicates for attachment verification, and a high-level validateKelChain function that runs all checks in sequence.
Validation model assumptions
Witness receipt signature verification assumes the non-transferable basic identifier model: the witness identifier prefix directly encodes the current verification key. This is the KERI default for witnesses.
If transferable witnesses or non-basic identifier types are used, receipt verification must be replaced with witness key state resolution. This assumption applies to both the verifyWitnessReceipt predicate and the fully-witnessed validation mode.
Validation modes
validateKelChain supports two active validation modes via the options.mode parameter:
| Mode | Default | Description |
|---|---|---|
structural | Yes | SAID integrity, required fields, prior-event linkage, controller signatures, signing threshold, key-chain continuity, delegation VRC verification, AID derivation |
fully-witnessed | No | Everything in structural plus witness receipt threshold and witness receipt signature verification |
A strict mode is reserved in the type system for future use but is not currently implemented.
What validateKelChain checks
In structural mode (default):
- SAID integrity — recomputes each event's SAID and compares to
dfield - AID derivation — for
icp/dipevents, verifiesi === d(only after SAID passes) - Required fields — all mandatory fields present for the event type
- Prior-event linkage —
pfield matches previous event'sd - Controller signature verification — Ed25519 signatures over canonical event bytes
- Signing threshold — valid signatures satisfy
kt(simple numeric or weighted multi-clause) - Key-chain continuity — rotation keys hash to previous establishment event's
ncommitments - Delegation VRC verification — parent signatures verified against parent's
ktthreshold (supports multi-sig delegators)
In fully-witnessed mode, additionally:
- Witness receipt signature verification — each receipt's Ed25519 signature verified using the witness AID prefix as verification key (non-transferable basic model)
- Witness receipt threshold — verified receipt count satisfies
bt
What validateKelChain does NOT check
Delegation anchoring — validateKelChain verifies VRC signatures from the delegator, but does NOT verify that the delegator's KEL contains an event with a seal anchoring the delegated event's SAID. This requires traversing the parent KEL, which is an SDK-level orchestration concern.
Use the exported predicates eventContainsAnchorForSaid and isDelegationAnchor as building blocks for SDK-level anchor verification. See the Predicates reference below.
Check SAID integrity
validateEventSaid recomputes an event's SAID from its body and compares it to the d field.
import { validateEventSaid } from '@kerits/core';
import type { KELEvent } from '@kerits/core';
const result = validateEventSaid(event);
if (!result.valid) {
console.error('SAID mismatch', { expected: result.expected, actual: result.actual });
}Check required fields
validateRequiredFields verifies that every field mandated by the event type is present.
import { validateRequiredFields, isValidKeriEvent } from '@kerits/core';
if (!isValidKeriEvent(rawData)) {
throw new Error('Not a valid KERI event');
}
const result = validateRequiredFields(rawData);
if (!result.valid) {
console.error('Missing fields:', result.missing);
}Required fields by event type:
| Event | Required fields |
|---|---|
icp | v t d i s kt k nt n bt b c a |
rot | v t d i s p kt k nt n bt br ba c a |
ixn | v t d i s p a |
dip | v t d i s kt k nt n bt b c a di |
drt | v t d i s p kt k nt n bt br ba c a |
Validate key-chain continuity
validateKeyChain checks that a rotation event's current keys satisfy the previous establishment event's next-key commitments.
import { validateKeyChain } from '@kerits/core';
const result = validateKeyChain(rotEvent, previousEstablishment);
if (!result.valid) {
console.error('Key chain broken', {
expected: result.expectedDigests,
actual: result.actualDigests,
});
}Full chain validation
import { validateKelChain } from '@kerits/core';
import type { CESREvent } from '@kerits/core';
const events: CESREvent[] = [
{ event: icpEvent, attachments: [{ kind: 'sig', form: 'indexed', keyIndex: 0, sig: 'AAsig...' }], enc: 'JSON' },
{ event: rotEvent, attachments: [{ kind: 'sig', form: 'indexed', keyIndex: 0, sig: 'ABsig...' }], enc: 'JSON' },
];
const result = validateKelChain(events);
if (!result.valid) {
const err = result.firstError;
console.error(`Validation failed at event ${err?.eventIndex}: [${err?.code}] ${err?.message}`);
}Fully-witnessed mode
const result = validateKelChain(events, { mode: 'fully-witnessed' });Delegated KELs
const result = validateKelChain(delegatedEvents, { parentKel: parentEvents });Incremental validation
const result = validateKelChain(events, { startIndex: events.length - 3 });Predicates reference
Pure predicates for attachment and anchor verification. These are independently importable from @kerits/core.
verifyWitnessReceipt
Verify a witness receipt signature against an event's canonical bytes. Assumes the non-transferable basic witness model.
import { verifyWitnessReceipt } from '@kerits/core';
const valid = verifyWitnessReceipt(
{ by: witnessAid, sig: receiptSignature },
event,
);verifyVrcAgainstThreshold
Aggregate VRC verifier: validates attachment structure, verifies each signature, and evaluates the delegator's signing threshold. Returns a discriminated union with typed failure reasons.
import { verifyVrcAgainstThreshold } from '@kerits/core';
import type { VrcVerificationResult } from '@kerits/core';
const result: VrcVerificationResult = verifyVrcAgainstThreshold(
vrcAttachments,
childEvent,
{ k: parentKeys, kt: parentThreshold },
);
if (!result.passed) {
// result.reason: 'cid-mismatch' | 'signature-invalid' | 'key-index-out-of-range' | 'threshold-not-met'
console.error(result.reason, result.validKeyIndices);
}eventContainsAnchorForSaid
SAID-only convenience check: does any entry in the event's a[] array have a d field matching the given SAID? Not a full seal matcher — matches SAID only.
import { eventContainsAnchorForSaid } from '@kerits/core';
const hasAnchor = eventContainsAnchorForSaid(parentIxnEvent, delegatedEventSaid);isDelegationAnchor
Checks that a parent event can carry delegation seals (ixn, rot, drt) and contains a matching SAID anchor. This is a convenience pre-check — complete delegation anchor verification also requires finding the sealing event in the parent KEL and verifying it in its own KEL context.
import { isDelegationAnchor } from '@kerits/core';
for (const parentEvent of parentKel) {
if (isDelegationAnchor(parentEvent.event, delegatedEventSaid)) {
console.log('Found delegation anchor at', parentEvent.event.s);
}
}Validation error codes
| Code | Mode | Meaning |
|---|---|---|
SAID_MISMATCH | all | d field does not match recomputed SAID |
AID_DERIVATION_INVALID | all | icp/dip event has i !== d |
MISSING_REQUIRED_FIELD | all | A mandatory field is absent |
PREVIOUS_EVENT_MISMATCH | all | p does not equal the prior event's d |
NEXT_KEY_MISMATCH | all | Rotation keys do not satisfy prior n commitments |
SIGNATURE_INVALID | all | A controller signature failed verification |
THRESHOLD_NOT_MET | all | Valid signatures do not meet the signing threshold |
AID_INCONSISTENT | all | Event i field differs from KEL's established AID |
SEQUENCE_INVALID | all | Event sequence number is out of order |
FIRST_EVENT_NOT_INCEPTION | all | First event is not icp or dip |
NON_TRANSFERABLE_VIOLATION | all | Non-transferable AID has events after inception |
CONFIG_TRAIT_VIOLATION | all | Establishment-only AID has interaction event |
CONFIG_TRAIT_REMOVED | all | Rotation removes an inception config trait |
DUPLICATE_KEYS | all | Duplicate entries in signing key list |
DUPLICATE_NEXT_DIGESTS | all | Duplicate entries in next key digest list |
WITNESS_THRESHOLD_UNSATISFIABLE | all | bt exceeds witness count |
WITNESS_DELTA_INVALID | all | Malformed witness add/remove in rotation |
MISSING_PARENT_KEL | all | Delegated event but no parent KEL supplied |
PARENT_SIGNATURE_INVALID | all | VRC signature or CID mismatch |
VRC_KEY_INDEX_INVALID | all | VRC keyIndex out of range for parent key list |
PARENT_THRESHOLD_NOT_MET | all | Valid VRC signatures don't meet delegator's kt |
WITNESS_RECEIPT_SIGNATURE_INVALID | fully-witnessed | A witness receipt signature failed verification |
WITNESS_RECEIPT_THRESHOLD_NOT_MET | fully-witnessed | Verified receipt count below bt |