@orangecheck/agent-signer 0.5.1 → 0.6.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
@@ -89,7 +89,7 @@ interface CreateDelegationInput {
89
89
  nonce?: string; // default: random 32-hex
90
90
  revocationHolders?: ('principal' | 'agent')[]; // default: ['principal']
91
91
  revocationRef?: string | null;
92
- scopeMode?: 'strict' | 'permissive'; // default: 'strict'
92
+ // Scopes are checked strictly, as a strict verifier checks them; 'permissive' is refused.
93
93
  }
94
94
  ```
95
95
 
@@ -118,7 +118,7 @@ Returns an `ActionEnvelope` — a strict extension of an [OC Stamp](https://gith
118
118
  interface RevokeInput {
119
119
  signer: SignerRef; // the delegation's principal or agent
120
120
  delegation: DelegationEnvelope;
121
- reason?: string; // ASCII, <=128 bytes
121
+ reason?: string; // printable ASCII, <=128 bytes
122
122
  signedAt?: Date;
123
123
  ots?: ActionOts | null;
124
124
  }
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { DelegationBond, RevocationHolder, ValidationOptions, DelegationEnvelope, ActionOts, RevocationEnvelope, ActionEnvelope } from '@orangecheck/agent-core';
2
- export { ActionEnvelope, ActionOts, AgentErrorCode, DEFAULT_MAX_SKEW_MS, DelegationBond, DelegationEnvelope, REGISTERED_SCOPES, RevocationEnvelope, Scope, ScopeConstraint, VerifyActionAsOfSignedAtInput, VerifyActionInput, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, createReplayGuard, isSubScope, parseScope, verifyAction, verifyActionAsOfSignedAt, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
1
+ import { DelegationBond, RevocationHolder, DelegationEnvelope, ActionOts, RevocationEnvelope, ActionEnvelope } from '@orangecheck/agent-core';
2
+ export { ActionEnvelope, ActionOts, AgentErrorCode, DEFAULT_MAX_SKEW_MS, DelegationBond, DelegationEnvelope, REGISTERED_SCOPES, REVOCATION_REASON_MAX_BYTES, RevocationEnvelope, Scope, ScopeConstraint, VerifyActionAsOfSignedAtInput, VerifyActionInput, authoredReasonProblem, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, createReplayGuard, isSubScope, parseScope, shownReason, verifyAction, verifyActionAsOfSignedAt, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
3
3
 
4
4
  interface SignerRef {
5
5
  address: string;
@@ -16,7 +16,7 @@ interface CreateDelegationInput {
16
16
  nonce?: string;
17
17
  revocationHolders?: RevocationHolder[];
18
18
  revocationRef?: string | null;
19
- scopeMode?: ValidationOptions['mode'];
19
+ scopeMode?: 'strict';
20
20
  privateScopes?: {
21
21
  recipients: PrivateScopeRecipient[];
22
22
  hint?: string;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { DelegationBond, RevocationHolder, ValidationOptions, DelegationEnvelope, ActionOts, RevocationEnvelope, ActionEnvelope } from '@orangecheck/agent-core';
2
- export { ActionEnvelope, ActionOts, AgentErrorCode, DEFAULT_MAX_SKEW_MS, DelegationBond, DelegationEnvelope, REGISTERED_SCOPES, RevocationEnvelope, Scope, ScopeConstraint, VerifyActionAsOfSignedAtInput, VerifyActionInput, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, createReplayGuard, isSubScope, parseScope, verifyAction, verifyActionAsOfSignedAt, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
1
+ import { DelegationBond, RevocationHolder, DelegationEnvelope, ActionOts, RevocationEnvelope, ActionEnvelope } from '@orangecheck/agent-core';
2
+ export { ActionEnvelope, ActionOts, AgentErrorCode, DEFAULT_MAX_SKEW_MS, DelegationBond, DelegationEnvelope, REGISTERED_SCOPES, REVOCATION_REASON_MAX_BYTES, RevocationEnvelope, Scope, ScopeConstraint, VerifyActionAsOfSignedAtInput, VerifyActionInput, authoredReasonProblem, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, createReplayGuard, isSubScope, parseScope, shownReason, verifyAction, verifyActionAsOfSignedAt, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
3
3
 
4
4
  interface SignerRef {
5
5
  address: string;
@@ -16,7 +16,7 @@ interface CreateDelegationInput {
16
16
  nonce?: string;
17
17
  revocationHolders?: RevocationHolder[];
18
18
  revocationRef?: string | null;
19
- scopeMode?: ValidationOptions['mode'];
19
+ scopeMode?: 'strict';
20
20
  privateScopes?: {
21
21
  recipients: PrivateScopeRecipient[];
22
22
  hint?: string;
package/dist/index.js CHANGED
@@ -8,8 +8,10 @@ async function createDelegation(input) {
8
8
  if (!input.scopes || input.scopes.length === 0) {
9
9
  throw new Error("createDelegation: at least one scope is required");
10
10
  }
11
- const mode = input.scopeMode ?? "strict";
12
- for (const s of input.scopes) agentCore.validateScope(agentCore.parseScope(s), { mode });
11
+ if (input.scopeMode !== void 0 && input.scopeMode !== "strict") {
12
+ throw new Error('createDelegation: a signer is strict; scopeMode "permissive" is not offered');
13
+ }
14
+ for (const s of input.scopes) agentCore.validateScope(agentCore.parseScope(s), { mode: "strict" });
13
15
  const scopes = agentCore.canonicalizeScopes(input.scopes);
14
16
  const issuedAt = isoUtcSeconds(input.issuedAt ?? /* @__PURE__ */ new Date());
15
17
  let expiresAtDate;
@@ -115,6 +117,17 @@ async function signAsAgent(input) {
115
117
  throw new Error("signAsAgent: content.length must be a non-negative integer");
116
118
  }
117
119
  }
120
+ const exercised = agentCore.parseScope(input.scopeExercised);
121
+ const fromContent = agentCore.contentBoundScope(exercised, {
122
+ hash: contentHash,
123
+ length: contentLength,
124
+ mime: input.mime
125
+ });
126
+ if (!agentCore.isSubScope(fromContent, exercised)) {
127
+ throw new Error(
128
+ `signAsAgent: the content (${input.mime}, ${contentLength} bytes, ${contentHash}) is outside scope_exercised ${input.scopeExercised}`
129
+ );
130
+ }
118
131
  const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
119
132
  const canonInput = {
120
133
  address: input.agent.address,
@@ -154,7 +167,18 @@ async function revoke(input) {
154
167
  );
155
168
  }
156
169
  const reason = input.reason ?? "";
157
- if (reason.length > 128) throw new Error("revoke: reason must be <=128 bytes");
170
+ switch (agentCore.authoredReasonProblem(reason)) {
171
+ case null:
172
+ break;
173
+ case "too_long":
174
+ throw new Error(`revoke: reason must be at most ${agentCore.REVOCATION_REASON_MAX_BYTES} bytes`);
175
+ case "not_ascii":
176
+ throw new Error("revoke: reason must be ASCII");
177
+ case "control_character":
178
+ throw new Error("revoke: reason must have no line break or other control character");
179
+ case "not_text":
180
+ throw new Error("revoke: reason must be text");
181
+ }
158
182
  const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
159
183
  const canonInput = {
160
184
  address: input.signer.address,
@@ -204,6 +228,14 @@ Object.defineProperty(exports, "REGISTERED_SCOPES", {
204
228
  enumerable: true,
205
229
  get: function () { return agentCore.REGISTERED_SCOPES; }
206
230
  });
231
+ Object.defineProperty(exports, "REVOCATION_REASON_MAX_BYTES", {
232
+ enumerable: true,
233
+ get: function () { return agentCore.REVOCATION_REASON_MAX_BYTES; }
234
+ });
235
+ Object.defineProperty(exports, "authoredReasonProblem", {
236
+ enumerable: true,
237
+ get: function () { return agentCore.authoredReasonProblem; }
238
+ });
207
239
  Object.defineProperty(exports, "canonicalizeScope", {
208
240
  enumerable: true,
209
241
  get: function () { return agentCore.canonicalizeScope; }
@@ -236,6 +268,10 @@ Object.defineProperty(exports, "parseScope", {
236
268
  enumerable: true,
237
269
  get: function () { return agentCore.parseScope; }
238
270
  });
271
+ Object.defineProperty(exports, "shownReason", {
272
+ enumerable: true,
273
+ get: function () { return agentCore.shownReason; }
274
+ });
239
275
  Object.defineProperty(exports, "verifyAction", {
240
276
  enumerable: true,
241
277
  get: function () { return agentCore.verifyAction; }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["validateScope","parseScope","canonicalizeScopes","computeDelegationId","sealScopes","ENVELOPE_VERSION","hexEncode","sha256","computeActionId","computeRevocationId"],"mappings":";;;;;;AAuGA,eAAsB,iBAAiB,KAAA,EAA2D;AAC9F,EAAA,IAAI,CAAC,KAAA,CAAM,MAAA,IAAU,KAAA,CAAM,MAAA,CAAO,WAAW,CAAA,EAAG;AAC5C,IAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,EACtE;AACA,EAAA,MAAM,IAAA,GAAO,MAAM,SAAA,IAAa,QAAA;AAChC,EAAA,KAAA,MAAW,CAAA,IAAK,MAAM,MAAA,EAAQA,uBAAA,CAAcC,qBAAW,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,CAAA;AACnE,EAAA,MAAM,MAAA,GAASC,4BAAA,CAAmB,KAAA,CAAM,MAAM,CAAA;AAE9C,EAAA,MAAM,WAAW,aAAA,CAAc,KAAA,CAAM,QAAA,oBAAY,IAAI,MAAM,CAAA;AAC3D,EAAA,IAAI,aAAA;AACJ,EAAA,IAAI,MAAM,SAAA,EAAW;AACjB,IAAA,aAAA,GAAgB,KAAA,CAAM,SAAA;AAAA,EAC1B,CAAA,MAAO;AACH,IAAA,MAAM,MAAM,KAAA,CAAM,KAAA,IAAS,CAAA,GAAI,EAAA,GAAK,KAAK,EAAA,GAAK,GAAA;AAC9C,IAAA,aAAA,GAAgB,IAAI,KAAK,IAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,OAAA,KAAY,GAAG,CAAA;AAAA,EAC/D;AACA,EAAA,MAAM,SAAA,GAAY,cAAc,aAAa,CAAA;AAE7C,EAAA,MAAM,UAAA,GAAa,IAAI,IAAA,CAAK,SAAS,CAAA,CAAE,OAAA,EAAQ,GAAI,IAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,OAAA,EAAQ;AAC9E,EAAA,IAAI,UAAA,IAAc,CAAA,EAAG,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAC3F,EAAA,IAAI,UAAA,GAAa,GAAA,GAAM,EAAA,GAAK,EAAA,GAAK,KAAK,GAAA,EAAM;AACxC,IAAA,MAAM,IAAI,MAAM,gEAAgE,CAAA;AAAA,EACpF;AAEA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,IAAS,SAAA,CAAU,EAAE,CAAA;AACzC,EAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,CAAK,KAAK,CAAA,EAAG;AAC/B,IAAA,MAAM,IAAI,MAAM,wDAAwD,CAAA;AAAA,EAC5E;AAEA,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,IAAQ,IAAA;AAC3B,EAAA,IAAI,IAAA,EAAM;AACN,IAAA,IAAI,CAAC,OAAO,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA,IAAK,IAAA,CAAK,OAAO,CAAA,EAAG;AAC/C,MAAA,MAAM,IAAI,MAAM,4DAA4D,CAAA;AAAA,IAChF;AACA,IAAA,IAAI,CAAC,gBAAA,CAAiB,IAAA,CAAK,IAAA,CAAK,cAAc,CAAA,EAAG;AAC7C,MAAA,MAAM,IAAI,MAAM,sEAAsE,CAAA;AAAA,IAC1F;AAAA,EACJ;AAEA,EAAA,MAAM,UAAA,GAAa;AAAA,IACf,SAAA,EAAW,MAAM,SAAA,CAAU,OAAA;AAAA,IAC3B,OAAO,KAAA,CAAM,YAAA;AAAA,IACb,MAAA;AAAA,IACA,SAAA,EAAW,MAAM,IAAA,IAAQ,CAAA;AAAA,IACzB,gBAAA,EAAkB,MAAM,cAAA,IAAkB,MAAA;AAAA,IAC1C,SAAA,EAAW,QAAA;AAAA,IACX,UAAA,EAAY,SAAA;AAAA,IACZ;AAAA,GACJ;AACA,EAAA,MAAM,EAAA,GAAKC,8BAAoB,UAAU,CAAA;AAKzC,EAAA,IAAI,eAAA;AACJ,EAAA,IAAI,MAAM,aAAA,EAAe;AACrB,IAAA,IAAI,CAAC,MAAM,aAAA,CAAc,UAAA,IAAc,MAAM,aAAA,CAAc,UAAA,CAAW,WAAW,CAAA,EAAG;AAChF,MAAA,MAAM,IAAI,KAAA;AAAA,QACN;AAAA,OACJ;AAAA,IACJ;AACA,IAAA,eAAA,GAAkB,MAAMC,oBAAA,CAAW;AAAA,MAC/B,MAAA;AAAA,MACA,MAAA,EAAQ;AAAA,QACJ,OAAA,EAAS,MAAM,SAAA,CAAU,OAAA;AAAA,QACzB,WAAA,EAAa,MAAM,SAAA,CAAU;AAAA,OACjC;AAAA,MACA,UAAA,EAAY,MAAM,aAAA,CAAc,UAAA;AAAA,MAChC,GAAI,KAAA,CAAM,aAAA,CAAc,IAAA,KAAS,MAAA,IAAa;AAAA,QAC1C,IAAA,EAAM,MAAM,aAAA,CAAc;AAAA,OAC9B;AAAA,MACA,GAAI,KAAA,CAAM,aAAA,CAAc,iBAAA,KAAsB,MAAA,IAAa;AAAA,QACvD,SAAA,EAAW,MAAM,aAAA,CAAc;AAAA;AACnC,KACH,CAAA;AAAA,EACL;AAEA,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,SAAA,CAAU,YAAY,EAAE,CAAA;AAErD,EAAA,MAAM,QAAA,GAA+B;AAAA,IACjC,CAAA,EAAGC,0BAAA;AAAA,IACH,IAAA,EAAM,kBAAA;AAAA,IACN,EAAA;AAAA,IACA,WAAW,EAAE,OAAA,EAAS,MAAM,SAAA,CAAU,OAAA,EAAS,KAAK,QAAA,EAAS;AAAA,IAC7D,OAAO,EAAE,OAAA,EAAS,KAAA,CAAM,YAAA,EAAc,KAAK,QAAA,EAAS;AAAA;AAAA,IAEpD,GAAI,eAAA,GAAkB,EAAE,kBAAkB,eAAA,EAAgB,GAAI,EAAE,MAAA,EAAO;AAAA,IACvE,IAAA;AAAA,IACA,SAAA,EAAW,QAAA;AAAA,IACX,UAAA,EAAY,SAAA;AAAA,IACZ,KAAA;AAAA,IACA,UAAA,EAAY;AAAA,MACR,OAAA,EAAS,KAAA,CAAM,iBAAA,IAAqB,CAAC,WAAW,CAAA;AAAA,MAChD,GAAA,EAAK,MAAM,aAAA,IAAiB;AAAA,KAChC;AAAA,IACA,GAAA,EAAK;AAAA,MACD,GAAA,EAAK,QAAA;AAAA,MACL,MAAA,EAAQ,MAAM,SAAA,CAAU,OAAA;AAAA,MACxB,KAAA,EAAO;AAAA;AACX,GACJ;AACA,EAAA,OAAO,QAAA;AACX;AAoBA,eAAsB,YAAY,KAAA,EAAkD;AAChF,EAAA,IAAI,MAAM,KAAA,CAAM,OAAA,KAAY,KAAA,CAAM,UAAA,CAAW,MAAM,OAAA,EAAS;AACxD,IAAA,MAAM,IAAI,MAAM,qEAAqE,CAAA;AAAA,EACzF;AAEA,EAAA,IAAI,WAAA;AACJ,EAAA,IAAI,aAAA;AACJ,EAAA,IAAI,KAAA,CAAM,mBAAmB,UAAA,EAAY;AACrC,IAAA,WAAA,GAAc,SAAA,GAAYC,mBAAA,CAAUC,aAAA,CAAO,KAAA,CAAM,OAAO,CAAC,CAAA;AACzD,IAAA,aAAA,GAAgB,MAAM,OAAA,CAAQ,UAAA;AAAA,EAClC,CAAA,MAAO;AACH,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,UAAA,CAAW,SAAS,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,MAAA,KAAW,SAAA,CAAU,SAAS,EAAA,EAAI;AAClG,MAAA,MAAM,IAAI,MAAM,sEAAsE,CAAA;AAAA,IAC1F;AACA,IAAA,WAAA,GAAc,MAAM,OAAA,CAAQ,IAAA;AAC5B,IAAA,aAAA,GAAgB,MAAM,OAAA,CAAQ,MAAA;AAC9B,IAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,aAAa,CAAA,IAAK,gBAAgB,CAAA,EAAG;AACvD,MAAA,MAAM,IAAI,MAAM,4DAA4D,CAAA;AAAA,IAChF;AAAA,EACJ;AAEA,EAAA,MAAM,WAAW,aAAA,CAAc,KAAA,CAAM,QAAA,oBAAY,IAAI,MAAM,CAAA;AAE3D,EAAA,MAAM,UAAA,GAAa;AAAA,IACf,OAAA,EAAS,MAAM,KAAA,CAAM,OAAA;AAAA,IACrB,YAAA,EAAc,WAAA;AAAA,IACd,cAAA,EAAgB,aAAA;AAAA,IAChB,cAAc,KAAA,CAAM,IAAA;AAAA,IACpB,SAAA,EAAW,QAAA;AAAA,IACX,aAAA,EAAe,MAAM,UAAA,CAAW,EAAA;AAAA,IAChC,iBAAiB,KAAA,CAAM;AAAA,GAC3B;AACA,EAAA,MAAM,EAAA,GAAKC,0BAAgB,UAAU,CAAA;AAErC,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,KAAA,CAAM,YAAY,EAAE,CAAA;AAEjD,EAAA,MAAM,QAAA,GAA2B;AAAA,IAC7B,CAAA,EAAGH,0BAAA;AAAA,IACH,IAAA,EAAM,cAAA;AAAA,IACN,EAAA;AAAA,IACA,OAAA,EAAS;AAAA,MACL,IAAA,EAAM,WAAA;AAAA,MACN,MAAA,EAAQ,aAAA;AAAA,MACR,MAAM,KAAA,CAAM,IAAA;AAAA,MACZ,GAAA,EAAK,MAAM,GAAA,IAAO;AAAA,KACtB;AAAA,IACA,QAAQ,EAAE,OAAA,EAAS,MAAM,KAAA,CAAM,OAAA,EAAS,KAAK,QAAA,EAAS;AAAA,IACtD,SAAA,EAAW,QAAA;AAAA,IACX,aAAA,EAAe,MAAM,UAAA,CAAW,EAAA;AAAA,IAChC,iBAAiB,KAAA,CAAM,cAAA;AAAA,IACvB,GAAA,EAAK,MAAM,GAAA,IAAO,IAAA;AAAA,IAClB,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,KAAA,CAAM,OAAA,EAAS,KAAA,EAAO,QAAA;AAAS,GACvE;AACA,EAAA,OAAO,QAAA;AACX;AAgBA,eAAsB,OAAO,KAAA,EAAiD;AAE1E,EAAA,MAAM,EAAE,SAAA,EAAW,KAAA,EAAM,GAAI,KAAA,CAAM,UAAA;AACnC,EAAA,IAAI,KAAA,CAAM,OAAO,OAAA,KAAY,SAAA,CAAU,WAAW,KAAA,CAAM,MAAA,CAAO,OAAA,KAAY,KAAA,CAAM,OAAA,EAAS;AACtF,IAAA,MAAM,IAAI,KAAA;AAAA,MACN,CAAA,eAAA,EAAkB,KAAA,CAAM,MAAA,CAAO,OAAO,CAAA,yCAAA;AAAA,KAC1C;AAAA,EACJ;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,IAAU,EAAA;AAC/B,EAAA,IAAI,OAAO,MAAA,GAAS,GAAA,EAAK,MAAM,IAAI,MAAM,oCAAoC,CAAA;AAE7E,EAAA,MAAM,WAAW,aAAA,CAAc,KAAA,CAAM,QAAA,oBAAY,IAAI,MAAM,CAAA;AAC3D,EAAA,MAAM,UAAA,GAAa;AAAA,IACf,OAAA,EAAS,MAAM,MAAA,CAAO,OAAA;AAAA,IACtB,aAAA,EAAe,MAAM,UAAA,CAAW,EAAA;AAAA,IAChC,MAAA;AAAA,IACA,SAAA,EAAW;AAAA,GACf;AACA,EAAA,MAAM,EAAA,GAAKI,8BAAoB,UAAU,CAAA;AAEzC,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,MAAA,CAAO,YAAY,EAAE,CAAA;AAElD,EAAA,MAAM,QAAA,GAA+B;AAAA,IACjC,CAAA,EAAGJ,0BAAA;AAAA,IACH,IAAA,EAAM,kBAAA;AAAA,IACN,EAAA;AAAA,IACA,aAAA,EAAe,MAAM,UAAA,CAAW,EAAA;AAAA,IAChC,QAAQ,EAAE,OAAA,EAAS,MAAM,MAAA,CAAO,OAAA,EAAS,KAAK,QAAA,EAAS;AAAA,IACvD,MAAA;AAAA,IACA,SAAA,EAAW,QAAA;AAAA,IACX,GAAA,EAAK,MAAM,GAAA,IAAO,IAAA;AAAA,IAClB,GAAA,EAAK,EAAE,GAAA,EAAK,QAAA,EAAU,QAAQ,KAAA,CAAM,MAAA,CAAO,OAAA,EAAS,KAAA,EAAO,QAAA;AAAS,GACxE;AACA,EAAA,OAAO,QAAA;AACX;AAuCA,SAAS,cAAc,CAAA,EAAiB;AACpC,EAAA,OAAO,CAAA,CAAE,WAAA,EAAY,CAAE,OAAA,CAAQ,WAAW,GAAG,CAAA;AACjD;AAEA,SAAS,UAAU,KAAA,EAAuB;AACtC,EAAA,MAAM,GAAA,GAAM,IAAI,UAAA,CAAW,KAAK,CAAA;AAChC,EAAA,MAAM,IAAyB,UAAA,CAA8C,MAAA;AAC7E,EAAA,IAAI,CAAA,IAAK,OAAO,CAAA,CAAE,eAAA,KAAoB,UAAA,EAAY;AAC9C,IAAA,CAAA,CAAE,gBAAgB,GAAG,CAAA;AAAA,EACzB,CAAA,MAAO;AAEH,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK,GAAA,CAAI,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,KAAW,GAAG,CAAA;AAAA,EAC3E;AACA,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC5B,IAAA,MAAM,CAAA,GAAI,IAAI,CAAC,CAAA;AACf,IAAA,GAAA,IAAO,EAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAAA,EACzC;AACA,EAAA,OAAO,GAAA;AACX","file":"index.js","sourcesContent":["// @orangecheck/agent-signer — high-level API for producing OC Agent envelopes.\n//\n// createDelegation — principal signs a scoped grant to an agent address.\n// signAsAgent — agent produces an agent-action envelope over content.\n// revoke — principal (or agent, if authorized) burns a delegation.\n//\n// Each function wraps @orangecheck/agent-core's canonical-message + id\n// computation with a caller-supplied `signMessage` wallet adapter. Nothing here\n// touches the network — OTS anchoring and Nostr publication are separate steps\n// the caller wires up.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n ENVELOPE_VERSION,\n hexEncode,\n parseScope,\n sealScopes,\n validateScope,\n} from '@orangecheck/agent-core';\nimport type {\n ScopesEncryptedEnvelope,\n ValidationOptions,\n} from '@orangecheck/agent-core';\nimport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n RevocationHolder,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared wallet-adapter shape (matches `@orangecheck/stamp-core` StampInput).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignerRef {\n address: string;\n /** Returns a base64 BIP-322 signature over the given message as UTF-8 bytes. */\n signMessage: (msg: string) => Promise<string>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// createDelegation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface CreateDelegationInput {\n principal: SignerRef;\n agentAddress: string;\n scopes: string[];\n bond?: DelegationBond | null;\n issuedAt?: Date;\n /** Duration from issuedAt in ms. Default 7 days. MAX 365 days per SPEC §4.4. */\n ttlMs?: number;\n /** Explicit expires_at overrides ttlMs. */\n expiresAt?: Date;\n /** 32-hex. Defaults to crypto.getRandomValues(16-byte) rendered as hex. */\n nonce?: string;\n /** Default ['principal']. */\n revocationHolders?: RevocationHolder[];\n /** Optional Nostr pointer to a revocation event. Non-cryptographic. */\n revocationRef?: string | null;\n /** Default 'strict'. 'permissive' allows unregistered scopes / constraint keys. */\n scopeMode?: ValidationOptions['mode'];\n /**\n * v1.2 private-scope (PRIVATE-SCOPE.md). When supplied, the resulting\n * delegation envelope omits the public `scopes` field and instead carries\n * `scopes_encrypted` — an OC Lock envelope wrapping the canonical scope\n * list, sealed to one or more recipient device keys. The principal signs\n * BOTH the inner OC Lock envelope (via the `signMessage` callback already\n * provided on `principal`) AND the outer OC Agent canonical message.\n *\n * Recipients MUST include at least the agent's device. Additional\n * recipients (compliance auditors, third-party verifiers) are optional.\n */\n privateScopes?: {\n /**\n * Devices authorized to read the scope set. Each is `{ address,\n * device_id, device_pk }` matching OC Lock's `DeviceRecord` shape.\n * The same `signMessage` callback on `principal` is used to sign the\n * inner OC Lock envelope.\n */\n recipients: PrivateScopeRecipient[];\n /** Optional human hint stored in the OC Lock envelope. */\n hint?: string;\n /** Optional expiry on the OC Lock envelope. Independent of the\n * delegation's expires_at; usually left unset. */\n envelopeExpiresAt?: Date | null;\n };\n}\n\n/** Mirror of @orangecheck/lock-core's DeviceRecord. */\nexport interface PrivateScopeRecipient {\n address: string;\n device_id: string;\n /** 32-byte hex X25519 public key. */\n device_pk: string;\n}\n\nexport async function createDelegation(input: CreateDelegationInput): Promise<DelegationEnvelope> {\n if (!input.scopes || input.scopes.length === 0) {\n throw new Error('createDelegation: at least one scope is required');\n }\n const mode = input.scopeMode ?? 'strict';\n for (const s of input.scopes) validateScope(parseScope(s), { mode });\n const scopes = canonicalizeScopes(input.scopes);\n\n const issuedAt = isoUtcSeconds(input.issuedAt ?? new Date());\n let expiresAtDate: Date;\n if (input.expiresAt) {\n expiresAtDate = input.expiresAt;\n } else {\n const ttl = input.ttlMs ?? 7 * 24 * 60 * 60 * 1000;\n expiresAtDate = new Date(new Date(issuedAt).getTime() + ttl);\n }\n const expiresAt = isoUtcSeconds(expiresAtDate);\n\n const durationMs = new Date(expiresAt).getTime() - new Date(issuedAt).getTime();\n if (durationMs <= 0) throw new Error('createDelegation: expires_at must be after issued_at');\n if (durationMs > 365 * 24 * 60 * 60 * 1000) {\n throw new Error('createDelegation: delegation duration must not exceed 365 days');\n }\n\n const nonce = input.nonce ?? randomHex(16);\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n throw new Error('createDelegation: nonce must be 32 lowercase hex chars');\n }\n\n const bond = input.bond ?? null;\n if (bond) {\n if (!Number.isInteger(bond.sats) || bond.sats < 0) {\n throw new Error('createDelegation: bond.sats must be a non-negative integer');\n }\n if (!/^[0-9a-f]{64}$/.test(bond.attestation_id)) {\n throw new Error('createDelegation: bond.attestation_id must be 64 lowercase hex chars');\n }\n }\n\n const canonInput = {\n principal: input.principal.address,\n agent: input.agentAddress,\n scopes,\n bond_sats: bond?.sats ?? 0,\n bond_attestation: bond?.attestation_id ?? 'none',\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n };\n const id = computeDelegationId(canonInput);\n\n // v1.2 private-scope: seal BEFORE the principal's outer signature so the\n // user's wallet sees both prompts in a deterministic order (inner Lock\n // sig first, outer OC Agent sig second).\n let scopesEncrypted: ScopesEncryptedEnvelope | undefined;\n if (input.privateScopes) {\n if (!input.privateScopes.recipients || input.privateScopes.recipients.length === 0) {\n throw new Error(\n 'createDelegation: privateScopes.recipients must contain at least the agent device'\n );\n }\n scopesEncrypted = await sealScopes({\n scopes,\n sender: {\n address: input.principal.address,\n signMessage: input.principal.signMessage,\n },\n recipients: input.privateScopes.recipients,\n ...(input.privateScopes.hint !== undefined && {\n hint: input.privateScopes.hint,\n }),\n ...(input.privateScopes.envelopeExpiresAt !== undefined && {\n expiresAt: input.privateScopes.envelopeExpiresAt,\n }),\n });\n }\n\n const sigValue = await input.principal.signMessage(id);\n\n const envelope: DelegationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-delegation',\n id,\n principal: { address: input.principal.address, alg: 'bip322' },\n agent: { address: input.agentAddress, alg: 'bip322' },\n // Public OR private mode — never both. PRIVATE-SCOPE.md §1.1.\n ...(scopesEncrypted ? { scopes_encrypted: scopesEncrypted } : { scopes }),\n bond,\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n revocation: {\n holders: input.revocationHolders ?? ['principal'],\n ref: input.revocationRef ?? null,\n },\n sig: {\n alg: 'bip322',\n pubkey: input.principal.address,\n value: sigValue,\n },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// signAsAgent\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignAsAgentInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Raw bytes to stamp, or a pre-computed { hash, length } pair. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n /** Which scope from delegation.scopes is being exercised (or a narrower sub-scope). */\n scopeExercised: string;\n ref?: string | null;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelope> {\n if (input.agent.address !== input.delegation.agent.address) {\n throw new Error('signAsAgent: signer address does not match delegation.agent.address');\n }\n\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n if (!input.content.hash.startsWith('sha256:') || input.content.hash.length !== 'sha256:'.length + 64) {\n throw new Error('signAsAgent: content.hash must be \"sha256:\" + 64 lowercase hex chars');\n }\n contentHash = input.content.hash;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new Error('signAsAgent: content.length must be a non-negative integer');\n }\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canonInput = {\n address: input.agent.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n };\n const id = computeActionId(canonInput);\n\n const sigValue = await input.agent.signMessage(id);\n\n const envelope: ActionEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-action',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: { address: input.agent.address, alg: 'bip322' },\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.agent.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// revoke\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevokeInput {\n signer: SignerRef;\n delegation: DelegationEnvelope;\n /** Short ASCII rationale. Defaults to empty string. */\n reason?: string;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function revoke(input: RevokeInput): Promise<RevocationEnvelope> {\n // The principal and the agent may always revoke (SPEC §9.5).\n const { principal, agent } = input.delegation;\n if (input.signer.address !== principal.address && input.signer.address !== agent.address) {\n throw new Error(\n `revoke: signer ${input.signer.address} not authorized to revoke this delegation`\n );\n }\n\n const reason = input.reason ?? '';\n if (reason.length > 128) throw new Error('revoke: reason must be <=128 bytes');\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n const canonInput = {\n address: input.signer.address,\n delegation_id: input.delegation.id,\n reason,\n signed_at: signedAt,\n };\n const id = computeRevocationId(canonInput);\n\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: RevocationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-revocation',\n id,\n delegation_id: input.delegation.id,\n signer: { address: input.signer.address, alg: 'bip322' },\n reason,\n signed_at: signedAt,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.signer.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Re-exports for convenience\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n verifyAction,\n verifyActionAsOfSignedAt,\n createReplayGuard,\n DEFAULT_MAX_SKEW_MS,\n verifyDelegation,\n verifyRevocation,\n parseScope,\n isSubScope,\n canonicalizeScope,\n REGISTERED_SCOPES,\n} from '@orangecheck/agent-core';\nexport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n Scope,\n ScopeConstraint,\n AgentErrorCode,\n VerifyActionInput,\n VerifyActionAsOfSignedAtInput,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction isoUtcSeconds(d: Date): string {\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n\nfunction randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes);\n const c: Crypto | undefined = (globalThis as unknown as { crypto?: Crypto }).crypto;\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(buf);\n } else {\n // Non-browser fallback. Node ≥18 exposes globalThis.crypto, so this is unlikely to fire.\n for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);\n }\n let out = '';\n for (let i = 0; i < bytes; i++) {\n const b = buf[i]!;\n out += b.toString(16).padStart(2, '0');\n }\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["validateScope","parseScope","canonicalizeScopes","computeDelegationId","sealScopes","ENVELOPE_VERSION","hexEncode","sha256","contentBoundScope","isSubScope","computeActionId","authoredReasonProblem","REVOCATION_REASON_MAX_BYTES","computeRevocationId"],"mappings":"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","file":"index.js","sourcesContent":["// @orangecheck/agent-signer — high-level API for producing OC Agent envelopes.\n//\n// createDelegation — principal signs a scoped grant to an agent address.\n// signAsAgent — agent produces an agent-action envelope over content.\n// revoke — principal (or agent, if authorized) burns a delegation.\n//\n// Each function wraps @orangecheck/agent-core's canonical-message + id\n// computation with a caller-supplied `signMessage` wallet adapter. Nothing here\n// touches the network — OTS anchoring and Nostr publication are separate steps\n// the caller wires up.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n contentBoundScope,\n ENVELOPE_VERSION,\n hexEncode,\n authoredReasonProblem,\n isSubScope,\n parseScope,\n REVOCATION_REASON_MAX_BYTES,\n sealScopes,\n validateScope,\n} from '@orangecheck/agent-core';\nimport type { ScopesEncryptedEnvelope } from '@orangecheck/agent-core';\nimport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n RevocationHolder,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared wallet-adapter shape (matches `@orangecheck/stamp-core` StampInput).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignerRef {\n address: string;\n /** Returns a base64 BIP-322 signature over the given message as UTF-8 bytes. */\n signMessage: (msg: string) => Promise<string>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// createDelegation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface CreateDelegationInput {\n principal: SignerRef;\n agentAddress: string;\n scopes: string[];\n bond?: DelegationBond | null;\n issuedAt?: Date;\n /** Duration from issuedAt in ms. Default 7 days. MAX 365 days per SPEC §4.4. */\n ttlMs?: number;\n /** Explicit expires_at overrides ttlMs. */\n expiresAt?: Date;\n /** 32-hex. Defaults to crypto.getRandomValues(16-byte) rendered as hex. */\n nonce?: string;\n /** Default ['principal']. */\n revocationHolders?: RevocationHolder[];\n /** Optional Nostr pointer to a revocation event. Non-cryptographic. */\n revocationRef?: string | null;\n /** A signer is strict (SPEC §7.6): scopes are always checked as a strict verifier checks them. */\n scopeMode?: 'strict';\n /**\n * v1.2 private-scope (PRIVATE-SCOPE.md). When supplied, the resulting\n * delegation envelope omits the public `scopes` field and instead carries\n * `scopes_encrypted` — an OC Lock envelope wrapping the canonical scope\n * list, sealed to one or more recipient device keys. The principal signs\n * BOTH the inner OC Lock envelope (via the `signMessage` callback already\n * provided on `principal`) AND the outer OC Agent canonical message.\n *\n * Recipients MUST include at least the agent's device. Additional\n * recipients (compliance auditors, third-party verifiers) are optional.\n */\n privateScopes?: {\n /**\n * Devices authorized to read the scope set. Each is `{ address,\n * device_id, device_pk }` matching OC Lock's `DeviceRecord` shape.\n * The same `signMessage` callback on `principal` is used to sign the\n * inner OC Lock envelope.\n */\n recipients: PrivateScopeRecipient[];\n /** Optional human hint stored in the OC Lock envelope. */\n hint?: string;\n /** Optional expiry on the OC Lock envelope. Independent of the\n * delegation's expires_at; usually left unset. */\n envelopeExpiresAt?: Date | null;\n };\n}\n\n/** Mirror of @orangecheck/lock-core's DeviceRecord. */\nexport interface PrivateScopeRecipient {\n address: string;\n device_id: string;\n /** 32-byte hex X25519 public key. */\n device_pk: string;\n}\n\nexport async function createDelegation(input: CreateDelegationInput): Promise<DelegationEnvelope> {\n if (!input.scopes || input.scopes.length === 0) {\n throw new Error('createDelegation: at least one scope is required');\n }\n if (input.scopeMode !== undefined && input.scopeMode !== 'strict') {\n throw new Error('createDelegation: a signer is strict; scopeMode \"permissive\" is not offered');\n }\n for (const s of input.scopes) validateScope(parseScope(s), { mode: 'strict' });\n const scopes = canonicalizeScopes(input.scopes);\n\n const issuedAt = isoUtcSeconds(input.issuedAt ?? new Date());\n let expiresAtDate: Date;\n if (input.expiresAt) {\n expiresAtDate = input.expiresAt;\n } else {\n const ttl = input.ttlMs ?? 7 * 24 * 60 * 60 * 1000;\n expiresAtDate = new Date(new Date(issuedAt).getTime() + ttl);\n }\n const expiresAt = isoUtcSeconds(expiresAtDate);\n\n const durationMs = new Date(expiresAt).getTime() - new Date(issuedAt).getTime();\n if (durationMs <= 0) throw new Error('createDelegation: expires_at must be after issued_at');\n if (durationMs > 365 * 24 * 60 * 60 * 1000) {\n throw new Error('createDelegation: delegation duration must not exceed 365 days');\n }\n\n const nonce = input.nonce ?? randomHex(16);\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n throw new Error('createDelegation: nonce must be 32 lowercase hex chars');\n }\n\n const bond = input.bond ?? null;\n if (bond) {\n if (!Number.isInteger(bond.sats) || bond.sats < 0) {\n throw new Error('createDelegation: bond.sats must be a non-negative integer');\n }\n if (!/^[0-9a-f]{64}$/.test(bond.attestation_id)) {\n throw new Error('createDelegation: bond.attestation_id must be 64 lowercase hex chars');\n }\n }\n\n const canonInput = {\n principal: input.principal.address,\n agent: input.agentAddress,\n scopes,\n bond_sats: bond?.sats ?? 0,\n bond_attestation: bond?.attestation_id ?? 'none',\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n };\n const id = computeDelegationId(canonInput);\n\n // v1.2 private-scope: seal BEFORE the principal's outer signature so the\n // user's wallet sees both prompts in a deterministic order (inner Lock\n // sig first, outer OC Agent sig second).\n let scopesEncrypted: ScopesEncryptedEnvelope | undefined;\n if (input.privateScopes) {\n if (!input.privateScopes.recipients || input.privateScopes.recipients.length === 0) {\n throw new Error(\n 'createDelegation: privateScopes.recipients must contain at least the agent device'\n );\n }\n scopesEncrypted = await sealScopes({\n scopes,\n sender: {\n address: input.principal.address,\n signMessage: input.principal.signMessage,\n },\n recipients: input.privateScopes.recipients,\n ...(input.privateScopes.hint !== undefined && {\n hint: input.privateScopes.hint,\n }),\n ...(input.privateScopes.envelopeExpiresAt !== undefined && {\n expiresAt: input.privateScopes.envelopeExpiresAt,\n }),\n });\n }\n\n const sigValue = await input.principal.signMessage(id);\n\n const envelope: DelegationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-delegation',\n id,\n principal: { address: input.principal.address, alg: 'bip322' },\n agent: { address: input.agentAddress, alg: 'bip322' },\n // Public OR private mode — never both. PRIVATE-SCOPE.md §1.1.\n ...(scopesEncrypted ? { scopes_encrypted: scopesEncrypted } : { scopes }),\n bond,\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n revocation: {\n holders: input.revocationHolders ?? ['principal'],\n ref: input.revocationRef ?? null,\n },\n sig: {\n alg: 'bip322',\n pubkey: input.principal.address,\n value: sigValue,\n },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// signAsAgent\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignAsAgentInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Raw bytes to stamp, or a pre-computed { hash, length } pair. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n /** Which scope from delegation.scopes is being exercised (or a narrower sub-scope). */\n scopeExercised: string;\n ref?: string | null;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelope> {\n if (input.agent.address !== input.delegation.agent.address) {\n throw new Error('signAsAgent: signer address does not match delegation.agent.address');\n }\n\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n if (!input.content.hash.startsWith('sha256:') || input.content.hash.length !== 'sha256:'.length + 64) {\n throw new Error('signAsAgent: content.hash must be \"sha256:\" + 64 lowercase hex chars');\n }\n contentHash = input.content.hash;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new Error('signAsAgent: content.length must be a non-negative integer');\n }\n }\n\n // A verifier reads content-bound keys from the content (SPEC §8.3 step 12a);\n // sign nothing whose content falls outside the scope it claims.\n const exercised = parseScope(input.scopeExercised);\n const fromContent = contentBoundScope(exercised, {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n });\n if (!isSubScope(fromContent, exercised)) {\n throw new Error(\n `signAsAgent: the content (${input.mime}, ${contentLength} bytes, ${contentHash}) is outside ` +\n `scope_exercised ${input.scopeExercised}`\n );\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canonInput = {\n address: input.agent.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n };\n const id = computeActionId(canonInput);\n\n const sigValue = await input.agent.signMessage(id);\n\n const envelope: ActionEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-action',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: { address: input.agent.address, alg: 'bip322' },\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.agent.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// revoke\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevokeInput {\n signer: SignerRef;\n delegation: DelegationEnvelope;\n /** Printable ASCII, at most 128 bytes: SPEC §9.1 allows any ASCII; we write no control character. Defaults to \"\". */\n reason?: string;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function revoke(input: RevokeInput): Promise<RevocationEnvelope> {\n // The principal and the agent may always revoke (SPEC §9.5).\n const { principal, agent } = input.delegation;\n if (input.signer.address !== principal.address && input.signer.address !== agent.address) {\n throw new Error(\n `revoke: signer ${input.signer.address} not authorized to revoke this delegation`\n );\n }\n\n const reason = input.reason ?? '';\n // SPEC §9.1: a signer writes printable ASCII of at most 128 bytes.\n switch (authoredReasonProblem(reason)) {\n case null:\n break;\n case 'too_long':\n throw new Error(`revoke: reason must be at most ${REVOCATION_REASON_MAX_BYTES} bytes`);\n case 'not_ascii':\n throw new Error('revoke: reason must be ASCII');\n case 'control_character':\n throw new Error('revoke: reason must have no line break or other control character');\n case 'not_text':\n throw new Error('revoke: reason must be text');\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n const canonInput = {\n address: input.signer.address,\n delegation_id: input.delegation.id,\n reason,\n signed_at: signedAt,\n };\n const id = computeRevocationId(canonInput);\n\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: RevocationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-revocation',\n id,\n delegation_id: input.delegation.id,\n signer: { address: input.signer.address, alg: 'bip322' },\n reason,\n signed_at: signedAt,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.signer.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Re-exports for convenience\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n verifyAction,\n verifyActionAsOfSignedAt,\n createReplayGuard,\n DEFAULT_MAX_SKEW_MS,\n verifyDelegation,\n verifyRevocation,\n parseScope,\n isSubScope,\n canonicalizeScope,\n REGISTERED_SCOPES,\n authoredReasonProblem,\n REVOCATION_REASON_MAX_BYTES,\n shownReason,\n} from '@orangecheck/agent-core';\nexport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n Scope,\n ScopeConstraint,\n AgentErrorCode,\n VerifyActionInput,\n VerifyActionAsOfSignedAtInput,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction isoUtcSeconds(d: Date): string {\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n\nfunction randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes);\n const c: Crypto | undefined = (globalThis as unknown as { crypto?: Crypto }).crypto;\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(buf);\n } else {\n // Non-browser fallback. Node ≥18 exposes globalThis.crypto, so this is unlikely to fire.\n for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);\n }\n let out = '';\n for (let i = 0; i < bytes; i++) {\n const b = buf[i]!;\n out += b.toString(16).padStart(2, '0');\n }\n return out;\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,14 +1,16 @@
1
1
  import { sha256 } from '@noble/hashes/sha256';
2
- import { validateScope, parseScope, canonicalizeScopes, computeDelegationId, sealScopes, ENVELOPE_VERSION, hexEncode, computeActionId, computeRevocationId } from '@orangecheck/agent-core';
3
- export { DEFAULT_MAX_SKEW_MS, REGISTERED_SCOPES, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, createReplayGuard, isSubScope, parseScope, verifyAction, verifyActionAsOfSignedAt, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
2
+ import { validateScope, parseScope, canonicalizeScopes, computeDelegationId, sealScopes, ENVELOPE_VERSION, hexEncode, contentBoundScope, isSubScope, computeActionId, authoredReasonProblem, REVOCATION_REASON_MAX_BYTES, computeRevocationId } from '@orangecheck/agent-core';
3
+ export { DEFAULT_MAX_SKEW_MS, REGISTERED_SCOPES, REVOCATION_REASON_MAX_BYTES, authoredReasonProblem, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, createReplayGuard, isSubScope, parseScope, shownReason, verifyAction, verifyActionAsOfSignedAt, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
4
4
 
5
5
  // src/index.ts
6
6
  async function createDelegation(input) {
7
7
  if (!input.scopes || input.scopes.length === 0) {
8
8
  throw new Error("createDelegation: at least one scope is required");
9
9
  }
10
- const mode = input.scopeMode ?? "strict";
11
- for (const s of input.scopes) validateScope(parseScope(s), { mode });
10
+ if (input.scopeMode !== void 0 && input.scopeMode !== "strict") {
11
+ throw new Error('createDelegation: a signer is strict; scopeMode "permissive" is not offered');
12
+ }
13
+ for (const s of input.scopes) validateScope(parseScope(s), { mode: "strict" });
12
14
  const scopes = canonicalizeScopes(input.scopes);
13
15
  const issuedAt = isoUtcSeconds(input.issuedAt ?? /* @__PURE__ */ new Date());
14
16
  let expiresAtDate;
@@ -114,6 +116,17 @@ async function signAsAgent(input) {
114
116
  throw new Error("signAsAgent: content.length must be a non-negative integer");
115
117
  }
116
118
  }
119
+ const exercised = parseScope(input.scopeExercised);
120
+ const fromContent = contentBoundScope(exercised, {
121
+ hash: contentHash,
122
+ length: contentLength,
123
+ mime: input.mime
124
+ });
125
+ if (!isSubScope(fromContent, exercised)) {
126
+ throw new Error(
127
+ `signAsAgent: the content (${input.mime}, ${contentLength} bytes, ${contentHash}) is outside scope_exercised ${input.scopeExercised}`
128
+ );
129
+ }
117
130
  const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
118
131
  const canonInput = {
119
132
  address: input.agent.address,
@@ -153,7 +166,18 @@ async function revoke(input) {
153
166
  );
154
167
  }
155
168
  const reason = input.reason ?? "";
156
- if (reason.length > 128) throw new Error("revoke: reason must be <=128 bytes");
169
+ switch (authoredReasonProblem(reason)) {
170
+ case null:
171
+ break;
172
+ case "too_long":
173
+ throw new Error(`revoke: reason must be at most ${REVOCATION_REASON_MAX_BYTES} bytes`);
174
+ case "not_ascii":
175
+ throw new Error("revoke: reason must be ASCII");
176
+ case "control_character":
177
+ throw new Error("revoke: reason must have no line break or other control character");
178
+ case "not_text":
179
+ throw new Error("revoke: reason must be text");
180
+ }
157
181
  const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
158
182
  const canonInput = {
159
183
  address: input.signer.address,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @orangecheck/agent-signer — high-level API for producing OC Agent envelopes.\n//\n// createDelegation — principal signs a scoped grant to an agent address.\n// signAsAgent — agent produces an agent-action envelope over content.\n// revoke — principal (or agent, if authorized) burns a delegation.\n//\n// Each function wraps @orangecheck/agent-core's canonical-message + id\n// computation with a caller-supplied `signMessage` wallet adapter. Nothing here\n// touches the network — OTS anchoring and Nostr publication are separate steps\n// the caller wires up.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n ENVELOPE_VERSION,\n hexEncode,\n parseScope,\n sealScopes,\n validateScope,\n} from '@orangecheck/agent-core';\nimport type {\n ScopesEncryptedEnvelope,\n ValidationOptions,\n} from '@orangecheck/agent-core';\nimport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n RevocationHolder,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared wallet-adapter shape (matches `@orangecheck/stamp-core` StampInput).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignerRef {\n address: string;\n /** Returns a base64 BIP-322 signature over the given message as UTF-8 bytes. */\n signMessage: (msg: string) => Promise<string>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// createDelegation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface CreateDelegationInput {\n principal: SignerRef;\n agentAddress: string;\n scopes: string[];\n bond?: DelegationBond | null;\n issuedAt?: Date;\n /** Duration from issuedAt in ms. Default 7 days. MAX 365 days per SPEC §4.4. */\n ttlMs?: number;\n /** Explicit expires_at overrides ttlMs. */\n expiresAt?: Date;\n /** 32-hex. Defaults to crypto.getRandomValues(16-byte) rendered as hex. */\n nonce?: string;\n /** Default ['principal']. */\n revocationHolders?: RevocationHolder[];\n /** Optional Nostr pointer to a revocation event. Non-cryptographic. */\n revocationRef?: string | null;\n /** Default 'strict'. 'permissive' allows unregistered scopes / constraint keys. */\n scopeMode?: ValidationOptions['mode'];\n /**\n * v1.2 private-scope (PRIVATE-SCOPE.md). When supplied, the resulting\n * delegation envelope omits the public `scopes` field and instead carries\n * `scopes_encrypted` — an OC Lock envelope wrapping the canonical scope\n * list, sealed to one or more recipient device keys. The principal signs\n * BOTH the inner OC Lock envelope (via the `signMessage` callback already\n * provided on `principal`) AND the outer OC Agent canonical message.\n *\n * Recipients MUST include at least the agent's device. Additional\n * recipients (compliance auditors, third-party verifiers) are optional.\n */\n privateScopes?: {\n /**\n * Devices authorized to read the scope set. Each is `{ address,\n * device_id, device_pk }` matching OC Lock's `DeviceRecord` shape.\n * The same `signMessage` callback on `principal` is used to sign the\n * inner OC Lock envelope.\n */\n recipients: PrivateScopeRecipient[];\n /** Optional human hint stored in the OC Lock envelope. */\n hint?: string;\n /** Optional expiry on the OC Lock envelope. Independent of the\n * delegation's expires_at; usually left unset. */\n envelopeExpiresAt?: Date | null;\n };\n}\n\n/** Mirror of @orangecheck/lock-core's DeviceRecord. */\nexport interface PrivateScopeRecipient {\n address: string;\n device_id: string;\n /** 32-byte hex X25519 public key. */\n device_pk: string;\n}\n\nexport async function createDelegation(input: CreateDelegationInput): Promise<DelegationEnvelope> {\n if (!input.scopes || input.scopes.length === 0) {\n throw new Error('createDelegation: at least one scope is required');\n }\n const mode = input.scopeMode ?? 'strict';\n for (const s of input.scopes) validateScope(parseScope(s), { mode });\n const scopes = canonicalizeScopes(input.scopes);\n\n const issuedAt = isoUtcSeconds(input.issuedAt ?? new Date());\n let expiresAtDate: Date;\n if (input.expiresAt) {\n expiresAtDate = input.expiresAt;\n } else {\n const ttl = input.ttlMs ?? 7 * 24 * 60 * 60 * 1000;\n expiresAtDate = new Date(new Date(issuedAt).getTime() + ttl);\n }\n const expiresAt = isoUtcSeconds(expiresAtDate);\n\n const durationMs = new Date(expiresAt).getTime() - new Date(issuedAt).getTime();\n if (durationMs <= 0) throw new Error('createDelegation: expires_at must be after issued_at');\n if (durationMs > 365 * 24 * 60 * 60 * 1000) {\n throw new Error('createDelegation: delegation duration must not exceed 365 days');\n }\n\n const nonce = input.nonce ?? randomHex(16);\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n throw new Error('createDelegation: nonce must be 32 lowercase hex chars');\n }\n\n const bond = input.bond ?? null;\n if (bond) {\n if (!Number.isInteger(bond.sats) || bond.sats < 0) {\n throw new Error('createDelegation: bond.sats must be a non-negative integer');\n }\n if (!/^[0-9a-f]{64}$/.test(bond.attestation_id)) {\n throw new Error('createDelegation: bond.attestation_id must be 64 lowercase hex chars');\n }\n }\n\n const canonInput = {\n principal: input.principal.address,\n agent: input.agentAddress,\n scopes,\n bond_sats: bond?.sats ?? 0,\n bond_attestation: bond?.attestation_id ?? 'none',\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n };\n const id = computeDelegationId(canonInput);\n\n // v1.2 private-scope: seal BEFORE the principal's outer signature so the\n // user's wallet sees both prompts in a deterministic order (inner Lock\n // sig first, outer OC Agent sig second).\n let scopesEncrypted: ScopesEncryptedEnvelope | undefined;\n if (input.privateScopes) {\n if (!input.privateScopes.recipients || input.privateScopes.recipients.length === 0) {\n throw new Error(\n 'createDelegation: privateScopes.recipients must contain at least the agent device'\n );\n }\n scopesEncrypted = await sealScopes({\n scopes,\n sender: {\n address: input.principal.address,\n signMessage: input.principal.signMessage,\n },\n recipients: input.privateScopes.recipients,\n ...(input.privateScopes.hint !== undefined && {\n hint: input.privateScopes.hint,\n }),\n ...(input.privateScopes.envelopeExpiresAt !== undefined && {\n expiresAt: input.privateScopes.envelopeExpiresAt,\n }),\n });\n }\n\n const sigValue = await input.principal.signMessage(id);\n\n const envelope: DelegationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-delegation',\n id,\n principal: { address: input.principal.address, alg: 'bip322' },\n agent: { address: input.agentAddress, alg: 'bip322' },\n // Public OR private mode — never both. PRIVATE-SCOPE.md §1.1.\n ...(scopesEncrypted ? { scopes_encrypted: scopesEncrypted } : { scopes }),\n bond,\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n revocation: {\n holders: input.revocationHolders ?? ['principal'],\n ref: input.revocationRef ?? null,\n },\n sig: {\n alg: 'bip322',\n pubkey: input.principal.address,\n value: sigValue,\n },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// signAsAgent\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignAsAgentInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Raw bytes to stamp, or a pre-computed { hash, length } pair. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n /** Which scope from delegation.scopes is being exercised (or a narrower sub-scope). */\n scopeExercised: string;\n ref?: string | null;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelope> {\n if (input.agent.address !== input.delegation.agent.address) {\n throw new Error('signAsAgent: signer address does not match delegation.agent.address');\n }\n\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n if (!input.content.hash.startsWith('sha256:') || input.content.hash.length !== 'sha256:'.length + 64) {\n throw new Error('signAsAgent: content.hash must be \"sha256:\" + 64 lowercase hex chars');\n }\n contentHash = input.content.hash;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new Error('signAsAgent: content.length must be a non-negative integer');\n }\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canonInput = {\n address: input.agent.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n };\n const id = computeActionId(canonInput);\n\n const sigValue = await input.agent.signMessage(id);\n\n const envelope: ActionEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-action',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: { address: input.agent.address, alg: 'bip322' },\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.agent.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// revoke\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevokeInput {\n signer: SignerRef;\n delegation: DelegationEnvelope;\n /** Short ASCII rationale. Defaults to empty string. */\n reason?: string;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function revoke(input: RevokeInput): Promise<RevocationEnvelope> {\n // The principal and the agent may always revoke (SPEC §9.5).\n const { principal, agent } = input.delegation;\n if (input.signer.address !== principal.address && input.signer.address !== agent.address) {\n throw new Error(\n `revoke: signer ${input.signer.address} not authorized to revoke this delegation`\n );\n }\n\n const reason = input.reason ?? '';\n if (reason.length > 128) throw new Error('revoke: reason must be <=128 bytes');\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n const canonInput = {\n address: input.signer.address,\n delegation_id: input.delegation.id,\n reason,\n signed_at: signedAt,\n };\n const id = computeRevocationId(canonInput);\n\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: RevocationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-revocation',\n id,\n delegation_id: input.delegation.id,\n signer: { address: input.signer.address, alg: 'bip322' },\n reason,\n signed_at: signedAt,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.signer.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Re-exports for convenience\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n verifyAction,\n verifyActionAsOfSignedAt,\n createReplayGuard,\n DEFAULT_MAX_SKEW_MS,\n verifyDelegation,\n verifyRevocation,\n parseScope,\n isSubScope,\n canonicalizeScope,\n REGISTERED_SCOPES,\n} from '@orangecheck/agent-core';\nexport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n Scope,\n ScopeConstraint,\n AgentErrorCode,\n VerifyActionInput,\n VerifyActionAsOfSignedAtInput,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction isoUtcSeconds(d: Date): string {\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n\nfunction randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes);\n const c: Crypto | undefined = (globalThis as unknown as { crypto?: Crypto }).crypto;\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(buf);\n } else {\n // Non-browser fallback. Node ≥18 exposes globalThis.crypto, so this is unlikely to fire.\n for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);\n }\n let out = '';\n for (let i = 0; i < bytes; i++) {\n const b = buf[i]!;\n out += b.toString(16).padStart(2, '0');\n }\n return out;\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @orangecheck/agent-signer — high-level API for producing OC Agent envelopes.\n//\n// createDelegation — principal signs a scoped grant to an agent address.\n// signAsAgent — agent produces an agent-action envelope over content.\n// revoke — principal (or agent, if authorized) burns a delegation.\n//\n// Each function wraps @orangecheck/agent-core's canonical-message + id\n// computation with a caller-supplied `signMessage` wallet adapter. Nothing here\n// touches the network — OTS anchoring and Nostr publication are separate steps\n// the caller wires up.\n\nimport { sha256 } from '@noble/hashes/sha256';\nimport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n contentBoundScope,\n ENVELOPE_VERSION,\n hexEncode,\n authoredReasonProblem,\n isSubScope,\n parseScope,\n REVOCATION_REASON_MAX_BYTES,\n sealScopes,\n validateScope,\n} from '@orangecheck/agent-core';\nimport type { ScopesEncryptedEnvelope } from '@orangecheck/agent-core';\nimport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n RevocationHolder,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared wallet-adapter shape (matches `@orangecheck/stamp-core` StampInput).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignerRef {\n address: string;\n /** Returns a base64 BIP-322 signature over the given message as UTF-8 bytes. */\n signMessage: (msg: string) => Promise<string>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// createDelegation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface CreateDelegationInput {\n principal: SignerRef;\n agentAddress: string;\n scopes: string[];\n bond?: DelegationBond | null;\n issuedAt?: Date;\n /** Duration from issuedAt in ms. Default 7 days. MAX 365 days per SPEC §4.4. */\n ttlMs?: number;\n /** Explicit expires_at overrides ttlMs. */\n expiresAt?: Date;\n /** 32-hex. Defaults to crypto.getRandomValues(16-byte) rendered as hex. */\n nonce?: string;\n /** Default ['principal']. */\n revocationHolders?: RevocationHolder[];\n /** Optional Nostr pointer to a revocation event. Non-cryptographic. */\n revocationRef?: string | null;\n /** A signer is strict (SPEC §7.6): scopes are always checked as a strict verifier checks them. */\n scopeMode?: 'strict';\n /**\n * v1.2 private-scope (PRIVATE-SCOPE.md). When supplied, the resulting\n * delegation envelope omits the public `scopes` field and instead carries\n * `scopes_encrypted` — an OC Lock envelope wrapping the canonical scope\n * list, sealed to one or more recipient device keys. The principal signs\n * BOTH the inner OC Lock envelope (via the `signMessage` callback already\n * provided on `principal`) AND the outer OC Agent canonical message.\n *\n * Recipients MUST include at least the agent's device. Additional\n * recipients (compliance auditors, third-party verifiers) are optional.\n */\n privateScopes?: {\n /**\n * Devices authorized to read the scope set. Each is `{ address,\n * device_id, device_pk }` matching OC Lock's `DeviceRecord` shape.\n * The same `signMessage` callback on `principal` is used to sign the\n * inner OC Lock envelope.\n */\n recipients: PrivateScopeRecipient[];\n /** Optional human hint stored in the OC Lock envelope. */\n hint?: string;\n /** Optional expiry on the OC Lock envelope. Independent of the\n * delegation's expires_at; usually left unset. */\n envelopeExpiresAt?: Date | null;\n };\n}\n\n/** Mirror of @orangecheck/lock-core's DeviceRecord. */\nexport interface PrivateScopeRecipient {\n address: string;\n device_id: string;\n /** 32-byte hex X25519 public key. */\n device_pk: string;\n}\n\nexport async function createDelegation(input: CreateDelegationInput): Promise<DelegationEnvelope> {\n if (!input.scopes || input.scopes.length === 0) {\n throw new Error('createDelegation: at least one scope is required');\n }\n if (input.scopeMode !== undefined && input.scopeMode !== 'strict') {\n throw new Error('createDelegation: a signer is strict; scopeMode \"permissive\" is not offered');\n }\n for (const s of input.scopes) validateScope(parseScope(s), { mode: 'strict' });\n const scopes = canonicalizeScopes(input.scopes);\n\n const issuedAt = isoUtcSeconds(input.issuedAt ?? new Date());\n let expiresAtDate: Date;\n if (input.expiresAt) {\n expiresAtDate = input.expiresAt;\n } else {\n const ttl = input.ttlMs ?? 7 * 24 * 60 * 60 * 1000;\n expiresAtDate = new Date(new Date(issuedAt).getTime() + ttl);\n }\n const expiresAt = isoUtcSeconds(expiresAtDate);\n\n const durationMs = new Date(expiresAt).getTime() - new Date(issuedAt).getTime();\n if (durationMs <= 0) throw new Error('createDelegation: expires_at must be after issued_at');\n if (durationMs > 365 * 24 * 60 * 60 * 1000) {\n throw new Error('createDelegation: delegation duration must not exceed 365 days');\n }\n\n const nonce = input.nonce ?? randomHex(16);\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n throw new Error('createDelegation: nonce must be 32 lowercase hex chars');\n }\n\n const bond = input.bond ?? null;\n if (bond) {\n if (!Number.isInteger(bond.sats) || bond.sats < 0) {\n throw new Error('createDelegation: bond.sats must be a non-negative integer');\n }\n if (!/^[0-9a-f]{64}$/.test(bond.attestation_id)) {\n throw new Error('createDelegation: bond.attestation_id must be 64 lowercase hex chars');\n }\n }\n\n const canonInput = {\n principal: input.principal.address,\n agent: input.agentAddress,\n scopes,\n bond_sats: bond?.sats ?? 0,\n bond_attestation: bond?.attestation_id ?? 'none',\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n };\n const id = computeDelegationId(canonInput);\n\n // v1.2 private-scope: seal BEFORE the principal's outer signature so the\n // user's wallet sees both prompts in a deterministic order (inner Lock\n // sig first, outer OC Agent sig second).\n let scopesEncrypted: ScopesEncryptedEnvelope | undefined;\n if (input.privateScopes) {\n if (!input.privateScopes.recipients || input.privateScopes.recipients.length === 0) {\n throw new Error(\n 'createDelegation: privateScopes.recipients must contain at least the agent device'\n );\n }\n scopesEncrypted = await sealScopes({\n scopes,\n sender: {\n address: input.principal.address,\n signMessage: input.principal.signMessage,\n },\n recipients: input.privateScopes.recipients,\n ...(input.privateScopes.hint !== undefined && {\n hint: input.privateScopes.hint,\n }),\n ...(input.privateScopes.envelopeExpiresAt !== undefined && {\n expiresAt: input.privateScopes.envelopeExpiresAt,\n }),\n });\n }\n\n const sigValue = await input.principal.signMessage(id);\n\n const envelope: DelegationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-delegation',\n id,\n principal: { address: input.principal.address, alg: 'bip322' },\n agent: { address: input.agentAddress, alg: 'bip322' },\n // Public OR private mode — never both. PRIVATE-SCOPE.md §1.1.\n ...(scopesEncrypted ? { scopes_encrypted: scopesEncrypted } : { scopes }),\n bond,\n issued_at: issuedAt,\n expires_at: expiresAt,\n nonce,\n revocation: {\n holders: input.revocationHolders ?? ['principal'],\n ref: input.revocationRef ?? null,\n },\n sig: {\n alg: 'bip322',\n pubkey: input.principal.address,\n value: sigValue,\n },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// signAsAgent\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SignAsAgentInput {\n agent: SignerRef;\n delegation: DelegationEnvelope;\n /** Raw bytes to stamp, or a pre-computed { hash, length } pair. */\n content: Uint8Array | { hash: string; length: number };\n mime: string;\n /** Which scope from delegation.scopes is being exercised (or a narrower sub-scope). */\n scopeExercised: string;\n ref?: string | null;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelope> {\n if (input.agent.address !== input.delegation.agent.address) {\n throw new Error('signAsAgent: signer address does not match delegation.agent.address');\n }\n\n let contentHash: string;\n let contentLength: number;\n if (input.content instanceof Uint8Array) {\n contentHash = 'sha256:' + hexEncode(sha256(input.content));\n contentLength = input.content.byteLength;\n } else {\n if (!input.content.hash.startsWith('sha256:') || input.content.hash.length !== 'sha256:'.length + 64) {\n throw new Error('signAsAgent: content.hash must be \"sha256:\" + 64 lowercase hex chars');\n }\n contentHash = input.content.hash;\n contentLength = input.content.length;\n if (!Number.isInteger(contentLength) || contentLength < 0) {\n throw new Error('signAsAgent: content.length must be a non-negative integer');\n }\n }\n\n // A verifier reads content-bound keys from the content (SPEC §8.3 step 12a);\n // sign nothing whose content falls outside the scope it claims.\n const exercised = parseScope(input.scopeExercised);\n const fromContent = contentBoundScope(exercised, {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n });\n if (!isSubScope(fromContent, exercised)) {\n throw new Error(\n `signAsAgent: the content (${input.mime}, ${contentLength} bytes, ${contentHash}) is outside ` +\n `scope_exercised ${input.scopeExercised}`\n );\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n\n const canonInput = {\n address: input.agent.address,\n content_hash: contentHash,\n content_length: contentLength,\n content_mime: input.mime,\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n };\n const id = computeActionId(canonInput);\n\n const sigValue = await input.agent.signMessage(id);\n\n const envelope: ActionEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-action',\n id,\n content: {\n hash: contentHash,\n length: contentLength,\n mime: input.mime,\n ref: input.ref ?? null,\n },\n signer: { address: input.agent.address, alg: 'bip322' },\n signed_at: signedAt,\n delegation_id: input.delegation.id,\n scope_exercised: input.scopeExercised,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.agent.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// revoke\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RevokeInput {\n signer: SignerRef;\n delegation: DelegationEnvelope;\n /** Printable ASCII, at most 128 bytes: SPEC §9.1 allows any ASCII; we write no control character. Defaults to \"\". */\n reason?: string;\n signedAt?: Date;\n /** OTS proof / anchor data if the caller has already submitted; otherwise null. */\n ots?: ActionOts | null;\n}\n\nexport async function revoke(input: RevokeInput): Promise<RevocationEnvelope> {\n // The principal and the agent may always revoke (SPEC §9.5).\n const { principal, agent } = input.delegation;\n if (input.signer.address !== principal.address && input.signer.address !== agent.address) {\n throw new Error(\n `revoke: signer ${input.signer.address} not authorized to revoke this delegation`\n );\n }\n\n const reason = input.reason ?? '';\n // SPEC §9.1: a signer writes printable ASCII of at most 128 bytes.\n switch (authoredReasonProblem(reason)) {\n case null:\n break;\n case 'too_long':\n throw new Error(`revoke: reason must be at most ${REVOCATION_REASON_MAX_BYTES} bytes`);\n case 'not_ascii':\n throw new Error('revoke: reason must be ASCII');\n case 'control_character':\n throw new Error('revoke: reason must have no line break or other control character');\n case 'not_text':\n throw new Error('revoke: reason must be text');\n }\n\n const signedAt = isoUtcSeconds(input.signedAt ?? new Date());\n const canonInput = {\n address: input.signer.address,\n delegation_id: input.delegation.id,\n reason,\n signed_at: signedAt,\n };\n const id = computeRevocationId(canonInput);\n\n const sigValue = await input.signer.signMessage(id);\n\n const envelope: RevocationEnvelope = {\n v: ENVELOPE_VERSION,\n kind: 'agent-revocation',\n id,\n delegation_id: input.delegation.id,\n signer: { address: input.signer.address, alg: 'bip322' },\n reason,\n signed_at: signedAt,\n ots: input.ots ?? null,\n sig: { alg: 'bip322', pubkey: input.signer.address, value: sigValue },\n };\n return envelope;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Re-exports for convenience\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport {\n canonicalizeScopes,\n computeActionId,\n computeDelegationId,\n computeRevocationId,\n verifyAction,\n verifyActionAsOfSignedAt,\n createReplayGuard,\n DEFAULT_MAX_SKEW_MS,\n verifyDelegation,\n verifyRevocation,\n parseScope,\n isSubScope,\n canonicalizeScope,\n REGISTERED_SCOPES,\n authoredReasonProblem,\n REVOCATION_REASON_MAX_BYTES,\n shownReason,\n} from '@orangecheck/agent-core';\nexport type {\n ActionEnvelope,\n ActionOts,\n DelegationBond,\n DelegationEnvelope,\n RevocationEnvelope,\n Scope,\n ScopeConstraint,\n AgentErrorCode,\n VerifyActionInput,\n VerifyActionAsOfSignedAtInput,\n} from '@orangecheck/agent-core';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction isoUtcSeconds(d: Date): string {\n return d.toISOString().replace(/\\.\\d+Z$/, 'Z');\n}\n\nfunction randomHex(bytes: number): string {\n const buf = new Uint8Array(bytes);\n const c: Crypto | undefined = (globalThis as unknown as { crypto?: Crypto }).crypto;\n if (c && typeof c.getRandomValues === 'function') {\n c.getRandomValues(buf);\n } else {\n // Non-browser fallback. Node ≥18 exposes globalThis.crypto, so this is unlikely to fire.\n for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);\n }\n let out = '';\n for (let i = 0; i < bytes; i++) {\n const b = buf[i]!;\n out += b.toString(16).padStart(2, '0');\n }\n return out;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangecheck/agent-signer",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "OC Agent signer: createDelegation(), signAsAgent(), revoke(). Composes @orangecheck/agent-core with a wallet-adapter signing function and optional OTS anchoring.",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@noble/hashes": "^1.5.0",
52
- "@orangecheck/agent-core": "^2.7.1"
52
+ "@orangecheck/agent-core": "^2.10.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^22.10.2",
package/src/index.test.ts CHANGED
@@ -160,6 +160,35 @@ describe('revoke', () => {
160
160
  expect(r.ok).toBe(true);
161
161
  });
162
162
 
163
+ it('refuses a reason the spec refuses, before it asks for a signature', async () => {
164
+ const delegation = await createDelegation({
165
+ principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
166
+ agentAddress: AGENT,
167
+ scopes: ['stamp:sign(mime=text/markdown)'],
168
+ ttlMs: 60_000,
169
+ });
170
+ let asked = 0;
171
+ const signer = {
172
+ address: PRINCIPAL,
173
+ signMessage: async (m: string) => {
174
+ asked++;
175
+ return fakeSign(PRINCIPAL)(m);
176
+ },
177
+ };
178
+ for (const [reason, why] of [
179
+ ['x'.repeat(129), /at most 128 bytes/],
180
+ ['\u00e9'.repeat(128), /must be ASCII/],
181
+ ['key lost\nsigned_at: 2020-01-01T00:00:00Z', /control character/],
182
+ ['tab\there', /control character/],
183
+ ] as const) {
184
+ await expect(revoke({ signer, delegation, reason })).rejects.toThrow(why);
185
+ }
186
+ expect(asked).toBe(0);
187
+ const rev = await revoke({ signer, delegation, reason: 'x'.repeat(128) });
188
+ expect(rev.reason).toHaveLength(128);
189
+ expect(asked).toBe(1);
190
+ });
191
+
163
192
  it('rejects a revocation from a signer who is neither principal nor agent', async () => {
164
193
  const OTHER = 'bc1qother0000000000000000000000000000000000';
165
194
  const delegation = await createDelegation({
@@ -261,3 +290,64 @@ describe('re-exported verifiers', () => {
261
290
  expect(guard.firstUse(action, at)).toBe(false);
262
291
  });
263
292
  });
293
+
294
+ // SPEC §8.3 step 12a: a verifier reads content-bound keys from the content, so the
295
+ // signer refuses content its own claim, or the grant, does not cover.
296
+ describe('signAsAgent and content-bound keys', () => {
297
+ const GRANT = 'stamp:sign(content_hash_prefix=ab,max_bytes<=10,mime=text/plain)';
298
+ const grant = () =>
299
+ createDelegation({
300
+ principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
301
+ agentAddress: AGENT,
302
+ scopes: [GRANT],
303
+ ttlMs: 60 * 60 * 1000,
304
+ });
305
+ const sign = async (content: { hash: string; length: number }, mime: string, scopeExercised = GRANT) =>
306
+ signAsAgent({
307
+ agent: { address: AGENT, signMessage: fakeSign(AGENT) },
308
+ delegation: await grant(),
309
+ content,
310
+ mime,
311
+ scopeExercised,
312
+ });
313
+ const AB = 'sha256:ab' + 'e'.repeat(62);
314
+
315
+ it('signs content the claim and the grant cover, and it verifies', async () => {
316
+ const delegation = await grant();
317
+ const action = await signAsAgent({
318
+ agent: { address: AGENT, signMessage: fakeSign(AGENT) },
319
+ delegation,
320
+ content: { hash: AB, length: 5 },
321
+ mime: 'text/plain',
322
+ scopeExercised: GRANT,
323
+ });
324
+ const r = await verifyAction({ action, delegation, verifyBip322: fakeVerify, skipRevocationCheck: true });
325
+ expect(r.ok).toBe(true);
326
+ });
327
+
328
+ it('refuses oversize content, another mime, or a hash outside the prefix', async () => {
329
+ await expect(sign({ hash: AB, length: 999999 }, 'text/plain')).rejects.toThrow(/outside/);
330
+ await expect(sign({ hash: AB, length: 5 }, 'text/html')).rejects.toThrow(/outside/);
331
+ await expect(sign({ hash: 'sha256:' + '9'.repeat(64), length: 5 }, 'text/plain')).rejects.toThrow(/outside/);
332
+ });
333
+
334
+ it('refuses content outside a narrower claim it makes', async () => {
335
+ await expect(
336
+ sign({ hash: AB, length: 8 }, 'text/plain', 'stamp:sign(content_hash_prefix=abe,max_bytes<=5,mime=text/plain)')
337
+ ).rejects.toThrow(/outside/);
338
+ });
339
+ });
340
+
341
+ describe('createDelegation is strict', () => {
342
+ it('refuses scopeMode "permissive"', async () => {
343
+ await expect(
344
+ createDelegation({
345
+ principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
346
+ agentAddress: AGENT,
347
+ scopes: ['acme:deploy(region=eu)'],
348
+ ttlMs: 60_000,
349
+ scopeMode: 'permissive' as never,
350
+ })
351
+ ).rejects.toThrow(/strict/);
352
+ });
353
+ });
package/src/index.ts CHANGED
@@ -15,16 +15,17 @@ import {
15
15
  computeActionId,
16
16
  computeDelegationId,
17
17
  computeRevocationId,
18
+ contentBoundScope,
18
19
  ENVELOPE_VERSION,
19
20
  hexEncode,
21
+ authoredReasonProblem,
22
+ isSubScope,
20
23
  parseScope,
24
+ REVOCATION_REASON_MAX_BYTES,
21
25
  sealScopes,
22
26
  validateScope,
23
27
  } from '@orangecheck/agent-core';
24
- import type {
25
- ScopesEncryptedEnvelope,
26
- ValidationOptions,
27
- } from '@orangecheck/agent-core';
28
+ import type { ScopesEncryptedEnvelope } from '@orangecheck/agent-core';
28
29
  import type {
29
30
  ActionEnvelope,
30
31
  ActionOts,
@@ -64,8 +65,8 @@ export interface CreateDelegationInput {
64
65
  revocationHolders?: RevocationHolder[];
65
66
  /** Optional Nostr pointer to a revocation event. Non-cryptographic. */
66
67
  revocationRef?: string | null;
67
- /** Default 'strict'. 'permissive' allows unregistered scopes / constraint keys. */
68
- scopeMode?: ValidationOptions['mode'];
68
+ /** A signer is strict (SPEC §7.6): scopes are always checked as a strict verifier checks them. */
69
+ scopeMode?: 'strict';
69
70
  /**
70
71
  * v1.2 private-scope (PRIVATE-SCOPE.md). When supplied, the resulting
71
72
  * delegation envelope omits the public `scopes` field and instead carries
@@ -105,8 +106,10 @@ export async function createDelegation(input: CreateDelegationInput): Promise<De
105
106
  if (!input.scopes || input.scopes.length === 0) {
106
107
  throw new Error('createDelegation: at least one scope is required');
107
108
  }
108
- const mode = input.scopeMode ?? 'strict';
109
- for (const s of input.scopes) validateScope(parseScope(s), { mode });
109
+ if (input.scopeMode !== undefined && input.scopeMode !== 'strict') {
110
+ throw new Error('createDelegation: a signer is strict; scopeMode "permissive" is not offered');
111
+ }
112
+ for (const s of input.scopes) validateScope(parseScope(s), { mode: 'strict' });
110
113
  const scopes = canonicalizeScopes(input.scopes);
111
114
 
112
115
  const issuedAt = isoUtcSeconds(input.issuedAt ?? new Date());
@@ -244,6 +247,21 @@ export async function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelo
244
247
  }
245
248
  }
246
249
 
250
+ // A verifier reads content-bound keys from the content (SPEC §8.3 step 12a);
251
+ // sign nothing whose content falls outside the scope it claims.
252
+ const exercised = parseScope(input.scopeExercised);
253
+ const fromContent = contentBoundScope(exercised, {
254
+ hash: contentHash,
255
+ length: contentLength,
256
+ mime: input.mime,
257
+ });
258
+ if (!isSubScope(fromContent, exercised)) {
259
+ throw new Error(
260
+ `signAsAgent: the content (${input.mime}, ${contentLength} bytes, ${contentHash}) is outside ` +
261
+ `scope_exercised ${input.scopeExercised}`
262
+ );
263
+ }
264
+
247
265
  const signedAt = isoUtcSeconds(input.signedAt ?? new Date());
248
266
 
249
267
  const canonInput = {
@@ -286,7 +304,7 @@ export async function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelo
286
304
  export interface RevokeInput {
287
305
  signer: SignerRef;
288
306
  delegation: DelegationEnvelope;
289
- /** Short ASCII rationale. Defaults to empty string. */
307
+ /** Printable ASCII, at most 128 bytes: SPEC §9.1 allows any ASCII; we write no control character. Defaults to "". */
290
308
  reason?: string;
291
309
  signedAt?: Date;
292
310
  /** OTS proof / anchor data if the caller has already submitted; otherwise null. */
@@ -303,7 +321,19 @@ export async function revoke(input: RevokeInput): Promise<RevocationEnvelope> {
303
321
  }
304
322
 
305
323
  const reason = input.reason ?? '';
306
- if (reason.length > 128) throw new Error('revoke: reason must be <=128 bytes');
324
+ // SPEC §9.1: a signer writes printable ASCII of at most 128 bytes.
325
+ switch (authoredReasonProblem(reason)) {
326
+ case null:
327
+ break;
328
+ case 'too_long':
329
+ throw new Error(`revoke: reason must be at most ${REVOCATION_REASON_MAX_BYTES} bytes`);
330
+ case 'not_ascii':
331
+ throw new Error('revoke: reason must be ASCII');
332
+ case 'control_character':
333
+ throw new Error('revoke: reason must have no line break or other control character');
334
+ case 'not_text':
335
+ throw new Error('revoke: reason must be text');
336
+ }
307
337
 
308
338
  const signedAt = isoUtcSeconds(input.signedAt ?? new Date());
309
339
  const canonInput = {
@@ -349,6 +379,9 @@ export {
349
379
  isSubScope,
350
380
  canonicalizeScope,
351
381
  REGISTERED_SCOPES,
382
+ authoredReasonProblem,
383
+ REVOCATION_REASON_MAX_BYTES,
384
+ shownReason,
352
385
  } from '@orangecheck/agent-core';
353
386
  export type {
354
387
  ActionEnvelope,