-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_ots.php
More file actions
130 lines (118 loc) · 4.6 KB
/
api_ots.php
File metadata and controls
130 lines (118 loc) · 4.6 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
119
120
121
122
123
124
125
126
127
128
129
130
<?php
// api_ots.php — OpenTimestamps calendar proxy.
//
// Flow:
// client POSTs {"hash":"<64 hex chars>"} to this endpoint.
// server POSTs the raw 32-byte digest to an OpenTimestamps calendar server
// (https://a.pool.opentimestamps.org/digest), receives a binary .ots
// timestamp proof, returns it to the client as base64.
//
// Why proxy server-side rather than letting the browser hit the calendar
// directly:
// (1) OTS calendars don't advertise CORS, so browsers can't POST to them
// directly without hitting a preflight failure.
// (2) We own the rate limit. Without this, a malicious client could burn
// our IP reputation against the public calendars.
// (3) Lets us fan-out to multiple calendars in one server round-trip
// later (stronger proof if BTC halts a single calendar).
//
// The .ots proof returned is a CALENDAR commitment — it becomes a full
// Bitcoin-anchored timestamp once the calendar publishes its Merkle root on
// chain (usually within 6–24 hours). The .ots file can then be "upgraded"
// to contain the Bitcoin block attestation using the standard ots CLI.
//
// Rate limit: 20 requests/hour per IP.
// Response: {"ok":bool, "ots_base64":"...", "calendar":"url", "hash":"..."}
// or {"ok":false, "error":"..."}.
require_once __DIR__ . '/api_keys.php';
header('Content-Type: application/json');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: no-referrer');
header('Cache-Control: no-store');
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if ($method === 'OPTIONS') { http_response_code(200); exit; }
if ($method !== 'POST') {
http_response_code(405);
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
exit;
}
if (!fca_rate_ok('ots', 20)) {
http_response_code(429);
echo json_encode(['ok' => false, 'error' => 'Rate limit: 20 requests per hour per IP.']);
exit;
}
// Accept both JSON and form POST. Hash is 64 hex chars (SHA-256).
$hash = '';
$ctype = strtolower($_SERVER['CONTENT_TYPE'] ?? '');
if (strpos($ctype, 'application/json') !== false) {
$raw = file_get_contents('php://input', false, null, 0, 8192);
$j = json_decode($raw, true);
if (is_array($j)) $hash = (string)($j['hash'] ?? '');
} else {
$hash = (string)($_POST['hash'] ?? '');
}
$hash = strtolower(trim($hash));
if (!preg_match('/^[0-9a-f]{64}$/', $hash)) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Expected 64-character SHA-256 hex digest.']);
exit;
}
// Convert hex → raw 32 bytes for the calendar POST body.
$digest = hex2bin($hash);
if (strlen($digest) !== 32) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Digest must be exactly 32 bytes.']);
exit;
}
// Calendar pool. Try in order; first one that responds wins.
// These are the public OTS calendars maintained by Peter Todd + community.
$CALENDARS = [
'https://a.pool.opentimestamps.org/digest',
'https://b.pool.opentimestamps.org/digest',
'https://a.pool.eternitywall.com/digest',
'https://finney.calendar.eternitywall.com/digest',
];
$error = null;
$ots = null;
$used = null;
foreach ($CALENDARS as $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $digest,
CURLOPT_HTTPHEADER => ['Content-Type: application/octet-stream', 'Accept: application/octet-stream'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
CURLOPT_USERAGENT => 'unsealed-ots/1.0',
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
]);
$body = curl_exec($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$cerr = curl_error($ch);
curl_close($ch);
if ($body !== false && $status >= 200 && $status < 300 && strlen($body) > 0) {
$ots = $body;
$used = $url;
break;
}
$error = ($cerr ?: ('HTTP ' . $status)) . ' @ ' . $url;
}
if ($ots === null) {
http_response_code(502);
echo json_encode([
'ok' => false,
'error' => 'All OpenTimestamps calendars returned an error.',
'detail' => $error,
]);
exit;
}
echo json_encode([
'ok' => true,
'hash' => $hash,
'calendar' => $used,
'ots_base64' => base64_encode($ots),
'ots_length' => strlen($ots),
'note' => 'This is a calendar commitment. Within 6–24h the calendar publishes its Merkle root to the Bitcoin blockchain. Use the `ots upgrade` CLI or opentimestamps.org to upgrade the .ots proof to a full Bitcoin-anchored timestamp.',
]);