Files
dat-thang/extra/sleep_verify.c
T
divorce b735900f2e 0.0.002
ddat interop with dat js
cable interop with cabal js
ddat's frontend = dat-thang
cable fe = cable-thang
dillo support based on cel's dillo-dat
bootstrap node
xplatform support

Signed-off-by: divo <418@cryspace.space>
2026-10-19 00:17:02 -04:00

576 lines
10 KiB
C

#include "sleep.h"
#include "sleep_verify.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef SLEEP_SALT
#include <sodium.h>
#endif
static uint16_t
be16(const uint8_t *p)
{
return ((uint16_t)p[0] << 8) | p[1];
}
static uint32_t
be32(const uint8_t *p)
{
return ((uint32_t)p[0] << 24) |
((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) |
p[3];
}
static uint64_t
be64(const uint8_t *p)
{
return ((uint64_t)p[0] << 56) |
((uint64_t)p[1] << 48) |
((uint64_t)p[2] << 40) |
((uint64_t)p[3] << 32) |
((uint64_t)p[4] << 24) |
((uint64_t)p[5] << 16) |
((uint64_t)p[6] << 8) |
p[7];
}
static int
load_file(const char *path,
uint8_t **buf,
size_t *len)
{
FILE *f;
long size;
uint8_t *p;
f = fopen(path, "rb");
if (!f) {
fprintf(stderr,
"sleep-verify: %s: %s\n",
path,
strerror(errno));
return -1;
}
if (fseek(f, 0, SEEK_END) != 0 ||
(size = ftell(f)) < 0 ||
fseek(f, 0, SEEK_SET) != 0) {
fclose(f);
return -1;
}
p = malloc(size ? (size_t)size : 1);
if (!p) {
fclose(f);
errno = ENOMEM;
return -1;
}
if (size &&
fread(p, 1, (size_t)size, f) != (size_t)size) {
free(p);
fclose(f);
errno = EIO;
return -1;
}
fclose(f);
*buf = p;
*len = (size_t)size;
return 0;
}
static int
check_header(const uint8_t *buf,
size_t len,
uint32_t magic,
uint16_t entry_size,
const char *algo)
{
size_t algo_len;
if (len < SLEEP_HEADER_SIZE)
return -1;
if (be32(buf) != magic ||
buf[4] != 0 ||
be16(buf + 5) != entry_size)
return -1;
algo_len = buf[7];
if (algo_len != strlen(algo))
return -1;
return memcmp(buf + 8, algo, algo_len) == 0 ? 0 : -1;
}
static int
verify_tree_structure(const sleep_tree_entry_t *tree,
size_t node_count)
{
size_t i;
for (i = 1; i < node_count; i += 2) {
uint64_t left;
uint64_t right;
uint64_t left_len;
uint64_t right_len;
uint64_t length;
uint8_t hash[32];
if (!sleep_tree_left_child(i, &left) ||
!sleep_tree_right_child(i, &right))
return -1;
/*
* This parent belongs to a subtree that does not
* completely exist yet.
*/
if (right >= node_count) {
static const uint8_t zero[40];
if (memcmp(&tree[i],
zero,
sizeof(tree[i])) != 0) {
fprintf(stderr,
" tree: nonzero incomplete node %zu\n",
i);
return -1;
}
continue;
}
left_len =
be64((const uint8_t *)&tree[left].length_be);
right_len =
be64((const uint8_t *)&tree[right].length_be);
if (UINT64_MAX - left_len < right_len)
return -1;
length = left_len + right_len;
if (sleep_tree_hash_parent(tree[left].hash,
tree[right].hash,
length,
hash) != 0)
return -1;
if (memcmp(hash, tree[i].hash, 32) != 0 ||
be64((const uint8_t *)&tree[i].length_be) != length) {
fprintf(stderr,
" tree: parent mismatch at node %zu\n",
i);
return -1;
}
}
return 0;
}
static int
verify_data_leaves(const uint8_t *data,
size_t data_len,
const sleep_tree_entry_t *tree,
size_t node_count)
{
size_t leaf_count = (node_count + 1) / 2;
size_t offset = 0;
size_t i;
for (i = 0; i < leaf_count; i++) {
size_t node = i * 2;
uint64_t len =
be64((const uint8_t *)&tree[node].length_be);
uint8_t hash[32];
if (len > data_len - offset) {
fprintf(stderr,
" data: leaf %zu exceeds metadata.data\n",
i);
return -1;
}
if (sleep_tree_hash_block(data + offset,
(size_t)len,
hash) != 0)
return -1;
if (memcmp(hash, tree[node].hash, 32) != 0) {
fprintf(stderr,
" data: leaf hash mismatch at %zu\n",
i);
return -1;
}
offset += (size_t)len;
}
if (offset != data_len) {
fprintf(stderr,
" data: tree consumes %zu bytes, file is %zu\n",
offset,
data_len);
return -1;
}
return 0;
}
#ifdef SLEEP_SALT
static int
verify_signatures(const uint8_t *signatures,
size_t signatures_len,
const uint8_t pubkey[32],
const sleep_tree_entry_t *tree,
size_t node_count,
size_t feed_length)
{
size_t sequence;
uint64_t roots[64];
uint8_t message[1 + 64 * 48];
uint8_t digest[32];
if (signatures_len < SLEEP_HEADER_SIZE ||
(signatures_len - SLEEP_HEADER_SIZE) %
SLEEP_SIG_ENTRY_SIZE != 0)
return -1;
if ((signatures_len - SLEEP_HEADER_SIZE) /
SLEEP_SIG_ENTRY_SIZE != feed_length)
return -1;
for (sequence = 1;
sequence <= feed_length;
sequence++) {
size_t root_count;
size_t off = 0;
size_t r;
root_count =
sleep_tree_full_roots(sequence,
roots,
64);
message[off++] = SLEEP_HASH_ROOT;
for (r = 0; r < root_count; r++) {
uint64_t index = roots[r];
uint64_t length;
if (index >= node_count)
return -1;
memcpy(message + off,
tree[index].hash,
32);
off += 32;
message[off++] = (uint8_t)(index >> 56);
message[off++] = (uint8_t)(index >> 48);
message[off++] = (uint8_t)(index >> 40);
message[off++] = (uint8_t)(index >> 32);
message[off++] = (uint8_t)(index >> 24);
message[off++] = (uint8_t)(index >> 16);
message[off++] = (uint8_t)(index >> 8);
message[off++] = (uint8_t)index;
length =
be64((const uint8_t *)
&tree[index].length_be);
message[off++] = (uint8_t)(length >> 56);
message[off++] = (uint8_t)(length >> 48);
message[off++] = (uint8_t)(length >> 40);
message[off++] = (uint8_t)(length >> 32);
message[off++] = (uint8_t)(length >> 24);
message[off++] = (uint8_t)(length >> 16);
message[off++] = (uint8_t)(length >> 8);
message[off++] = (uint8_t)length;
}
if (crypto_generichash(digest,
sizeof(digest),
message,
off,
NULL,
0) != 0)
return -1;
if (crypto_sign_verify_detached(
signatures +
SLEEP_HEADER_SIZE +
(sequence - 1) * SLEEP_SIG_ENTRY_SIZE,
digest,
sizeof(digest),
pubkey) != 0) {
fprintf(stderr,
" signatures: failed at sequence %zu\n",
sequence);
return -1;
}
}
return 0;
}
#endif
static int
verify_feed(const char *dir,
const char *name,
int verify_data,
size_t *out_length)
{
char path[4096];
uint8_t *tree_file = NULL;
uint8_t *sig_file = NULL;
uint8_t *key_file = NULL;
uint8_t *data_file = NULL;
uint8_t *bitfield = NULL;
size_t tree_len = 0;
size_t sig_len = 0;
size_t key_len = 0;
size_t data_len = 0;
size_t bitfield_len = 0;
size_t node_count;
size_t feed_length;
int rc = -1;
snprintf(path,
sizeof(path),
"%s/%s.tree",
dir,
name);
if (load_file(path, &tree_file, &tree_len) != 0)
goto done;
if (check_header(tree_file,
tree_len,
SLEEP_MAGIC_TREE,
SLEEP_TREE_ENTRY_SIZE,
SLEEP_ALGO_BLAKE2B) != 0) {
fprintf(stderr, " tree: invalid header\n");
goto done;
}
if ((tree_len - SLEEP_HEADER_SIZE) %
sizeof(sleep_tree_entry_t) != 0)
goto done;
node_count =
(tree_len - SLEEP_HEADER_SIZE) /
sizeof(sleep_tree_entry_t);
if (!node_count || !(node_count & 1))
goto done;
feed_length = (node_count + 1) / 2;
if (verify_tree_structure(
(const sleep_tree_entry_t *)(tree_file +
SLEEP_HEADER_SIZE),
node_count) != 0)
goto done;
snprintf(path,
sizeof(path),
"%s/%s.key",
dir,
name);
if (load_file(path, &key_file, &key_len) != 0 ||
key_len != 32)
goto done;
snprintf(path,
sizeof(path),
"%s/%s.signatures",
dir,
name);
if (load_file(path, &sig_file, &sig_len) != 0)
goto done;
if (check_header(sig_file,
sig_len,
SLEEP_MAGIC_SIGNATURE,
SLEEP_SIG_ENTRY_SIZE,
SLEEP_ALGO_ED25519) != 0)
goto done;
#ifdef SLEEP_SALT
if (verify_signatures(
sig_file,
sig_len,
key_file,
(const sleep_tree_entry_t *)(tree_file +
SLEEP_HEADER_SIZE),
node_count,
feed_length) != 0)
goto done;
#else
fprintf(stderr,
" signatures: build with SLEEP_SALT\n");
goto done;
#endif
snprintf(path,
sizeof(path),
"%s/%s.bitfield",
dir,
name);
if (load_file(path,
&bitfield,
&bitfield_len) != 0)
goto done;
if (bitfield_len < SLEEP_HEADER_SIZE ||
be32(bitfield) != SLEEP_MAGIC_BITFIELD ||
bitfield[4] != 0)
goto done;
/*
* The SLEEP V2 document says 3328 here.
*
* The supplied historical Dat fixture says 3584.
* We deliberately accept both until the extra 256 bytes
* in the historical format have been identified.
*/
if (be16(bitfield + 5) != SLEEP_BITFIELD_ENTRY_SIZE &&
be16(bitfield + 5) != 3584)
goto done;
if (verify_data) {
snprintf(path,
sizeof(path),
"%s/%s.data",
dir,
name);
if (load_file(path,
&data_file,
&data_len) != 0)
goto done;
if (verify_data_leaves(
data_file,
data_len,
(const sleep_tree_entry_t *)(tree_file +
SLEEP_HEADER_SIZE),
node_count) != 0)
goto done;
}
if (out_length)
*out_length = feed_length;
rc = 0;
done:
free(tree_file);
free(sig_file);
free(key_file);
free(data_file);
free(bitfield);
return rc;
}
int
sleep_verify_dat(const char *dir)
{
size_t metadata_length;
size_t content_length;
printf("sleep-verify: %s\n", dir);
printf("metadata: ");
if (verify_feed(dir,
"metadata",
1,
&metadata_length) != 0) {
printf("FAIL\n");
return -1;
}
printf("OK (%zu entries)\n", metadata_length);
printf("content: ");
if (verify_feed(dir,
"content",
0,
&content_length) != 0) {
printf("FAIL\n");
return -1;
}
printf("OK (%zu entries)\n", content_length);
printf("sleep-verify: PASS\n");
return 0;
}
int
main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr,
"usage: %s .dat\n",
argv[0]);
return 2;
}
#ifdef SLEEP_SALT
if (sodium_init() < 0)
return 1;
#else
fprintf(stderr,
"sleep-verify: built without SLEEP_SALT\n");
return 1;
#endif
return sleep_verify_dat(argv[1]) == 0 ? 0 : 1;
}