@orangecheck/agent-core 2.1.0 → 2.2.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/README.md +3 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +37 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/federation.test.ts +26 -0
- package/src/federation.ts +14 -0
- package/src/revocation-authority.test.ts +258 -0
- package/src/verify.ts +61 -49
package/README.md
CHANGED
|
@@ -95,7 +95,9 @@ if (!result.ok) throw new Error(result.code + ': ' + result.message);
|
|
|
95
95
|
|
|
96
96
|
- `verifyDelegation({ envelope, verifyBip322, now?, skipTemporalCheck?, scopeMode? })`
|
|
97
97
|
- `verifyAction({ action, delegation, revocations?, verifyBip322, verifyOtsAnchor?, content?, resolveAnchorBlockHeight?, scopeMode? })`
|
|
98
|
-
- `verifyRevocation({ envelope, delegation, verifyBip322 })`
|
|
98
|
+
- `verifyRevocation({ envelope, delegation, verifyBip322 })` — the principal may always revoke; `revocation.holders` can only add the agent
|
|
99
|
+
- `verifyOtsAnchor(proofB64, blockHeight, blockHash, envelopeId)` — an anchor orders an action against a revocation only when this returns true
|
|
100
|
+
- `verifyFederationRevocation({ envelope, delegation, verifyBip322 })`
|
|
99
101
|
|
|
100
102
|
Each returns a discriminated union:
|
|
101
103
|
|
package/dist/index.d.mts
CHANGED
|
@@ -34,7 +34,7 @@ interface VerifyActionInput extends VerifyBase {
|
|
|
34
34
|
subdelegationChain?: SubdelegationEnvelope[];
|
|
35
35
|
maxChainDepth?: number;
|
|
36
36
|
content?: Uint8Array;
|
|
37
|
-
verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;
|
|
37
|
+
verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>;
|
|
38
38
|
resolveAnchorBlockHeight?: (env: ActionEnvelope | RevocationEnvelope) => number | null;
|
|
39
39
|
decryptScopesWith?: {
|
|
40
40
|
device_id: string;
|
|
@@ -131,6 +131,7 @@ interface VerifyFederationDelegationInput extends VerifyFederationBase {
|
|
|
131
131
|
declare function verifyFederationDelegation(input: VerifyFederationDelegationInput): Promise<FederationVerifyResult>;
|
|
132
132
|
interface VerifyFederationRevocationInput extends VerifyFederationBase {
|
|
133
133
|
envelope: FederationRevocationEnvelope;
|
|
134
|
+
delegation: FederationDelegationEnvelope;
|
|
134
135
|
}
|
|
135
136
|
declare function verifyFederationRevocation(input: VerifyFederationRevocationInput): Promise<FederationVerifyResult>;
|
|
136
137
|
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ interface VerifyActionInput extends VerifyBase {
|
|
|
34
34
|
subdelegationChain?: SubdelegationEnvelope[];
|
|
35
35
|
maxChainDepth?: number;
|
|
36
36
|
content?: Uint8Array;
|
|
37
|
-
verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string) => Promise<boolean>;
|
|
37
|
+
verifyOtsAnchor?: (proofB64: string, blockHeight: number, blockHash: string, envelopeId: string) => Promise<boolean>;
|
|
38
38
|
resolveAnchorBlockHeight?: (env: ActionEnvelope | RevocationEnvelope) => number | null;
|
|
39
39
|
decryptScopesWith?: {
|
|
40
40
|
device_id: string;
|
|
@@ -131,6 +131,7 @@ interface VerifyFederationDelegationInput extends VerifyFederationBase {
|
|
|
131
131
|
declare function verifyFederationDelegation(input: VerifyFederationDelegationInput): Promise<FederationVerifyResult>;
|
|
132
132
|
interface VerifyFederationRevocationInput extends VerifyFederationBase {
|
|
133
133
|
envelope: FederationRevocationEnvelope;
|
|
134
|
+
delegation: FederationDelegationEnvelope;
|
|
134
135
|
}
|
|
135
136
|
declare function verifyFederationRevocation(input: VerifyFederationRevocationInput): Promise<FederationVerifyResult>;
|
|
136
137
|
|
package/dist/index.js
CHANGED
|
@@ -636,6 +636,12 @@ async function verifyAction(input) {
|
|
|
636
636
|
"verifyAction requires `revocations` (fetch kind-30085 by #delegation for every link in the chain) or an explicit skipRevocationCheck:true"
|
|
637
637
|
);
|
|
638
638
|
}
|
|
639
|
+
let actionAnchor;
|
|
640
|
+
try {
|
|
641
|
+
actionAnchor = await verifiedAnchorHeight(a, input);
|
|
642
|
+
} catch (e) {
|
|
643
|
+
return err("E_MALFORMED", `anchor verifier threw: ${e.message}`);
|
|
644
|
+
}
|
|
639
645
|
if (input.revocations && input.revocations.length > 0) {
|
|
640
646
|
const allLinks = [rootHydrated, ...hydratedChain];
|
|
641
647
|
for (const link of allLinks) {
|
|
@@ -648,9 +654,13 @@ async function verifyAction(input) {
|
|
|
648
654
|
skipSignatureVerification: input.skipSignatureVerification
|
|
649
655
|
});
|
|
650
656
|
if (!rr.ok) continue;
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
657
|
+
let revAnchor;
|
|
658
|
+
try {
|
|
659
|
+
revAnchor = await verifiedAnchorHeight(rev, input);
|
|
660
|
+
} catch (e) {
|
|
661
|
+
return err("E_MALFORMED", `anchor verifier threw: ${e.message}`);
|
|
662
|
+
}
|
|
663
|
+
if (revocationPrecedes(rev, revAnchor, a, actionAnchor)) {
|
|
654
664
|
return err("E_REVOKED", `chain link ${link.id} was revoked by ${rev.id} before action was signed`);
|
|
655
665
|
}
|
|
656
666
|
}
|
|
@@ -673,14 +683,7 @@ async function verifyAction(input) {
|
|
|
673
683
|
if (h === null || hash === null) {
|
|
674
684
|
return err("E_MALFORMED", "confirmed OTS proof missing block_height or block_hash");
|
|
675
685
|
}
|
|
676
|
-
|
|
677
|
-
if (input.verifyOtsAnchor) {
|
|
678
|
-
try {
|
|
679
|
-
verified = await input.verifyOtsAnchor(a.ots.proof, h, hash);
|
|
680
|
-
} catch (e) {
|
|
681
|
-
return err("E_MALFORMED", `anchor verifier threw: ${e.message}`);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
686
|
+
const verified = actionAnchor !== null;
|
|
684
687
|
anchor = { status: "confirmed", blockHeight: h, blockHash: hash, verified };
|
|
685
688
|
}
|
|
686
689
|
return {
|
|
@@ -750,9 +753,8 @@ async function verifyRevocation(input) {
|
|
|
750
753
|
return err("E_DELEGATION_MISMATCH", `revocation.delegation_id (${env.delegation_id}) != delegation.id (${d.id})`);
|
|
751
754
|
}
|
|
752
755
|
const holders = d.revocation?.holders ?? ["principal"];
|
|
753
|
-
const holderAddrs = /* @__PURE__ */ new Set();
|
|
754
|
-
if (holders.includes("
|
|
755
|
-
if (holders.includes("agent")) holderAddrs.add(d.agent.address);
|
|
756
|
+
const holderAddrs = /* @__PURE__ */ new Set([d.principal.address]);
|
|
757
|
+
if (Array.isArray(holders) && holders.includes("agent")) holderAddrs.add(d.agent.address);
|
|
756
758
|
if (!holderAddrs.has(env.signer.address)) {
|
|
757
759
|
return err("E_REVOKER_UNAUTHORIZED", `revocation signer ${env.signer.address} not in delegation holders`);
|
|
758
760
|
}
|
|
@@ -952,25 +954,17 @@ async function verifySubdelegation(input) {
|
|
|
952
954
|
}
|
|
953
955
|
return r;
|
|
954
956
|
}
|
|
955
|
-
function
|
|
956
|
-
if (
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
return
|
|
957
|
+
async function verifiedAnchorHeight(env, input) {
|
|
958
|
+
if (env.ots?.status !== "confirmed" || !input.verifyOtsAnchor) return null;
|
|
959
|
+
const h = input.resolveAnchorBlockHeight ? input.resolveAnchorBlockHeight(env) : env.ots.block_height;
|
|
960
|
+
const hash = env.ots.block_hash;
|
|
961
|
+
if (h === null || h === void 0 || !Number.isInteger(h) || !hash) return null;
|
|
962
|
+
return await input.verifyOtsAnchor(env.ots.proof, h, hash, env.id) ? h : null;
|
|
961
963
|
}
|
|
962
|
-
function
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
967
|
-
return { kind: "signed", ms: new Date(r.signed_at).getTime() };
|
|
968
|
-
}
|
|
969
|
-
function compareTimes(a, b) {
|
|
970
|
-
if (a.kind === "anchor" && b.kind === "anchor") return a.blockHeight - b.blockHeight;
|
|
971
|
-
if (a.kind === "anchor") return -1;
|
|
972
|
-
if (b.kind === "anchor") return 1;
|
|
973
|
-
return a.ms - b.ms;
|
|
964
|
+
function revocationPrecedes(rev, revAnchor, action, actionAnchor) {
|
|
965
|
+
if (revAnchor !== null && actionAnchor !== null) return revAnchor <= actionAnchor;
|
|
966
|
+
if (actionAnchor === null && rev.ots?.status === "confirmed") return true;
|
|
967
|
+
return new Date(rev.signed_at).getTime() <= new Date(action.signed_at).getTime();
|
|
974
968
|
}
|
|
975
969
|
function err(code, message) {
|
|
976
970
|
return { ok: false, code, message };
|
|
@@ -1120,6 +1114,17 @@ async function verifyFederationRevocation(input) {
|
|
|
1120
1114
|
return fail("E_MALFORMED", "reason must be a string \u2264128 bytes");
|
|
1121
1115
|
}
|
|
1122
1116
|
if (!ISO_UTC.test(env.signed_at)) return fail("E_MALFORMED", "signed_at must be ISO 8601 UTC");
|
|
1117
|
+
const d = input.delegation;
|
|
1118
|
+
if (!d || !HEX64.test(d.id ?? "")) return fail("E_MALFORMED", "the revoked delegation is required");
|
|
1119
|
+
if (env.delegation_id !== d.id) {
|
|
1120
|
+
return fail("E_DELEGATION_MISMATCH", `revocation.delegation_id (${env.delegation_id}) != delegation.id (${d.id})`);
|
|
1121
|
+
}
|
|
1122
|
+
if (d.principal?.alg !== "federation" || env.signer?.descriptor_id !== d.principal.descriptor_id) {
|
|
1123
|
+
return fail(
|
|
1124
|
+
"E_REVOKER_UNAUTHORIZED",
|
|
1125
|
+
`revoking federation (${env.signer?.descriptor_id}) is not the delegation's principal`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1123
1128
|
const canonInput = {
|
|
1124
1129
|
address: `federation:${env.signer?.descriptor_id ?? ""}`,
|
|
1125
1130
|
delegation_id: env.delegation_id,
|