provared 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/CHANGELOG.md +1062 -0
- package/LICENSE +202 -0
- package/LICENSE-CC-BY-4.0.txt +396 -0
- package/README.md +689 -0
- package/bin/provared-check.mjs +565 -0
- package/bin/provared-headers.mjs +110 -0
- package/docs/threat-model.md +997 -0
- package/net/headers.js +403 -0
- package/package.json +57 -0
- package/page/check-page.js +162 -0
- package/page/index.html +47 -0
- package/page/render.js +530 -0
- package/page/style.css +150 -0
- package/samples/office-supplies.expected.json +82 -0
- package/samples/office-supplies.jsonl +10 -0
- package/samples/office-supplies.show.json +59 -0
- package/spec/provared-format.md +1672 -0
- package/src/actions.js +90 -0
- package/src/approval.js +73 -0
- package/src/blockstamp.js +198 -0
- package/src/book.js +2232 -0
- package/src/check.d.ts +21 -0
- package/src/check.js +15 -0
- package/src/compare.js +276 -0
- package/src/cover.js +198 -0
- package/src/der.js +192 -0
- package/src/encoding.js +286 -0
- package/src/fields.js +94 -0
- package/src/guard.js +171 -0
- package/src/headers.js +380 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +37 -0
- package/src/jws.js +235 -0
- package/src/keys.js +149 -0
- package/src/pass.js +67 -0
- package/src/passkey-browser.d.ts +20 -0
- package/src/passkey-browser.js +171 -0
- package/src/recorder.js +1115 -0
- package/src/seal.js +133 -0
- package/src/service.js +124 -0
- package/src/signatures.js +147 -0
- package/src/slip.js +401 -0
- package/src/standing.js +181 -0
- package/src/stub.js +140 -0
- package/src/timestamp.js +351 -0
- package/src/tools.js +192 -0
- package/src/tree.js +143 -0
- package/src/types.d.ts +628 -0
- package/src/webauthn.js +119 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// provared-headers: fetch the chain of Bitcoin block headers from nodes you
|
|
3
|
+
// name, check every header on this device, and keep the chain in a file.
|
|
4
|
+
// The checker then reads that file (provared-check --headers <file>) to
|
|
5
|
+
// know, without asking anyone, which blocks are part of the chain.
|
|
6
|
+
//
|
|
7
|
+
// provared-headers --node <host:port>... [--nodes-file <file>] [--chain <file>]
|
|
8
|
+
//
|
|
9
|
+
// Exit codes:
|
|
10
|
+
// 0 the chain is up to date, checked, and a second node on another
|
|
11
|
+
// network handed back the latest blocks itself
|
|
12
|
+
// 2 it could not be done, or what you gave on the command line could
|
|
13
|
+
// not be used. A chain file is never left half written, never made
|
|
14
|
+
// shorter, and keeps only headers that were checked
|
|
15
|
+
//
|
|
16
|
+
// This command, and only this one, makes network requests: to the nodes
|
|
17
|
+
// you name, and to no one else.
|
|
18
|
+
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { nodeAddress, updateChainFile } from '../net/headers.js';
|
|
21
|
+
|
|
22
|
+
// Node.js marks some of its methods as experimental and says so; this
|
|
23
|
+
// command does not repeat that warning. Any other warning is shown.
|
|
24
|
+
process.removeAllListeners('warning');
|
|
25
|
+
process.on('warning', (w) => {
|
|
26
|
+
if (w.name !== 'ExperimentalWarning') console.error(`${w.name}: ${w.message}`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const USAGE = `Usage: provared-headers --node <host:port>... [--nodes-file <file>] [--chain <file>]
|
|
30
|
+
|
|
31
|
+
Experimental: this command is new in version 0.1.0 and may change.
|
|
32
|
+
|
|
33
|
+
--node a Bitcoin node to fetch headers from: your own (127.0.0.1:8333), or any you choose; may be repeated
|
|
34
|
+
--nodes-file a file of nodes, one "host:port" to a line; anything after "#" on a line is set aside
|
|
35
|
+
--chain the file that holds the chain (default: block-headers.bin in this folder); it is checked
|
|
36
|
+
in full when it is read, and only newer headers are fetched
|
|
37
|
+
|
|
38
|
+
Only the nodes you name are asked, in the order given. At least two are needed: a second node, on another
|
|
39
|
+
network, must hand back the latest blocks itself. Each node sees this device's address; a node named by
|
|
40
|
+
its name is looked up by this device's own name service. Every header is checked here,
|
|
41
|
+
by the rules every Bitcoin node applies, from the chain's first block; a node can refuse, but cannot have a
|
|
42
|
+
false header kept.
|
|
43
|
+
|
|
44
|
+
Evidence, not a verdict.`;
|
|
45
|
+
|
|
46
|
+
function parseArguments(argv) {
|
|
47
|
+
const out = { nodes: [], chain: 'block-headers.bin', setAside: 0 };
|
|
48
|
+
let chainGiven = false;
|
|
49
|
+
for (let i = 0; i < argv.length; i++) {
|
|
50
|
+
const a = argv[i];
|
|
51
|
+
if (a === '--help' || a === '-h') return { help: true };
|
|
52
|
+
if (a === '--node' || a === '--nodes-file' || a === '--chain') {
|
|
53
|
+
if (i + 1 >= argv.length) return { error: `${a} needs a value.` };
|
|
54
|
+
const value = argv[++i];
|
|
55
|
+
if (a === '--node') {
|
|
56
|
+
const node = nodeAddress(value);
|
|
57
|
+
if (!node) return { error: `The value of --node is not the address of a node: host:port, [IPv6]:port, or a host alone.` };
|
|
58
|
+
out.nodes.push(node);
|
|
59
|
+
} else if (a === '--nodes-file') {
|
|
60
|
+
let text;
|
|
61
|
+
try {
|
|
62
|
+
text = readFileSync(value, 'utf8');
|
|
63
|
+
} catch {
|
|
64
|
+
return { error: `The file of nodes could not be read: ${value}` };
|
|
65
|
+
}
|
|
66
|
+
for (const line of text.split('\n')) {
|
|
67
|
+
if (line.split('#')[0].trim() === '') continue;
|
|
68
|
+
const node = nodeAddress(line);
|
|
69
|
+
if (node) out.nodes.push(node);
|
|
70
|
+
else out.setAside++;
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
if (chainGiven) return { error: '--chain may be given once.' };
|
|
74
|
+
chainGiven = true;
|
|
75
|
+
out.chain = value;
|
|
76
|
+
}
|
|
77
|
+
} else return { error: `Unknown option: ${a}` };
|
|
78
|
+
}
|
|
79
|
+
if (out.nodes.length < 2) return { error: 'Name at least two nodes: a second node, on another network, must hand back the latest blocks itself.' };
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const args = parseArguments(process.argv.slice(2));
|
|
84
|
+
if (args.help) {
|
|
85
|
+
console.log(USAGE);
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
if (args.error) {
|
|
89
|
+
console.error(`${args.error}\n\n${USAGE}`);
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
if (args.setAside) console.log(`${args.setAside} line${args.setAside === 1 ? '' : 's'} of the file of nodes ${args.setAside === 1 ? 'is' : 'are'} not the address of a node, and ${args.setAside === 1 ? 'was' : 'were'} set aside.`);
|
|
93
|
+
|
|
94
|
+
console.log('Experimental: provared-headers is new in version 0.1.0 and may change.');
|
|
95
|
+
|
|
96
|
+
let done;
|
|
97
|
+
try {
|
|
98
|
+
done = await updateChainFile({ file: args.chain, nodes: args.nodes, log: (line) => console.log(line.startsWith('Checking') || line.startsWith('It holds') ? line : ` ${line}`) });
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.error(e.message);
|
|
101
|
+
process.exit(2);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { chain, before } = done;
|
|
105
|
+
const tip = chain.height;
|
|
106
|
+
console.log(`\nThe chain in ${args.chain}: blocks 0 to ${tip}, every header checked on this device.`);
|
|
107
|
+
console.log(`The latest block, ${tip}: ${chain.fingerprintAt(tip)}, dated ${new Date(chain.timeAt(tip)).toISOString().slice(0, 19)}Z.`);
|
|
108
|
+
console.log(`${before >= 0 ? `${tip - before} new headers fetched. ` : ''}A second node, on another network, handed back the latest blocks itself.`);
|
|
109
|
+
console.log(`\nTo use it: provared-check <record> --headers ${args.chain} --issuer <thumbprint> ...`);
|
|
110
|
+
console.log('A block counts only if at least six blocks come after it in this chain.');
|