provenance-protocol 0.1.2 → 0.1.4
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 +50 -19
- package/package.json +11 -1
- package/src/cli.js +196 -0
- package/src/keygen.d.ts +48 -0
- package/src/keygen.js +142 -0
package/README.md
CHANGED
|
@@ -21,9 +21,10 @@ const trust = await provenance.check('provenance:github:alice/research-assistant
|
|
|
21
21
|
console.log(trust);
|
|
22
22
|
// {
|
|
23
23
|
// found: true,
|
|
24
|
+
// identity: 'verified', // 'inferred' | 'declared' | 'verified'
|
|
25
|
+
// identity_verified: true,
|
|
24
26
|
// declared: true,
|
|
25
27
|
// age_days: 142,
|
|
26
|
-
// confidence: 0.9,
|
|
27
28
|
// capabilities: ['read:web', 'write:summaries'],
|
|
28
29
|
// constraints: ['no:financial:transact', 'no:pii'],
|
|
29
30
|
// incidents: 0,
|
|
@@ -39,11 +40,11 @@ The most useful method for receiving systems.
|
|
|
39
40
|
|
|
40
41
|
```js
|
|
41
42
|
const result = await provenance.gate('provenance:github:alice/agent', {
|
|
42
|
-
requireDeclared: true,
|
|
43
|
+
requireDeclared: true, // must have PROVENANCE.yml
|
|
44
|
+
requireVerified: true, // must have identity_verified: true
|
|
43
45
|
requireConstraints: ['no:financial:transact', 'no:pii'], // must have committed to these
|
|
44
|
-
requireClean: true,
|
|
45
|
-
requireMinAge: 30,
|
|
46
|
-
requireMinConfidence: 0.7, // classification confidence
|
|
46
|
+
requireClean: true, // no open incidents
|
|
47
|
+
requireMinAge: 30, // must be at least 30 days old
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
if (!result.allowed) {
|
|
@@ -119,35 +120,42 @@ if (result.fallback) {
|
|
|
119
120
|
| Field | Type | Description |
|
|
120
121
|
|---|---|---|
|
|
121
122
|
| `found` | boolean | Agent exists in Provenance index |
|
|
122
|
-
| `
|
|
123
|
+
| `identity` | string | `'inferred'` \| `'declared'` \| `'verified'` — see below |
|
|
124
|
+
| `identity_verified` | boolean | Cryptographic key ownership confirmed against a public URL |
|
|
125
|
+
| `declared` | boolean | Agent has a PROVENANCE.yml (or registered via API with full fields) |
|
|
123
126
|
| `age_days` | number | Days since first indexed |
|
|
124
|
-
| `confidence` | number | 0–1 classification confidence |
|
|
125
127
|
| `capabilities` | string[] | What the agent declares it can do |
|
|
126
128
|
| `constraints` | string[] | What the agent has publicly committed never to do |
|
|
127
129
|
| `incidents` | number | Number of open incidents |
|
|
128
|
-
| `status` | string | active / suspended / removed |
|
|
130
|
+
| `status` | string | `active` / `suspended` / `removed` |
|
|
129
131
|
| `model` | object | `{ provider, model_id }` if declared |
|
|
130
132
|
| `public_key` | string\|null | Base64 Ed25519 public key, if registered |
|
|
131
|
-
| `identity_verified` | boolean | Cryptographic proof of key ownership confirmed |
|
|
132
133
|
| `ajp_endpoint` | string\|null | AJP job endpoint URL, if the agent accepts delegated jobs |
|
|
133
134
|
| `first_seen` | string | ISO date of first public appearance |
|
|
134
135
|
|
|
136
|
+
### Identity states
|
|
137
|
+
|
|
138
|
+
| State | Meaning |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `inferred` | Indexed by crawler from a public repo. No PROVENANCE.yml, no self-registration. |
|
|
141
|
+
| `declared` | Agent registered itself (or has PROVENANCE.yml) but without cryptographic key verification. |
|
|
142
|
+
| `verified` | Agent registered with a keypair and the public key was confirmed against a publicly fetchable PROVENANCE.yml. **Independently auditable** — anyone can re-verify without trusting the Provenance registry. |
|
|
143
|
+
|
|
135
144
|
---
|
|
136
145
|
|
|
137
146
|
## Registering your own agent
|
|
138
147
|
|
|
139
|
-
|
|
140
|
-
import { Provenance } from 'provenance-protocol';
|
|
141
|
-
import { generateProvenanceKeyPair, signChallenge } from 'provenance-protocol/keygen';
|
|
148
|
+
### Public repo (GitHub / HuggingFace / npm)
|
|
142
149
|
|
|
143
|
-
|
|
150
|
+
Push a `PROVENANCE.yml` containing your public key to the repo, then register. The server fetches the file and confirms the key — independently verifiable by anyone.
|
|
144
151
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// Store privateKey as PROVENANCE_PRIVATE_KEY env var — never commit it
|
|
152
|
+
```js
|
|
153
|
+
import { generateProvenanceKeyPair, signForProvenance, signChallenge } from 'provenance-protocol/keygen';
|
|
148
154
|
|
|
149
|
-
|
|
155
|
+
const { publicKey, privateKey } = generateProvenanceKeyPair();
|
|
150
156
|
const provenanceId = 'provenance:github:your-org/your-agent';
|
|
157
|
+
|
|
158
|
+
// Add to PROVENANCE.yml → commit → push, then:
|
|
151
159
|
const signed_challenge = signChallenge(privateKey, provenanceId, 'REGISTER');
|
|
152
160
|
|
|
153
161
|
await provenance.register({
|
|
@@ -158,11 +166,34 @@ await provenance.register({
|
|
|
158
166
|
capabilities: ['read:web', 'write:summaries'],
|
|
159
167
|
constraints: ['no:pii'],
|
|
160
168
|
public_key: publicKey,
|
|
161
|
-
signed_challenge,
|
|
169
|
+
signed_challenge,
|
|
162
170
|
});
|
|
163
|
-
// → { created: true,
|
|
171
|
+
// → { created: true, agent: { identity: 'verified', identity_verified: true } }
|
|
164
172
|
```
|
|
165
173
|
|
|
174
|
+
### Private agent (no public repo)
|
|
175
|
+
|
|
176
|
+
Use `provenance:custom:` platform. Host your `PROVENANCE.yml` at any public URL you control and pass it as `url` — this makes the identity independently verifiable. Without a `url`, verification is registry-dependent (key control only).
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
const provenanceId = 'provenance:custom:your-org/your-agent';
|
|
180
|
+
const signed_challenge = signChallenge(privateKey, provenanceId, 'REGISTER');
|
|
181
|
+
|
|
182
|
+
await provenance.register({
|
|
183
|
+
id: provenanceId,
|
|
184
|
+
url: 'https://yourdomain.com/.well-known/provenance.yml', // optional but recommended
|
|
185
|
+
name: 'Your Agent',
|
|
186
|
+
description: 'What it does',
|
|
187
|
+
capabilities: ['read:web'],
|
|
188
|
+
constraints: ['no:pii'],
|
|
189
|
+
public_key: publicKey,
|
|
190
|
+
signed_challenge,
|
|
191
|
+
});
|
|
192
|
+
// → { created: true, agent: { identity: 'verified', identity_verified: true } }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
See [getprovenance.dev/docs#ai-quickstart](https://getprovenance.dev/docs#ai-quickstart) for full automated scripts.
|
|
196
|
+
|
|
166
197
|
## Revoking a compromised key
|
|
167
198
|
|
|
168
199
|
```js
|
package/package.json
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "provenance-protocol",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "SDK for querying the Provenance agent identity index",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./src/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"provenance": "./src/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"types": "./src/index.d.ts",
|
|
11
14
|
"import": "./src/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./keygen": {
|
|
17
|
+
"types": "./src/keygen.d.ts",
|
|
18
|
+
"import": "./src/keygen.js"
|
|
12
19
|
}
|
|
13
20
|
},
|
|
14
21
|
"files": [
|
|
15
22
|
"src/index.js",
|
|
16
23
|
"src/index.d.ts",
|
|
24
|
+
"src/keygen.js",
|
|
25
|
+
"src/keygen.d.ts",
|
|
26
|
+
"src/cli.js",
|
|
17
27
|
"README.md"
|
|
18
28
|
],
|
|
19
29
|
"keywords": [
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* provenance CLI
|
|
4
|
+
*
|
|
5
|
+
* npx provenance keygen
|
|
6
|
+
* npx provenance register --id provenance:github:org/agent --url https://...
|
|
7
|
+
* npx provenance status provenance:github:org/agent
|
|
8
|
+
* npx provenance revoke --id provenance:github:org/agent
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import { generateProvenanceKeyPair, signChallenge, signForProvenance, signRevocation } from './keygen.js';
|
|
14
|
+
|
|
15
|
+
const API = 'https://getprovenance.dev/api/agents';
|
|
16
|
+
const KEY_FILE = '.provenance-key';
|
|
17
|
+
|
|
18
|
+
function readPrivateKey() {
|
|
19
|
+
if (process.env.PROVENANCE_PRIVATE_KEY) return process.env.PROVENANCE_PRIVATE_KEY.trim();
|
|
20
|
+
if (existsSync(KEY_FILE)) return readFileSync(KEY_FILE, 'utf8').trim();
|
|
21
|
+
console.error(`No private key found. Set PROVENANCE_PRIVATE_KEY or run: npx provenance keygen`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseArgs(argv) {
|
|
26
|
+
const args = {};
|
|
27
|
+
for (let i = 0; i < argv.length; i++) {
|
|
28
|
+
if (argv[i].startsWith('--')) {
|
|
29
|
+
args[argv[i].slice(2)] = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[++i] : true;
|
|
30
|
+
} else {
|
|
31
|
+
args._ = args._ || [];
|
|
32
|
+
args._.push(argv[i]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [,, command, ...rest] = process.argv;
|
|
39
|
+
const args = parseArgs(rest);
|
|
40
|
+
|
|
41
|
+
// ── keygen ──────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
if (command === 'keygen') {
|
|
44
|
+
const { publicKey, privateKey } = generateProvenanceKeyPair();
|
|
45
|
+
|
|
46
|
+
writeFileSync(KEY_FILE, privateKey, { mode: 0o600 });
|
|
47
|
+
try { execSync(`grep -qxF "${KEY_FILE}" .gitignore 2>/dev/null || echo "${KEY_FILE}" >> .gitignore`); } catch {}
|
|
48
|
+
|
|
49
|
+
console.log(`\nKeypair generated.\n`);
|
|
50
|
+
console.log(`Private key → ${KEY_FILE} (chmod 600, added to .gitignore)`);
|
|
51
|
+
console.log(`\nPublic key (add to PROVENANCE.yml):\n`);
|
|
52
|
+
console.log(` identity:`);
|
|
53
|
+
console.log(` public_key: "${publicKey}"`);
|
|
54
|
+
console.log(` algorithm: ed25519`);
|
|
55
|
+
console.log(`\nNext: npx provenance register --id provenance:<platform>:<org>/<name>\n`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── register ─────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
if (command === 'register') {
|
|
62
|
+
const id = args.id || args._?.[0];
|
|
63
|
+
if (!id) { console.error('Usage: npx provenance register --id provenance:<platform>:<org>/<name> [options]'); process.exit(1); }
|
|
64
|
+
|
|
65
|
+
const privateKey = readPrivateKey();
|
|
66
|
+
const { publicKey } = (() => {
|
|
67
|
+
// derive public key from private key for display — we just use the stored one
|
|
68
|
+
// We can't derive public from private easily here, so read from PROVENANCE.yml or require --public-key
|
|
69
|
+
return { publicKey: args['public-key'] || null };
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
// If no public key arg, re-generate won't work — need the stored public key
|
|
73
|
+
// Best path: require keygen was run, read public key from PROVENANCE.yml if present
|
|
74
|
+
let pubKey = args['public-key'];
|
|
75
|
+
if (!pubKey) {
|
|
76
|
+
if (existsSync('PROVENANCE.yml')) {
|
|
77
|
+
const yml = readFileSync('PROVENANCE.yml', 'utf8');
|
|
78
|
+
const match = yml.match(/public_key:\s*["']?([A-Za-z0-9+/=]+)["']?/);
|
|
79
|
+
if (match) pubKey = match[1];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!pubKey) {
|
|
83
|
+
console.error('Public key required. Pass --public-key or add identity.public_key to PROVENANCE.yml first.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const signed_challenge = signChallenge(privateKey, id, 'REGISTER');
|
|
88
|
+
|
|
89
|
+
const body = {
|
|
90
|
+
provenance_id: id,
|
|
91
|
+
public_key: pubKey,
|
|
92
|
+
signed_challenge,
|
|
93
|
+
...(args.url ? { url: args.url } : {}),
|
|
94
|
+
...(args.name ? { name: args.name } : {}),
|
|
95
|
+
...(args.description ? { description: args.description } : {}),
|
|
96
|
+
...(args.capabilities ? { capabilities: args.capabilities.split(',').map(s => s.trim()) } : {}),
|
|
97
|
+
...(args.constraints ? { constraints: args.constraints.split(',').map(s => s.trim()) } : {}),
|
|
98
|
+
...(args.model ? { model_provider: args.model } : {}),
|
|
99
|
+
...(args['model-id'] ? { model_id: args['model-id'] } : {}),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(`${API}/register`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json' },
|
|
106
|
+
body: JSON.stringify(body),
|
|
107
|
+
});
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
console.error(`\nRegistration failed: ${data.error}`);
|
|
111
|
+
if (data.hint) console.error(`Hint: ${data.hint}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
console.log(`\n${data.created ? 'Registered' : 'Updated'}: ${id}`);
|
|
115
|
+
console.log(`identity: ${data.agent?.identity}`);
|
|
116
|
+
console.log(`profile: https://getprovenance.dev/agent/${id.replace('provenance:', '').replace(':', '/')}\n`);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error('Network error:', e.message);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── status ───────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
if (command === 'status') {
|
|
127
|
+
const id = args._?.[0] || args.id;
|
|
128
|
+
if (!id) { console.error('Usage: npx provenance status <provenance_id>'); process.exit(1); }
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(`${API}/register?provenance_id=${encodeURIComponent(id)}`);
|
|
132
|
+
const data = await res.json();
|
|
133
|
+
if (!data.registered) { console.log(`\nNot registered: ${id}\n`); process.exit(0); }
|
|
134
|
+
const a = data.agent;
|
|
135
|
+
console.log(`\n${id}`);
|
|
136
|
+
console.log(`name: ${a.name || '—'}`);
|
|
137
|
+
console.log(`identity: ${a.identity}`);
|
|
138
|
+
console.log(`status: ${a.status}`);
|
|
139
|
+
console.log(`profile: https://getprovenance.dev/agent/${id.replace('provenance:', '').replace(':', '/')}\n`);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error('Network error:', e.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── revoke ───────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
if (command === 'revoke') {
|
|
150
|
+
const id = args.id || args._?.[0];
|
|
151
|
+
if (!id) { console.error('Usage: npx provenance revoke --id <provenance_id>'); process.exit(1); }
|
|
152
|
+
|
|
153
|
+
const privateKey = readPrivateKey();
|
|
154
|
+
const signed_challenge = signRevocation(privateKey, id);
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetch(`${API}/revoke`, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
body: JSON.stringify({ provenance_id: id, signed_challenge }),
|
|
161
|
+
});
|
|
162
|
+
const data = await res.json();
|
|
163
|
+
if (!res.ok) { console.error(`Revocation failed: ${data.error}`); process.exit(1); }
|
|
164
|
+
console.log(`\nKey revoked for ${id}.`);
|
|
165
|
+
console.log(`Run npx provenance keygen && npx provenance register --id ${id} to re-register with a new key.\n`);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.error('Network error:', e.message);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── help ─────────────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
console.log(`
|
|
176
|
+
provenance <command>
|
|
177
|
+
|
|
178
|
+
keygen Generate an Ed25519 keypair
|
|
179
|
+
register --id <id> [options] Register or update your agent
|
|
180
|
+
status <id> Check registration status
|
|
181
|
+
revoke --id <id> Revoke your registered key
|
|
182
|
+
|
|
183
|
+
register options:
|
|
184
|
+
--id provenance:<platform>:<org>/<name> (required)
|
|
185
|
+
--url URL to your PROVENANCE.yml (for independent verification)
|
|
186
|
+
--name Agent display name
|
|
187
|
+
--description One-sentence description
|
|
188
|
+
--capabilities read:web,write:summaries
|
|
189
|
+
--constraints no:pii,no:financial:transact
|
|
190
|
+
--model anthropic / openai / etc.
|
|
191
|
+
--model-id claude-sonnet-4-6 / gpt-4o / etc.
|
|
192
|
+
--public-key Base64 public key (auto-read from PROVENANCE.yml if present)
|
|
193
|
+
|
|
194
|
+
Full docs: https://getprovenance.dev/docs
|
|
195
|
+
`);
|
|
196
|
+
process.exit(0);
|
package/src/keygen.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* provenance-protocol — Key generation and signing utilities (TypeScript definitions)
|
|
3
|
+
* Node.js only. Not for browser use.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface KeyPair {
|
|
7
|
+
/** Base64-encoded SPKI DER public key. Put this in PROVENANCE.yml identity.public_key */
|
|
8
|
+
publicKey: string;
|
|
9
|
+
/** Base64-encoded PKCS8 DER private key. Store as PROVENANCE_PRIVATE_KEY env var. Never commit. */
|
|
10
|
+
privateKey: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate a new Ed25519 keypair for Provenance identity.
|
|
15
|
+
* Run once during agent setup.
|
|
16
|
+
*/
|
|
17
|
+
export function generateProvenanceKeyPair(): KeyPair;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sign a challenge nonce from a receiving system.
|
|
21
|
+
* Returns a base64-encoded signature over `${provenanceId}:${nonce}`.
|
|
22
|
+
*
|
|
23
|
+
* @param privateKeyBase64 Your PROVENANCE_PRIVATE_KEY (base64 PKCS8 DER)
|
|
24
|
+
* @param provenanceId Your agent's Provenance ID
|
|
25
|
+
* @param nonce The nonce sent by the receiving system
|
|
26
|
+
*/
|
|
27
|
+
export function signChallenge(privateKeyBase64: string, provenanceId: string, nonce: string): string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sign your PROVENANCE.yml identity claim.
|
|
31
|
+
*
|
|
32
|
+
* Produces the `identity.signature` value for PROVENANCE.yml.
|
|
33
|
+
* Signs `${provenanceId}:${publicKeyBase64}` — binding the keypair to your specific agent ID.
|
|
34
|
+
* Run once during setup, after generateProvenanceKeyPair().
|
|
35
|
+
*
|
|
36
|
+
* @param privateKeyBase64 Your PROVENANCE_PRIVATE_KEY (base64 PKCS8 DER)
|
|
37
|
+
* @param provenanceId Your agent's Provenance ID
|
|
38
|
+
* @param publicKeyBase64 The public key you generated (base64 SPKI DER)
|
|
39
|
+
* @returns Base64 signature — put in PROVENANCE.yml identity.signature
|
|
40
|
+
*/
|
|
41
|
+
export function signForProvenance(privateKeyBase64: string, provenanceId: string, publicKeyBase64: string): string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sign a revocation request — clears your agent's public key and identity_verified status.
|
|
45
|
+
* Use when your private key is compromised or you're rotating keys.
|
|
46
|
+
* Send the result as signed_challenge to POST /api/agents/revoke.
|
|
47
|
+
*/
|
|
48
|
+
export function signRevocation(privateKeyBase64: string, provenanceId: string): string;
|
package/src/keygen.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* provenance-protocol — Key generation and signing utilities
|
|
3
|
+
*
|
|
4
|
+
* Agent operators use this to:
|
|
5
|
+
* 1. Generate an Ed25519 keypair once (setup)
|
|
6
|
+
* 2. Put the public key in PROVENANCE.yml under identity.public_key
|
|
7
|
+
* 3. Keep the private key in their environment (never committed, never shared)
|
|
8
|
+
* 4. Sign challenges from receiving systems at runtime
|
|
9
|
+
*
|
|
10
|
+
* Usage (one-time setup):
|
|
11
|
+
* import { generateProvenanceKeyPair } from 'provenance-protocol/keygen';
|
|
12
|
+
* const { publicKey, privateKey } = generateProvenanceKeyPair();
|
|
13
|
+
* // Add publicKey to your PROVENANCE.yml:
|
|
14
|
+
* // identity:
|
|
15
|
+
* // public_key: "<publicKey>"
|
|
16
|
+
* // Store privateKey as an environment variable: PROVENANCE_PRIVATE_KEY=<privateKey>
|
|
17
|
+
*
|
|
18
|
+
* Usage (runtime — signing challenges):
|
|
19
|
+
* import { signChallenge } from 'provenance-protocol/keygen';
|
|
20
|
+
* const signature = signChallenge(process.env.PROVENANCE_PRIVATE_KEY, provenanceId, nonce);
|
|
21
|
+
* // Return signature to the receiving system
|
|
22
|
+
*
|
|
23
|
+
* Note: This module uses Node.js built-in crypto. It is Node-only (not browser).
|
|
24
|
+
* The verification side (in index.js) uses Web Crypto and works everywhere.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { generateKeyPairSync, sign, createPrivateKey } from 'crypto';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate a new Ed25519 keypair for use with Provenance identity.
|
|
31
|
+
*
|
|
32
|
+
* Run this once during agent setup. Add the public key to PROVENANCE.yml.
|
|
33
|
+
* Store the private key securely as an environment variable.
|
|
34
|
+
*
|
|
35
|
+
* @returns {{ publicKey: string, privateKey: string }}
|
|
36
|
+
* publicKey — base64-encoded SPKI DER. Goes in PROVENANCE.yml identity.public_key
|
|
37
|
+
* privateKey — base64-encoded PKCS8 DER. Store as PROVENANCE_PRIVATE_KEY env var
|
|
38
|
+
*
|
|
39
|
+
* Example:
|
|
40
|
+
* const { publicKey, privateKey } = generateProvenanceKeyPair();
|
|
41
|
+
* console.log('Add to PROVENANCE.yml:');
|
|
42
|
+
* console.log('identity:');
|
|
43
|
+
* console.log(` public_key: "${publicKey}"`);
|
|
44
|
+
* console.log('\nStore as environment variable:');
|
|
45
|
+
* console.log(`PROVENANCE_PRIVATE_KEY=${privateKey}`);
|
|
46
|
+
*/
|
|
47
|
+
export function generateProvenanceKeyPair() {
|
|
48
|
+
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
|
|
49
|
+
publicKeyEncoding: { type: 'spki', format: 'der' },
|
|
50
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'der' },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
publicKey: Buffer.from(publicKey).toString('base64'),
|
|
55
|
+
privateKey: Buffer.from(privateKey).toString('base64'),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sign a challenge from a receiving system.
|
|
61
|
+
*
|
|
62
|
+
* Call this when a receiving system sends you a nonce to prove your identity.
|
|
63
|
+
* The signed message is always `${provenanceId}:${nonce}` — this binds the
|
|
64
|
+
* signature to your specific identity and prevents replay attacks.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} privateKeyBase64 Your PROVENANCE_PRIVATE_KEY (base64 PKCS8 DER)
|
|
67
|
+
* @param {string} provenanceId Your provenance ID, e.g. "provenance:github:alice/agent"
|
|
68
|
+
* @param {string} nonce The nonce sent by the receiving system
|
|
69
|
+
* @returns {string} Base64-encoded signature to return to the receiver
|
|
70
|
+
*
|
|
71
|
+
* Example:
|
|
72
|
+
* app.post('/prove-identity', (req, res) => {
|
|
73
|
+
* const { provenanceId, nonce } = req.body;
|
|
74
|
+
* const signature = signChallenge(
|
|
75
|
+
* process.env.PROVENANCE_PRIVATE_KEY,
|
|
76
|
+
* provenanceId,
|
|
77
|
+
* nonce
|
|
78
|
+
* );
|
|
79
|
+
* res.json({ signature });
|
|
80
|
+
* });
|
|
81
|
+
*/
|
|
82
|
+
export function signChallenge(privateKeyBase64, provenanceId, nonce) {
|
|
83
|
+
const keyBuffer = Buffer.from(privateKeyBase64, 'base64');
|
|
84
|
+
const privateKey = createPrivateKey({ key: keyBuffer, format: 'der', type: 'pkcs8' });
|
|
85
|
+
const message = Buffer.from(`${provenanceId}:${nonce}`, 'utf8');
|
|
86
|
+
return sign(null, message, privateKey).toString('base64');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sign your PROVENANCE.yml identity claim.
|
|
91
|
+
*
|
|
92
|
+
* Call this once after generating your keypair to produce the `identity.signature`
|
|
93
|
+
* value that goes into PROVENANCE.yml. The signature proves you control the private
|
|
94
|
+
* key that matches the public key in the file.
|
|
95
|
+
*
|
|
96
|
+
* The signed message is `${provenanceId}:${publicKeyBase64}` — this binds the key
|
|
97
|
+
* pair to your specific Provenance ID, preventing key reuse across identities.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} privateKeyBase64 Your PROVENANCE_PRIVATE_KEY (base64 PKCS8 DER)
|
|
100
|
+
* @param {string} provenanceId Your agent's Provenance ID, e.g. "provenance:github:alice/agent"
|
|
101
|
+
* @param {string} publicKeyBase64 The public key you're registering (base64 SPKI DER)
|
|
102
|
+
* @returns {string} Base64-encoded signature — put this in identity.signature
|
|
103
|
+
*
|
|
104
|
+
* Example (one-time setup):
|
|
105
|
+
* import { generateProvenanceKeyPair, signForProvenance } from 'provenance-protocol/keygen';
|
|
106
|
+
* const { publicKey, privateKey } = generateProvenanceKeyPair();
|
|
107
|
+
* const id = 'provenance:github:your-org/your-agent';
|
|
108
|
+
* const signature = signForProvenance(privateKey, id, publicKey);
|
|
109
|
+
* console.log('Add to PROVENANCE.yml:');
|
|
110
|
+
* console.log('identity:');
|
|
111
|
+
* console.log(` public_key: "${publicKey}"`);
|
|
112
|
+
* console.log(` signature: "${signature}"`);
|
|
113
|
+
*/
|
|
114
|
+
/**
|
|
115
|
+
* Sign a revocation request.
|
|
116
|
+
*
|
|
117
|
+
* Call this when you want to revoke your agent's cryptographic identity —
|
|
118
|
+
* e.g. if your private key was compromised or you're rotating keys.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} privateKeyBase64 Your current PROVENANCE_PRIVATE_KEY
|
|
121
|
+
* @param {string} provenanceId Your agent's Provenance ID
|
|
122
|
+
* @returns {string} Base64 signature — send as signed_challenge to POST /api/agents/revoke
|
|
123
|
+
*
|
|
124
|
+
* Example:
|
|
125
|
+
* import { signRevocation } from 'provenance-protocol/keygen';
|
|
126
|
+
* const signed_challenge = signRevocation(process.env.PROVENANCE_PRIVATE_KEY, provenanceId);
|
|
127
|
+
* await fetch('https://getprovenance.dev/api/agents/revoke', {
|
|
128
|
+
* method: 'POST',
|
|
129
|
+
* headers: { 'Content-Type': 'application/json' },
|
|
130
|
+
* body: JSON.stringify({ provenance_id: provenanceId, signed_challenge }),
|
|
131
|
+
* });
|
|
132
|
+
*/
|
|
133
|
+
export function signRevocation(privateKeyBase64, provenanceId) {
|
|
134
|
+
return signChallenge(privateKeyBase64, provenanceId, 'REVOKE');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function signForProvenance(privateKeyBase64, provenanceId, publicKeyBase64) {
|
|
138
|
+
const keyBuffer = Buffer.from(privateKeyBase64, 'base64');
|
|
139
|
+
const privateKey = createPrivateKey({ key: keyBuffer, format: 'der', type: 'pkcs8' });
|
|
140
|
+
const message = Buffer.from(`${provenanceId}:${publicKeyBase64}`, 'utf8');
|
|
141
|
+
return sign(null, message, privateKey).toString('base64');
|
|
142
|
+
}
|