ocp-verify 1.0.0 โ 1.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/README.md +46 -1
- package/package.json +3 -3
- package/reference-cli/revoke.js +148 -0
package/README.md
CHANGED
|
@@ -28,6 +28,18 @@ If one byte changes, verification fails โ across any system.
|
|
|
28
28
|
|
|
29
29
|
## ๐ Integrate in 2 Minutes
|
|
30
30
|
|
|
31
|
+
### Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g ocp-verify
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or use without installing:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx ocp-verify myfile.txt myfile.proof.json
|
|
41
|
+
```
|
|
42
|
+
|
|
31
43
|
### 1) Commit (produce a proof)
|
|
32
44
|
|
|
33
45
|
```bash
|
|
@@ -301,6 +313,7 @@ The network only confirms that a commitment exists.
|
|
|
301
313
|
- ๐ค AI Inference Attestation โ `/docs/spec/appendix-ai-inference-attestation.md`
|
|
302
314
|
- ๐งพ Proof Format โ `/docs/spec/proof-format-v1.md`
|
|
303
315
|
- ๐ Examples โ `/examples`
|
|
316
|
+
- โ
Conformance Suite โ `/conformance/run-conformance.sh`
|
|
304
317
|
- โ๏ธ Contracts โ `/contracts`
|
|
305
318
|
- ๐ Live Demo โ https://observation-commitment-protocol.vercel.app/
|
|
306
319
|
|
|
@@ -328,5 +341,37 @@ VALID
|
|
|
328
341
|
v1.0.0 โ Cross-Chain Primitive
|
|
329
342
|
Phase 1 complete โ proof envelope schema
|
|
330
343
|
Phase 2 complete โ EVM reference implementation live
|
|
331
|
-
Phase 3 complete โ Solana
|
|
344
|
+
Phase 3 complete โ Solana devnet live, Gate 3 verified โ same observation committed on EVM and Solana
|
|
345
|
+
Phase 4 complete โ ocp-verify published to npm, zero dependencies
|
|
346
|
+
Phase 5 complete โ conformance suite, 11/11 tests pass
|
|
332
347
|
First external contribution merged โ dinamic.eth / ERC-8004 (PR #1)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Revocation Extension (v1.1.0)
|
|
353
|
+
|
|
354
|
+
OCP now includes an optional revocation layer. Original commitments are never deleted or mutated โ revocation is additive, represented as a new on-chain commitment referencing a prior digest.
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
const { verifyWithRevocation } = require('ocp-verify/reference-cli/revoke.js');
|
|
358
|
+
|
|
359
|
+
const status = await verifyWithRevocation(
|
|
360
|
+
digest,
|
|
361
|
+
asOfTimestamp,
|
|
362
|
+
'eip155:84532'
|
|
363
|
+
);
|
|
364
|
+
// returns VALID | REVOKED | NOT_FOUND
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
- ๐ Revocation Spec โ `/docs/spec/appendix-revocation-r.md`
|
|
368
|
+
- โ๏ธ Deployed Contract โ `0x2fa07c85439850ff6C5688d926bDa6DaEe62Db15` (Base Sepolia)
|
|
369
|
+
- โ
Revocation Conformance โ `/conformance/revocation/run-revocation-conformance.sh`
|
|
370
|
+
- ๐ Event Topic โ `0xc19951599a519bc320c0f352b2f92f315e8a2368bd0efb2e5dca3b1196e76112`
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Status
|
|
375
|
+
|
|
376
|
+
v1.1.0 โ Revocation Extension
|
|
377
|
+
Phase 6 complete โ additive revocation primitive, 8/8 conformance tests pass
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ocp-verify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Zero-dependency verifier for the Observation Commitment Protocol โ independently verify that a file was committed to a public blockchain",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"homepage": "https://github.com/damonzwicker/observation-commitment-protocol",
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|
|
36
|
-
"url": "https://github.com/damonzwicker/observation-commitment-protocol.git"
|
|
36
|
+
"url": "git+https://github.com/damonzwicker/observation-commitment-protocol.git"
|
|
37
37
|
},
|
|
38
38
|
"bugs": {
|
|
39
39
|
"url": "https://github.com/damonzwicker/observation-commitment-protocol/issues"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// revoke.js โ zero-dependency revocation helpers for OCP
|
|
2
|
+
// CommonJS, Node.js >=18
|
|
3
|
+
// Spec: docs/spec/appendix-revocation-r.md
|
|
4
|
+
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
const crypto = require("crypto");
|
|
8
|
+
const https = require("https");
|
|
9
|
+
|
|
10
|
+
// Deployed contract addresses
|
|
11
|
+
const REVOCATION_CONTRACTS = {
|
|
12
|
+
"eip155:84532": "0x2fa07c85439850ff6C5688d926bDa6DaEe62Db15",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// RPC endpoints โ no API key required
|
|
16
|
+
const RPC = {
|
|
17
|
+
"eip155:84532": "https://sepolia.base.org",
|
|
18
|
+
"eip155:8453": "https://mainnet.base.org",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// keccak256("RevocationCommitted(bytes32,bytes32,address,uint256)")
|
|
22
|
+
const REVOCATION_EVENT_TOPIC = "0xc19951599a519bc320c0f352b2f92f315e8a2368bd0efb2e5dca3b1196e76112";
|
|
23
|
+
|
|
24
|
+
// getRevocation(bytes32) selector
|
|
25
|
+
const GET_REVOCATION_SELECTOR = "0x8d4b2a4d";
|
|
26
|
+
|
|
27
|
+
function normalizeHexDigest(value, fieldName) {
|
|
28
|
+
if (typeof value !== "string") {
|
|
29
|
+
throw new TypeError(`${fieldName} must be a string`);
|
|
30
|
+
}
|
|
31
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {
|
|
32
|
+
throw new Error(`${fieldName} must be a 0x-prefixed 32-byte hex digest`);
|
|
33
|
+
}
|
|
34
|
+
return value.toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stableJson(value) {
|
|
38
|
+
if (value === null || typeof value !== "object") {
|
|
39
|
+
return JSON.stringify(value);
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
43
|
+
}
|
|
44
|
+
const keys = Object.keys(value).sort();
|
|
45
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function rpcCall(url, method, params) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id: 1, method, params });
|
|
51
|
+
const urlObj = new URL(url);
|
|
52
|
+
const options = {
|
|
53
|
+
hostname: urlObj.hostname,
|
|
54
|
+
path: urlObj.pathname,
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"Content-Length": Buffer.byteLength(body),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const req = https.request(options, (res) => {
|
|
62
|
+
let data = "";
|
|
63
|
+
res.on("data", (chunk) => { data += chunk; });
|
|
64
|
+
res.on("end", () => {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(data);
|
|
67
|
+
if (parsed.error) reject(new Error(`RPC error: ${parsed.error.message}`));
|
|
68
|
+
else resolve(parsed.result);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
reject(new Error(`Failed to parse RPC response: ${data}`));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
req.on("error", reject);
|
|
75
|
+
req.write(body);
|
|
76
|
+
req.end();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function decodeRevocationRecord(hex) {
|
|
81
|
+
const data = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
82
|
+
const revocationDigest = "0x" + data.slice(0, 64);
|
|
83
|
+
const revoker = "0x" + data.slice(64, 128).slice(24);
|
|
84
|
+
const timestamp = parseInt(data.slice(128, 192), 16);
|
|
85
|
+
const exists = data.slice(192, 256) !== "0".repeat(64);
|
|
86
|
+
return { revocationDigest, revoker, timestamp, exists };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function buildRevocationDigest(originalDigest, metadata = {}) {
|
|
90
|
+
const normalizedOriginalDigest = normalizeHexDigest(originalDigest, "originalDigest");
|
|
91
|
+
const payload = { originalDigest: normalizedOriginalDigest, ...metadata };
|
|
92
|
+
const encoded = stableJson(payload);
|
|
93
|
+
return `0x${crypto.createHash("sha256").update(encoded).digest("hex")}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function checkRevocationStatus(originalDigest, chainId, rpcUrl, contractAddress) {
|
|
97
|
+
normalizeHexDigest(originalDigest, "originalDigest");
|
|
98
|
+
|
|
99
|
+
const resolvedRpc = rpcUrl || RPC[chainId];
|
|
100
|
+
const resolvedContract = contractAddress || REVOCATION_CONTRACTS[chainId];
|
|
101
|
+
|
|
102
|
+
if (!resolvedRpc) throw new Error(`no RPC endpoint for chain ${chainId} โ pass rpcUrl explicitly`);
|
|
103
|
+
if (!resolvedContract) throw new Error(`no contract address for chain ${chainId} โ pass contractAddress explicitly`);
|
|
104
|
+
|
|
105
|
+
const digestHex = originalDigest.slice(2).toLowerCase();
|
|
106
|
+
const calldata = GET_REVOCATION_SELECTOR + digestHex;
|
|
107
|
+
|
|
108
|
+
let result;
|
|
109
|
+
try {
|
|
110
|
+
result = await rpcCall(resolvedRpc, "eth_call", [
|
|
111
|
+
{ to: resolvedContract, data: calldata },
|
|
112
|
+
"latest",
|
|
113
|
+
]);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
throw new Error(`RPC call failed: ${e.message}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!result || result === "0x") return { status: "NOT_FOUND", record: null };
|
|
119
|
+
|
|
120
|
+
const record = decodeRevocationRecord(result);
|
|
121
|
+
if (!record.exists) return { status: "NOT_FOUND", record: null };
|
|
122
|
+
|
|
123
|
+
return { status: "REVOKED", record };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function verifyWithRevocation(originalDigest, asOfTimestamp, chainId, rpcUrl, contractAddress) {
|
|
127
|
+
normalizeHexDigest(originalDigest, "originalDigest");
|
|
128
|
+
|
|
129
|
+
if (typeof asOfTimestamp !== "number") {
|
|
130
|
+
throw new TypeError("asOfTimestamp must be a number (Unix seconds)");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { status, record } = await checkRevocationStatus(
|
|
134
|
+
originalDigest, chainId, rpcUrl, contractAddress
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (status === "NOT_FOUND") return "NOT_FOUND";
|
|
138
|
+
if (record.timestamp <= asOfTimestamp) return "REVOKED";
|
|
139
|
+
return "VALID";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
buildRevocationDigest,
|
|
144
|
+
checkRevocationStatus,
|
|
145
|
+
verifyWithRevocation,
|
|
146
|
+
stableJson,
|
|
147
|
+
REVOCATION_EVENT_TOPIC,
|
|
148
|
+
};
|