645 lines
21 KiB
C
645 lines
21 KiB
C
/*
|
|
* OpenTimestamps Command-Line Tool
|
|
*
|
|
* A simple tool for timestamping files with OpenTimestamps.
|
|
*
|
|
* Usage:
|
|
* ./ots_tool stamp Interactive: type text, save to file, submit to OTS
|
|
* ./ots_tool stamp <file> Timestamp an existing file
|
|
* ./ots_tool check <file.ots> Check if a proof is complete
|
|
* ./ots_tool verify <file> <file.ots> Verify a proof against a file
|
|
* ./ots_tool upgrade <file.ots> Upgrade a pending proof
|
|
*
|
|
* The "stamp" command:
|
|
* 1. If no file argument, prompts for text input and saves it to a .txt file
|
|
* 2. Computes the SHA-256 hash of the file
|
|
* 3. Submits the hash to an OpenTimestamps calendar
|
|
* 4. Saves the initial .ots proof to <file>.ots
|
|
* 5. Polls every 60 seconds to check if the proof is complete
|
|
* 6. When complete, saves the upgraded proof and verifies it
|
|
*
|
|
* Options:
|
|
* --calendar <url> Use a specific OTS calendar (default: https://alice.btc.calendar.opentimestamps.org)
|
|
* --interval <sec> Poll interval in seconds (default: 60)
|
|
* --max-wait <min> Maximum wait time in minutes (default: 120 = 2 hours)
|
|
*/
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "nostr_core/nostr_core.h"
|
|
#include "nostr_core/nip003.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <sys/stat.h>
|
|
|
|
#define DEFAULT_CALENDAR "https://alice.btc.calendar.opentimestamps.org"
|
|
#define DEFAULT_INTERVAL_SEC 60
|
|
#define DEFAULT_MAX_WAIT_MIN 120
|
|
|
|
/* Read an entire file into a malloc'd buffer. Returns NULL on error. */
|
|
static unsigned char* read_file(const char* path, size_t* len_out) {
|
|
FILE* f = fopen(path, "rb");
|
|
if (!f) return NULL;
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
long size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
if (size < 0) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
unsigned char* buf = (unsigned char*)malloc(size + 1);
|
|
if (!buf) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
size_t nread = fread(buf, 1, size, f);
|
|
fclose(f);
|
|
|
|
if (nread != (size_t)size) {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
buf[size] = '\0';
|
|
*len_out = (size_t)size;
|
|
return buf;
|
|
}
|
|
|
|
/* Write data to a file. Returns 0 on success, -1 on error. */
|
|
static int write_file(const char* path, const unsigned char* data, size_t len) {
|
|
FILE* f = fopen(path, "wb");
|
|
if (!f) return -1;
|
|
|
|
size_t nwritten = fwrite(data, 1, len, f);
|
|
fclose(f);
|
|
|
|
return (nwritten == len) ? 0 : -1;
|
|
}
|
|
|
|
/* Compute SHA-256 of a file and return as hex string (64 chars + null). */
|
|
static int compute_file_sha256_hex(const char* path, char* hex_out) {
|
|
size_t file_len = 0;
|
|
unsigned char* file_data = read_file(path, &file_len);
|
|
if (!file_data) return -1;
|
|
|
|
unsigned char digest[32];
|
|
int rc = nostr_sha256(file_data, file_len, digest);
|
|
free(file_data);
|
|
|
|
if (rc != NOSTR_SUCCESS) return -1;
|
|
|
|
nostr_bytes_to_hex(digest, 32, hex_out);
|
|
return 0;
|
|
}
|
|
|
|
/* Convert base64 string to binary file and save. */
|
|
static int save_base64_to_file(const char* path, const char* b64) {
|
|
if (!b64) return -1;
|
|
|
|
size_t b64_len = strlen(b64);
|
|
size_t max_decoded = (b64_len / 4) * 3 + 3;
|
|
unsigned char* data = (unsigned char*)malloc(max_decoded);
|
|
if (!data) return -1;
|
|
|
|
size_t data_len = base64_decode(b64, data);
|
|
if (data_len == 0) {
|
|
free(data);
|
|
return -1;
|
|
}
|
|
|
|
int rc = write_file(path, data, data_len);
|
|
free(data);
|
|
return rc;
|
|
}
|
|
|
|
/* Read a binary file and convert to base64 string. Caller must free. */
|
|
static char* read_file_as_base64(const char* path) {
|
|
size_t file_len = 0;
|
|
unsigned char* file_data = read_file(path, &file_len);
|
|
if (!file_data) return NULL;
|
|
|
|
size_t b64_size = ((file_len + 2) / 3) * 4 + 1;
|
|
char* b64 = (char*)malloc(b64_size);
|
|
if (!b64) {
|
|
free(file_data);
|
|
return NULL;
|
|
}
|
|
|
|
base64_encode(file_data, file_len, b64, b64_size);
|
|
free(file_data);
|
|
return b64;
|
|
}
|
|
|
|
static void print_usage(const char* prog) {
|
|
printf("OpenTimestamps Command-Line Tool\n");
|
|
printf("\n");
|
|
printf("Usage:\n");
|
|
printf(" %s stamp Interactive: type text, save, submit to OTS\n", prog);
|
|
printf(" %s stamp <file> Timestamp an existing file\n", prog);
|
|
printf(" %s check <file.ots> Check if a proof is complete\n", prog);
|
|
printf(" %s verify <file> <file.ots> Verify a proof against a file\n", prog);
|
|
printf(" %s upgrade <file.ots> [file] Upgrade a pending proof (optional: original file)\n", prog);
|
|
printf("\n");
|
|
printf("Options:\n");
|
|
printf(" --calendar <url> OTS calendar URL (default: %s)\n", DEFAULT_CALENDAR);
|
|
printf(" --interval <sec> Poll interval in seconds (default: %d)\n", DEFAULT_INTERVAL_SEC);
|
|
printf(" --max-wait <min> Max wait time in minutes (default: %d)\n", DEFAULT_MAX_WAIT_MIN);
|
|
printf("\n");
|
|
printf("Examples:\n");
|
|
printf(" %s stamp # Type text, timestamp it\n", prog);
|
|
printf(" %s stamp mydocument.txt # Timestamp a file\n", prog);
|
|
printf(" %s check mydocument.txt.ots # Check proof status\n", prog);
|
|
printf(" %s verify mydocument.txt mydocument.txt.ots # Verify proof\n", prog);
|
|
}
|
|
|
|
static int do_stamp(const char* file_path, const char* calendar_url,
|
|
int interval_sec, int max_wait_min) {
|
|
char auto_file[256] = {0};
|
|
const char* target_file = file_path;
|
|
|
|
/* If no file specified, prompt for text input */
|
|
if (!target_file) {
|
|
printf("📝 Enter text to timestamp (press Enter twice to finish):\n\n");
|
|
|
|
/* Read multi-line input until empty line */
|
|
char* content = NULL;
|
|
size_t content_cap = 0;
|
|
size_t content_len = 0;
|
|
char line[1024];
|
|
int empty_count = 0;
|
|
|
|
while (fgets(line, sizeof(line), stdin)) {
|
|
if (line[0] == '\n') {
|
|
empty_count++;
|
|
if (empty_count >= 1 && content_len > 0) {
|
|
break;
|
|
}
|
|
} else {
|
|
empty_count = 0;
|
|
}
|
|
|
|
size_t line_len = strlen(line);
|
|
if (content_len + line_len + 1 > content_cap) {
|
|
content_cap = (content_len + line_len + 1) * 2;
|
|
char* new_content = (char*)realloc(content, content_cap);
|
|
if (!new_content) {
|
|
free(content);
|
|
printf("❌ Out of memory\n");
|
|
return 1;
|
|
}
|
|
content = new_content;
|
|
}
|
|
memcpy(content + content_len, line, line_len);
|
|
content_len += line_len;
|
|
content[content_len] = '\0';
|
|
}
|
|
|
|
if (!content || content_len == 0) {
|
|
printf("❌ No text entered.\n");
|
|
free(content);
|
|
return 1;
|
|
}
|
|
|
|
/* Generate a filename based on timestamp */
|
|
time_t now = time(NULL);
|
|
struct tm* tm_info = localtime(&now);
|
|
strftime(auto_file, sizeof(auto_file), "timestamped_%Y%m%d_%H%M%S.txt", tm_info);
|
|
|
|
/* Save the text to a file */
|
|
if (write_file(auto_file, (const unsigned char*)content, content_len) != 0) {
|
|
printf("❌ Failed to save text to file: %s\n", auto_file);
|
|
free(content);
|
|
return 1;
|
|
}
|
|
|
|
printf("\n💾 Saved text to: %s (%zu bytes)\n", auto_file, content_len);
|
|
free(content);
|
|
target_file = auto_file;
|
|
}
|
|
|
|
/* Compute SHA-256 of the file */
|
|
char digest_hex[65];
|
|
if (compute_file_sha256_hex(target_file, digest_hex) != 0) {
|
|
printf("❌ Failed to compute SHA-256 of file: %s\n", target_file);
|
|
return 1;
|
|
}
|
|
|
|
printf("🔐 SHA-256: %s\n", digest_hex);
|
|
|
|
/* Submit to multiple OTS calendars for redundancy */
|
|
const char* default_calendars[] = {
|
|
"https://alice.btc.calendar.opentimestamps.org",
|
|
"https://bob.btc.calendar.opentimestamps.org",
|
|
"https://finney.calendar.eternitywall.com",
|
|
"https://btc.calendar.catallaxy.com",
|
|
};
|
|
int num_calendars = 4;
|
|
|
|
/* If a specific calendar was specified, use only that one */
|
|
const char** calendars_to_use;
|
|
int num_to_use;
|
|
if (strcmp(calendar_url, DEFAULT_CALENDAR) != 0) {
|
|
calendars_to_use = &calendar_url;
|
|
num_to_use = 1;
|
|
} else {
|
|
calendars_to_use = default_calendars;
|
|
num_to_use = num_calendars;
|
|
}
|
|
|
|
printf("\n📤 Submitting to %d OpenTimestamps calendar(s)...\n", num_to_use);
|
|
|
|
/* Submit to all calendars and create a combined DetachedTimestampFile */
|
|
char* ots_b64 = nostr_nip03_stamp_with_multiple_calendars(
|
|
digest_hex, calendars_to_use, num_to_use, 30);
|
|
if (!ots_b64) {
|
|
printf("❌ Failed to submit to any calendar.\n");
|
|
printf(" Check your internet connection and try again.\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Save the .ots proof */
|
|
char ots_path[512];
|
|
snprintf(ots_path, sizeof(ots_path), "%s.ots", target_file);
|
|
|
|
if (save_base64_to_file(ots_path, ots_b64) != 0) {
|
|
printf("❌ Failed to save .ots proof to: %s\n", ots_path);
|
|
free(ots_b64);
|
|
return 1;
|
|
}
|
|
|
|
printf("✅ Proof saved to: %s\n", ots_path);
|
|
|
|
/* Show attestation info - list all pending calendars */
|
|
char** pending_uris = NULL;
|
|
int pending_count = nostr_nip03_get_pending_uris(ots_b64, &pending_uris, 16);
|
|
if (pending_count > 0) {
|
|
printf(" Pending at %d calendar(s):\n", pending_count);
|
|
for (int i = 0; i < pending_count; i++) {
|
|
printf(" • %s\n", pending_uris[i]);
|
|
free(pending_uris[i]);
|
|
}
|
|
free(pending_uris);
|
|
}
|
|
|
|
/* Check if already complete (unlikely but possible) */
|
|
int complete = nostr_nip03_is_proof_complete(ots_b64);
|
|
if (complete == 1) {
|
|
printf("\n🎉 Proof is already complete!\n");
|
|
free(ots_b64);
|
|
return 0;
|
|
}
|
|
|
|
/* Poll for completion */
|
|
int max_attempts = (max_wait_min * 60) / interval_sec;
|
|
printf("\n⏳ Waiting for Bitcoin attestation...\n");
|
|
printf(" Polling every %d seconds (max %d minutes)\n", interval_sec, max_wait_min);
|
|
printf(" Press Ctrl+C to stop. The .ots file can be checked later with 'check'.\n\n");
|
|
|
|
int attempts = 0;
|
|
while (!complete && attempts < max_attempts) {
|
|
attempts++;
|
|
|
|
time_t now = time(NULL);
|
|
char time_str[26];
|
|
ctime_r(&now, time_str);
|
|
time_str[24] = '\0';
|
|
|
|
printf("[%s] Attempt %d/%d: %s\n", time_str, attempts, max_attempts,
|
|
complete ? "✅ COMPLETE" : "⏳ PENDING");
|
|
fflush(stdout);
|
|
|
|
if (complete) break;
|
|
|
|
sleep(interval_sec);
|
|
|
|
/* Try to upgrade the proof by walking the tree and fetching upgraded sub-proofs */
|
|
char* upgraded = nostr_nip03_upgrade_proof_tree(ots_b64, 30);
|
|
if (upgraded) {
|
|
free(ots_b64);
|
|
ots_b64 = upgraded;
|
|
|
|
/* Save the upgraded proof */
|
|
save_base64_to_file(ots_path, ots_b64);
|
|
|
|
/* Check if now complete */
|
|
complete = nostr_nip03_is_proof_complete(ots_b64);
|
|
}
|
|
}
|
|
|
|
if (complete) {
|
|
printf("\n🎉 SUCCESS! Proof is complete with a Bitcoin attestation!\n");
|
|
|
|
/* Save the final proof */
|
|
save_base64_to_file(ots_path, ots_b64);
|
|
printf(" Final proof saved to: %s\n", ots_path);
|
|
|
|
/* Show attestation details */
|
|
int has_bitcoin = 0;
|
|
uint64_t btc_height = 0;
|
|
if (nostr_nip03_get_attestation_info(ots_b64, &has_bitcoin, NULL, NULL,
|
|
&btc_height, NULL, NULL) == 0) {
|
|
if (has_bitcoin) {
|
|
printf(" Bitcoin block height: %llu\n", (unsigned long long)btc_height);
|
|
}
|
|
}
|
|
|
|
/* Verify the proof */
|
|
uint64_t verify_height = 0;
|
|
int verify_rc = nostr_nip03_verify_proof(ots_b64, digest_hex, &verify_height, NULL);
|
|
if (verify_rc == 0) {
|
|
printf(" ✅ Verified: file digest matches, Bitcoin height=%llu\n",
|
|
(unsigned long long)verify_height);
|
|
} else {
|
|
printf(" ⚠️ Verification returned: %d\n", verify_rc);
|
|
}
|
|
} else {
|
|
printf("\n⏳ Proof is still pending after %d minutes.\n", max_wait_min);
|
|
printf(" The .ots file has been saved: %s\n", ots_path);
|
|
printf(" Check again later with: ./ots_tool check %s\n", ots_path);
|
|
printf(" Or upgrade with: ./ots_tool upgrade %s\n", ots_path);
|
|
}
|
|
|
|
free(ots_b64);
|
|
return complete ? 0 : 2;
|
|
}
|
|
|
|
static int do_check(const char* ots_path) {
|
|
printf("🔎 Checking proof: %s\n\n", ots_path);
|
|
|
|
char* ots_b64 = read_file_as_base64(ots_path);
|
|
if (!ots_b64) {
|
|
printf("❌ Failed to read .ots file: %s\n", ots_path);
|
|
return 1;
|
|
}
|
|
|
|
int complete = nostr_nip03_is_proof_complete(ots_b64);
|
|
|
|
if (complete == 1) {
|
|
printf("✅ Proof is COMPLETE (has Bitcoin/Litecoin attestation)\n");
|
|
} else if (complete == 0) {
|
|
printf("⏳ Proof is PENDING (no Bitcoin attestation yet)\n");
|
|
} else {
|
|
printf("❌ Failed to parse proof (invalid OTS file?)\n");
|
|
free(ots_b64);
|
|
return 1;
|
|
}
|
|
|
|
/* Show detailed attestation info */
|
|
int has_bitcoin = 0, has_litecoin = 0, has_pending = 0;
|
|
uint64_t btc_height = 0, ltc_height = 0;
|
|
|
|
if (nostr_nip03_get_attestation_info(ots_b64, &has_bitcoin, &has_litecoin, &has_pending,
|
|
&btc_height, <c_height, NULL) == 0) {
|
|
if (has_bitcoin) {
|
|
printf(" ✅ Bitcoin attestation: block height %llu\n", (unsigned long long)btc_height);
|
|
}
|
|
if (has_litecoin) {
|
|
printf(" ✅ Litecoin attestation: block height %llu\n", (unsigned long long)ltc_height);
|
|
}
|
|
}
|
|
|
|
/* Show all pending calendar URIs */
|
|
if (has_pending) {
|
|
char** pending_uris = NULL;
|
|
int pending_count = nostr_nip03_get_pending_uris(ots_b64, &pending_uris, 16);
|
|
if (pending_count > 0) {
|
|
printf(" ⏳ Pending at %d calendar(s):\n", pending_count);
|
|
for (int i = 0; i < pending_count; i++) {
|
|
printf(" • %s\n", pending_uris[i]);
|
|
free(pending_uris[i]);
|
|
}
|
|
free(pending_uris);
|
|
}
|
|
}
|
|
|
|
free(ots_b64);
|
|
return 0;
|
|
}
|
|
|
|
static int do_verify(const char* file_path, const char* ots_path) {
|
|
printf("🔍 Verifying proof...\n");
|
|
printf(" File: %s\n", file_path);
|
|
printf(" Proof: %s\n\n", ots_path);
|
|
|
|
/* Compute SHA-256 of the file */
|
|
char digest_hex[65];
|
|
if (compute_file_sha256_hex(file_path, digest_hex) != 0) {
|
|
printf("❌ Failed to compute SHA-256 of file: %s\n", file_path);
|
|
return 1;
|
|
}
|
|
printf(" File SHA-256: %s\n", digest_hex);
|
|
|
|
/* Read the .ots file */
|
|
char* ots_b64 = read_file_as_base64(ots_path);
|
|
if (!ots_b64) {
|
|
printf("❌ Failed to read .ots file: %s\n", ots_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Verify the proof */
|
|
uint64_t block_height = 0;
|
|
time_t block_time = 0;
|
|
int rc = nostr_nip03_verify_proof(ots_b64, digest_hex, &block_height, &block_time);
|
|
|
|
switch (rc) {
|
|
case 0:
|
|
printf("\n✅ Proof VERIFIED!\n");
|
|
printf(" Bitcoin block height: %llu\n", (unsigned long long)block_height);
|
|
printf(" The file's hash is committed in the Bitcoin blockchain.\n");
|
|
free(ots_b64);
|
|
return 0;
|
|
|
|
case 1:
|
|
printf("\n⏳ Proof is valid but still PENDING (no Bitcoin attestation yet).\n");
|
|
printf(" The file digest matches the proof.\n");
|
|
printf(" Wait for the calendar to commit to the blockchain, then run:\n");
|
|
printf(" ./ots_tool upgrade %s\n", ots_path);
|
|
free(ots_b64);
|
|
return 1;
|
|
|
|
case -1:
|
|
printf("\n❌ Failed to parse OTS proof. The .ots file may be corrupted.\n");
|
|
free(ots_b64);
|
|
return 1;
|
|
|
|
case -2:
|
|
printf("\n❌ DIGEST MISMATCH! The proof does not match this file.\n");
|
|
printf(" The file may have been modified after timestamping.\n");
|
|
free(ots_b64);
|
|
return 1;
|
|
|
|
default:
|
|
printf("\n❌ Unknown error: %d\n", rc);
|
|
free(ots_b64);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int do_upgrade(const char* ots_path, const char* file_path,
|
|
const char* calendar_url) {
|
|
printf("⬆️ Upgrading proof: %s\n\n", ots_path);
|
|
|
|
char* ots_b64 = read_file_as_base64(ots_path);
|
|
if (!ots_b64) {
|
|
printf("❌ Failed to read .ots file: %s\n", ots_path);
|
|
return 1;
|
|
}
|
|
|
|
/* Check current status */
|
|
int was_complete = nostr_nip03_is_proof_complete(ots_b64);
|
|
if (was_complete == 1) {
|
|
printf("✅ Proof is already complete! No upgrade needed.\n");
|
|
free(ots_b64);
|
|
return 0;
|
|
}
|
|
|
|
(void)calendar_url; /* upgrade now uses pending-attestation URIs from proof */
|
|
(void)file_path;
|
|
|
|
printf("📤 Checking pending attestations and fetching upgrades...\n");
|
|
|
|
char* upgraded = NULL;
|
|
/* Use the tree-walking upgrade which correctly queries commitment hashes */
|
|
upgraded = nostr_nip03_upgrade_proof_tree(ots_b64, 30);
|
|
|
|
if (!upgraded) {
|
|
printf("⏳ No upgrade available yet. The proof is still pending.\n");
|
|
printf(" Bitcoin attestations typically take 1-12 hours.\n");
|
|
if (!file_path) {
|
|
printf(" Tip: provide the original file path to enable digest-based upgrade:\n");
|
|
printf(" ./ots_tool upgrade %s <original-file>\n", ots_path);
|
|
}
|
|
free(ots_b64);
|
|
return 1;
|
|
}
|
|
|
|
/* Save the upgraded proof */
|
|
if (save_base64_to_file(ots_path, upgraded) != 0) {
|
|
printf("❌ Failed to save upgraded proof.\n");
|
|
free(ots_b64);
|
|
free(upgraded);
|
|
return 1;
|
|
}
|
|
|
|
/* Check if now complete */
|
|
int complete = nostr_nip03_is_proof_complete(upgraded);
|
|
if (complete == 1) {
|
|
printf("✅ Upgrade successful! Proof is now COMPLETE with a Bitcoin attestation.\n");
|
|
|
|
int has_bitcoin = 0;
|
|
uint64_t btc_height = 0;
|
|
if (nostr_nip03_get_attestation_info(upgraded, &has_bitcoin, NULL, NULL,
|
|
&btc_height, NULL, NULL) == 0) {
|
|
if (has_bitcoin) {
|
|
printf(" Bitcoin block height: %llu\n", (unsigned long long)btc_height);
|
|
}
|
|
}
|
|
} else {
|
|
printf("⏳ Upgrade received but proof is still pending.\n");
|
|
printf(" The upgraded proof has been saved. Try again later.\n");
|
|
}
|
|
|
|
printf(" Updated proof saved to: %s\n", ots_path);
|
|
|
|
free(ots_b64);
|
|
free(upgraded);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
if (nostr_init() != NOSTR_SUCCESS) {
|
|
fprintf(stderr, "Failed to initialize NOSTR library\n");
|
|
return 1;
|
|
}
|
|
|
|
const char* calendar_url = DEFAULT_CALENDAR;
|
|
int interval_sec = DEFAULT_INTERVAL_SEC;
|
|
int max_wait_min = DEFAULT_MAX_WAIT_MIN;
|
|
|
|
enum { MODE_NONE, MODE_STAMP, MODE_CHECK, MODE_VERIFY, MODE_UPGRADE } mode = MODE_NONE;
|
|
const char* arg1 = NULL;
|
|
const char* arg2 = NULL;
|
|
|
|
/* Parse arguments */
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
|
print_usage(argv[0]);
|
|
nostr_cleanup();
|
|
return 0;
|
|
} else if (strcmp(argv[i], "--calendar") == 0 && i + 1 < argc) {
|
|
calendar_url = argv[++i];
|
|
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
|
|
interval_sec = atoi(argv[++i]);
|
|
} else if (strcmp(argv[i], "--max-wait") == 0 && i + 1 < argc) {
|
|
max_wait_min = atoi(argv[++i]);
|
|
} else if (strcmp(argv[i], "stamp") == 0) {
|
|
mode = MODE_STAMP;
|
|
} else if (strcmp(argv[i], "check") == 0) {
|
|
mode = MODE_CHECK;
|
|
} else if (strcmp(argv[i], "verify") == 0) {
|
|
mode = MODE_VERIFY;
|
|
} else if (strcmp(argv[i], "upgrade") == 0) {
|
|
mode = MODE_UPGRADE;
|
|
} else if (argv[i][0] != '-') {
|
|
if (!arg1) {
|
|
arg1 = argv[i];
|
|
} else if (!arg2) {
|
|
arg2 = argv[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
int rc = 0;
|
|
|
|
switch (mode) {
|
|
case MODE_STAMP:
|
|
rc = do_stamp(arg1, calendar_url, interval_sec, max_wait_min);
|
|
break;
|
|
|
|
case MODE_CHECK:
|
|
if (!arg1) {
|
|
printf("❌ Missing .ots file path\n");
|
|
print_usage(argv[0]);
|
|
rc = 1;
|
|
} else {
|
|
rc = do_check(arg1);
|
|
}
|
|
break;
|
|
|
|
case MODE_VERIFY:
|
|
if (!arg1 || !arg2) {
|
|
printf("❌ Missing file or .ots path\n");
|
|
print_usage(argv[0]);
|
|
rc = 1;
|
|
} else {
|
|
rc = do_verify(arg1, arg2);
|
|
}
|
|
break;
|
|
|
|
case MODE_UPGRADE:
|
|
if (!arg1) {
|
|
printf("❌ Missing .ots file path\n");
|
|
print_usage(argv[0]);
|
|
rc = 1;
|
|
} else {
|
|
rc = do_upgrade(arg1, arg2, calendar_url);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
print_usage(argv[0]);
|
|
rc = 1;
|
|
break;
|
|
}
|
|
|
|
nostr_cleanup();
|
|
return rc;
|
|
}
|