@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 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
- const effective = effectiveRevocationTime(rev, input.resolveAnchorBlockHeight);
652
- const actionTime = actionEffectiveTime(a, input.resolveAnchorBlockHeight);
653
- if (compareTimes(effective, actionTime) <= 0) {
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
- let verified = false;
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("principal")) holderAddrs.add(d.principal.address);
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 actionEffectiveTime(a, resolve) {
956
- if (a.ots?.status === "confirmed") {
957
- const h = resolve ? resolve(a) : a.ots.block_height;
958
- if (h !== null && h !== void 0) return { kind: "anchor", blockHeight: h };
959
- }
960
- return { kind: "signed", ms: new Date(a.signed_at).getTime() };
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 effectiveRevocationTime(r, resolve) {
963
- if (r.ots?.status === "confirmed") {
964
- const h = resolve ? resolve(r) : r.ots.block_height;
965
- if (h !== null && h !== void 0) return { kind: "anchor", blockHeight: h };
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,