File size: 4,966 Bytes
2166d7e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include "l8_worm.h"

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "cs_sha256.h"

static void genesis(uint8_t out[32])
{
    cs_sha256("cstack-worm-genesis-v1", 22, out);
}

static void next_seal(uint64_t index, const uint8_t prev[32], const uint8_t witness[32], uint8_t out[32])
{
    cs_sha256_ctx c;
    cs_sha256_init(&c);
    cs_sha256_update(&c, "cstack-worm-seal-v1", 19);
    cs_sha256_update_u64(&c, index);
    cs_sha256_update(&c, prev, 32);
    cs_sha256_update(&c, witness, 32);
    cs_sha256_final(&c, out);
}

cs_status worm_verify_chain(const worm_seal *seals, size_t n)
{
    if (!seals || n == 0) return CS_ERR_TAMPER;
    uint8_t g[32], zero[32] = {0};
    genesis(g);
    if (seals[0].index != 0 || !cs_ct_equal(seals[0].seal, g, 32) ||
        !cs_ct_equal(seals[0].witness_digest, zero, 32))
        return CS_ERR_TAMPER;
    for (size_t i = 1; i < n; i++) {
        uint8_t want[32];
        if (seals[i].index != i) return CS_ERR_TAMPER;
        next_seal(i, seals[i - 1].seal, seals[i].witness_digest, want);
        if (!cs_ct_equal(want, seals[i].seal, 32)) return CS_ERR_TAMPER;
    }
    return CS_OK;
}

static cs_status push(worm_ledger *l, const worm_seal *s)
{
    if (l->count == l->cap) {
        size_t ncap = l->cap ? l->cap * 2 : 16;
        worm_seal *p = realloc(l->seals, ncap * sizeof *p);
        if (!p) return CS_ERR_NOMEM;
        l->seals = p;
        l->cap = ncap;
    }
    l->seals[l->count++] = *s;
    return CS_OK;
}

static cs_status write_line(int fd, const worm_seal *s)
{
    char line[8 + 20 + 64 + 64 + 8], wh[65], sh[65];
    cs_hex(s->witness_digest, 32, wh);
    cs_hex(s->seal, 32, sh);
    int n = snprintf(line, sizeof line, "%llu %s %s\n", (unsigned long long)s->index, wh, sh);
    if (n <= 0 || (size_t)n >= sizeof line) return CS_ERR_IO;
    ssize_t w = write(fd, line, (size_t)n);          /* O_APPEND: one atomic append per seal */
    if (w != n) return CS_ERR_IO;
    return fsync(fd) == 0 ? CS_OK : CS_ERR_IO;
}

static cs_status load_file(worm_ledger *l, const char *path)
{
    FILE *f = fopen(path, "r");
    if (!f) return errno == ENOENT ? CS_OK : CS_ERR_IO;
    char line[256];
    cs_status st = CS_OK;
    while (fgets(line, sizeof line, f)) {
        size_t len = strlen(line);
        if (len == 0 || line[len - 1] != '\n') { st = CS_ERR_TAMPER; break; }   /* truncated line */
        line[len - 1] = '\0';
        unsigned long long idx;
        char wh[65], sh[65], extra;
        if (sscanf(line, "%llu %64s %64s %c", &idx, wh, sh, &extra) != 3) { st = CS_ERR_TAMPER; break; }
        worm_seal s;
        memset(&s, 0, sizeof s);
        s.index = idx;
        if (cs_unhex(wh, s.witness_digest, 32) != 0 || cs_unhex(sh, s.seal, 32) != 0) { st = CS_ERR_TAMPER; break; }
        st = push(l, &s);
        if (st != CS_OK) break;
    }
    if (st == CS_OK && ferror(f)) st = CS_ERR_IO;
    fclose(f);
    if (st == CS_OK && l->count > 0) st = worm_verify_chain(l->seals, l->count);
    return st;
}

cs_status worm_open(worm_ledger *l, const char *path)
{
    if (!l) return CS_ERR_ARG;
    memset(l, 0, sizeof *l);
    l->fd = -1;
    cs_status st = CS_OK;
    if (path) {
        st = load_file(l, path);
        if (st != CS_OK) { worm_close(l); return st; }
        l->fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
        if (l->fd < 0) { worm_close(l); return CS_ERR_IO; }
    }
    if (l->count == 0) {
        worm_seal g;
        memset(&g, 0, sizeof g);
        genesis(g.seal);
        st = push(l, &g);
        if (st == CS_OK && l->fd >= 0) st = write_line(l->fd, &g);
        if (st != CS_OK) { worm_close(l); return st; }
    }
    return CS_OK;
}

cs_status worm_append(worm_ledger *l, const uint8_t witness_digest[32], worm_seal *out)
{
    if (!l || !witness_digest || l->count == 0) return CS_ERR_ARG;
    worm_seal s;
    memset(&s, 0, sizeof s);
    s.index = l->count;
    memcpy(s.witness_digest, witness_digest, 32);
    next_seal(s.index, l->seals[l->count - 1].seal, witness_digest, s.seal);
    if (l->fd >= 0) {
        cs_status st = write_line(l->fd, &s);        /* persist before acknowledging */
        if (st != CS_OK) return st;
    }
    cs_status st = push(l, &s);
    if (st != CS_OK) return st;
    if (out) *out = s;
    return CS_OK;
}

void worm_close(worm_ledger *l)
{
    if (!l) return;
    if (l->fd >= 0) close(l->fd);
    free(l->seals);
    memset(l, 0, sizeof *l);
    l->fd = -1;
}

size_t worm_count(const worm_ledger *l) { return l ? l->count : 0; }
const worm_seal *worm_at(const worm_ledger *l, size_t i) { return (l && i < l->count) ? &l->seals[i] : NULL; }
const worm_seal *worm_head(const worm_ledger *l) { return (l && l->count) ? &l->seals[l->count - 1] : NULL; }

cs_status worm_verify(const worm_ledger *l)
{
    if (!l) return CS_ERR_ARG;
    return worm_verify_chain(l->seals, l->count);
}