@jterrazz/attestation 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/dist/cli.js ADDED
@@ -0,0 +1,285 @@
1
+ import { d as canonicalize, i as stringify, o as buildAttestationMessage, r as parse, t as verifyAttestation } from "./verify.js";
2
+ import { t as audit } from "./audit.js";
3
+ import { i as upgradeProof, n as verifyOts, r as stampDigest, t as signViaBrowser } from "./sign-flow.js";
4
+ import { parseArgs } from "node:util";
5
+ import { readFile, writeFile } from "node:fs/promises";
6
+ import { basename, dirname, extname, join } from "node:path";
7
+ //#region src/cli/io.ts
8
+ const supportsColor = process.stdout.isTTY === true && !process.env.NO_COLOR;
9
+ const fmt = {
10
+ bold: (s) => supportsColor ? `\x1b[1m${s}\x1b[0m` : s,
11
+ dim: (s) => supportsColor ? `\x1b[2m${s}\x1b[0m` : s,
12
+ fail: (s) => supportsColor ? `\x1b[31m${s}\x1b[0m` : s,
13
+ info: (s) => supportsColor ? `\x1b[36m${s}\x1b[0m` : s,
14
+ ok: (s) => supportsColor ? `\x1b[32m${s}\x1b[0m` : s,
15
+ warn: (s) => supportsColor ? `\x1b[33m${s}\x1b[0m` : s
16
+ };
17
+ function checkmark() {
18
+ return fmt.ok("✓");
19
+ }
20
+ function crossmark() {
21
+ return fmt.fail("✗");
22
+ }
23
+ //#endregion
24
+ //#region src/cli/sign.ts
25
+ async function runSign(args) {
26
+ const content = await readFile(args.file, "utf8");
27
+ const locale = args.locale ?? deriveLocale(args.file);
28
+ const publishedAt = args.publishedAt ? new Date(args.publishedAt) : /* @__PURE__ */ new Date();
29
+ const canonical = canonicalize(content);
30
+ process.stdout.write(`${checkmark()} Canonicalized ${canonical.length} bytes (${locale})\n`);
31
+ if (!args.skipAudit) {
32
+ const findings = audit(canonical);
33
+ if (findings.length > 0) {
34
+ process.stdout.write(`${fmt.warn("!")} ${findings.length} suspicious char(s) found:\n`);
35
+ for (const f of findings) process.stdout.write(` line ${f.line}, col ${f.column}: U+${f.codepoint.toString(16).toUpperCase().padStart(4, "0")} (${f.name})\n`);
36
+ process.stdout.write(` Pass --skip-audit to sign anyway.\n`);
37
+ throw new Error("Audit failed: clean up suspicious characters first.");
38
+ }
39
+ }
40
+ const message = buildAttestationMessage({
41
+ content,
42
+ locale,
43
+ priorAttestation: args.priorAttestation,
44
+ publishedAt,
45
+ revision: args.revision,
46
+ slug: args.slug,
47
+ title: args.title
48
+ });
49
+ process.stdout.write(`${fmt.info("→")} Content digest: ${message.subject.contentDigest}\n`);
50
+ process.stdout.write(`${fmt.info("→")} Opening browser to sign with your wallet…\n`);
51
+ const { signature, signerAddress } = await signViaBrowser({ message });
52
+ const signed = {
53
+ ...message,
54
+ signature,
55
+ signerAddress
56
+ };
57
+ process.stdout.write(`${checkmark()} Signed by ${fmt.bold(signerAddress)}\n`);
58
+ const baseName = basename(args.file, extname(args.file));
59
+ const dir = dirname(args.file);
60
+ const attestationPath = join(dir, `${baseName}.attestation.json`);
61
+ await writeFile(attestationPath, stringify(signed), "utf8");
62
+ process.stdout.write(`${checkmark()} Wrote ${attestationPath}\n`);
63
+ if (!args.skipStamp) {
64
+ process.stdout.write(`${fmt.info("→")} Submitting to OpenTimestamps calendars…\n`);
65
+ const otsBytes = await stampDigest(hexToBytes$1(message.subject.contentDigest));
66
+ const otsPath = join(dir, `${baseName}.ots`);
67
+ await writeFile(otsPath, Buffer.from(otsBytes));
68
+ process.stdout.write(`${checkmark()} Wrote ${otsPath} (${otsBytes.length} bytes, calendar-only)\n`);
69
+ process.stdout.write(` ${fmt.dim("Run `attestation upgrade` in ~24h to anchor in Bitcoin.")}\n`);
70
+ }
71
+ }
72
+ function deriveLocale(file) {
73
+ const name = basename(file, extname(file));
74
+ if (/^[a-z]{2}$/.test(name)) return name;
75
+ throw new Error(`Cannot derive locale from filename "${basename(file)}". Pass --locale explicitly.`);
76
+ }
77
+ function hexToBytes$1(hex) {
78
+ const stripped = hex.slice(2);
79
+ const out = new Uint8Array(stripped.length / 2);
80
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);
81
+ return out;
82
+ }
83
+ //#endregion
84
+ //#region src/cli/upgrade.ts
85
+ async function runUpgrade(args) {
86
+ if (args.files.length === 0) throw new Error("No .ots files provided.");
87
+ for (const path of args.files) {
88
+ process.stdout.write(`${fmt.info("→")} ${path}\n`);
89
+ const before = await readFile(path);
90
+ const { bytes, upgraded } = await upgradeProof(new Uint8Array(before));
91
+ if (upgraded) {
92
+ await writeFile(path, Buffer.from(bytes));
93
+ process.stdout.write(` ${checkmark()} Upgraded to Bitcoin attestation (${bytes.length} bytes)\n`);
94
+ } else process.stdout.write(` ${fmt.dim("· No new attestation available yet, leaving file untouched.")}\n`);
95
+ }
96
+ }
97
+ //#endregion
98
+ //#region src/cli/verify.ts
99
+ async function runVerify(args) {
100
+ const sources = await loadSources(args);
101
+ const attestation = parse(sources.attestationJson);
102
+ const sigResult = await verifyAttestation({
103
+ attestation,
104
+ content: sources.content
105
+ });
106
+ if (!sigResult.ok) {
107
+ printSignatureFailure(sigResult.error);
108
+ return false;
109
+ }
110
+ process.stdout.write(`${checkmark()} Content digest matches signature\n`);
111
+ process.stdout.write(`${checkmark()} Signature valid — signed by ${fmt.bold(sigResult.signerAddress)}\n`);
112
+ if (args.skipOts || sources.otsBytes === null) {
113
+ process.stdout.write(`${fmt.dim("· OTS verification skipped")}\n`);
114
+ return true;
115
+ }
116
+ const otsResult = await verifyOts(hexToBytes(attestation.subject.contentDigest), sources.otsBytes);
117
+ if (!otsResult.ok) {
118
+ process.stdout.write(`${crossmark()} OTS: ${otsResult.reason}`);
119
+ if (otsResult.details) process.stdout.write(` — ${otsResult.details}`);
120
+ process.stdout.write(`\n`);
121
+ return otsResult.reason === "pending-bitcoin";
122
+ }
123
+ process.stdout.write(`${checkmark()} Bitcoin timestamp confirmed at ${otsResult.bitcoinBlockTime.toISOString()}\n`);
124
+ return true;
125
+ }
126
+ async function loadSources(args) {
127
+ if (/^https?:\/\//.test(args.target)) return loadFromUrl(args.target);
128
+ return loadFromFile(args.target);
129
+ }
130
+ async function loadFromFile(filePath) {
131
+ const content = await readFile(filePath, "utf8");
132
+ const dir = dirname(filePath);
133
+ const base = basename(filePath, extname(filePath));
134
+ const attestationJson = await readFile(join(dir, `${base}.attestation.json`), "utf8");
135
+ let otsBytes = null;
136
+ try {
137
+ const buf = await readFile(join(dir, `${base}.ots`));
138
+ otsBytes = new Uint8Array(buf);
139
+ } catch {
140
+ otsBytes = null;
141
+ }
142
+ return {
143
+ attestationJson,
144
+ content,
145
+ otsBytes
146
+ };
147
+ }
148
+ async function loadFromUrl(url) {
149
+ const proofManifestUrl = await resolveManifestUrl(url);
150
+ const manifest = await fetchJson(proofManifestUrl);
151
+ const base = new URL(proofManifestUrl);
152
+ const [content, attestationJson, otsBuf] = await Promise.all([
153
+ fetchText(new URL(manifest.content, base).toString()),
154
+ fetchText(new URL(manifest.attestation, base).toString()),
155
+ fetchBytes(new URL(manifest.ots, base).toString()).catch(() => null)
156
+ ]);
157
+ return {
158
+ attestationJson,
159
+ content,
160
+ otsBytes: otsBuf
161
+ };
162
+ }
163
+ async function resolveManifestUrl(articleUrl) {
164
+ const url = new URL(articleUrl);
165
+ if (!url.pathname.endsWith("/")) url.pathname += "/";
166
+ url.pathname += "proof.json";
167
+ return url.toString();
168
+ }
169
+ async function fetchJson(url) {
170
+ const res = await fetch(url);
171
+ if (!res.ok) throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);
172
+ return await res.json();
173
+ }
174
+ async function fetchText(url) {
175
+ const res = await fetch(url);
176
+ if (!res.ok) throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);
177
+ return res.text();
178
+ }
179
+ async function fetchBytes(url) {
180
+ const res = await fetch(url);
181
+ if (!res.ok) throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);
182
+ return new Uint8Array(await res.arrayBuffer());
183
+ }
184
+ function printSignatureFailure(error) {
185
+ process.stdout.write(`${crossmark()} ${fmt.fail("Verification failed")}: ${error.kind}\n`);
186
+ for (const [k, v] of Object.entries(error)) {
187
+ if (k === "kind") continue;
188
+ process.stdout.write(` ${k}: ${String(v)}\n`);
189
+ }
190
+ }
191
+ function hexToBytes(hex) {
192
+ const stripped = hex.slice(2);
193
+ const out = new Uint8Array(stripped.length / 2);
194
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);
195
+ return out;
196
+ }
197
+ //#endregion
198
+ //#region src/cli/index.ts
199
+ const HELP = `${fmt.bold("attestation")} — sign articles with EIP-712 + Bitcoin (OpenTimestamps).
200
+
201
+ Usage:
202
+ attestation sign <file> --title <t> --slug <s> [--published-at <iso>]
203
+ attestation verify <url|file> [--skip-ots]
204
+ attestation upgrade <ots-file> [<ots-file> …]
205
+
206
+ Run \`attestation <command> --help\` for command-specific options.
207
+ `;
208
+ async function runCli(argv) {
209
+ const [command, ...rest] = argv;
210
+ if (command === void 0 || command === "-h" || command === "--help" || command === "help") {
211
+ process.stdout.write(HELP);
212
+ return 0;
213
+ }
214
+ try {
215
+ if (command === "sign") return await dispatchSign(rest);
216
+ if (command === "verify") return await dispatchVerify(rest);
217
+ if (command === "upgrade") return await dispatchUpgrade(rest);
218
+ process.stderr.write(`Unknown command: ${command}\n${HELP}`);
219
+ return 2;
220
+ } catch (error) {
221
+ process.stderr.write(`${fmt.fail("Error:")} ${error.message}\n`);
222
+ return 1;
223
+ }
224
+ }
225
+ async function dispatchSign(argv) {
226
+ const { positionals, values } = parseArgs({
227
+ allowPositionals: true,
228
+ args: argv,
229
+ options: {
230
+ locale: { type: "string" },
231
+ "prior-attestation": { type: "string" },
232
+ "published-at": { type: "string" },
233
+ revision: { type: "string" },
234
+ "skip-audit": { type: "boolean" },
235
+ "skip-stamp": { type: "boolean" },
236
+ slug: { type: "string" },
237
+ title: { type: "string" }
238
+ }
239
+ });
240
+ const file = positionals[0];
241
+ if (!file) throw new Error("Missing required <file> argument.");
242
+ if (!values.title) throw new Error("Missing required --title.");
243
+ if (!values.slug) throw new Error("Missing required --slug.");
244
+ await runSign({
245
+ file,
246
+ locale: values.locale,
247
+ priorAttestation: values["prior-attestation"],
248
+ publishedAt: values["published-at"],
249
+ revision: values.revision ? Number(values.revision) : void 0,
250
+ skipAudit: values["skip-audit"],
251
+ skipStamp: values["skip-stamp"],
252
+ slug: values.slug,
253
+ title: values.title
254
+ });
255
+ return 0;
256
+ }
257
+ async function dispatchVerify(argv) {
258
+ const { positionals, values } = parseArgs({
259
+ allowPositionals: true,
260
+ args: argv,
261
+ options: { "skip-ots": { type: "boolean" } }
262
+ });
263
+ const target = positionals[0];
264
+ if (!target) throw new Error("Missing required <url|file> argument.");
265
+ return await runVerify({
266
+ skipOts: values["skip-ots"],
267
+ target
268
+ }) ? 0 : 1;
269
+ }
270
+ async function dispatchUpgrade(argv) {
271
+ const { positionals } = parseArgs({
272
+ allowPositionals: true,
273
+ args: argv,
274
+ options: {}
275
+ });
276
+ await runUpgrade({ files: positionals });
277
+ return 0;
278
+ }
279
+ //#endregion
280
+ //#region src/cli.ts
281
+ runCli(process.argv.slice(2)).then((code) => process.exit(code));
282
+ //#endregion
283
+ export {};
284
+
285
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":["hexToBytes"],"sources":["../src/cli/io.ts","../src/cli/sign.ts","../src/cli/upgrade.ts","../src/cli/verify.ts","../src/cli/index.ts","../src/cli.ts"],"sourcesContent":["/* ANSI helpers — kept tiny so we don't pull in chalk. */\n\nconst supportsColor = process.stdout.isTTY === true && !process.env.NO_COLOR;\n\nexport const fmt = {\n bold: (s: string) => (supportsColor ? `\\x1b[1m${s}\\x1b[0m` : s),\n dim: (s: string) => (supportsColor ? `\\x1b[2m${s}\\x1b[0m` : s),\n fail: (s: string) => (supportsColor ? `\\x1b[31m${s}\\x1b[0m` : s),\n info: (s: string) => (supportsColor ? `\\x1b[36m${s}\\x1b[0m` : s),\n ok: (s: string) => (supportsColor ? `\\x1b[32m${s}\\x1b[0m` : s),\n warn: (s: string) => (supportsColor ? `\\x1b[33m${s}\\x1b[0m` : s),\n};\n\nexport function checkmark(): string {\n return fmt.ok('✓');\n}\n\nexport function crossmark(): string {\n return fmt.fail('✗');\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, extname, join } from 'node:path';\n\nimport { buildAttestationMessage } from '../attestation/create.js';\nimport { stringify } from '../attestation/serialize.js';\nimport { type SignedAttestation } from '../attestation/types.js';\nimport { audit } from '../core/audit.js';\nimport { canonicalize } from '../core/canonicalize.js';\nimport { signViaBrowser } from '../eth/sign-flow.js';\nimport { stampDigest } from '../ots/stamp.js';\nimport { checkmark, fmt } from './io.js';\n\nexport type SignArgs = {\n file: string;\n title: string;\n slug: string;\n locale?: string;\n publishedAt?: string;\n revision?: number;\n priorAttestation?: `0x${string}`;\n skipStamp?: boolean;\n skipAudit?: boolean;\n};\n\nexport async function runSign(args: SignArgs): Promise<void> {\n const content = await readFile(args.file, 'utf8');\n const locale = args.locale ?? deriveLocale(args.file);\n const publishedAt = args.publishedAt ? new Date(args.publishedAt) : new Date();\n\n const canonical = canonicalize(content);\n process.stdout.write(`${checkmark()} Canonicalized ${canonical.length} bytes (${locale})\\n`);\n\n if (!args.skipAudit) {\n const findings = audit(canonical);\n if (findings.length > 0) {\n process.stdout.write(`${fmt.warn('!')} ${findings.length} suspicious char(s) found:\\n`);\n for (const f of findings) {\n process.stdout.write(\n ` line ${f.line}, col ${f.column}: U+${f.codepoint\n .toString(16)\n .toUpperCase()\n .padStart(4, '0')} (${f.name})\\n`,\n );\n }\n process.stdout.write(` Pass --skip-audit to sign anyway.\\n`);\n throw new Error('Audit failed: clean up suspicious characters first.');\n }\n }\n\n const message = buildAttestationMessage({\n content,\n locale,\n priorAttestation: args.priorAttestation,\n publishedAt,\n revision: args.revision,\n slug: args.slug,\n title: args.title,\n });\n\n process.stdout.write(`${fmt.info('→')} Content digest: ${message.subject.contentDigest}\\n`);\n process.stdout.write(`${fmt.info('→')} Opening browser to sign with your wallet…\\n`);\n\n const { signature, signerAddress } = await signViaBrowser({ message });\n const signed: SignedAttestation = { ...message, signature, signerAddress };\n\n process.stdout.write(`${checkmark()} Signed by ${fmt.bold(signerAddress)}\\n`);\n\n const baseName = basename(args.file, extname(args.file));\n const dir = dirname(args.file);\n const attestationPath = join(dir, `${baseName}.attestation.json`);\n await writeFile(attestationPath, stringify(signed), 'utf8');\n process.stdout.write(`${checkmark()} Wrote ${attestationPath}\\n`);\n\n if (!args.skipStamp) {\n process.stdout.write(`${fmt.info('→')} Submitting to OpenTimestamps calendars…\\n`);\n const digest = hexToBytes(message.subject.contentDigest);\n const otsBytes = await stampDigest(digest);\n const otsPath = join(dir, `${baseName}.ots`);\n await writeFile(otsPath, Buffer.from(otsBytes));\n process.stdout.write(\n `${checkmark()} Wrote ${otsPath} (${otsBytes.length} bytes, calendar-only)\\n`,\n );\n process.stdout.write(\n ` ${fmt.dim('Run `attestation upgrade` in ~24h to anchor in Bitcoin.')}\\n`,\n );\n }\n}\n\nfunction deriveLocale(file: string): string {\n const name = basename(file, extname(file));\n if (/^[a-z]{2}$/.test(name)) {\n return name;\n }\n throw new Error(\n `Cannot derive locale from filename \"${basename(file)}\". Pass --locale explicitly.`,\n );\n}\n\nfunction hexToBytes(hex: `0x${string}`): Uint8Array {\n const stripped = hex.slice(2);\n const out = new Uint8Array(stripped.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\n\nimport { upgradeProof } from '../ots/stamp.js';\nimport { checkmark, fmt } from './io.js';\n\nexport type UpgradeArgs = {\n files: string[];\n};\n\nexport async function runUpgrade(args: UpgradeArgs): Promise<void> {\n if (args.files.length === 0) {\n throw new Error('No .ots files provided.');\n }\n\n for (const path of args.files) {\n process.stdout.write(`${fmt.info('→')} ${path}\\n`);\n const before = await readFile(path);\n const { bytes, upgraded } = await upgradeProof(new Uint8Array(before));\n\n if (upgraded) {\n await writeFile(path, Buffer.from(bytes));\n process.stdout.write(\n ` ${checkmark()} Upgraded to Bitcoin attestation (${bytes.length} bytes)\\n`,\n );\n } else {\n process.stdout.write(\n ` ${fmt.dim('· No new attestation available yet, leaving file untouched.')}\\n`,\n );\n }\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { basename, dirname, extname, join } from 'node:path';\n\nimport { parse } from '../attestation/serialize.js';\nimport { verifyAttestation } from '../attestation/verify.js';\nimport { verifyOts } from '../ots/verify.js';\nimport { checkmark, crossmark, fmt } from './io.js';\n\nexport type VerifyArgs = {\n /** A URL to an article, or a path to a local markdown file. */\n target: string;\n /** Skip OTS Bitcoin verification (signature-only). */\n skipOts?: boolean;\n};\n\ntype Sources = {\n content: string;\n attestationJson: string;\n otsBytes: null | Uint8Array;\n};\n\nexport async function runVerify(args: VerifyArgs): Promise<boolean> {\n const sources = await loadSources(args);\n\n const attestation = parse(sources.attestationJson);\n const sigResult = await verifyAttestation({ attestation, content: sources.content });\n\n if (!sigResult.ok) {\n printSignatureFailure(sigResult.error);\n return false;\n }\n\n process.stdout.write(`${checkmark()} Content digest matches signature\\n`);\n process.stdout.write(\n `${checkmark()} Signature valid — signed by ${fmt.bold(sigResult.signerAddress)}\\n`,\n );\n\n if (args.skipOts || sources.otsBytes === null) {\n process.stdout.write(`${fmt.dim('· OTS verification skipped')}\\n`);\n return true;\n }\n\n const digest = hexToBytes(attestation.subject.contentDigest);\n const otsResult = await verifyOts(digest, sources.otsBytes);\n\n if (!otsResult.ok) {\n process.stdout.write(`${crossmark()} OTS: ${otsResult.reason}`);\n if (otsResult.details) {\n process.stdout.write(` — ${otsResult.details}`);\n }\n process.stdout.write(`\\n`);\n return otsResult.reason === 'pending-bitcoin'; // Pending is not a hard fail\n }\n\n process.stdout.write(\n `${checkmark()} Bitcoin timestamp confirmed at ${otsResult.bitcoinBlockTime.toISOString()}\\n`,\n );\n return true;\n}\n\nasync function loadSources(args: VerifyArgs): Promise<Sources> {\n if (/^https?:\\/\\//.test(args.target)) {\n return loadFromUrl(args.target);\n }\n return loadFromFile(args.target);\n}\n\nasync function loadFromFile(filePath: string): Promise<Sources> {\n const content = await readFile(filePath, 'utf8');\n const dir = dirname(filePath);\n const base = basename(filePath, extname(filePath));\n const attestationJson = await readFile(join(dir, `${base}.attestation.json`), 'utf8');\n let otsBytes: null | Uint8Array = null;\n try {\n const buf = await readFile(join(dir, `${base}.ots`));\n otsBytes = new Uint8Array(buf);\n } catch {\n otsBytes = null;\n }\n return { attestationJson, content, otsBytes };\n}\n\nasync function loadFromUrl(url: string): Promise<Sources> {\n const proofManifestUrl = await resolveManifestUrl(url);\n const manifest = await fetchJson<ProofManifest>(proofManifestUrl);\n\n const base = new URL(proofManifestUrl);\n const [content, attestationJson, otsBuf] = await Promise.all([\n fetchText(new URL(manifest.content, base).toString()),\n fetchText(new URL(manifest.attestation, base).toString()),\n fetchBytes(new URL(manifest.ots, base).toString()).catch(() => null),\n ]);\n return { attestationJson, content, otsBytes: otsBuf };\n}\n\ntype ProofManifest = {\n schemaVersion: number;\n slug: string;\n content: string;\n attestation: string;\n ots: string;\n};\n\nasync function resolveManifestUrl(articleUrl: string): Promise<string> {\n // Convention: append /proof.json to the article URL (trailing slash tolerated).\n const url = new URL(articleUrl);\n if (!url.pathname.endsWith('/')) {\n url.pathname += '/';\n }\n url.pathname += 'proof.json';\n return url.toString();\n}\n\nasync function fetchJson<T>(url: string): Promise<T> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);\n }\n return (await res.json()) as T;\n}\n\nasync function fetchText(url: string): Promise<string> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);\n }\n return res.text();\n}\n\nasync function fetchBytes(url: string): Promise<Uint8Array> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);\n }\n return new Uint8Array(await res.arrayBuffer());\n}\n\nfunction printSignatureFailure(error: Record<string, unknown> & { kind: string }): void {\n process.stdout.write(`${crossmark()} ${fmt.fail('Verification failed')}: ${error.kind}\\n`);\n for (const [k, v] of Object.entries(error)) {\n if (k === 'kind') {\n continue;\n }\n process.stdout.write(` ${k}: ${String(v)}\\n`);\n }\n}\n\nfunction hexToBytes(hex: `0x${string}`): Uint8Array {\n const stripped = hex.slice(2);\n const out = new Uint8Array(stripped.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { parseArgs } from 'node:util';\n\nimport { fmt } from './io.js';\nimport { runSign } from './sign.js';\nimport { runUpgrade } from './upgrade.js';\nimport { runVerify } from './verify.js';\n\nconst HELP = `${fmt.bold('attestation')} — sign articles with EIP-712 + Bitcoin (OpenTimestamps).\n\nUsage:\n attestation sign <file> --title <t> --slug <s> [--published-at <iso>]\n attestation verify <url|file> [--skip-ots]\n attestation upgrade <ots-file> [<ots-file> …]\n\nRun \\`attestation <command> --help\\` for command-specific options.\n`;\n\nexport async function runCli(argv: string[]): Promise<number> {\n const [command, ...rest] = argv;\n\n if (command === undefined || command === '-h' || command === '--help' || command === 'help') {\n process.stdout.write(HELP);\n return 0;\n }\n\n try {\n if (command === 'sign') {\n return await dispatchSign(rest);\n }\n if (command === 'verify') {\n return await dispatchVerify(rest);\n }\n if (command === 'upgrade') {\n return await dispatchUpgrade(rest);\n }\n process.stderr.write(`Unknown command: ${command}\\n${HELP}`);\n return 2;\n } catch (error) {\n process.stderr.write(`${fmt.fail('Error:')} ${(error as Error).message}\\n`);\n return 1;\n }\n}\n\nasync function dispatchSign(argv: string[]): Promise<number> {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n args: argv,\n options: {\n locale: { type: 'string' },\n 'prior-attestation': { type: 'string' },\n 'published-at': { type: 'string' },\n revision: { type: 'string' },\n 'skip-audit': { type: 'boolean' },\n 'skip-stamp': { type: 'boolean' },\n slug: { type: 'string' },\n title: { type: 'string' },\n },\n });\n\n const file = positionals[0];\n if (!file) {\n throw new Error('Missing required <file> argument.');\n }\n if (!values.title) {\n throw new Error('Missing required --title.');\n }\n if (!values.slug) {\n throw new Error('Missing required --slug.');\n }\n\n await runSign({\n file,\n locale: values.locale,\n priorAttestation: values['prior-attestation'] as `0x${string}` | undefined,\n publishedAt: values['published-at'],\n revision: values.revision ? Number(values.revision) : undefined,\n skipAudit: values['skip-audit'],\n skipStamp: values['skip-stamp'],\n slug: values.slug,\n title: values.title,\n });\n return 0;\n}\n\nasync function dispatchVerify(argv: string[]): Promise<number> {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n args: argv,\n options: {\n 'skip-ots': { type: 'boolean' },\n },\n });\n\n const target = positionals[0];\n if (!target) {\n throw new Error('Missing required <url|file> argument.');\n }\n\n const ok = await runVerify({ skipOts: values['skip-ots'], target });\n return ok ? 0 : 1;\n}\n\nasync function dispatchUpgrade(argv: string[]): Promise<number> {\n const { positionals } = parseArgs({ allowPositionals: true, args: argv, options: {} });\n await runUpgrade({ files: positionals });\n return 0;\n}\n","import { runCli } from './cli/index.js';\n\nvoid runCli(process.argv.slice(2)).then((code) => process.exit(code));\n"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ //#region src/core/eip712-schema.ts
2
+ /**
3
+ * EIP-712 typed-data schema for attestation v1 — FROZEN.
4
+ *
5
+ * Once a single attestation is published, every byte of this file is part of an
6
+ * immutable contract. Modifying any value below changes the typed-data digest
7
+ * and invalidates every previously signed attestation.
8
+ *
9
+ * To evolve: introduce v2 alongside (`eip712-schema-v2.ts`), bump SCHEMA_VERSION,
10
+ * keep v1 verifier alive forever, dispatch by attestation.schemaVersion.
11
+ */
12
+ const ATTESTATION_DOMAIN_V1 = {
13
+ chainId: 1,
14
+ name: "jterrazz.com Article Attestation",
15
+ version: "1"
16
+ };
17
+ const ATTESTATION_TYPES_V1 = {
18
+ Attestation: [
19
+ {
20
+ name: "schemaVersion",
21
+ type: "uint16"
22
+ },
23
+ {
24
+ name: "subject",
25
+ type: "ArticleSubject"
26
+ },
27
+ {
28
+ name: "claims",
29
+ type: "ArticleClaims"
30
+ }
31
+ ],
32
+ ArticleClaims: [
33
+ {
34
+ name: "slug",
35
+ type: "string"
36
+ },
37
+ {
38
+ name: "publishedAt",
39
+ type: "uint64"
40
+ },
41
+ {
42
+ name: "revision",
43
+ type: "uint16"
44
+ },
45
+ {
46
+ name: "priorAttestation",
47
+ type: "bytes32"
48
+ }
49
+ ],
50
+ ArticleSubject: [
51
+ {
52
+ name: "title",
53
+ type: "string"
54
+ },
55
+ {
56
+ name: "contentDigest",
57
+ type: "bytes32"
58
+ },
59
+ {
60
+ name: "locale",
61
+ type: "string"
62
+ }
63
+ ]
64
+ };
65
+ const ATTESTATION_PRIMARY_TYPE = "Attestation";
66
+ /**
67
+ * Sentinel value for `priorAttestation` when this is the first revision.
68
+ */
69
+ const NO_PRIOR_ATTESTATION = "0x0000000000000000000000000000000000000000000000000000000000000000";
70
+ //#endregion
71
+ Object.defineProperty(exports, "ATTESTATION_DOMAIN_V1", {
72
+ enumerable: true,
73
+ get: function() {
74
+ return ATTESTATION_DOMAIN_V1;
75
+ }
76
+ });
77
+ Object.defineProperty(exports, "ATTESTATION_PRIMARY_TYPE", {
78
+ enumerable: true,
79
+ get: function() {
80
+ return ATTESTATION_PRIMARY_TYPE;
81
+ }
82
+ });
83
+ Object.defineProperty(exports, "ATTESTATION_TYPES_V1", {
84
+ enumerable: true,
85
+ get: function() {
86
+ return ATTESTATION_TYPES_V1;
87
+ }
88
+ });
89
+ Object.defineProperty(exports, "NO_PRIOR_ATTESTATION", {
90
+ enumerable: true,
91
+ get: function() {
92
+ return NO_PRIOR_ATTESTATION;
93
+ }
94
+ });
95
+
96
+ //# sourceMappingURL=eip712-schema.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip712-schema.cjs","names":[],"sources":["../src/core/eip712-schema.ts"],"sourcesContent":["import { type TypedData, type TypedDataDomain } from 'viem';\n\n/**\n * EIP-712 typed-data schema for attestation v1 — FROZEN.\n *\n * Once a single attestation is published, every byte of this file is part of an\n * immutable contract. Modifying any value below changes the typed-data digest\n * and invalidates every previously signed attestation.\n *\n * To evolve: introduce v2 alongside (`eip712-schema-v2.ts`), bump SCHEMA_VERSION,\n * keep v1 verifier alive forever, dispatch by attestation.schemaVersion.\n */\n\nexport const ATTESTATION_DOMAIN_V1 = {\n chainId: 1,\n name: 'jterrazz.com Article Attestation',\n version: '1',\n} as const satisfies TypedDataDomain;\n\nexport const ATTESTATION_TYPES_V1 = {\n Attestation: [\n { name: 'schemaVersion', type: 'uint16' },\n { name: 'subject', type: 'ArticleSubject' },\n { name: 'claims', type: 'ArticleClaims' },\n ],\n ArticleClaims: [\n { name: 'slug', type: 'string' },\n { name: 'publishedAt', type: 'uint64' },\n { name: 'revision', type: 'uint16' },\n { name: 'priorAttestation', type: 'bytes32' },\n ],\n ArticleSubject: [\n { name: 'title', type: 'string' },\n { name: 'contentDigest', type: 'bytes32' },\n { name: 'locale', type: 'string' },\n ],\n} as const satisfies TypedData;\n\nexport const ATTESTATION_PRIMARY_TYPE = 'Attestation' as const;\n\n/**\n * Sentinel value for `priorAttestation` when this is the first revision.\n */\nexport const NO_PRIOR_ATTESTATION =\n '0x0000000000000000000000000000000000000000000000000000000000000000' as const;\n\nexport type ArticleSubject = {\n title: string;\n contentDigest: `0x${string}`;\n locale: string;\n};\n\nexport type ArticleClaims = {\n slug: string;\n publishedAt: bigint;\n revision: number;\n priorAttestation: `0x${string}`;\n};\n\nexport type AttestationMessage = {\n schemaVersion: number;\n subject: ArticleSubject;\n claims: ArticleClaims;\n};\n\nexport { SCHEMA_VERSION as currentSchemaVersion } from '../version.js';\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,wBAAwB;CACjC,SAAS;CACT,MAAM;CACN,SAAS;AACb;AAEA,MAAa,uBAAuB;CAChC,aAAa;EACT;GAAE,MAAM;GAAiB,MAAM;EAAS;EACxC;GAAE,MAAM;GAAW,MAAM;EAAiB;EAC1C;GAAE,MAAM;GAAU,MAAM;EAAgB;CAC5C;CACA,eAAe;EACX;GAAE,MAAM;GAAQ,MAAM;EAAS;EAC/B;GAAE,MAAM;GAAe,MAAM;EAAS;EACtC;GAAE,MAAM;GAAY,MAAM;EAAS;EACnC;GAAE,MAAM;GAAoB,MAAM;EAAU;CAChD;CACA,gBAAgB;EACZ;GAAE,MAAM;GAAS,MAAM;EAAS;EAChC;GAAE,MAAM;GAAiB,MAAM;EAAU;EACzC;GAAE,MAAM;GAAU,MAAM;EAAS;CACrC;AACJ;AAEA,MAAa,2BAA2B;;;;AAKxC,MAAa,uBACT"}
@@ -0,0 +1,75 @@
1
+ //#region src/core/eip712-schema.d.ts
2
+ /**
3
+ * EIP-712 typed-data schema for attestation v1 — FROZEN.
4
+ *
5
+ * Once a single attestation is published, every byte of this file is part of an
6
+ * immutable contract. Modifying any value below changes the typed-data digest
7
+ * and invalidates every previously signed attestation.
8
+ *
9
+ * To evolve: introduce v2 alongside (`eip712-schema-v2.ts`), bump SCHEMA_VERSION,
10
+ * keep v1 verifier alive forever, dispatch by attestation.schemaVersion.
11
+ */
12
+ declare const ATTESTATION_DOMAIN_V1: {
13
+ readonly chainId: 1;
14
+ readonly name: "jterrazz.com Article Attestation";
15
+ readonly version: "1";
16
+ };
17
+ declare const ATTESTATION_TYPES_V1: {
18
+ readonly Attestation: readonly [{
19
+ readonly name: "schemaVersion";
20
+ readonly type: "uint16";
21
+ }, {
22
+ readonly name: "subject";
23
+ readonly type: "ArticleSubject";
24
+ }, {
25
+ readonly name: "claims";
26
+ readonly type: "ArticleClaims";
27
+ }];
28
+ readonly ArticleClaims: readonly [{
29
+ readonly name: "slug";
30
+ readonly type: "string";
31
+ }, {
32
+ readonly name: "publishedAt";
33
+ readonly type: "uint64";
34
+ }, {
35
+ readonly name: "revision";
36
+ readonly type: "uint16";
37
+ }, {
38
+ readonly name: "priorAttestation";
39
+ readonly type: "bytes32";
40
+ }];
41
+ readonly ArticleSubject: readonly [{
42
+ readonly name: "title";
43
+ readonly type: "string";
44
+ }, {
45
+ readonly name: "contentDigest";
46
+ readonly type: "bytes32";
47
+ }, {
48
+ readonly name: "locale";
49
+ readonly type: "string";
50
+ }];
51
+ };
52
+ declare const ATTESTATION_PRIMARY_TYPE: "Attestation";
53
+ /**
54
+ * Sentinel value for `priorAttestation` when this is the first revision.
55
+ */
56
+ declare const NO_PRIOR_ATTESTATION: "0x0000000000000000000000000000000000000000000000000000000000000000";
57
+ type ArticleSubject = {
58
+ title: string;
59
+ contentDigest: `0x${string}`;
60
+ locale: string;
61
+ };
62
+ type ArticleClaims = {
63
+ slug: string;
64
+ publishedAt: bigint;
65
+ revision: number;
66
+ priorAttestation: `0x${string}`;
67
+ };
68
+ type AttestationMessage = {
69
+ schemaVersion: number;
70
+ subject: ArticleSubject;
71
+ claims: ArticleClaims;
72
+ };
73
+ //#endregion
74
+ export { ArticleSubject as a, ArticleClaims as i, ATTESTATION_PRIMARY_TYPE as n, AttestationMessage as o, ATTESTATION_TYPES_V1 as r, NO_PRIOR_ATTESTATION as s, ATTESTATION_DOMAIN_V1 as t };
75
+ //# sourceMappingURL=eip712-schema.d.cts.map
@@ -0,0 +1,75 @@
1
+ //#region src/core/eip712-schema.d.ts
2
+ /**
3
+ * EIP-712 typed-data schema for attestation v1 — FROZEN.
4
+ *
5
+ * Once a single attestation is published, every byte of this file is part of an
6
+ * immutable contract. Modifying any value below changes the typed-data digest
7
+ * and invalidates every previously signed attestation.
8
+ *
9
+ * To evolve: introduce v2 alongside (`eip712-schema-v2.ts`), bump SCHEMA_VERSION,
10
+ * keep v1 verifier alive forever, dispatch by attestation.schemaVersion.
11
+ */
12
+ declare const ATTESTATION_DOMAIN_V1: {
13
+ readonly chainId: 1;
14
+ readonly name: "jterrazz.com Article Attestation";
15
+ readonly version: "1";
16
+ };
17
+ declare const ATTESTATION_TYPES_V1: {
18
+ readonly Attestation: readonly [{
19
+ readonly name: "schemaVersion";
20
+ readonly type: "uint16";
21
+ }, {
22
+ readonly name: "subject";
23
+ readonly type: "ArticleSubject";
24
+ }, {
25
+ readonly name: "claims";
26
+ readonly type: "ArticleClaims";
27
+ }];
28
+ readonly ArticleClaims: readonly [{
29
+ readonly name: "slug";
30
+ readonly type: "string";
31
+ }, {
32
+ readonly name: "publishedAt";
33
+ readonly type: "uint64";
34
+ }, {
35
+ readonly name: "revision";
36
+ readonly type: "uint16";
37
+ }, {
38
+ readonly name: "priorAttestation";
39
+ readonly type: "bytes32";
40
+ }];
41
+ readonly ArticleSubject: readonly [{
42
+ readonly name: "title";
43
+ readonly type: "string";
44
+ }, {
45
+ readonly name: "contentDigest";
46
+ readonly type: "bytes32";
47
+ }, {
48
+ readonly name: "locale";
49
+ readonly type: "string";
50
+ }];
51
+ };
52
+ declare const ATTESTATION_PRIMARY_TYPE: "Attestation";
53
+ /**
54
+ * Sentinel value for `priorAttestation` when this is the first revision.
55
+ */
56
+ declare const NO_PRIOR_ATTESTATION: "0x0000000000000000000000000000000000000000000000000000000000000000";
57
+ type ArticleSubject = {
58
+ title: string;
59
+ contentDigest: `0x${string}`;
60
+ locale: string;
61
+ };
62
+ type ArticleClaims = {
63
+ slug: string;
64
+ publishedAt: bigint;
65
+ revision: number;
66
+ priorAttestation: `0x${string}`;
67
+ };
68
+ type AttestationMessage = {
69
+ schemaVersion: number;
70
+ subject: ArticleSubject;
71
+ claims: ArticleClaims;
72
+ };
73
+ //#endregion
74
+ export { ArticleSubject as a, ArticleClaims as i, ATTESTATION_PRIMARY_TYPE as n, AttestationMessage as o, ATTESTATION_TYPES_V1 as r, NO_PRIOR_ATTESTATION as s, ATTESTATION_DOMAIN_V1 as t };
75
+ //# sourceMappingURL=eip712-schema.d.ts.map
@@ -0,0 +1,73 @@
1
+ //#region src/core/eip712-schema.ts
2
+ /**
3
+ * EIP-712 typed-data schema for attestation v1 — FROZEN.
4
+ *
5
+ * Once a single attestation is published, every byte of this file is part of an
6
+ * immutable contract. Modifying any value below changes the typed-data digest
7
+ * and invalidates every previously signed attestation.
8
+ *
9
+ * To evolve: introduce v2 alongside (`eip712-schema-v2.ts`), bump SCHEMA_VERSION,
10
+ * keep v1 verifier alive forever, dispatch by attestation.schemaVersion.
11
+ */
12
+ const ATTESTATION_DOMAIN_V1 = {
13
+ chainId: 1,
14
+ name: "jterrazz.com Article Attestation",
15
+ version: "1"
16
+ };
17
+ const ATTESTATION_TYPES_V1 = {
18
+ Attestation: [
19
+ {
20
+ name: "schemaVersion",
21
+ type: "uint16"
22
+ },
23
+ {
24
+ name: "subject",
25
+ type: "ArticleSubject"
26
+ },
27
+ {
28
+ name: "claims",
29
+ type: "ArticleClaims"
30
+ }
31
+ ],
32
+ ArticleClaims: [
33
+ {
34
+ name: "slug",
35
+ type: "string"
36
+ },
37
+ {
38
+ name: "publishedAt",
39
+ type: "uint64"
40
+ },
41
+ {
42
+ name: "revision",
43
+ type: "uint16"
44
+ },
45
+ {
46
+ name: "priorAttestation",
47
+ type: "bytes32"
48
+ }
49
+ ],
50
+ ArticleSubject: [
51
+ {
52
+ name: "title",
53
+ type: "string"
54
+ },
55
+ {
56
+ name: "contentDigest",
57
+ type: "bytes32"
58
+ },
59
+ {
60
+ name: "locale",
61
+ type: "string"
62
+ }
63
+ ]
64
+ };
65
+ const ATTESTATION_PRIMARY_TYPE = "Attestation";
66
+ /**
67
+ * Sentinel value for `priorAttestation` when this is the first revision.
68
+ */
69
+ const NO_PRIOR_ATTESTATION = "0x0000000000000000000000000000000000000000000000000000000000000000";
70
+ //#endregion
71
+ export { NO_PRIOR_ATTESTATION as i, ATTESTATION_PRIMARY_TYPE as n, ATTESTATION_TYPES_V1 as r, ATTESTATION_DOMAIN_V1 as t };
72
+
73
+ //# sourceMappingURL=eip712-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip712-schema.js","names":[],"sources":["../src/core/eip712-schema.ts"],"sourcesContent":["import { type TypedData, type TypedDataDomain } from 'viem';\n\n/**\n * EIP-712 typed-data schema for attestation v1 — FROZEN.\n *\n * Once a single attestation is published, every byte of this file is part of an\n * immutable contract. Modifying any value below changes the typed-data digest\n * and invalidates every previously signed attestation.\n *\n * To evolve: introduce v2 alongside (`eip712-schema-v2.ts`), bump SCHEMA_VERSION,\n * keep v1 verifier alive forever, dispatch by attestation.schemaVersion.\n */\n\nexport const ATTESTATION_DOMAIN_V1 = {\n chainId: 1,\n name: 'jterrazz.com Article Attestation',\n version: '1',\n} as const satisfies TypedDataDomain;\n\nexport const ATTESTATION_TYPES_V1 = {\n Attestation: [\n { name: 'schemaVersion', type: 'uint16' },\n { name: 'subject', type: 'ArticleSubject' },\n { name: 'claims', type: 'ArticleClaims' },\n ],\n ArticleClaims: [\n { name: 'slug', type: 'string' },\n { name: 'publishedAt', type: 'uint64' },\n { name: 'revision', type: 'uint16' },\n { name: 'priorAttestation', type: 'bytes32' },\n ],\n ArticleSubject: [\n { name: 'title', type: 'string' },\n { name: 'contentDigest', type: 'bytes32' },\n { name: 'locale', type: 'string' },\n ],\n} as const satisfies TypedData;\n\nexport const ATTESTATION_PRIMARY_TYPE = 'Attestation' as const;\n\n/**\n * Sentinel value for `priorAttestation` when this is the first revision.\n */\nexport const NO_PRIOR_ATTESTATION =\n '0x0000000000000000000000000000000000000000000000000000000000000000' as const;\n\nexport type ArticleSubject = {\n title: string;\n contentDigest: `0x${string}`;\n locale: string;\n};\n\nexport type ArticleClaims = {\n slug: string;\n publishedAt: bigint;\n revision: number;\n priorAttestation: `0x${string}`;\n};\n\nexport type AttestationMessage = {\n schemaVersion: number;\n subject: ArticleSubject;\n claims: ArticleClaims;\n};\n\nexport { SCHEMA_VERSION as currentSchemaVersion } from '../version.js';\n"],"mappings":";;;;;;;;;;;AAaA,MAAa,wBAAwB;CACjC,SAAS;CACT,MAAM;CACN,SAAS;AACb;AAEA,MAAa,uBAAuB;CAChC,aAAa;EACT;GAAE,MAAM;GAAiB,MAAM;EAAS;EACxC;GAAE,MAAM;GAAW,MAAM;EAAiB;EAC1C;GAAE,MAAM;GAAU,MAAM;EAAgB;CAC5C;CACA,eAAe;EACX;GAAE,MAAM;GAAQ,MAAM;EAAS;EAC/B;GAAE,MAAM;GAAe,MAAM;EAAS;EACtC;GAAE,MAAM;GAAY,MAAM;EAAS;EACnC;GAAE,MAAM;GAAoB,MAAM;EAAU;CAChD;CACA,gBAAgB;EACZ;GAAE,MAAM;GAAS,MAAM;EAAS;EAChC;GAAE,MAAM;GAAiB,MAAM;EAAU;EACzC;GAAE,MAAM;GAAU,MAAM;EAAS;CACrC;AACJ;AAEA,MAAa,2BAA2B;;;;AAKxC,MAAa,uBACT"}