@orangecheck/agent-signer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +227 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +180 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
- package/src/index.test.ts +185 -0
- package/src/index.ts +322 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OrangeCheck
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# @orangecheck/agent-signer
|
|
2
|
+
|
|
3
|
+
High-level API for producing [OC Agent](https://github.com/orangecheck/oc-agent-protocol) envelopes from a BIP-322-capable signer.
|
|
4
|
+
|
|
5
|
+
Three functions:
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `createDelegation(input)` | Principal signs a scoped grant for an agent address. |
|
|
10
|
+
| `signAsAgent(input)` | Agent produces an `agent-action` envelope over content, citing the delegation. |
|
|
11
|
+
| `revoke(input)` | Principal (or agent, if authorized) burns the delegation. |
|
|
12
|
+
|
|
13
|
+
The signer layer wraps [`@orangecheck/agent-core`](../agent-core): canonical messages, scope canonicalization, envelope ids, and verification are delegated to the core package. This module handles wallet plumbing, default timestamps and nonces, and input validation.
|
|
14
|
+
|
|
15
|
+
Nothing here touches the network — OpenTimestamps submission and Nostr publication are separate steps. If you already have an OTS proof, pass it in via the `ots` field; otherwise the resulting envelope has `ots: null` and the caller submits to a calendar out of band.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm i @orangecheck/agent-signer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import {
|
|
27
|
+
createDelegation,
|
|
28
|
+
signAsAgent,
|
|
29
|
+
revoke,
|
|
30
|
+
} from '@orangecheck/agent-signer';
|
|
31
|
+
|
|
32
|
+
// A SignerRef is any { address, signMessage } — the wallet adapter produced by
|
|
33
|
+
// @orangecheck/wallet-adapter, or a custom BIP-322 signer for headless agents.
|
|
34
|
+
const principal = {
|
|
35
|
+
address: 'bc1qprincipal…',
|
|
36
|
+
signMessage: async (msg: string) => wallet.signMessage(msg),
|
|
37
|
+
};
|
|
38
|
+
const agent = {
|
|
39
|
+
address: 'bc1qagent…',
|
|
40
|
+
signMessage: async (msg: string) => agentKey.signBip322(msg),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// 1. Principal grants the agent scoped authority.
|
|
44
|
+
const delegation = await createDelegation({
|
|
45
|
+
principal,
|
|
46
|
+
agentAddress: agent.address,
|
|
47
|
+
scopes: [
|
|
48
|
+
'lock:seal(recipient=bc1qalice)',
|
|
49
|
+
'stamp:sign(mime=text/markdown)',
|
|
50
|
+
],
|
|
51
|
+
bond: { sats: 500_000, attestation_id: '22…22' },
|
|
52
|
+
ttlMs: 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 2. Agent exercises the delegation.
|
|
56
|
+
const action = await signAsAgent({
|
|
57
|
+
agent,
|
|
58
|
+
delegation,
|
|
59
|
+
content: new TextEncoder().encode('hello world'),
|
|
60
|
+
mime: 'text/plain',
|
|
61
|
+
scopeExercised: 'lock:seal(recipient=bc1qalice)',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// 3. Principal revokes early (optional).
|
|
65
|
+
const revocation = await revoke({
|
|
66
|
+
signer: principal,
|
|
67
|
+
delegation,
|
|
68
|
+
reason: 'key rotated',
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## API
|
|
73
|
+
|
|
74
|
+
### `createDelegation(input)`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
interface CreateDelegationInput {
|
|
78
|
+
principal: SignerRef;
|
|
79
|
+
agentAddress: string;
|
|
80
|
+
scopes: string[]; // validated + canonicalized
|
|
81
|
+
bond?: { sats: number; attestation_id: string } | null;
|
|
82
|
+
issuedAt?: Date; // default: now
|
|
83
|
+
ttlMs?: number; // default: 7d, max: 365d
|
|
84
|
+
expiresAt?: Date; // overrides ttlMs if set
|
|
85
|
+
nonce?: string; // default: random 32-hex
|
|
86
|
+
revocationHolders?: ('principal' | 'agent')[]; // default: ['principal']
|
|
87
|
+
revocationRef?: string | null;
|
|
88
|
+
scopeMode?: 'strict' | 'permissive'; // default: 'strict'
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Returns a `DelegationEnvelope`. The principal's `signMessage` is called exactly once with the ASCII hex of the envelope id.
|
|
93
|
+
|
|
94
|
+
### `signAsAgent(input)`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface SignAsAgentInput {
|
|
98
|
+
agent: SignerRef; // must equal delegation.agent.address
|
|
99
|
+
delegation: DelegationEnvelope;
|
|
100
|
+
content: Uint8Array | { hash: string; length: number };
|
|
101
|
+
mime: string;
|
|
102
|
+
scopeExercised: string; // must be a sub-scope of some granted scope (checked at verify time)
|
|
103
|
+
ref?: string | null;
|
|
104
|
+
signedAt?: Date; // default: now
|
|
105
|
+
ots?: ActionOts | null; // default: null (caller submits separately)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Returns an `ActionEnvelope` — a strict extension of an [OC Stamp](https://github.com/orangecheck/oc-stamp-protocol) envelope, so any stamp verifier reads it as a valid stamp.
|
|
110
|
+
|
|
111
|
+
### `revoke(input)`
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
interface RevokeInput {
|
|
115
|
+
signer: SignerRef; // must be in delegation.revocation.holders
|
|
116
|
+
delegation: DelegationEnvelope;
|
|
117
|
+
reason?: string; // ASCII, <=128 bytes
|
|
118
|
+
signedAt?: Date;
|
|
119
|
+
ots?: ActionOts | null;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Returns a `RevocationEnvelope`. The function throws if the signer isn't authorized to revoke per `delegation.revocation.holders`.
|
|
124
|
+
|
|
125
|
+
## Verification
|
|
126
|
+
|
|
127
|
+
Re-exports `verifyDelegation`, `verifyAction`, and `verifyRevocation` from `@orangecheck/agent-core` for convenience. See that package's README for full details.
|
|
128
|
+
|
|
129
|
+
## Companion packages
|
|
130
|
+
|
|
131
|
+
- [`@orangecheck/agent-core`](../agent-core) — canonical messages, scopes, verification.
|
|
132
|
+
- [`@orangecheck/agent-mcp`](../agent-mcp) — MCP tool wrapper.
|
|
133
|
+
- [`@orangecheck/wallet-adapter`](../wallet-adapter) — browser BIP-322 signers (UniSat, Xverse, Leather).
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT. See [LICENSE](./LICENSE).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DelegationBond, RevocationHolder, ValidationOptions, DelegationEnvelope, ActionOts, RevocationEnvelope, ActionEnvelope } from '@orangecheck/agent-core';
|
|
2
|
+
export { ActionEnvelope, ActionOts, AgentErrorCode, DelegationBond, DelegationEnvelope, REGISTERED_SCOPES, RevocationEnvelope, Scope, ScopeConstraint, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, isSubScope, parseScope, verifyAction, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
|
|
3
|
+
|
|
4
|
+
interface SignerRef {
|
|
5
|
+
address: string;
|
|
6
|
+
signMessage: (msg: string) => Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
interface CreateDelegationInput {
|
|
9
|
+
principal: SignerRef;
|
|
10
|
+
agentAddress: string;
|
|
11
|
+
scopes: string[];
|
|
12
|
+
bond?: DelegationBond | null;
|
|
13
|
+
issuedAt?: Date;
|
|
14
|
+
ttlMs?: number;
|
|
15
|
+
expiresAt?: Date;
|
|
16
|
+
nonce?: string;
|
|
17
|
+
revocationHolders?: RevocationHolder[];
|
|
18
|
+
revocationRef?: string | null;
|
|
19
|
+
scopeMode?: ValidationOptions['mode'];
|
|
20
|
+
}
|
|
21
|
+
declare function createDelegation(input: CreateDelegationInput): Promise<DelegationEnvelope>;
|
|
22
|
+
interface SignAsAgentInput {
|
|
23
|
+
agent: SignerRef;
|
|
24
|
+
delegation: DelegationEnvelope;
|
|
25
|
+
content: Uint8Array | {
|
|
26
|
+
hash: string;
|
|
27
|
+
length: number;
|
|
28
|
+
};
|
|
29
|
+
mime: string;
|
|
30
|
+
scopeExercised: string;
|
|
31
|
+
ref?: string | null;
|
|
32
|
+
signedAt?: Date;
|
|
33
|
+
ots?: ActionOts | null;
|
|
34
|
+
}
|
|
35
|
+
declare function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelope>;
|
|
36
|
+
interface RevokeInput {
|
|
37
|
+
signer: SignerRef;
|
|
38
|
+
delegation: DelegationEnvelope;
|
|
39
|
+
reason?: string;
|
|
40
|
+
signedAt?: Date;
|
|
41
|
+
ots?: ActionOts | null;
|
|
42
|
+
}
|
|
43
|
+
declare function revoke(input: RevokeInput): Promise<RevocationEnvelope>;
|
|
44
|
+
|
|
45
|
+
export { type CreateDelegationInput, type RevokeInput, type SignAsAgentInput, type SignerRef, createDelegation, revoke, signAsAgent };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DelegationBond, RevocationHolder, ValidationOptions, DelegationEnvelope, ActionOts, RevocationEnvelope, ActionEnvelope } from '@orangecheck/agent-core';
|
|
2
|
+
export { ActionEnvelope, ActionOts, AgentErrorCode, DelegationBond, DelegationEnvelope, REGISTERED_SCOPES, RevocationEnvelope, Scope, ScopeConstraint, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, isSubScope, parseScope, verifyAction, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
|
|
3
|
+
|
|
4
|
+
interface SignerRef {
|
|
5
|
+
address: string;
|
|
6
|
+
signMessage: (msg: string) => Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
interface CreateDelegationInput {
|
|
9
|
+
principal: SignerRef;
|
|
10
|
+
agentAddress: string;
|
|
11
|
+
scopes: string[];
|
|
12
|
+
bond?: DelegationBond | null;
|
|
13
|
+
issuedAt?: Date;
|
|
14
|
+
ttlMs?: number;
|
|
15
|
+
expiresAt?: Date;
|
|
16
|
+
nonce?: string;
|
|
17
|
+
revocationHolders?: RevocationHolder[];
|
|
18
|
+
revocationRef?: string | null;
|
|
19
|
+
scopeMode?: ValidationOptions['mode'];
|
|
20
|
+
}
|
|
21
|
+
declare function createDelegation(input: CreateDelegationInput): Promise<DelegationEnvelope>;
|
|
22
|
+
interface SignAsAgentInput {
|
|
23
|
+
agent: SignerRef;
|
|
24
|
+
delegation: DelegationEnvelope;
|
|
25
|
+
content: Uint8Array | {
|
|
26
|
+
hash: string;
|
|
27
|
+
length: number;
|
|
28
|
+
};
|
|
29
|
+
mime: string;
|
|
30
|
+
scopeExercised: string;
|
|
31
|
+
ref?: string | null;
|
|
32
|
+
signedAt?: Date;
|
|
33
|
+
ots?: ActionOts | null;
|
|
34
|
+
}
|
|
35
|
+
declare function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelope>;
|
|
36
|
+
interface RevokeInput {
|
|
37
|
+
signer: SignerRef;
|
|
38
|
+
delegation: DelegationEnvelope;
|
|
39
|
+
reason?: string;
|
|
40
|
+
signedAt?: Date;
|
|
41
|
+
ots?: ActionOts | null;
|
|
42
|
+
}
|
|
43
|
+
declare function revoke(input: RevokeInput): Promise<RevocationEnvelope>;
|
|
44
|
+
|
|
45
|
+
export { type CreateDelegationInput, type RevokeInput, type SignAsAgentInput, type SignerRef, createDelegation, revoke, signAsAgent };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var sha256 = require('@noble/hashes/sha256');
|
|
4
|
+
var agentCore = require('@orangecheck/agent-core');
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
async function createDelegation(input) {
|
|
8
|
+
if (!input.scopes || input.scopes.length === 0) {
|
|
9
|
+
throw new Error("createDelegation: at least one scope is required");
|
|
10
|
+
}
|
|
11
|
+
const mode = input.scopeMode ?? "strict";
|
|
12
|
+
for (const s of input.scopes) agentCore.validateScope(agentCore.parseScope(s), { mode });
|
|
13
|
+
const scopes = agentCore.canonicalizeScopes(input.scopes);
|
|
14
|
+
const issuedAt = isoUtcSeconds(input.issuedAt ?? /* @__PURE__ */ new Date());
|
|
15
|
+
let expiresAtDate;
|
|
16
|
+
if (input.expiresAt) {
|
|
17
|
+
expiresAtDate = input.expiresAt;
|
|
18
|
+
} else {
|
|
19
|
+
const ttl = input.ttlMs ?? 7 * 24 * 60 * 60 * 1e3;
|
|
20
|
+
expiresAtDate = new Date(new Date(issuedAt).getTime() + ttl);
|
|
21
|
+
}
|
|
22
|
+
const expiresAt = isoUtcSeconds(expiresAtDate);
|
|
23
|
+
const durationMs = new Date(expiresAt).getTime() - new Date(issuedAt).getTime();
|
|
24
|
+
if (durationMs <= 0) throw new Error("createDelegation: expires_at must be after issued_at");
|
|
25
|
+
if (durationMs > 365 * 24 * 60 * 60 * 1e3) {
|
|
26
|
+
throw new Error("createDelegation: delegation duration must not exceed 365 days");
|
|
27
|
+
}
|
|
28
|
+
const nonce = input.nonce ?? randomHex(16);
|
|
29
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) {
|
|
30
|
+
throw new Error("createDelegation: nonce must be 32 lowercase hex chars");
|
|
31
|
+
}
|
|
32
|
+
const bond = input.bond ?? null;
|
|
33
|
+
if (bond) {
|
|
34
|
+
if (!Number.isInteger(bond.sats) || bond.sats < 0) {
|
|
35
|
+
throw new Error("createDelegation: bond.sats must be a non-negative integer");
|
|
36
|
+
}
|
|
37
|
+
if (!/^[0-9a-f]{64}$/.test(bond.attestation_id)) {
|
|
38
|
+
throw new Error("createDelegation: bond.attestation_id must be 64 lowercase hex chars");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const canonInput = {
|
|
42
|
+
principal: input.principal.address,
|
|
43
|
+
agent: input.agentAddress,
|
|
44
|
+
scopes,
|
|
45
|
+
bond_sats: bond?.sats ?? 0,
|
|
46
|
+
bond_attestation: bond?.attestation_id ?? "none",
|
|
47
|
+
issued_at: issuedAt,
|
|
48
|
+
expires_at: expiresAt,
|
|
49
|
+
nonce
|
|
50
|
+
};
|
|
51
|
+
const id = agentCore.computeDelegationId(canonInput);
|
|
52
|
+
const sigValue = await input.principal.signMessage(id);
|
|
53
|
+
const envelope = {
|
|
54
|
+
v: agentCore.ENVELOPE_VERSION,
|
|
55
|
+
kind: "agent-delegation",
|
|
56
|
+
id,
|
|
57
|
+
principal: { address: input.principal.address, alg: "bip322" },
|
|
58
|
+
agent: { address: input.agentAddress, alg: "bip322" },
|
|
59
|
+
scopes,
|
|
60
|
+
bond,
|
|
61
|
+
issued_at: issuedAt,
|
|
62
|
+
expires_at: expiresAt,
|
|
63
|
+
nonce,
|
|
64
|
+
revocation: {
|
|
65
|
+
holders: input.revocationHolders ?? ["principal"],
|
|
66
|
+
ref: input.revocationRef ?? null
|
|
67
|
+
},
|
|
68
|
+
sig: {
|
|
69
|
+
alg: "bip322",
|
|
70
|
+
pubkey: input.principal.address,
|
|
71
|
+
value: sigValue
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return envelope;
|
|
75
|
+
}
|
|
76
|
+
async function signAsAgent(input) {
|
|
77
|
+
if (input.agent.address !== input.delegation.agent.address) {
|
|
78
|
+
throw new Error("signAsAgent: signer address does not match delegation.agent.address");
|
|
79
|
+
}
|
|
80
|
+
let contentHash;
|
|
81
|
+
let contentLength;
|
|
82
|
+
if (input.content instanceof Uint8Array) {
|
|
83
|
+
contentHash = "sha256:" + agentCore.hexEncode(sha256.sha256(input.content));
|
|
84
|
+
contentLength = input.content.byteLength;
|
|
85
|
+
} else {
|
|
86
|
+
if (!input.content.hash.startsWith("sha256:") || input.content.hash.length !== "sha256:".length + 64) {
|
|
87
|
+
throw new Error('signAsAgent: content.hash must be "sha256:" + 64 lowercase hex chars');
|
|
88
|
+
}
|
|
89
|
+
contentHash = input.content.hash;
|
|
90
|
+
contentLength = input.content.length;
|
|
91
|
+
if (!Number.isInteger(contentLength) || contentLength < 0) {
|
|
92
|
+
throw new Error("signAsAgent: content.length must be a non-negative integer");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
|
|
96
|
+
const canonInput = {
|
|
97
|
+
address: input.agent.address,
|
|
98
|
+
content_hash: contentHash,
|
|
99
|
+
content_length: contentLength,
|
|
100
|
+
content_mime: input.mime,
|
|
101
|
+
signed_at: signedAt,
|
|
102
|
+
delegation_id: input.delegation.id,
|
|
103
|
+
scope_exercised: input.scopeExercised
|
|
104
|
+
};
|
|
105
|
+
const id = agentCore.computeActionId(canonInput);
|
|
106
|
+
const sigValue = await input.agent.signMessage(id);
|
|
107
|
+
const envelope = {
|
|
108
|
+
v: agentCore.ENVELOPE_VERSION,
|
|
109
|
+
kind: "agent-action",
|
|
110
|
+
id,
|
|
111
|
+
content: {
|
|
112
|
+
hash: contentHash,
|
|
113
|
+
length: contentLength,
|
|
114
|
+
mime: input.mime,
|
|
115
|
+
ref: input.ref ?? null
|
|
116
|
+
},
|
|
117
|
+
signer: { address: input.agent.address, alg: "bip322" },
|
|
118
|
+
signed_at: signedAt,
|
|
119
|
+
delegation_id: input.delegation.id,
|
|
120
|
+
scope_exercised: input.scopeExercised,
|
|
121
|
+
ots: input.ots ?? null,
|
|
122
|
+
sig: { alg: "bip322", pubkey: input.agent.address, value: sigValue }
|
|
123
|
+
};
|
|
124
|
+
return envelope;
|
|
125
|
+
}
|
|
126
|
+
async function revoke(input) {
|
|
127
|
+
const holders = input.delegation.revocation?.holders ?? ["principal"];
|
|
128
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
129
|
+
if (holders.includes("principal")) allowed.add(input.delegation.principal.address);
|
|
130
|
+
if (holders.includes("agent")) allowed.add(input.delegation.agent.address);
|
|
131
|
+
if (!allowed.has(input.signer.address)) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`revoke: signer ${input.signer.address} not authorized to revoke this delegation`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const reason = input.reason ?? "";
|
|
137
|
+
if (reason.length > 128) throw new Error("revoke: reason must be <=128 bytes");
|
|
138
|
+
const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
|
|
139
|
+
const canonInput = {
|
|
140
|
+
address: input.signer.address,
|
|
141
|
+
delegation_id: input.delegation.id,
|
|
142
|
+
reason,
|
|
143
|
+
signed_at: signedAt
|
|
144
|
+
};
|
|
145
|
+
const id = agentCore.computeRevocationId(canonInput);
|
|
146
|
+
const sigValue = await input.signer.signMessage(id);
|
|
147
|
+
const envelope = {
|
|
148
|
+
v: agentCore.ENVELOPE_VERSION,
|
|
149
|
+
kind: "agent-revocation",
|
|
150
|
+
id,
|
|
151
|
+
delegation_id: input.delegation.id,
|
|
152
|
+
signer: { address: input.signer.address, alg: "bip322" },
|
|
153
|
+
reason,
|
|
154
|
+
signed_at: signedAt,
|
|
155
|
+
ots: input.ots ?? null,
|
|
156
|
+
sig: { alg: "bip322", pubkey: input.signer.address, value: sigValue }
|
|
157
|
+
};
|
|
158
|
+
return envelope;
|
|
159
|
+
}
|
|
160
|
+
function isoUtcSeconds(d) {
|
|
161
|
+
return d.toISOString().replace(/\.\d+Z$/, "Z");
|
|
162
|
+
}
|
|
163
|
+
function randomHex(bytes) {
|
|
164
|
+
const buf = new Uint8Array(bytes);
|
|
165
|
+
const c = globalThis.crypto;
|
|
166
|
+
if (c && typeof c.getRandomValues === "function") {
|
|
167
|
+
c.getRandomValues(buf);
|
|
168
|
+
} else {
|
|
169
|
+
for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);
|
|
170
|
+
}
|
|
171
|
+
let out = "";
|
|
172
|
+
for (let i = 0; i < bytes; i++) {
|
|
173
|
+
const b = buf[i];
|
|
174
|
+
out += b.toString(16).padStart(2, "0");
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
Object.defineProperty(exports, "REGISTERED_SCOPES", {
|
|
180
|
+
enumerable: true,
|
|
181
|
+
get: function () { return agentCore.REGISTERED_SCOPES; }
|
|
182
|
+
});
|
|
183
|
+
Object.defineProperty(exports, "canonicalizeScope", {
|
|
184
|
+
enumerable: true,
|
|
185
|
+
get: function () { return agentCore.canonicalizeScope; }
|
|
186
|
+
});
|
|
187
|
+
Object.defineProperty(exports, "canonicalizeScopes", {
|
|
188
|
+
enumerable: true,
|
|
189
|
+
get: function () { return agentCore.canonicalizeScopes; }
|
|
190
|
+
});
|
|
191
|
+
Object.defineProperty(exports, "computeActionId", {
|
|
192
|
+
enumerable: true,
|
|
193
|
+
get: function () { return agentCore.computeActionId; }
|
|
194
|
+
});
|
|
195
|
+
Object.defineProperty(exports, "computeDelegationId", {
|
|
196
|
+
enumerable: true,
|
|
197
|
+
get: function () { return agentCore.computeDelegationId; }
|
|
198
|
+
});
|
|
199
|
+
Object.defineProperty(exports, "computeRevocationId", {
|
|
200
|
+
enumerable: true,
|
|
201
|
+
get: function () { return agentCore.computeRevocationId; }
|
|
202
|
+
});
|
|
203
|
+
Object.defineProperty(exports, "isSubScope", {
|
|
204
|
+
enumerable: true,
|
|
205
|
+
get: function () { return agentCore.isSubScope; }
|
|
206
|
+
});
|
|
207
|
+
Object.defineProperty(exports, "parseScope", {
|
|
208
|
+
enumerable: true,
|
|
209
|
+
get: function () { return agentCore.parseScope; }
|
|
210
|
+
});
|
|
211
|
+
Object.defineProperty(exports, "verifyAction", {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
get: function () { return agentCore.verifyAction; }
|
|
214
|
+
});
|
|
215
|
+
Object.defineProperty(exports, "verifyDelegation", {
|
|
216
|
+
enumerable: true,
|
|
217
|
+
get: function () { return agentCore.verifyDelegation; }
|
|
218
|
+
});
|
|
219
|
+
Object.defineProperty(exports, "verifyRevocation", {
|
|
220
|
+
enumerable: true,
|
|
221
|
+
get: function () { return agentCore.verifyRevocation; }
|
|
222
|
+
});
|
|
223
|
+
exports.createDelegation = createDelegation;
|
|
224
|
+
exports.revoke = revoke;
|
|
225
|
+
exports.signAsAgent = signAsAgent;
|
|
226
|
+
//# sourceMappingURL=index.js.map
|
|
227
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["validateScope","parseScope","canonicalizeScopes","computeDelegationId","ENVELOPE_VERSION","hexEncode","sha256","computeActionId","computeRevocationId"],"mappings":";;;;;;AAkEA,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;AAEzC,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,IACpD,MAAA;AAAA,IACA,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;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,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;AAkCA,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 validateScope,\n} from '@orangecheck/agent-core';\nimport type { ValidationOptions } 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\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 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 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 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} 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
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
+
import { validateScope, parseScope, canonicalizeScopes, computeDelegationId, ENVELOPE_VERSION, hexEncode, computeActionId, computeRevocationId } from '@orangecheck/agent-core';
|
|
3
|
+
export { REGISTERED_SCOPES, canonicalizeScope, canonicalizeScopes, computeActionId, computeDelegationId, computeRevocationId, isSubScope, parseScope, verifyAction, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
async function createDelegation(input) {
|
|
7
|
+
if (!input.scopes || input.scopes.length === 0) {
|
|
8
|
+
throw new Error("createDelegation: at least one scope is required");
|
|
9
|
+
}
|
|
10
|
+
const mode = input.scopeMode ?? "strict";
|
|
11
|
+
for (const s of input.scopes) validateScope(parseScope(s), { mode });
|
|
12
|
+
const scopes = canonicalizeScopes(input.scopes);
|
|
13
|
+
const issuedAt = isoUtcSeconds(input.issuedAt ?? /* @__PURE__ */ new Date());
|
|
14
|
+
let expiresAtDate;
|
|
15
|
+
if (input.expiresAt) {
|
|
16
|
+
expiresAtDate = input.expiresAt;
|
|
17
|
+
} else {
|
|
18
|
+
const ttl = input.ttlMs ?? 7 * 24 * 60 * 60 * 1e3;
|
|
19
|
+
expiresAtDate = new Date(new Date(issuedAt).getTime() + ttl);
|
|
20
|
+
}
|
|
21
|
+
const expiresAt = isoUtcSeconds(expiresAtDate);
|
|
22
|
+
const durationMs = new Date(expiresAt).getTime() - new Date(issuedAt).getTime();
|
|
23
|
+
if (durationMs <= 0) throw new Error("createDelegation: expires_at must be after issued_at");
|
|
24
|
+
if (durationMs > 365 * 24 * 60 * 60 * 1e3) {
|
|
25
|
+
throw new Error("createDelegation: delegation duration must not exceed 365 days");
|
|
26
|
+
}
|
|
27
|
+
const nonce = input.nonce ?? randomHex(16);
|
|
28
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) {
|
|
29
|
+
throw new Error("createDelegation: nonce must be 32 lowercase hex chars");
|
|
30
|
+
}
|
|
31
|
+
const bond = input.bond ?? null;
|
|
32
|
+
if (bond) {
|
|
33
|
+
if (!Number.isInteger(bond.sats) || bond.sats < 0) {
|
|
34
|
+
throw new Error("createDelegation: bond.sats must be a non-negative integer");
|
|
35
|
+
}
|
|
36
|
+
if (!/^[0-9a-f]{64}$/.test(bond.attestation_id)) {
|
|
37
|
+
throw new Error("createDelegation: bond.attestation_id must be 64 lowercase hex chars");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const canonInput = {
|
|
41
|
+
principal: input.principal.address,
|
|
42
|
+
agent: input.agentAddress,
|
|
43
|
+
scopes,
|
|
44
|
+
bond_sats: bond?.sats ?? 0,
|
|
45
|
+
bond_attestation: bond?.attestation_id ?? "none",
|
|
46
|
+
issued_at: issuedAt,
|
|
47
|
+
expires_at: expiresAt,
|
|
48
|
+
nonce
|
|
49
|
+
};
|
|
50
|
+
const id = computeDelegationId(canonInput);
|
|
51
|
+
const sigValue = await input.principal.signMessage(id);
|
|
52
|
+
const envelope = {
|
|
53
|
+
v: ENVELOPE_VERSION,
|
|
54
|
+
kind: "agent-delegation",
|
|
55
|
+
id,
|
|
56
|
+
principal: { address: input.principal.address, alg: "bip322" },
|
|
57
|
+
agent: { address: input.agentAddress, alg: "bip322" },
|
|
58
|
+
scopes,
|
|
59
|
+
bond,
|
|
60
|
+
issued_at: issuedAt,
|
|
61
|
+
expires_at: expiresAt,
|
|
62
|
+
nonce,
|
|
63
|
+
revocation: {
|
|
64
|
+
holders: input.revocationHolders ?? ["principal"],
|
|
65
|
+
ref: input.revocationRef ?? null
|
|
66
|
+
},
|
|
67
|
+
sig: {
|
|
68
|
+
alg: "bip322",
|
|
69
|
+
pubkey: input.principal.address,
|
|
70
|
+
value: sigValue
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
return envelope;
|
|
74
|
+
}
|
|
75
|
+
async function signAsAgent(input) {
|
|
76
|
+
if (input.agent.address !== input.delegation.agent.address) {
|
|
77
|
+
throw new Error("signAsAgent: signer address does not match delegation.agent.address");
|
|
78
|
+
}
|
|
79
|
+
let contentHash;
|
|
80
|
+
let contentLength;
|
|
81
|
+
if (input.content instanceof Uint8Array) {
|
|
82
|
+
contentHash = "sha256:" + hexEncode(sha256(input.content));
|
|
83
|
+
contentLength = input.content.byteLength;
|
|
84
|
+
} else {
|
|
85
|
+
if (!input.content.hash.startsWith("sha256:") || input.content.hash.length !== "sha256:".length + 64) {
|
|
86
|
+
throw new Error('signAsAgent: content.hash must be "sha256:" + 64 lowercase hex chars');
|
|
87
|
+
}
|
|
88
|
+
contentHash = input.content.hash;
|
|
89
|
+
contentLength = input.content.length;
|
|
90
|
+
if (!Number.isInteger(contentLength) || contentLength < 0) {
|
|
91
|
+
throw new Error("signAsAgent: content.length must be a non-negative integer");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
|
|
95
|
+
const canonInput = {
|
|
96
|
+
address: input.agent.address,
|
|
97
|
+
content_hash: contentHash,
|
|
98
|
+
content_length: contentLength,
|
|
99
|
+
content_mime: input.mime,
|
|
100
|
+
signed_at: signedAt,
|
|
101
|
+
delegation_id: input.delegation.id,
|
|
102
|
+
scope_exercised: input.scopeExercised
|
|
103
|
+
};
|
|
104
|
+
const id = computeActionId(canonInput);
|
|
105
|
+
const sigValue = await input.agent.signMessage(id);
|
|
106
|
+
const envelope = {
|
|
107
|
+
v: ENVELOPE_VERSION,
|
|
108
|
+
kind: "agent-action",
|
|
109
|
+
id,
|
|
110
|
+
content: {
|
|
111
|
+
hash: contentHash,
|
|
112
|
+
length: contentLength,
|
|
113
|
+
mime: input.mime,
|
|
114
|
+
ref: input.ref ?? null
|
|
115
|
+
},
|
|
116
|
+
signer: { address: input.agent.address, alg: "bip322" },
|
|
117
|
+
signed_at: signedAt,
|
|
118
|
+
delegation_id: input.delegation.id,
|
|
119
|
+
scope_exercised: input.scopeExercised,
|
|
120
|
+
ots: input.ots ?? null,
|
|
121
|
+
sig: { alg: "bip322", pubkey: input.agent.address, value: sigValue }
|
|
122
|
+
};
|
|
123
|
+
return envelope;
|
|
124
|
+
}
|
|
125
|
+
async function revoke(input) {
|
|
126
|
+
const holders = input.delegation.revocation?.holders ?? ["principal"];
|
|
127
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
128
|
+
if (holders.includes("principal")) allowed.add(input.delegation.principal.address);
|
|
129
|
+
if (holders.includes("agent")) allowed.add(input.delegation.agent.address);
|
|
130
|
+
if (!allowed.has(input.signer.address)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`revoke: signer ${input.signer.address} not authorized to revoke this delegation`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const reason = input.reason ?? "";
|
|
136
|
+
if (reason.length > 128) throw new Error("revoke: reason must be <=128 bytes");
|
|
137
|
+
const signedAt = isoUtcSeconds(input.signedAt ?? /* @__PURE__ */ new Date());
|
|
138
|
+
const canonInput = {
|
|
139
|
+
address: input.signer.address,
|
|
140
|
+
delegation_id: input.delegation.id,
|
|
141
|
+
reason,
|
|
142
|
+
signed_at: signedAt
|
|
143
|
+
};
|
|
144
|
+
const id = computeRevocationId(canonInput);
|
|
145
|
+
const sigValue = await input.signer.signMessage(id);
|
|
146
|
+
const envelope = {
|
|
147
|
+
v: ENVELOPE_VERSION,
|
|
148
|
+
kind: "agent-revocation",
|
|
149
|
+
id,
|
|
150
|
+
delegation_id: input.delegation.id,
|
|
151
|
+
signer: { address: input.signer.address, alg: "bip322" },
|
|
152
|
+
reason,
|
|
153
|
+
signed_at: signedAt,
|
|
154
|
+
ots: input.ots ?? null,
|
|
155
|
+
sig: { alg: "bip322", pubkey: input.signer.address, value: sigValue }
|
|
156
|
+
};
|
|
157
|
+
return envelope;
|
|
158
|
+
}
|
|
159
|
+
function isoUtcSeconds(d) {
|
|
160
|
+
return d.toISOString().replace(/\.\d+Z$/, "Z");
|
|
161
|
+
}
|
|
162
|
+
function randomHex(bytes) {
|
|
163
|
+
const buf = new Uint8Array(bytes);
|
|
164
|
+
const c = globalThis.crypto;
|
|
165
|
+
if (c && typeof c.getRandomValues === "function") {
|
|
166
|
+
c.getRandomValues(buf);
|
|
167
|
+
} else {
|
|
168
|
+
for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);
|
|
169
|
+
}
|
|
170
|
+
let out = "";
|
|
171
|
+
for (let i = 0; i < bytes; i++) {
|
|
172
|
+
const b = buf[i];
|
|
173
|
+
out += b.toString(16).padStart(2, "0");
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { createDelegation, revoke, signAsAgent };
|
|
179
|
+
//# sourceMappingURL=index.mjs.map
|
|
180
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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 validateScope,\n} from '@orangecheck/agent-core';\nimport type { ValidationOptions } 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\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 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 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 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} 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
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orangecheck/agent-signer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OC Agent signer: createDelegation(), signAsAgent(), revoke(). Composes @orangecheck/agent-core with a wallet-adapter signing function and optional OTS anchoring.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bitcoin",
|
|
7
|
+
"agent",
|
|
8
|
+
"authority",
|
|
9
|
+
"delegation",
|
|
10
|
+
"bip322",
|
|
11
|
+
"oc-agent",
|
|
12
|
+
"orangecheck"
|
|
13
|
+
],
|
|
14
|
+
"author": "OrangeCheck",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/orangecheck/oc-packages.git",
|
|
19
|
+
"directory": "agent-signer"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/orangecheck/oc-agent-protocol",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/orangecheck/oc-agent-protocol/issues"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.mjs",
|
|
32
|
+
"require": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"type-check": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"clean": "rm -rf dist",
|
|
48
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@noble/hashes": "^1.5.0",
|
|
52
|
+
"@orangecheck/agent-core": "^0.1.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.10.2",
|
|
56
|
+
"tsup": "^8.3.5",
|
|
57
|
+
"typescript": "^5.7.2",
|
|
58
|
+
"vitest": "^3.2.4"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { verifyAction, verifyDelegation, verifyRevocation } from '@orangecheck/agent-core';
|
|
4
|
+
|
|
5
|
+
import { createDelegation, revoke, signAsAgent } from './index.js';
|
|
6
|
+
|
|
7
|
+
// Fake BIP-322 signer/verifier: the signer just base64-encodes "sig:<address>:<hex(msg)>",
|
|
8
|
+
// and the verifier confirms the pattern. This is enough to exercise the envelope
|
|
9
|
+
// plumbing; real signatures need a wallet-adapter.
|
|
10
|
+
const fakeSign = (address: string) => async (msg: string) => {
|
|
11
|
+
const payload = `sig:${address}:${msg}`;
|
|
12
|
+
return Buffer.from(payload, 'utf8').toString('base64');
|
|
13
|
+
};
|
|
14
|
+
const fakeVerify = async (msg: string, sig: string, address: string) => {
|
|
15
|
+
const expected = Buffer.from(`sig:${address}:${msg}`, 'utf8').toString('base64');
|
|
16
|
+
return sig === expected;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const PRINCIPAL = 'bc1qprincipal000000000000000000000000000000';
|
|
20
|
+
const AGENT = 'bc1qagent0000000000000000000000000000000000';
|
|
21
|
+
|
|
22
|
+
describe('createDelegation', () => {
|
|
23
|
+
it('produces a delegation envelope that verifies', async () => {
|
|
24
|
+
const envelope = await createDelegation({
|
|
25
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
26
|
+
agentAddress: AGENT,
|
|
27
|
+
scopes: ['lock:seal(recipient=bc1qalice000000000000000000000000000000000)'],
|
|
28
|
+
ttlMs: 24 * 60 * 60 * 1000,
|
|
29
|
+
});
|
|
30
|
+
expect(envelope.kind).toBe('agent-delegation');
|
|
31
|
+
expect(envelope.id).toMatch(/^[0-9a-f]{64}$/);
|
|
32
|
+
expect(envelope.scopes).toHaveLength(1);
|
|
33
|
+
|
|
34
|
+
const r = await verifyDelegation({ envelope, verifyBip322: fakeVerify });
|
|
35
|
+
expect(r.ok).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rejects unregistered scopes by default (strict)', async () => {
|
|
39
|
+
await expect(
|
|
40
|
+
createDelegation({
|
|
41
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
42
|
+
agentAddress: AGENT,
|
|
43
|
+
scopes: ['made_up:scope'],
|
|
44
|
+
ttlMs: 60_000,
|
|
45
|
+
})
|
|
46
|
+
).rejects.toThrow();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('throws when scopes is empty', async () => {
|
|
50
|
+
await expect(
|
|
51
|
+
createDelegation({
|
|
52
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
53
|
+
agentAddress: AGENT,
|
|
54
|
+
scopes: [],
|
|
55
|
+
})
|
|
56
|
+
).rejects.toThrow(/at least one scope/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('normalizes nonce default and scope order', async () => {
|
|
60
|
+
const env = await createDelegation({
|
|
61
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
62
|
+
agentAddress: AGENT,
|
|
63
|
+
scopes: ['stamp:sign(mime=text/markdown)', 'lock:seal(recipient=bc1qalice000000000000000000000000000000000)'],
|
|
64
|
+
ttlMs: 60_000,
|
|
65
|
+
});
|
|
66
|
+
expect(env.scopes[0]!.startsWith('lock:')).toBe(true);
|
|
67
|
+
expect(/^[0-9a-f]{32}$/.test(env.nonce)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('signAsAgent', () => {
|
|
72
|
+
it('produces an action that verifies against its delegation', async () => {
|
|
73
|
+
const delegation = await createDelegation({
|
|
74
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
75
|
+
agentAddress: AGENT,
|
|
76
|
+
scopes: ['lock:seal(recipient=bc1qalice000000000000000000000000000000000)'],
|
|
77
|
+
ttlMs: 60 * 60 * 1000,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const content = new TextEncoder().encode('hello agent');
|
|
81
|
+
const action = await signAsAgent({
|
|
82
|
+
agent: { address: AGENT, signMessage: fakeSign(AGENT) },
|
|
83
|
+
delegation,
|
|
84
|
+
content,
|
|
85
|
+
mime: 'text/plain',
|
|
86
|
+
scopeExercised: 'lock:seal(recipient=bc1qalice000000000000000000000000000000000)',
|
|
87
|
+
});
|
|
88
|
+
expect(action.kind).toBe('agent-action');
|
|
89
|
+
expect(action.delegation_id).toBe(delegation.id);
|
|
90
|
+
|
|
91
|
+
const r = await verifyAction({
|
|
92
|
+
action,
|
|
93
|
+
delegation,
|
|
94
|
+
verifyBip322: fakeVerify,
|
|
95
|
+
});
|
|
96
|
+
expect(r.ok).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('rejects when signer address does not match delegation.agent', async () => {
|
|
100
|
+
const delegation = await createDelegation({
|
|
101
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
102
|
+
agentAddress: AGENT,
|
|
103
|
+
scopes: ['stamp:sign(mime=text/markdown)'],
|
|
104
|
+
ttlMs: 60_000,
|
|
105
|
+
});
|
|
106
|
+
await expect(
|
|
107
|
+
signAsAgent({
|
|
108
|
+
agent: { address: 'bc1qotheragent', signMessage: fakeSign('bc1qotheragent') },
|
|
109
|
+
delegation,
|
|
110
|
+
content: new Uint8Array([1, 2, 3]),
|
|
111
|
+
mime: 'text/plain',
|
|
112
|
+
scopeExercised: 'stamp:sign(mime=text/markdown)',
|
|
113
|
+
})
|
|
114
|
+
).rejects.toThrow(/delegation.agent/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('fails verification when exercised scope is not a sub-scope', async () => {
|
|
118
|
+
const delegation = await createDelegation({
|
|
119
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
120
|
+
agentAddress: AGENT,
|
|
121
|
+
scopes: ['stamp:sign(mime=text/markdown)'],
|
|
122
|
+
ttlMs: 60 * 60 * 1000,
|
|
123
|
+
});
|
|
124
|
+
const action = await signAsAgent({
|
|
125
|
+
agent: { address: AGENT, signMessage: fakeSign(AGENT) },
|
|
126
|
+
delegation,
|
|
127
|
+
content: new TextEncoder().encode('x'),
|
|
128
|
+
mime: 'application/pdf',
|
|
129
|
+
scopeExercised: 'stamp:sign(mime=application/pdf)',
|
|
130
|
+
});
|
|
131
|
+
const r = await verifyAction({ action, delegation, verifyBip322: fakeVerify });
|
|
132
|
+
expect(r.ok).toBe(false);
|
|
133
|
+
if (!r.ok) expect(r.code).toBe('E_SCOPE_DENIED');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('revoke', () => {
|
|
138
|
+
it('produces a revocation that verifies and applies to the delegation', async () => {
|
|
139
|
+
const delegation = await createDelegation({
|
|
140
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
141
|
+
agentAddress: AGENT,
|
|
142
|
+
scopes: ['lock:seal(recipient=bc1qalice000000000000000000000000000000000)'],
|
|
143
|
+
ttlMs: 60 * 60 * 1000,
|
|
144
|
+
});
|
|
145
|
+
const rev = await revoke({
|
|
146
|
+
signer: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
147
|
+
delegation,
|
|
148
|
+
reason: 'key rotated',
|
|
149
|
+
});
|
|
150
|
+
const r = await verifyRevocation({ envelope: rev, delegation, verifyBip322: fakeVerify });
|
|
151
|
+
expect(r.ok).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('rejects a revocation from an unauthorized signer', async () => {
|
|
155
|
+
const delegation = await createDelegation({
|
|
156
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
157
|
+
agentAddress: AGENT,
|
|
158
|
+
scopes: ['stamp:sign(mime=text/markdown)'],
|
|
159
|
+
ttlMs: 60_000,
|
|
160
|
+
});
|
|
161
|
+
await expect(
|
|
162
|
+
revoke({
|
|
163
|
+
signer: { address: AGENT, signMessage: fakeSign(AGENT) },
|
|
164
|
+
delegation, // revocation.holders default = ['principal']
|
|
165
|
+
})
|
|
166
|
+
).rejects.toThrow(/not authorized/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('allows agent revocation when delegation opts in', async () => {
|
|
170
|
+
const delegation = await createDelegation({
|
|
171
|
+
principal: { address: PRINCIPAL, signMessage: fakeSign(PRINCIPAL) },
|
|
172
|
+
agentAddress: AGENT,
|
|
173
|
+
scopes: ['stamp:sign(mime=text/markdown)'],
|
|
174
|
+
ttlMs: 60_000,
|
|
175
|
+
revocationHolders: ['principal', 'agent'],
|
|
176
|
+
});
|
|
177
|
+
const rev = await revoke({
|
|
178
|
+
signer: { address: AGENT, signMessage: fakeSign(AGENT) },
|
|
179
|
+
delegation,
|
|
180
|
+
reason: 'self-revoke',
|
|
181
|
+
});
|
|
182
|
+
const r = await verifyRevocation({ envelope: rev, delegation, verifyBip322: fakeVerify });
|
|
183
|
+
expect(r.ok).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// @orangecheck/agent-signer — high-level API for producing OC Agent envelopes.
|
|
2
|
+
//
|
|
3
|
+
// createDelegation — principal signs a scoped grant to an agent address.
|
|
4
|
+
// signAsAgent — agent produces an agent-action envelope over content.
|
|
5
|
+
// revoke — principal (or agent, if authorized) burns a delegation.
|
|
6
|
+
//
|
|
7
|
+
// Each function wraps @orangecheck/agent-core's canonical-message + id
|
|
8
|
+
// computation with a caller-supplied `signMessage` wallet adapter. Nothing here
|
|
9
|
+
// touches the network — OTS anchoring and Nostr publication are separate steps
|
|
10
|
+
// the caller wires up.
|
|
11
|
+
|
|
12
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
13
|
+
import {
|
|
14
|
+
canonicalizeScopes,
|
|
15
|
+
computeActionId,
|
|
16
|
+
computeDelegationId,
|
|
17
|
+
computeRevocationId,
|
|
18
|
+
ENVELOPE_VERSION,
|
|
19
|
+
hexEncode,
|
|
20
|
+
parseScope,
|
|
21
|
+
validateScope,
|
|
22
|
+
} from '@orangecheck/agent-core';
|
|
23
|
+
import type { ValidationOptions } from '@orangecheck/agent-core';
|
|
24
|
+
import type {
|
|
25
|
+
ActionEnvelope,
|
|
26
|
+
ActionOts,
|
|
27
|
+
DelegationBond,
|
|
28
|
+
DelegationEnvelope,
|
|
29
|
+
RevocationEnvelope,
|
|
30
|
+
RevocationHolder,
|
|
31
|
+
} from '@orangecheck/agent-core';
|
|
32
|
+
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
// Shared wallet-adapter shape (matches `@orangecheck/stamp-core` StampInput).
|
|
35
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export interface SignerRef {
|
|
38
|
+
address: string;
|
|
39
|
+
/** Returns a base64 BIP-322 signature over the given message as UTF-8 bytes. */
|
|
40
|
+
signMessage: (msg: string) => Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
// createDelegation
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export interface CreateDelegationInput {
|
|
48
|
+
principal: SignerRef;
|
|
49
|
+
agentAddress: string;
|
|
50
|
+
scopes: string[];
|
|
51
|
+
bond?: DelegationBond | null;
|
|
52
|
+
issuedAt?: Date;
|
|
53
|
+
/** Duration from issuedAt in ms. Default 7 days. MAX 365 days per SPEC §4.4. */
|
|
54
|
+
ttlMs?: number;
|
|
55
|
+
/** Explicit expires_at overrides ttlMs. */
|
|
56
|
+
expiresAt?: Date;
|
|
57
|
+
/** 32-hex. Defaults to crypto.getRandomValues(16-byte) rendered as hex. */
|
|
58
|
+
nonce?: string;
|
|
59
|
+
/** Default ['principal']. */
|
|
60
|
+
revocationHolders?: RevocationHolder[];
|
|
61
|
+
/** Optional Nostr pointer to a revocation event. Non-cryptographic. */
|
|
62
|
+
revocationRef?: string | null;
|
|
63
|
+
/** Default 'strict'. 'permissive' allows unregistered scopes / constraint keys. */
|
|
64
|
+
scopeMode?: ValidationOptions['mode'];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function createDelegation(input: CreateDelegationInput): Promise<DelegationEnvelope> {
|
|
68
|
+
if (!input.scopes || input.scopes.length === 0) {
|
|
69
|
+
throw new Error('createDelegation: at least one scope is required');
|
|
70
|
+
}
|
|
71
|
+
const mode = input.scopeMode ?? 'strict';
|
|
72
|
+
for (const s of input.scopes) validateScope(parseScope(s), { mode });
|
|
73
|
+
const scopes = canonicalizeScopes(input.scopes);
|
|
74
|
+
|
|
75
|
+
const issuedAt = isoUtcSeconds(input.issuedAt ?? new Date());
|
|
76
|
+
let expiresAtDate: Date;
|
|
77
|
+
if (input.expiresAt) {
|
|
78
|
+
expiresAtDate = input.expiresAt;
|
|
79
|
+
} else {
|
|
80
|
+
const ttl = input.ttlMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
81
|
+
expiresAtDate = new Date(new Date(issuedAt).getTime() + ttl);
|
|
82
|
+
}
|
|
83
|
+
const expiresAt = isoUtcSeconds(expiresAtDate);
|
|
84
|
+
|
|
85
|
+
const durationMs = new Date(expiresAt).getTime() - new Date(issuedAt).getTime();
|
|
86
|
+
if (durationMs <= 0) throw new Error('createDelegation: expires_at must be after issued_at');
|
|
87
|
+
if (durationMs > 365 * 24 * 60 * 60 * 1000) {
|
|
88
|
+
throw new Error('createDelegation: delegation duration must not exceed 365 days');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const nonce = input.nonce ?? randomHex(16);
|
|
92
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) {
|
|
93
|
+
throw new Error('createDelegation: nonce must be 32 lowercase hex chars');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const bond = input.bond ?? null;
|
|
97
|
+
if (bond) {
|
|
98
|
+
if (!Number.isInteger(bond.sats) || bond.sats < 0) {
|
|
99
|
+
throw new Error('createDelegation: bond.sats must be a non-negative integer');
|
|
100
|
+
}
|
|
101
|
+
if (!/^[0-9a-f]{64}$/.test(bond.attestation_id)) {
|
|
102
|
+
throw new Error('createDelegation: bond.attestation_id must be 64 lowercase hex chars');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const canonInput = {
|
|
107
|
+
principal: input.principal.address,
|
|
108
|
+
agent: input.agentAddress,
|
|
109
|
+
scopes,
|
|
110
|
+
bond_sats: bond?.sats ?? 0,
|
|
111
|
+
bond_attestation: bond?.attestation_id ?? 'none',
|
|
112
|
+
issued_at: issuedAt,
|
|
113
|
+
expires_at: expiresAt,
|
|
114
|
+
nonce,
|
|
115
|
+
};
|
|
116
|
+
const id = computeDelegationId(canonInput);
|
|
117
|
+
|
|
118
|
+
const sigValue = await input.principal.signMessage(id);
|
|
119
|
+
|
|
120
|
+
const envelope: DelegationEnvelope = {
|
|
121
|
+
v: ENVELOPE_VERSION,
|
|
122
|
+
kind: 'agent-delegation',
|
|
123
|
+
id,
|
|
124
|
+
principal: { address: input.principal.address, alg: 'bip322' },
|
|
125
|
+
agent: { address: input.agentAddress, alg: 'bip322' },
|
|
126
|
+
scopes,
|
|
127
|
+
bond,
|
|
128
|
+
issued_at: issuedAt,
|
|
129
|
+
expires_at: expiresAt,
|
|
130
|
+
nonce,
|
|
131
|
+
revocation: {
|
|
132
|
+
holders: input.revocationHolders ?? ['principal'],
|
|
133
|
+
ref: input.revocationRef ?? null,
|
|
134
|
+
},
|
|
135
|
+
sig: {
|
|
136
|
+
alg: 'bip322',
|
|
137
|
+
pubkey: input.principal.address,
|
|
138
|
+
value: sigValue,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
return envelope;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
// signAsAgent
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
export interface SignAsAgentInput {
|
|
149
|
+
agent: SignerRef;
|
|
150
|
+
delegation: DelegationEnvelope;
|
|
151
|
+
/** Raw bytes to stamp, or a pre-computed { hash, length } pair. */
|
|
152
|
+
content: Uint8Array | { hash: string; length: number };
|
|
153
|
+
mime: string;
|
|
154
|
+
/** Which scope from delegation.scopes is being exercised (or a narrower sub-scope). */
|
|
155
|
+
scopeExercised: string;
|
|
156
|
+
ref?: string | null;
|
|
157
|
+
signedAt?: Date;
|
|
158
|
+
/** OTS proof / anchor data if the caller has already submitted; otherwise null. */
|
|
159
|
+
ots?: ActionOts | null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function signAsAgent(input: SignAsAgentInput): Promise<ActionEnvelope> {
|
|
163
|
+
if (input.agent.address !== input.delegation.agent.address) {
|
|
164
|
+
throw new Error('signAsAgent: signer address does not match delegation.agent.address');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let contentHash: string;
|
|
168
|
+
let contentLength: number;
|
|
169
|
+
if (input.content instanceof Uint8Array) {
|
|
170
|
+
contentHash = 'sha256:' + hexEncode(sha256(input.content));
|
|
171
|
+
contentLength = input.content.byteLength;
|
|
172
|
+
} else {
|
|
173
|
+
if (!input.content.hash.startsWith('sha256:') || input.content.hash.length !== 'sha256:'.length + 64) {
|
|
174
|
+
throw new Error('signAsAgent: content.hash must be "sha256:" + 64 lowercase hex chars');
|
|
175
|
+
}
|
|
176
|
+
contentHash = input.content.hash;
|
|
177
|
+
contentLength = input.content.length;
|
|
178
|
+
if (!Number.isInteger(contentLength) || contentLength < 0) {
|
|
179
|
+
throw new Error('signAsAgent: content.length must be a non-negative integer');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const signedAt = isoUtcSeconds(input.signedAt ?? new Date());
|
|
184
|
+
|
|
185
|
+
const canonInput = {
|
|
186
|
+
address: input.agent.address,
|
|
187
|
+
content_hash: contentHash,
|
|
188
|
+
content_length: contentLength,
|
|
189
|
+
content_mime: input.mime,
|
|
190
|
+
signed_at: signedAt,
|
|
191
|
+
delegation_id: input.delegation.id,
|
|
192
|
+
scope_exercised: input.scopeExercised,
|
|
193
|
+
};
|
|
194
|
+
const id = computeActionId(canonInput);
|
|
195
|
+
|
|
196
|
+
const sigValue = await input.agent.signMessage(id);
|
|
197
|
+
|
|
198
|
+
const envelope: ActionEnvelope = {
|
|
199
|
+
v: ENVELOPE_VERSION,
|
|
200
|
+
kind: 'agent-action',
|
|
201
|
+
id,
|
|
202
|
+
content: {
|
|
203
|
+
hash: contentHash,
|
|
204
|
+
length: contentLength,
|
|
205
|
+
mime: input.mime,
|
|
206
|
+
ref: input.ref ?? null,
|
|
207
|
+
},
|
|
208
|
+
signer: { address: input.agent.address, alg: 'bip322' },
|
|
209
|
+
signed_at: signedAt,
|
|
210
|
+
delegation_id: input.delegation.id,
|
|
211
|
+
scope_exercised: input.scopeExercised,
|
|
212
|
+
ots: input.ots ?? null,
|
|
213
|
+
sig: { alg: 'bip322', pubkey: input.agent.address, value: sigValue },
|
|
214
|
+
};
|
|
215
|
+
return envelope;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
219
|
+
// revoke
|
|
220
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
export interface RevokeInput {
|
|
223
|
+
signer: SignerRef;
|
|
224
|
+
delegation: DelegationEnvelope;
|
|
225
|
+
/** Short ASCII rationale. Defaults to empty string. */
|
|
226
|
+
reason?: string;
|
|
227
|
+
signedAt?: Date;
|
|
228
|
+
/** OTS proof / anchor data if the caller has already submitted; otherwise null. */
|
|
229
|
+
ots?: ActionOts | null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function revoke(input: RevokeInput): Promise<RevocationEnvelope> {
|
|
233
|
+
const holders = input.delegation.revocation?.holders ?? ['principal'];
|
|
234
|
+
const allowed = new Set<string>();
|
|
235
|
+
if (holders.includes('principal')) allowed.add(input.delegation.principal.address);
|
|
236
|
+
if (holders.includes('agent')) allowed.add(input.delegation.agent.address);
|
|
237
|
+
if (!allowed.has(input.signer.address)) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`revoke: signer ${input.signer.address} not authorized to revoke this delegation`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const reason = input.reason ?? '';
|
|
244
|
+
if (reason.length > 128) throw new Error('revoke: reason must be <=128 bytes');
|
|
245
|
+
|
|
246
|
+
const signedAt = isoUtcSeconds(input.signedAt ?? new Date());
|
|
247
|
+
const canonInput = {
|
|
248
|
+
address: input.signer.address,
|
|
249
|
+
delegation_id: input.delegation.id,
|
|
250
|
+
reason,
|
|
251
|
+
signed_at: signedAt,
|
|
252
|
+
};
|
|
253
|
+
const id = computeRevocationId(canonInput);
|
|
254
|
+
|
|
255
|
+
const sigValue = await input.signer.signMessage(id);
|
|
256
|
+
|
|
257
|
+
const envelope: RevocationEnvelope = {
|
|
258
|
+
v: ENVELOPE_VERSION,
|
|
259
|
+
kind: 'agent-revocation',
|
|
260
|
+
id,
|
|
261
|
+
delegation_id: input.delegation.id,
|
|
262
|
+
signer: { address: input.signer.address, alg: 'bip322' },
|
|
263
|
+
reason,
|
|
264
|
+
signed_at: signedAt,
|
|
265
|
+
ots: input.ots ?? null,
|
|
266
|
+
sig: { alg: 'bip322', pubkey: input.signer.address, value: sigValue },
|
|
267
|
+
};
|
|
268
|
+
return envelope;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
272
|
+
// Re-exports for convenience
|
|
273
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
export {
|
|
276
|
+
canonicalizeScopes,
|
|
277
|
+
computeActionId,
|
|
278
|
+
computeDelegationId,
|
|
279
|
+
computeRevocationId,
|
|
280
|
+
verifyAction,
|
|
281
|
+
verifyDelegation,
|
|
282
|
+
verifyRevocation,
|
|
283
|
+
parseScope,
|
|
284
|
+
isSubScope,
|
|
285
|
+
canonicalizeScope,
|
|
286
|
+
REGISTERED_SCOPES,
|
|
287
|
+
} from '@orangecheck/agent-core';
|
|
288
|
+
export type {
|
|
289
|
+
ActionEnvelope,
|
|
290
|
+
ActionOts,
|
|
291
|
+
DelegationBond,
|
|
292
|
+
DelegationEnvelope,
|
|
293
|
+
RevocationEnvelope,
|
|
294
|
+
Scope,
|
|
295
|
+
ScopeConstraint,
|
|
296
|
+
AgentErrorCode,
|
|
297
|
+
} from '@orangecheck/agent-core';
|
|
298
|
+
|
|
299
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
300
|
+
// Helpers
|
|
301
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
function isoUtcSeconds(d: Date): string {
|
|
304
|
+
return d.toISOString().replace(/\.\d+Z$/, 'Z');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function randomHex(bytes: number): string {
|
|
308
|
+
const buf = new Uint8Array(bytes);
|
|
309
|
+
const c: Crypto | undefined = (globalThis as unknown as { crypto?: Crypto }).crypto;
|
|
310
|
+
if (c && typeof c.getRandomValues === 'function') {
|
|
311
|
+
c.getRandomValues(buf);
|
|
312
|
+
} else {
|
|
313
|
+
// Non-browser fallback. Node ≥18 exposes globalThis.crypto, so this is unlikely to fire.
|
|
314
|
+
for (let i = 0; i < bytes; i++) buf[i] = Math.floor(Math.random() * 256);
|
|
315
|
+
}
|
|
316
|
+
let out = '';
|
|
317
|
+
for (let i = 0; i < bytes; i++) {
|
|
318
|
+
const b = buf[i]!;
|
|
319
|
+
out += b.toString(16).padStart(2, '0');
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
}
|