@orangecheck/agent-core 2.5.0 → 2.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 +38 -16
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/federation.test.ts +3 -1
- package/src/federation.ts +2 -2
- package/src/revocation-failclosed.test.ts +46 -10
- package/src/verify.ts +11 -9
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ Canonical messages, envelope formats (delegation / action / revocation), scope g
|
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm i @orangecheck/agent-core
|
|
17
|
-
#
|
|
18
|
-
npm i @orangecheck/
|
|
17
|
+
# for the quickstart below: relay reads and a BIP-322 verifier
|
|
18
|
+
npm i @orangecheck/nostr-core @orangecheck/bip322
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Quickstart
|
|
@@ -24,11 +24,16 @@ npm i @orangecheck/stamp-core
|
|
|
24
24
|
import {
|
|
25
25
|
canonicalizeScopes,
|
|
26
26
|
computeDelegationId,
|
|
27
|
-
delegationCanonicalMessage,
|
|
28
27
|
verifyDelegation,
|
|
28
|
+
type DelegationEnvelope,
|
|
29
|
+
type RevocationEnvelope,
|
|
29
30
|
} from '@orangecheck/agent-core';
|
|
31
|
+
import { verifySignature } from '@orangecheck/bip322';
|
|
32
|
+
import { queryEvents } from '@orangecheck/nostr-core';
|
|
30
33
|
|
|
31
|
-
// 1. Build a delegation canonical
|
|
34
|
+
// 1. Build a delegation's canonical fields and id.
|
|
35
|
+
const iso = (d: Date) => d.toISOString().replace(/\.\d+Z$/, 'Z');
|
|
36
|
+
const issued = new Date();
|
|
32
37
|
const scopes = canonicalizeScopes([
|
|
33
38
|
'lock:seal(recipient=bc1qalice)',
|
|
34
39
|
'stamp:sign(mime=text/markdown)',
|
|
@@ -38,19 +43,18 @@ const canon = {
|
|
|
38
43
|
agent: 'bc1qagent…',
|
|
39
44
|
scopes,
|
|
40
45
|
bond_sats: 500_000,
|
|
41
|
-
bond_attestation: '22…22', // 64-hex OrangeCheck attestation id
|
|
42
|
-
issued_at:
|
|
43
|
-
expires_at:
|
|
44
|
-
nonce: '0123…cdef',
|
|
46
|
+
bond_attestation: '22…22', // 64-hex OrangeCheck attestation id, or 'none'
|
|
47
|
+
issued_at: iso(issued),
|
|
48
|
+
expires_at: iso(new Date(issued.getTime() + 7 * 86_400_000)), // one week
|
|
49
|
+
nonce: '0123…cdef', // 32-hex, random
|
|
45
50
|
};
|
|
46
|
-
const msg = delegationCanonicalMessage(canon);
|
|
47
51
|
const id = computeDelegationId(canon);
|
|
48
52
|
|
|
49
|
-
// 2. Have the wallet sign `id` (hex ASCII) via BIP-322.
|
|
53
|
+
// 2. Have the principal's wallet sign `id` (hex ASCII) via BIP-322.
|
|
50
54
|
const sigValue = await wallet.signMessage(id);
|
|
51
55
|
|
|
52
56
|
// 3. Build the wire envelope.
|
|
53
|
-
const envelope = {
|
|
57
|
+
const envelope: DelegationEnvelope = {
|
|
54
58
|
v: 1, kind: 'agent-delegation', id,
|
|
55
59
|
principal: { address: canon.principal, alg: 'bip322' },
|
|
56
60
|
agent: { address: canon.agent, alg: 'bip322' },
|
|
@@ -59,12 +63,28 @@ const envelope = {
|
|
|
59
63
|
issued_at: canon.issued_at, expires_at: canon.expires_at, nonce: canon.nonce,
|
|
60
64
|
revocation: { holders: ['principal'], ref: null },
|
|
61
65
|
sig: { alg: 'bip322', pubkey: canon.principal, value: sigValue },
|
|
62
|
-
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// 4. Fetch its revocations: kind 30085 by `#t` (SPEC §9.4). Relays do not
|
|
69
|
+
// index the multi-letter `delegation` tag. Only a relay that sent EOSE
|
|
70
|
+
// can show that none exists (SPEC §10.4).
|
|
71
|
+
const feed = await queryEvents({ kinds: [30085], '#t': [id] });
|
|
72
|
+
if (!feed.relayStatus.some((r) => r.eose)) throw new Error('no relay answered');
|
|
73
|
+
const revocations = feed.events.flatMap((e): RevocationEnvelope[] => {
|
|
74
|
+
try {
|
|
75
|
+
const r = JSON.parse(e.content);
|
|
76
|
+
return r?.kind === 'agent-revocation' && r.delegation_id === id ? [r] : [];
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
});
|
|
63
81
|
|
|
64
|
-
//
|
|
82
|
+
// 5. Verify. `revocations` is required: pass what you fetched, or state
|
|
83
|
+
// `skipRevocationCheck: true` if you are not checking.
|
|
65
84
|
const result = await verifyDelegation({
|
|
66
85
|
envelope,
|
|
67
|
-
|
|
86
|
+
revocations,
|
|
87
|
+
verifyBip322: async (msg, sig, addr) => verifySignature(addr, msg, sig),
|
|
68
88
|
});
|
|
69
89
|
if (!result.ok) throw new Error(result.code + ': ' + result.message);
|
|
70
90
|
```
|
|
@@ -93,12 +113,14 @@ if (!result.ok) throw new Error(result.code + ': ' + result.message);
|
|
|
93
113
|
|
|
94
114
|
### Verification (§SPEC 8)
|
|
95
115
|
|
|
96
|
-
- `verifyDelegation({ envelope, verifyBip322, now?, skipTemporalCheck?, scopeMode? })`
|
|
97
|
-
- `verifyAction({ action, delegation, revocations
|
|
116
|
+
- `verifyDelegation({ envelope, revocations | skipRevocationCheck, verifyBip322, now?, skipTemporalCheck?, scopeMode? })`
|
|
117
|
+
- `verifyAction({ action, delegation, revocations | skipRevocationCheck, verifyBip322, verifyOtsAnchor?, content?, resolveAnchorBlockHeight?, scopeMode? })`
|
|
98
118
|
- `verifyRevocation({ envelope, delegation, verifyBip322 })` — the principal may always revoke; `revocation.holders` can only add the agent
|
|
99
119
|
- `verifyOtsAnchor(proofB64, blockHeight, blockHash, envelopeId)` — an anchor orders an action against a revocation only when this returns true
|
|
100
120
|
- `verifyFederationRevocation({ envelope, delegation, verifyBip322 })`
|
|
101
121
|
|
|
122
|
+
`verifyDelegation`, `verifyAction` and `verifyFederationDelegation` refuse with `E_MALFORMED` unless you pass `revocations` or `skipRevocationCheck: true`. Fetch revocations with `{ kinds: [30085], "#t": [<delegation id>] }`; for an action under a sub-delegation chain, list every link's id. Pass `[]` only when a relay answered with EOSE and nothing matched.
|
|
123
|
+
|
|
102
124
|
Each returns a discriminated union:
|
|
103
125
|
|
|
104
126
|
```ts
|
package/dist/index.js
CHANGED
|
@@ -513,7 +513,7 @@ async function verifyDelegation(input) {
|
|
|
513
513
|
if (!input.skipRevocationCheck && !input.revocations) {
|
|
514
514
|
return err(
|
|
515
515
|
"E_MALFORMED",
|
|
516
|
-
|
|
516
|
+
'verifyDelegation requires `revocations` (query kind 30085 with "#t": [delegation id], SPEC \xA79.4) or an explicit skipRevocationCheck:true to state that revocation is not being checked'
|
|
517
517
|
);
|
|
518
518
|
}
|
|
519
519
|
if (input.revocations) {
|
|
@@ -641,7 +641,7 @@ async function verifyAction(input) {
|
|
|
641
641
|
if (!input.skipRevocationCheck && !input.revocations) {
|
|
642
642
|
return err(
|
|
643
643
|
"E_MALFORMED",
|
|
644
|
-
|
|
644
|
+
'verifyAction requires `revocations` (query kind 30085 with "#t": [id of every link in the chain], SPEC \xA79.4) or an explicit skipRevocationCheck:true'
|
|
645
645
|
);
|
|
646
646
|
}
|
|
647
647
|
let actionAnchor;
|
|
@@ -1105,7 +1105,7 @@ async function verifyFederationDelegation(input) {
|
|
|
1105
1105
|
if (!input.skipRevocationCheck && !input.revocations) {
|
|
1106
1106
|
return fail(
|
|
1107
1107
|
"E_MALFORMED",
|
|
1108
|
-
|
|
1108
|
+
'verifyFederationDelegation requires `revocations` (query kind 30085 with "#t": [delegation id], SPEC \xA79.4) or an explicit skipRevocationCheck:true to state that revocation is not being checked'
|
|
1109
1109
|
);
|
|
1110
1110
|
}
|
|
1111
1111
|
for (const rev of input.revocations ?? []) {
|