@orangecheck/agent-signer 0.5.0 → 0.5.1

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
@@ -116,7 +116,7 @@ Returns an `ActionEnvelope` — a strict extension of an [OC Stamp](https://gith
116
116
 
117
117
  ```ts
118
118
  interface RevokeInput {
119
- signer: SignerRef; // must be in delegation.revocation.holders
119
+ signer: SignerRef; // the delegation's principal or agent
120
120
  delegation: DelegationEnvelope;
121
121
  reason?: string; // ASCII, <=128 bytes
122
122
  signedAt?: Date;
@@ -124,7 +124,7 @@ interface RevokeInput {
124
124
  }
125
125
  ```
126
126
 
127
- Returns a `RevocationEnvelope`. The function throws if the signer isn't authorized to revoke per `delegation.revocation.holders`.
127
+ Returns a `RevocationEnvelope`. The function throws unless the signer is the delegation's principal or its agent, who may always revoke (OC Agent §9.5).
128
128
 
129
129
  ## Verification
130
130
 
package/dist/index.js CHANGED
@@ -147,11 +147,8 @@ async function signAsAgent(input) {
147
147
  return envelope;
148
148
  }
149
149
  async function revoke(input) {
150
- const holders = input.delegation.revocation?.holders ?? ["principal"];
151
- const allowed = /* @__PURE__ */ new Set();
152
- if (holders.includes("principal")) allowed.add(input.delegation.principal.address);
153
- if (holders.includes("agent")) allowed.add(input.delegation.agent.address);
154
- if (!allowed.has(input.signer.address)) {
150
+ const { principal, agent } = input.delegation;
151
+ if (input.signer.address !== principal.address && input.signer.address !== agent.address) {
155
152
  throw new Error(
156
153
  `revoke: signer ${input.signer.address} not authorized to revoke this delegation`
157
154
  );
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":"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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 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 const holders = input.delegation.revocation?.holders ?? ['principal'];\n const allowed = new Set<string>();\n if (holders.includes('principal')) allowed.add(input.delegation.principal.address);\n if (holders.includes('agent')) allowed.add(input.delegation.agent.address);\n if (!allowed.has(input.signer.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","computeActionId","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 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"]}
package/dist/index.mjs CHANGED
@@ -146,11 +146,8 @@ async function signAsAgent(input) {
146
146
  return envelope;
147
147
  }
148
148
  async function revoke(input) {
149
- const holders = input.delegation.revocation?.holders ?? ["principal"];
150
- const allowed = /* @__PURE__ */ new Set();
151
- if (holders.includes("principal")) allowed.add(input.delegation.principal.address);
152
- if (holders.includes("agent")) allowed.add(input.delegation.agent.address);
153
- if (!allowed.has(input.signer.address)) {
149
+ const { principal, agent } = input.delegation;
150
+ if (input.signer.address !== principal.address && input.signer.address !== agent.address) {
154
151
  throw new Error(
155
152
  `revoke: signer ${input.signer.address} not authorized to revoke this delegation`
156
153
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"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,EAAQ,aAAA,CAAc,WAAW,CAAC,CAAA,EAAG,EAAE,IAAA,EAAM,CAAA;AACnE,EAAA,MAAM,MAAA,GAAS,kBAAA,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,GAAK,oBAAoB,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,MAAM,UAAA,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,EAAG,gBAAA;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,GAAY,SAAA,CAAU,MAAA,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,GAAK,gBAAgB,UAAU,CAAA;AAErC,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,KAAA,CAAM,YAAY,EAAE,CAAA;AAEjD,EAAA,MAAM,QAAA,GAA2B;AAAA,IAC7B,CAAA,EAAG,gBAAA;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;AAC1E,EAAA,MAAM,UAAU,KAAA,CAAM,UAAA,CAAW,UAAA,EAAY,OAAA,IAAW,CAAC,WAAW,CAAA;AACpE,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,EAAA,IAAI,OAAA,CAAQ,SAAS,WAAW,CAAA,UAAW,GAAA,CAAI,KAAA,CAAM,UAAA,CAAW,SAAA,CAAU,OAAO,CAAA;AACjF,EAAA,IAAI,OAAA,CAAQ,SAAS,OAAO,CAAA,UAAW,GAAA,CAAI,KAAA,CAAM,UAAA,CAAW,KAAA,CAAM,OAAO,CAAA;AACzE,EAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,MAAA,CAAO,OAAO,CAAA,EAAG;AACpC,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,GAAK,oBAAoB,UAAU,CAAA;AAEzC,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,MAAA,CAAO,YAAY,EAAE,CAAA;AAElD,EAAA,MAAM,QAAA,GAA+B;AAAA,IACjC,CAAA,EAAG,gBAAA;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.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 const holders = input.delegation.revocation?.holders ?? ['principal'];\n const allowed = new Set<string>();\n if (holders.includes('principal')) allowed.add(input.delegation.principal.address);\n if (holders.includes('agent')) allowed.add(input.delegation.agent.address);\n if (!allowed.has(input.signer.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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangecheck/agent-signer",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
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.0"
52
+ "@orangecheck/agent-core": "^2.7.1"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^22.10.2",
package/src/index.test.ts CHANGED
@@ -160,7 +160,8 @@ describe('revoke', () => {
160
160
  expect(r.ok).toBe(true);
161
161
  });
162
162
 
163
- it('rejects a revocation from an unauthorized signer', async () => {
163
+ it('rejects a revocation from a signer who is neither principal nor agent', async () => {
164
+ const OTHER = 'bc1qother0000000000000000000000000000000000';
164
165
  const delegation = await createDelegation({
165
166
  principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
166
167
  agentAddress: AGENT,
@@ -168,29 +169,28 @@ describe('revoke', () => {
168
169
  ttlMs: 60_000,
169
170
  });
170
171
  await expect(
171
- revoke({
172
- signer: { address: AGENT, signMessage: fakeSign(AGENT) },
173
- delegation, // revocation.holders default = ['principal']
174
- })
172
+ revoke({ signer: { address: OTHER, signMessage: fakeSign(OTHER) }, delegation })
175
173
  ).rejects.toThrow(/not authorized/);
176
174
  });
177
175
 
178
- it('allows agent revocation when delegation opts in', async () => {
179
- const delegation = await createDelegation({
180
- principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
181
- agentAddress: AGENT,
182
- scopes: ['stamp:sign(mime=text/markdown)'],
183
- ttlMs: 60_000,
184
- revocationHolders: ['principal', 'agent'],
185
- });
186
- const rev = await revoke({
187
- signer: { address: AGENT, signMessage: fakeSign(AGENT) },
188
- delegation,
189
- reason: 'self-revoke',
176
+ for (const revocationHolders of [undefined, ['principal'] as const, ['principal', 'agent'] as const]) {
177
+ it(`lets the agent revoke whatever holders says (${JSON.stringify(revocationHolders)})`, async () => {
178
+ const delegation = await createDelegation({
179
+ principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
180
+ agentAddress: AGENT,
181
+ scopes: ['stamp:sign(mime=text/markdown)'],
182
+ ttlMs: 60_000,
183
+ ...(revocationHolders ? { revocationHolders: [...revocationHolders] } : {}),
184
+ });
185
+ const rev = await revoke({
186
+ signer: { address: AGENT, signMessage: fakeSign(AGENT) },
187
+ delegation,
188
+ reason: 'self-revoke',
189
+ });
190
+ const r = await verifyRevocation({ envelope: rev, delegation, verifyBip322: fakeVerify });
191
+ expect(r.ok).toBe(true);
190
192
  });
191
- const r = await verifyRevocation({ envelope: rev, delegation, verifyBip322: fakeVerify });
192
- expect(r.ok).toBe(true);
193
- });
193
+ }
194
194
  });
195
195
 
196
196
  describe('re-exported verifiers', () => {
package/src/index.ts CHANGED
@@ -294,11 +294,9 @@ export interface RevokeInput {
294
294
  }
295
295
 
296
296
  export async function revoke(input: RevokeInput): Promise<RevocationEnvelope> {
297
- const holders = input.delegation.revocation?.holders ?? ['principal'];
298
- const allowed = new Set<string>();
299
- if (holders.includes('principal')) allowed.add(input.delegation.principal.address);
300
- if (holders.includes('agent')) allowed.add(input.delegation.agent.address);
301
- if (!allowed.has(input.signer.address)) {
297
+ // The principal and the agent may always revoke (SPEC §9.5).
298
+ const { principal, agent } = input.delegation;
299
+ if (input.signer.address !== principal.address && input.signer.address !== agent.address) {
302
300
  throw new Error(
303
301
  `revoke: signer ${input.signer.address} not authorized to revoke this delegation`
304
302
  );