verifyhash 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +883 -0
- package/cli/abi/ContributionRegistry.json +881 -0
- package/cli/agent.js +2173 -0
- package/cli/anchor-artifact.js +853 -0
- package/cli/anchor.js +400 -0
- package/cli/claim.js +881 -0
- package/cli/core/agent-commit.js +448 -0
- package/cli/core/agent-session.js +598 -0
- package/cli/core/anchor-binding.js +663 -0
- package/cli/core/attestation.js +580 -0
- package/cli/core/evidence-plans.js +495 -0
- package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
- package/cli/core/fulfill-intake.js +1082 -0
- package/cli/core/go-live-preflight.js +481 -0
- package/cli/core/license.js +534 -0
- package/cli/core/manifest.js +243 -0
- package/cli/core/packetseal.js +591 -0
- package/cli/core/registryArtifact.js +49 -0
- package/cli/core/revocation.js +539 -0
- package/cli/core/rfc3161.js +389 -0
- package/cli/core/timestamp.js +482 -0
- package/cli/core/trust-asof.js +479 -0
- package/cli/dataset.js +2950 -0
- package/cli/evidence.js +2227 -0
- package/cli/fulfill-webhook-http.js +438 -0
- package/cli/git.js +220 -0
- package/cli/hash.js +550 -0
- package/cli/identity.js +1072 -0
- package/cli/journal-cli.js +1110 -0
- package/cli/journal-log.js +454 -0
- package/cli/journal.js +334 -0
- package/cli/lineage.js +447 -0
- package/cli/list.js +287 -0
- package/cli/parcel.js +1509 -0
- package/cli/proof.js +578 -0
- package/cli/prove.js +300 -0
- package/cli/receipt.js +631 -0
- package/cli/registry.js +331 -0
- package/cli/reputation.js +344 -0
- package/cli/revocation.js +495 -0
- package/cli/serve-verify-http.js +298 -0
- package/cli/serve-verify.js +333 -0
- package/cli/show.js +339 -0
- package/cli/verify.js +383 -0
- package/cli/vh.js +3927 -0
- package/docs/ADOPT.md +183 -0
- package/docs/ADOPTION.json +11 -0
- package/docs/AGENTTRACE.md +247 -0
- package/docs/ANCHORING.md +167 -0
- package/docs/AUDIT.md +55 -0
- package/docs/CONFORMANCE.md +107 -0
- package/docs/DATALEDGER.md +638 -0
- package/docs/DECIDE.md +47 -0
- package/docs/DECISIONS-PENDING.md +27 -0
- package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
- package/docs/ENGINE-LEDGER.json +12 -0
- package/docs/EVIDENCE.md +519 -0
- package/docs/GO-LIVE.md +66 -0
- package/docs/IDENTITY.md +123 -0
- package/docs/INDEPENDENT-VERIFICATION.md +377 -0
- package/docs/INTEGRITY-JOURNAL.md +337 -0
- package/docs/KEY-LIFECYCLE.md +179 -0
- package/docs/LICENSING.md +46 -0
- package/docs/LINEAGE.md +307 -0
- package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
- package/docs/LOOP-HARDENING-PLAN.md +44 -0
- package/docs/MERKLE-LEAVES.md +113 -0
- package/docs/METRICS.jsonl +31 -0
- package/docs/MORNING.md +204 -0
- package/docs/PILOT.md +444 -0
- package/docs/PROOFPARCEL.md +227 -0
- package/docs/PROOFS.md +262 -0
- package/docs/RECEIPTS.md +341 -0
- package/docs/REPUTATION.md +158 -0
- package/docs/SDK.md +301 -0
- package/docs/STRATEGY-ARCHIVE.md +5055 -0
- package/docs/SUPERVISOR-RUNBOOK.md +52 -0
- package/docs/TRUST-BOUNDARIES.md +335 -0
- package/docs/TRUSTLEDGER.md +1976 -0
- package/docs/USAGE-BUDGET.json +121 -0
- package/docs/VERIFY-SERVICE.md +168 -0
- package/index.js +160 -0
- package/package.json +41 -0
- package/trustledger/build-standalone.js +796 -0
- package/trustledger/cli.js +3179 -0
- package/trustledger/close.js +391 -0
- package/trustledger/corpus.js +159 -0
- package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
- package/trustledger/dist/trustledger-standalone.html +6197 -0
- package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
- package/trustledger/door-core.js +442 -0
- package/trustledger/fixtures/bank.csv +7 -0
- package/trustledger/fixtures/bank.malformed.csv +3 -0
- package/trustledger/fixtures/bank.noalias.csv +5 -0
- package/trustledger/fixtures/bank.ofx +34 -0
- package/trustledger/fixtures/bank.real.csv +5 -0
- package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
- package/trustledger/fixtures/e2e/bank.csv +4 -0
- package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
- package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
- package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.csv +6 -0
- package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
- package/trustledger/fixtures/plans/baseline.json +25 -0
- package/trustledger/fixtures/plans/price-binding.example.json +27 -0
- package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
- package/trustledger/fixtures/policy/baseline.json +19 -0
- package/trustledger/fixtures/policy/ca-example.json +12 -0
- package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
- package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
- package/trustledger/fixtures/quickbooks.csv +7 -0
- package/trustledger/fixtures/quickbooks.real.csv +5 -0
- package/trustledger/fixtures/rentroll.csv +6 -0
- package/trustledger/fixtures/rentroll.real.csv +4 -0
- package/trustledger/ingest.js +1163 -0
- package/trustledger/lib/policy-bundled-loader.js +44 -0
- package/trustledger/lib/sha256-vendored.js +227 -0
- package/trustledger/license.js +563 -0
- package/trustledger/match.js +551 -0
- package/trustledger/plans.js +551 -0
- package/trustledger/policy.js +398 -0
- package/trustledger/public/index.html +512 -0
- package/trustledger/reconcile.js +1486 -0
- package/trustledger/report.js +887 -0
- package/trustledger/seal.js +854 -0
- package/trustledger/server.js +391 -0
- package/trustledger/valueproof.js +350 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// cli/core/revocation.js — the PURE producer-key REVOCATION core for verifyhash (EPIC-51 / T-51.1).
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS EXISTS
|
|
6
|
+
// Every sealed/signed artifact this family mints (an evidence seal, a signed license, a dataset/parcel
|
|
7
|
+
// attestation, an identity card) is trusted because a vendor's signing KEY backs it. But a key can be
|
|
8
|
+
// compromised, leave with a contractor, or simply be rotated. Today there is NO first-class, OFFLINE-
|
|
9
|
+
// verifiable way for that vendor to SAY "this key is revoked as of D" — so every artifact the key ever
|
|
10
|
+
// signed keeps verifying as ACCEPTED forever, and a recipient has no way to ask "was this key still good
|
|
11
|
+
// when THIS exhibit was sealed?". The producer REVOCATION statement closes that first gap: a vendor
|
|
12
|
+
// SIGNS, with the SAME key that signed their evidence/licenses/cards, a small self-describing container
|
|
13
|
+
// that marks that key's own `vendorAddress` revoked as of a point in time, for a bounded reason, and
|
|
14
|
+
// OPTIONALLY names a `supersededBy` successor key. A recipient who holds the revocation can recover the
|
|
15
|
+
// signer and confirm it equals the revocation's OWN `vendorAddress` — all OFFLINE, no network, no key,
|
|
16
|
+
// no I/O.
|
|
17
|
+
//
|
|
18
|
+
// IT IS JUST ONE MORE PRODUCT ON THE SHARED SIGNED-ATTESTATION ENVELOPE.
|
|
19
|
+
// Exactly like the seal/license/dataset/identity-card, the revocation defines an UNSIGNED PAYLOAD (a
|
|
20
|
+
// versioned object: vendorAddress + a CLOSED reason + revokedAt + optional supersededBy + note), a
|
|
21
|
+
// canonical serializer, and a strict validator, then hands those to `cli/core/attestation.js` as the
|
|
22
|
+
// product framing. The attestation core does ALL the crypto: it embeds the EXACT canonical payload
|
|
23
|
+
// bytes as the `attestation`, attaches the detached EIP-191 signature, and later RE-DERIVES the signer
|
|
24
|
+
// from those bytes. There is NO new crypto here, NO new dependency, NO new scheme — `buildRevocation`
|
|
25
|
+
// wraps via `signAttestation`, `verifyRevocation` recovers via `verifySignedAttestation`, byte-for-byte
|
|
26
|
+
// the SAME shared paths the identity card uses. We REUSE core/attestation.js VERBATIM — we never fork
|
|
27
|
+
// the signing/recovery path.
|
|
28
|
+
//
|
|
29
|
+
// THE LOAD-BEARING SELF-CONTROL INVARIANT — a key REVOKES ITSELF.
|
|
30
|
+
// A revocation carries its OWN `vendorAddress` INSIDE the signed payload, so it asserts "the holder of
|
|
31
|
+
// THIS key declares THIS address revoked". `verifyRevocation` therefore REQUIRES recovered === the
|
|
32
|
+
// embedded `vendorAddress`: a revocation whose signature recovers to any OTHER key is REJECTED
|
|
33
|
+
// (`vendorAddressMatchesSigner` fails), never a silent accept. This is the whole point — a THIRD PARTY
|
|
34
|
+
// cannot revoke a key it does not control (otherwise anyone could grief any vendor by "revoking" their
|
|
35
|
+
// key). `buildRevocation` enforces the SAME invariant at mint time — it refuses to produce a revocation
|
|
36
|
+
// for an address the provisioned signer does not control — so a revocation can never round-trip into a
|
|
37
|
+
// false ACCEPT. This mirrors the EPIC-49 identity-card mint invariant exactly.
|
|
38
|
+
//
|
|
39
|
+
// PURE + I/O-FREE + KEY-AGNOSTIC.
|
|
40
|
+
// Every function here is pure: no filesystem, no clock, no network. (readRevocation parses a JSON
|
|
41
|
+
// STRING/object — it does NOT read a file; the file read is the CLI layer's job in T-51.3.) The only key
|
|
42
|
+
// handling is a passed-in ethers signer-like object (an ephemeral `Wallet.createRandom()` in tests; the
|
|
43
|
+
// loop NEVER holds a real key) whose private key lives ONLY inside that object — never read, persisted,
|
|
44
|
+
// or logged here. The `revokedAt` instant is a CALLER-supplied argument; this core never reads the system
|
|
45
|
+
// clock, so the same inputs always yield byte-identical bytes + verdict. PRODUCT-AGNOSTIC: this module
|
|
46
|
+
// requires the GENERIC attestation core, never the reverse — no back-edge.
|
|
47
|
+
|
|
48
|
+
const coreAttestation = require("./attestation");
|
|
49
|
+
const { getAddress } = require("ethers");
|
|
50
|
+
|
|
51
|
+
// On-disk schema discriminators. The revocation carries its OWN kind + version (distinct from every
|
|
52
|
+
// seal/license/manifest/identity-card kind) so a random JSON file, a license, a seal, or a card is never
|
|
53
|
+
// misread as a revocation.
|
|
54
|
+
const REVOCATION_KIND = "vh-key-revocation";
|
|
55
|
+
const REVOCATION_SCHEMA_VERSION = 1;
|
|
56
|
+
const SUPPORTED_REVOCATION_SCHEMA_VERSIONS = Object.freeze([1]);
|
|
57
|
+
|
|
58
|
+
// The SIGNED-container framing (the detached-signature envelope kind) — its OWN discriminator.
|
|
59
|
+
const SIGNED_REVOCATION_KIND = "vh-key-revocation-signed";
|
|
60
|
+
const SIGNED_REVOCATION_SCHEMA_VERSION = 1;
|
|
61
|
+
const SUPPORTED_SIGNED_REVOCATION_SCHEMA_VERSIONS = Object.freeze([1]);
|
|
62
|
+
|
|
63
|
+
// The CLOSED reason set. A revocation declares WHY the key is being revoked; an out-of-set value is a HARD
|
|
64
|
+
// build/validate error (never silently honored), exactly like the identity card's closed productLine and
|
|
65
|
+
// the license core's closed entitlement table. Frozen + a derived sorted list so error messages are
|
|
66
|
+
// deterministic. These are the lifecycle events that make a key's past signatures suspect (compromised /
|
|
67
|
+
// retired) or simply rotated (superseded / rotated) — a small, fixed vocabulary a recipient can reason about.
|
|
68
|
+
const REVOCATION_REASONS = Object.freeze(["compromised", "rotated", "superseded", "retired"]);
|
|
69
|
+
const REVOCATION_REASON_SET = Object.freeze(REVOCATION_REASONS.slice().sort());
|
|
70
|
+
|
|
71
|
+
// A claimed 0x-address INSIDE the payload: 0x + 40 LOWERCASE hex chars. Lowercase-only for the SAME
|
|
72
|
+
// byte-determinism reason the attestation core lowercases the signer — an EIP-55-checksummed address is the
|
|
73
|
+
// canonical address in a DIFFERENT encoding, so accepting it verbatim would let one vendor serialize two
|
|
74
|
+
// ways. A caller holding a checksummed address lowercases it before building the revocation (buildRevocation
|
|
75
|
+
// normalizes for them via getAddress, so a checksummed input is accepted and canonicalized).
|
|
76
|
+
const ADDRESS_RE = /^0x[0-9a-f]{40}$/;
|
|
77
|
+
|
|
78
|
+
// A strict ISO-8601 UTC instant ("YYYY-MM-DDTHH:MM:SS(.mmm)Z"). Same canonical instant grammar the identity
|
|
79
|
+
// card pins publishedAt to, so two logically-identical revocations serialize to identical bytes. We pin the
|
|
80
|
+
// SHAPE here and require the canonical millis round-trip below.
|
|
81
|
+
const ISO_INSTANT_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
|
|
82
|
+
|
|
83
|
+
// The in-band trust caveat — stated ONCE so the human + JSON paths agree and the boundary can never drift.
|
|
84
|
+
// It is the load-bearing honesty of the artifact: a revocation proves the KEY-HOLDER SAID "revoked as of D";
|
|
85
|
+
// it is NOT a trusted wall-clock timestamp (the `revokedAt` instant rides the human-owned timestamp trust-
|
|
86
|
+
// root, STRATEGY.md P-3), and it is NOT a legal opinion.
|
|
87
|
+
const REVOCATION_TRUST_NOTE =
|
|
88
|
+
"This is a verifyhash producer KEY REVOCATION: the holder of `vendorAddress`'s key SIGNED it, declaring " +
|
|
89
|
+
"that address REVOKED as of `revokedAt` for `reason` (optionally superseded by `supersededBy`). verify " +
|
|
90
|
+
"RE-DERIVES the signer from these exact bytes and REQUIRES it to equal `vendorAddress` — a key revokes " +
|
|
91
|
+
"ITSELF; a third party cannot revoke a key it does not control. It proves the KEY-HOLDER's SIGNED CLAIM " +
|
|
92
|
+
'ONLY: `revokedAt` is the holder\'s self-asserted instant, NOT a trusted TIMESTAMP (it rides the human-' +
|
|
93
|
+
"owned timestamp trust-root, STRATEGY.md P-3), and this is NOT a legal opinion.";
|
|
94
|
+
|
|
95
|
+
const SIGNED_REVOCATION_TRUST_NOTE =
|
|
96
|
+
"This is a SIGNED verifyhash key-revocation container: it WRAPS (never edits) the EXACT canonical " +
|
|
97
|
+
"revocation bytes in `attestation` and attaches a detached EIP-191 signature. verifyRevocation " +
|
|
98
|
+
"RE-DERIVES the signer from those bytes and pins it to the embedded `vendorAddress` — it never trusts " +
|
|
99
|
+
"the file's own claims. Every caveat of the embedded revocation applies. " +
|
|
100
|
+
REVOCATION_TRUST_NOTE;
|
|
101
|
+
|
|
102
|
+
// A dedicated error type so callers/tests catch ONE revocation error for the HARD validation failures
|
|
103
|
+
// (a closed-field/closed-reason/malformed-address/non-canonical-date violation, an out-of-control mint). An
|
|
104
|
+
// ordinary verify REJECT is NOT thrown — it is a clean verdict (verdict: "REJECTED"), exactly like the rest
|
|
105
|
+
// of the family.
|
|
106
|
+
class RevocationError extends Error {
|
|
107
|
+
constructor(message) {
|
|
108
|
+
super(message);
|
|
109
|
+
this.name = "RevocationError";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isPlainObject(v) {
|
|
114
|
+
return v != null && typeof v === "object" && !Array.isArray(v);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The CLOSED field set of an UNSIGNED revocation payload. An UNKNOWN/extraneous key is a HARD error (never
|
|
118
|
+
// silently dropped) so the revocation's shape ossifies with exactly these fields. `kind`/`schemaVersion`/
|
|
119
|
+
// `note` are the framing the core fixes; the rest is the producer-supplied revocation. `supersededBy` is
|
|
120
|
+
// OPTIONAL — it is a member of the closed set (so a present-but-malformed value HARD-errors) but absence is
|
|
121
|
+
// allowed (validated below).
|
|
122
|
+
const REVOCATION_FIELDS = Object.freeze([
|
|
123
|
+
"kind",
|
|
124
|
+
"schemaVersion",
|
|
125
|
+
"note",
|
|
126
|
+
"vendorAddress",
|
|
127
|
+
"reason",
|
|
128
|
+
"revokedAt",
|
|
129
|
+
"supersededBy",
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* STRICT structural validation of an UNSIGNED revocation payload. Throws a RevocationError on the FIRST
|
|
134
|
+
* problem (named + localized); returns the object unchanged on success. NEVER half-accepts and NEVER fills
|
|
135
|
+
* defaults. This is the `validateUnsigned` the attestation core re-runs on the embedded payload (the
|
|
136
|
+
* wrap-don't-edit invariant), so a signed container can never smuggle a malformed/edited revocation.
|
|
137
|
+
*
|
|
138
|
+
* REJECTS (HARD): a non-object; a wrong kind/schemaVersion/note; an UNKNOWN/extraneous field; a missing/
|
|
139
|
+
* malformed (not lowercase-0x) vendorAddress; an out-of-set reason; a non-canonical-ISO revokedAt; a
|
|
140
|
+
* present-but-malformed (not lowercase-0x) supersededBy. `supersededBy` may be ABSENT (optional); when
|
|
141
|
+
* present it must be a valid lowercase-0x address.
|
|
142
|
+
*
|
|
143
|
+
* @param {any} obj
|
|
144
|
+
* @returns {object} the same object, if valid
|
|
145
|
+
*/
|
|
146
|
+
function validateRevocation(obj) {
|
|
147
|
+
if (!isPlainObject(obj)) {
|
|
148
|
+
throw new RevocationError("revocation payload must be a JSON object");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// CLOSED FIELD SET: every key must be one of REVOCATION_FIELDS. An unknown/extraneous key HARD-errors
|
|
152
|
+
// (never silently kept) so the revocation can never carry a smuggled, unvalidated field.
|
|
153
|
+
for (const key of Object.keys(obj)) {
|
|
154
|
+
if (!REVOCATION_FIELDS.includes(key)) {
|
|
155
|
+
throw new RevocationError(
|
|
156
|
+
`revocation has an unknown field: ${JSON.stringify(key)} ` +
|
|
157
|
+
`(the closed field set is ${JSON.stringify(REVOCATION_FIELDS)})`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (obj.kind !== REVOCATION_KIND) {
|
|
163
|
+
throw new RevocationError(
|
|
164
|
+
`not a verifyhash revocation (kind: ${JSON.stringify(obj.kind)}; expected ${JSON.stringify(REVOCATION_KIND)})`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (!SUPPORTED_REVOCATION_SCHEMA_VERSIONS.includes(obj.schemaVersion)) {
|
|
168
|
+
throw new RevocationError(
|
|
169
|
+
`unsupported revocation schemaVersion: ${JSON.stringify(obj.schemaVersion)} ` +
|
|
170
|
+
`(this build understands ${JSON.stringify(SUPPORTED_REVOCATION_SCHEMA_VERSIONS)})`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (obj.note !== REVOCATION_TRUST_NOTE) {
|
|
174
|
+
throw new RevocationError(
|
|
175
|
+
"revocation `note` must be the standing REVOCATION_TRUST_NOTE (caveat must not drift)"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// vendorAddress — the address the revocation BINDS to the signing key (the key that revokes ITSELF). A
|
|
180
|
+
// lowercase 0x-address (checksummed/mixed-case is rejected here for byte-determinism; buildRevocation
|
|
181
|
+
// lowercases a checksummed input).
|
|
182
|
+
if (typeof obj.vendorAddress !== "string" || !ADDRESS_RE.test(obj.vendorAddress)) {
|
|
183
|
+
throw new RevocationError(
|
|
184
|
+
"revocation vendorAddress must be a 0x-prefixed 20-byte LOWERCASE-hex address " +
|
|
185
|
+
`(checksummed/mixed-case rejected for byte-determinism — lowercase it first), got: ${String(obj.vendorAddress)}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// reason — a single value drawn from the CLOSED REVOCATION_REASONS set. An out-of-set value HARD-errors.
|
|
190
|
+
if (typeof obj.reason !== "string" || !REVOCATION_REASON_SET.includes(obj.reason)) {
|
|
191
|
+
throw new RevocationError(
|
|
192
|
+
`revocation reason must be one of the closed set ${JSON.stringify(REVOCATION_REASON_SET)}, ` +
|
|
193
|
+
`got: ${JSON.stringify(obj.reason)}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// revokedAt — a strict, CANONICAL ISO-8601 UTC instant. The regex pins the SHAPE; the millis round-trip
|
|
198
|
+
// FORCES the `.mmm` form and REJECTS every rolled-over/impossible instant (e.g. Feb-29 in a non-leap year),
|
|
199
|
+
// exactly like the identity card, so a self-asserted date can never silently coerce.
|
|
200
|
+
if (typeof obj.revokedAt !== "string" || !ISO_INSTANT_RE.test(obj.revokedAt)) {
|
|
201
|
+
throw new RevocationError(
|
|
202
|
+
`revocation revokedAt must be an ISO-8601 UTC instant ("YYYY-MM-DDTHH:MM:SS(.mmm)Z"), got: ${String(obj.revokedAt)}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const ms = Date.parse(obj.revokedAt);
|
|
206
|
+
if (Number.isNaN(ms) || new Date(ms).toISOString() !== obj.revokedAt) {
|
|
207
|
+
throw new RevocationError(
|
|
208
|
+
`revocation revokedAt must be a canonical ISO-8601 UTC instant ("YYYY-MM-DDTHH:MM:SS.mmmZ", ` +
|
|
209
|
+
`millis required, no rolled-over/impossible fields), got: ${String(obj.revokedAt)}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// supersededBy — OPTIONAL. When ABSENT (the key is undefined OR not present), that is allowed: a plain
|
|
214
|
+
// revocation supersedes the key with nothing. When PRESENT it must be a valid lowercase-0x address (a
|
|
215
|
+
// present-but-malformed / checksummed / non-string value HARD-errors — never silently dropped). We do NOT
|
|
216
|
+
// permit `null` to mean "absent": a null value is a present-but-invalid address, rejected, so the wire
|
|
217
|
+
// form has exactly one encoding of "no successor" (the key simply omitted).
|
|
218
|
+
if (Object.prototype.hasOwnProperty.call(obj, "supersededBy") && obj.supersededBy !== undefined) {
|
|
219
|
+
if (typeof obj.supersededBy !== "string" || !ADDRESS_RE.test(obj.supersededBy)) {
|
|
220
|
+
throw new RevocationError(
|
|
221
|
+
"revocation supersededBy, when present, must be a 0x-prefixed 20-byte LOWERCASE-hex address " +
|
|
222
|
+
`(checksummed/mixed-case rejected for byte-determinism — lowercase it first), got: ${String(obj.supersededBy)}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return obj;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Serialize a validated UNSIGNED revocation payload to its canonical, byte-deterministic bytes: a FIXED key
|
|
232
|
+
* order, NO insignificant whitespace, a single trailing newline. `supersededBy` is emitted ONLY when present
|
|
233
|
+
* (an absent successor is OMITTED, never written as null) so the wire form has exactly one encoding of "no
|
|
234
|
+
* successor". This is the EXACT byte sequence the envelope signs over and verifyRevocation re-derives the
|
|
235
|
+
* signer from, so two logically-identical revocations sign identically.
|
|
236
|
+
* @param {object} payload a validated revocation payload
|
|
237
|
+
* @returns {string} the canonical serialization (newline-terminated)
|
|
238
|
+
*/
|
|
239
|
+
function serializeRevocation(payload) {
|
|
240
|
+
validateRevocation(payload);
|
|
241
|
+
const canonical = {
|
|
242
|
+
kind: payload.kind,
|
|
243
|
+
schemaVersion: payload.schemaVersion,
|
|
244
|
+
note: payload.note,
|
|
245
|
+
vendorAddress: payload.vendorAddress,
|
|
246
|
+
reason: payload.reason,
|
|
247
|
+
revokedAt: payload.revokedAt,
|
|
248
|
+
};
|
|
249
|
+
// OPTIONAL `supersededBy` is appended LAST and ONLY when present — so the canonical bytes of a revocation
|
|
250
|
+
// without a successor never carry a null/empty slot, and one with a successor always carries it in the
|
|
251
|
+
// SAME fixed position.
|
|
252
|
+
if (Object.prototype.hasOwnProperty.call(payload, "supersededBy") && payload.supersededBy !== undefined) {
|
|
253
|
+
canonical.supersededBy = payload.supersededBy;
|
|
254
|
+
}
|
|
255
|
+
return JSON.stringify(canonical) + "\n";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Assemble + strictly validate an UNSIGNED revocation payload from caller fields. PURE. This is the payload
|
|
260
|
+
* `buildRevocation` then wraps in the signed envelope. Splitting it out lets a caller hold/inspect the
|
|
261
|
+
* unsigned revocation before signing (and validates it the SAME way the embedded payload is re-validated on
|
|
262
|
+
* read). A checksummed/mixed-case vendorAddress/supersededBy is normalized to lowercase here (a syntactically
|
|
263
|
+
* invalid address HARD-errors); every other field passes through to validateRevocation.
|
|
264
|
+
*
|
|
265
|
+
* @param {object} params { vendorAddress, reason, revokedAt, [supersededBy] }
|
|
266
|
+
* @returns {object} a validated, canonicalized revocation payload
|
|
267
|
+
*/
|
|
268
|
+
function buildRevocationPayload(params) {
|
|
269
|
+
if (!isPlainObject(params)) {
|
|
270
|
+
throw new RevocationError(
|
|
271
|
+
"buildRevocationPayload requires a { vendorAddress, reason, revokedAt, [supersededBy] } object"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
// Normalize a checksummed/mixed-case vendorAddress to canonical lowercase (so a caller may paste an EIP-55
|
|
275
|
+
// address). A syntactically invalid address is a HARD error (named, no surprise).
|
|
276
|
+
let vendorAddress;
|
|
277
|
+
try {
|
|
278
|
+
vendorAddress = getAddress(params.vendorAddress).toLowerCase();
|
|
279
|
+
} catch (_e) {
|
|
280
|
+
throw new RevocationError(
|
|
281
|
+
`revocation vendorAddress must be a valid 0x-address, got: ${String(params.vendorAddress)}`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const payload = {
|
|
286
|
+
kind: REVOCATION_KIND,
|
|
287
|
+
schemaVersion: REVOCATION_SCHEMA_VERSION,
|
|
288
|
+
note: REVOCATION_TRUST_NOTE,
|
|
289
|
+
vendorAddress,
|
|
290
|
+
reason: params.reason,
|
|
291
|
+
revokedAt: params.revokedAt,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// OPTIONAL supersededBy — only set it when the caller supplied a non-undefined value, and normalize a
|
|
295
|
+
// checksummed/mixed-case successor to lowercase the same way (a syntactically invalid successor HARD-errors
|
|
296
|
+
// with a named, supersededBy-specific message; a null/empty/wrong-type value flows to validateRevocation
|
|
297
|
+
// which rejects it as a present-but-malformed successor).
|
|
298
|
+
if (params.supersededBy !== undefined) {
|
|
299
|
+
if (typeof params.supersededBy === "string") {
|
|
300
|
+
try {
|
|
301
|
+
payload.supersededBy = getAddress(params.supersededBy).toLowerCase();
|
|
302
|
+
} catch (_e) {
|
|
303
|
+
throw new RevocationError(
|
|
304
|
+
`revocation supersededBy, when present, must be a valid 0x-address, got: ${String(params.supersededBy)}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
// A non-string, non-undefined supersededBy (e.g. null, a number) is a present-but-malformed successor.
|
|
309
|
+
payload.supersededBy = params.supersededBy;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// validateRevocation throws a named error on any malformed/unknown/missing field — never silently accepts.
|
|
314
|
+
// Return the canonicalized payload (re-parsed from serializeRevocation) so the in-memory object's field
|
|
315
|
+
// order matches the signed bytes exactly.
|
|
316
|
+
validateRevocation(payload);
|
|
317
|
+
return JSON.parse(serializeRevocation(payload));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// The SIGNED-attestation framing passed to the GENERIC attestation core. The core does ALL the crypto + the
|
|
321
|
+
// wrap-don't-edit invariant; this supplies ONLY the revocation framing + the unsigned codec. SAME pattern
|
|
322
|
+
// the seal/license/dataset/identity-card use — frozen so it can never be mutated mid-flight.
|
|
323
|
+
const SIGNED_REVOCATION_CFG = Object.freeze({
|
|
324
|
+
kind: SIGNED_REVOCATION_KIND,
|
|
325
|
+
schemaVersion: SIGNED_REVOCATION_SCHEMA_VERSION,
|
|
326
|
+
supportedSchemaVersions: SUPPORTED_SIGNED_REVOCATION_SCHEMA_VERSIONS,
|
|
327
|
+
note: SIGNED_REVOCATION_TRUST_NOTE,
|
|
328
|
+
label: "signed key revocation",
|
|
329
|
+
validateUnsigned: validateRevocation,
|
|
330
|
+
serializeUnsigned: serializeRevocation,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Mint a SIGNED revocation container. Builds + validates the unsigned payload (canonicalizing vendorAddress/
|
|
335
|
+
* supersededBy), then routes it + the caller's signer through the SHARED `signAttestation` core, which signs
|
|
336
|
+
* the EXACT canonical bytes (EIP-191 personal_sign) and wraps + validates the container.
|
|
337
|
+
*
|
|
338
|
+
* THE LOAD-BEARING SELF-CONTROL INVARIANT — the key MUST control the address it revokes. After signing, the
|
|
339
|
+
* recovered signer is required to EQUAL the embedded `vendorAddress`. A provisioned key that does NOT control
|
|
340
|
+
* the claimed address HARD-errors (RevocationError) — the loop refuses to mint a revocation for an address
|
|
341
|
+
* the signer cannot back (a third party cannot revoke a key it does not control). So a built revocation
|
|
342
|
+
* ALWAYS round-trips to ACCEPT by construction.
|
|
343
|
+
*
|
|
344
|
+
* NO key handling here — the key lives only inside the signer object (an ephemeral Wallet in tests).
|
|
345
|
+
*
|
|
346
|
+
* @param {object} params { vendorAddress, reason, revokedAt, [supersededBy] }
|
|
347
|
+
* @param {object} signer an ethers signer-like object: async getAddress() + signMessage()
|
|
348
|
+
* @returns {Promise<object>} the validated signed-revocation container
|
|
349
|
+
*/
|
|
350
|
+
async function buildRevocation(params, signer) {
|
|
351
|
+
const payload = buildRevocationPayload(params);
|
|
352
|
+
const container = await coreAttestation.signAttestation(
|
|
353
|
+
{ attestation: payload, signer },
|
|
354
|
+
SIGNED_REVOCATION_CFG
|
|
355
|
+
);
|
|
356
|
+
// Enforce the self-control invariant: recover the signer from the just-signed bytes and require it to
|
|
357
|
+
// EQUAL the embedded vendorAddress. signAttestation already pinned the container's CLAIMED signer to the
|
|
358
|
+
// signer's own address, so this catches the genuine "revoking an address this key does not control" case —
|
|
359
|
+
// never a silent mint of an unbacked revocation.
|
|
360
|
+
const recovered = coreAttestation.recoverSigner(container); // lowercase 0x-address
|
|
361
|
+
if (recovered !== payload.vendorAddress) {
|
|
362
|
+
throw new RevocationError(
|
|
363
|
+
"refusing to mint a revocation the signing key does not control: the recovered signer " +
|
|
364
|
+
`(${recovered}) does NOT equal the revocation's vendorAddress (${payload.vendorAddress}). A key ` +
|
|
365
|
+
"revokes ITSELF — sign with the key that controls vendorAddress."
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return container;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Strictly validate a parsed SIGNED revocation container — thin wrapper over the shared core. */
|
|
372
|
+
function validateSignedRevocation(obj) {
|
|
373
|
+
return coreAttestation.validateSignedAttestation(obj, SIGNED_REVOCATION_CFG);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Serialize a SIGNED revocation container to its canonical bytes — thin wrapper over the shared core. */
|
|
377
|
+
function serializeSignedRevocation(container) {
|
|
378
|
+
return coreAttestation.serializeSignedAttestation(container, SIGNED_REVOCATION_CFG);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Read + strictly validate a SIGNED revocation container (JSON STRING or object). PURE — it parses bytes
|
|
383
|
+
* already in hand; it does NOT read a file (the file read is the CLI layer's job, T-51.3). A parse error is
|
|
384
|
+
* a RevocationError (never a raw SyntaxError); a malformed/corrupt container is rejected by the shared
|
|
385
|
+
* validator, never half-accepted.
|
|
386
|
+
* @param {string|object} input
|
|
387
|
+
* @returns {object} the validated container
|
|
388
|
+
*/
|
|
389
|
+
function readRevocation(input) {
|
|
390
|
+
let obj;
|
|
391
|
+
if (typeof input === "string") {
|
|
392
|
+
try {
|
|
393
|
+
obj = JSON.parse(input);
|
|
394
|
+
} catch (e) {
|
|
395
|
+
throw new RevocationError(`revocation container is not valid JSON: ${e.message}`);
|
|
396
|
+
}
|
|
397
|
+
} else if (isPlainObject(input)) {
|
|
398
|
+
obj = input;
|
|
399
|
+
} else {
|
|
400
|
+
throw new RevocationError(
|
|
401
|
+
"readRevocation requires a JSON string or a signed-revocation container object"
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
coreAttestation.validateSignedAttestation(obj, SIGNED_REVOCATION_CFG);
|
|
406
|
+
} catch (e) {
|
|
407
|
+
throw new RevocationError(e.message);
|
|
408
|
+
}
|
|
409
|
+
return obj;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Verify (purely, OFFLINE) a SIGNED revocation container — the STRICT, PURE verify path. It recovers the
|
|
414
|
+
* signer from the embedded canonical revocation bytes + signature and:
|
|
415
|
+
* (1) confirms it equals the container's CLAIMED `signer` (signatureMatchesSigner — ALWAYS run);
|
|
416
|
+
* (2) confirms it equals the revocation's OWN embedded `vendorAddress` (vendorAddressMatchesSigner —
|
|
417
|
+
* ALWAYS run; this is the load-bearing SELF-CONTROL check: a key revokes ITSELF);
|
|
418
|
+
* (3) OPTIONALLY pins it to an EXPECTED signer (`expectedSigner` — signerMatchesExpected, run ONLY when
|
|
419
|
+
* present).
|
|
420
|
+
* The verdict is ACCEPTED only when EVERY requested check passes; a forged/mismatched/tampered revocation
|
|
421
|
+
* is a clean REJECTED — NEVER a silent pass, NEVER a thrown error for an ordinary rejection.
|
|
422
|
+
*
|
|
423
|
+
* It is OFFLINE / key-free / network-free / I/O-free: it recovers a PUBLIC address from a signature, holds
|
|
424
|
+
* no private key, contacts nothing, writes nothing, and mutates the container NOT at all. The returned shape
|
|
425
|
+
* EXTENDS the FAMILY verdict shape (the byte-for-byte fields `verifySignedAttestation` returns, including
|
|
426
|
+
* checks/failedChecks/recoveredSigner/claimedSigner so a future indexer/UI depends on ONE stable shape) with
|
|
427
|
+
* the revocation-specific vendorAddress/reason/revokedAt/supersededBy + checks.vendorAddressMatchesSigner.
|
|
428
|
+
*
|
|
429
|
+
* STRUCTURAL SAFETY: the container is validated FIRST (validateSignedRevocation); a structurally invalid
|
|
430
|
+
* container HARD-errors (RevocationError) before any recovery, so an ordinary REJECTED verdict only ever
|
|
431
|
+
* describes a SOUND revocation whose signature simply doesn't back its claims.
|
|
432
|
+
*
|
|
433
|
+
* @param {object} params
|
|
434
|
+
* @param {object} params.container a signed-revocation container (from buildRevocation/readRevocation)
|
|
435
|
+
* @param {string} [params.expectedSigner] OPTIONAL expected signer 0x-address; checked when present
|
|
436
|
+
* @returns {{
|
|
437
|
+
* verdict: "ACCEPTED"|"REJECTED",
|
|
438
|
+
* accepted: boolean,
|
|
439
|
+
* recoveredSigner: string,
|
|
440
|
+
* claimedSigner: string,
|
|
441
|
+
* vendorAddress: string,
|
|
442
|
+
* reason: string,
|
|
443
|
+
* revokedAt: string,
|
|
444
|
+
* supersededBy: string|null,
|
|
445
|
+
* scheme: string,
|
|
446
|
+
* checks: {
|
|
447
|
+
* signatureMatchesSigner: boolean,
|
|
448
|
+
* vendorAddressMatchesSigner: boolean,
|
|
449
|
+
* signerMatchesExpected: boolean|null,
|
|
450
|
+
* },
|
|
451
|
+
* expectedSigner: string|null,
|
|
452
|
+
* failedChecks: string[],
|
|
453
|
+
* }}
|
|
454
|
+
*/
|
|
455
|
+
function verifyRevocation(params) {
|
|
456
|
+
if (!isPlainObject(params)) {
|
|
457
|
+
throw new RevocationError("verifyRevocation requires { container, [expectedSigner] }");
|
|
458
|
+
}
|
|
459
|
+
// Validate the container FIRST (and re-validate the embedded revocation) so an ordinary REJECTED verdict
|
|
460
|
+
// only ever describes a STRUCTURALLY SOUND revocation. A corrupt/foreign container is a HARD error, never
|
|
461
|
+
// a verdict.
|
|
462
|
+
const container = validateSignedRevocation(params.container);
|
|
463
|
+
const revocation = JSON.parse(container.attestation); // validated above (lowercase vendorAddress etc.)
|
|
464
|
+
const vendorAddress = revocation.vendorAddress;
|
|
465
|
+
|
|
466
|
+
// Route the signature recovery + the OPTIONAL expected-signer pin through the SHARED generic core (the
|
|
467
|
+
// SAME path the identity card uses). We do NOT pass expectedCanonical — the revocation-specific binding is
|
|
468
|
+
// the vendorAddress self-control check below, computed from the embedded revocation.
|
|
469
|
+
const att = coreAttestation.verifySignedAttestation({
|
|
470
|
+
container,
|
|
471
|
+
expectedSigner: params.expectedSigner,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// (2) The load-bearing SELF-CONTROL check: the RECOVERED signer must equal the revocation's OWN
|
|
475
|
+
// vendorAddress. We pin against the RECOVERED signer (not the merely-CLAIMED one), so a revocation that
|
|
476
|
+
// claims a vendorAddress its signature does not back is REJECTED — a third party cannot revoke a key it
|
|
477
|
+
// does not control. When the signature is unrecoverable, recoveredSigner is the "(unrecoverable)"
|
|
478
|
+
// sentinel, which can never equal a lowercase 0x-address — so this is false (REJECT).
|
|
479
|
+
const vendorAddressMatchesSigner = att.recoveredSigner === vendorAddress;
|
|
480
|
+
|
|
481
|
+
// The verdict is ACCEPTED only when EVERY requested check passes. signatureMatchesSigner +
|
|
482
|
+
// vendorAddressMatchesSigner are ALWAYS required; signerMatchesExpected only when expectedSigner was given
|
|
483
|
+
// (null = not requested, never fails the gate). We REBUILD failedChecks (the core's list does not know
|
|
484
|
+
// about vendorAddressMatchesSigner) so the verdict, the checks, and failedChecks can never disagree.
|
|
485
|
+
const failedChecks = [];
|
|
486
|
+
if (!att.checks.signatureMatchesSigner) failedChecks.push("signatureMatchesSigner");
|
|
487
|
+
if (!vendorAddressMatchesSigner) failedChecks.push("vendorAddressMatchesSigner");
|
|
488
|
+
if (att.checks.signerMatchesExpected === false) failedChecks.push("signerMatchesExpected");
|
|
489
|
+
const accepted = failedChecks.length === 0;
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
verdict: accepted ? "ACCEPTED" : "REJECTED",
|
|
493
|
+
accepted,
|
|
494
|
+
recoveredSigner: att.recoveredSigner,
|
|
495
|
+
claimedSigner: att.claimedSigner,
|
|
496
|
+
vendorAddress,
|
|
497
|
+
reason: revocation.reason,
|
|
498
|
+
revokedAt: revocation.revokedAt,
|
|
499
|
+
// Surface the OPTIONAL successor explicitly as null when absent (so a machine reader gets a stable field
|
|
500
|
+
// rather than an undefined that JSON drops).
|
|
501
|
+
supersededBy: Object.prototype.hasOwnProperty.call(revocation, "supersededBy")
|
|
502
|
+
? revocation.supersededBy
|
|
503
|
+
: null,
|
|
504
|
+
scheme: att.scheme,
|
|
505
|
+
checks: {
|
|
506
|
+
signatureMatchesSigner: att.checks.signatureMatchesSigner,
|
|
507
|
+
vendorAddressMatchesSigner,
|
|
508
|
+
signerMatchesExpected: att.checks.signerMatchesExpected,
|
|
509
|
+
},
|
|
510
|
+
expectedSigner: att.expectedSigner,
|
|
511
|
+
failedChecks,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
module.exports = {
|
|
516
|
+
// kinds + closed sets
|
|
517
|
+
REVOCATION_KIND,
|
|
518
|
+
REVOCATION_SCHEMA_VERSION,
|
|
519
|
+
SUPPORTED_REVOCATION_SCHEMA_VERSIONS,
|
|
520
|
+
SIGNED_REVOCATION_KIND,
|
|
521
|
+
SIGNED_REVOCATION_SCHEMA_VERSION,
|
|
522
|
+
SUPPORTED_SIGNED_REVOCATION_SCHEMA_VERSIONS,
|
|
523
|
+
REVOCATION_REASONS,
|
|
524
|
+
REVOCATION_REASON_SET,
|
|
525
|
+
REVOCATION_FIELDS,
|
|
526
|
+
REVOCATION_TRUST_NOTE,
|
|
527
|
+
SIGNED_REVOCATION_TRUST_NOTE,
|
|
528
|
+
RevocationError,
|
|
529
|
+
// unsigned-payload codec
|
|
530
|
+
validateRevocation,
|
|
531
|
+
serializeRevocation,
|
|
532
|
+
buildRevocationPayload,
|
|
533
|
+
// signed container
|
|
534
|
+
buildRevocation,
|
|
535
|
+
validateSignedRevocation,
|
|
536
|
+
serializeSignedRevocation,
|
|
537
|
+
readRevocation,
|
|
538
|
+
verifyRevocation,
|
|
539
|
+
};
|