-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverify.js
More file actions
118 lines (105 loc) · 3.45 KB
/
Copy pathverify.js
File metadata and controls
118 lines (105 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env node
/**
* quey-verify-demo — Node.js verification
*
* Verifies a Quey draw certificate locally using only Quey's public key.
* No trust in Quey or any server required. Zero npm dependencies.
*
* Usage:
* node verify.js # verify the demo draw
* node verify.js <draw_id> # verify a specific draw
* node verify.js --offline # verify the cached certificate.json
*/
const crypto = require("crypto");
const https = require("https");
const fs = require("fs");
// Quey's Ed25519 public key — constant, derived from the signing secret.
// You can fetch it yourself any time:
// curl https://getpublickey-33ssvbagba-uc.a.run.app
const QUEY_PUBLIC_KEY_HEX =
"4fad999c2b586aab53155afc97f564a231350087b4c66d6a8f868963f42caf15";
// Public endpoint serving signed draw certificates.
const CERT_ENDPOINT = (id) =>
`https://getdrawcertificate-33ssvbagba-uc.a.run.app/?id=${id}`;
// A real, permanent demo draw to verify against.
const DEMO_DRAW_ID = "89421ffc-6af3-427e-8123-fa6c799cc166";
/**
* Reconstruct the exact UTF-8 string Quey signs.
*
* Reference: buildCanonicalPayload() in Quey's Cloud Function.
* Nine fields joined by '|', in this order, with winning_indices
* joined by ','.
*/
function buildCanonical(cert) {
return [
"QUEY-DRAW-CERT-V1",
cert.draw_id,
cert.timestamp,
cert.input_hash,
String(cert.total_participants),
String(cert.num_winners),
cert.winning_indices.join(","),
String(cert.entropy_consumed_bytes),
cert.key_id,
].join("|");
}
function fetchCertificate(drawId) {
return new Promise((resolve, reject) => {
https
.get(CERT_ENDPOINT(drawId), (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(e);
}
});
})
.on("error", reject);
});
}
function verify(cert) {
const message = Buffer.from(buildCanonical(cert), "utf8");
const signature = Buffer.from(cert.signature, "hex");
// Wrap the raw 32-byte Ed25519 public key in a DER SubjectPublicKeyInfo
// structure, which Node's crypto API expects.
// The 12-byte prefix encodes "Ed25519 SPKI".
const publicKeyDer = Buffer.concat([
Buffer.from("302a300506032b6570032100", "hex"),
Buffer.from(QUEY_PUBLIC_KEY_HEX, "hex"),
]);
const publicKey = crypto.createPublicKey({
key: publicKeyDer,
format: "der",
type: "spki",
});
return crypto.verify(null, message, publicKey, signature);
}
async function main() {
const args = process.argv.slice(2);
const offline = args.includes("--offline");
const drawId = args.find((a) => !a.startsWith("--")) || DEMO_DRAW_ID;
const cert = offline
? JSON.parse(fs.readFileSync("certificate.json", "utf8"))
: await fetchCertificate(drawId);
const ok = verify(cert);
if (ok) {
console.log("✓ Signature verified");
console.log(` Draw ID: ${cert.draw_id}`);
console.log(` Timestamp: ${cert.timestamp}`);
console.log(
` Winners: indices [${cert.winning_indices}] out of ${cert.total_participants} participants`
);
console.log(` Public key: ${QUEY_PUBLIC_KEY_HEX}`);
process.exit(0);
} else {
console.log("✗ Signature verification FAILED");
process.exit(1);
}
}
main().catch((e) => {
console.error("Error:", e.message);
process.exit(2);
});