Add ASL log encoding/decoding

This commit is contained in:
Carl Niklas Rydberg 2026-01-17 12:51:32 +01:00
parent 0d810affb0
commit b7b4b2f195
4 changed files with 657 additions and 0 deletions

View file

@ -81,6 +81,7 @@ set(AMDUAT_ENC_SRCS
src/near_core/asl/ref_derive.c
src/near_core/enc/fer1_receipt.c
src/near_core/fer/receipt.c
src/near_core/enc/asl_log.c
src/near_core/enc/pel_program_dag.c
src/near_core/enc/pel_program_dag_desc.c
src/near_core/enc/pel_trace_dag.c
@ -247,6 +248,16 @@ target_link_libraries(amduat_test_pel1_result_invariants
add_test(NAME pel1_result_invariants
COMMAND amduat_test_pel1_result_invariants)
add_executable(amduat_test_asl_log tests/enc/test_asl_log.c)
target_include_directories(amduat_test_asl_log
PRIVATE ${AMDUAT_INTERNAL_DIR}
PRIVATE ${AMDUAT_INCLUDE_DIR}
)
target_link_libraries(amduat_test_asl_log
PRIVATE amduat_enc amduat_hash_asl1 amduat_asl amduat_util
)
add_test(NAME asl_log COMMAND amduat_test_asl_log)
add_executable(amduat_test_tgk1_edge tests/enc/test_tgk1_edge.c)
target_include_directories(amduat_test_tgk1_edge
PRIVATE ${AMDUAT_INTERNAL_DIR}

View file

@ -0,0 +1,45 @@
#ifndef AMDUAT_ENC_ASL_LOG_H
#define AMDUAT_ENC_ASL_LOG_H
#include "amduat/asl/core.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL = 0x01,
AMDUAT_ASL_LOG_RECORD_TOMBSTONE = 0x10,
AMDUAT_ASL_LOG_RECORD_TOMBSTONE_LIFT = 0x11,
AMDUAT_ASL_LOG_RECORD_SNAPSHOT_ANCHOR = 0x20,
AMDUAT_ASL_LOG_RECORD_ARTIFACT_PUBLISH = 0x30,
AMDUAT_ASL_LOG_RECORD_ARTIFACT_UNPUBLISH = 0x31
};
typedef struct {
uint64_t logseq;
uint32_t record_type;
amduat_octets_t payload;
uint8_t record_hash[32];
} amduat_asl_log_record_t;
bool amduat_enc_asl_log_encode_v1(const amduat_asl_log_record_t *records,
size_t record_count,
amduat_octets_t *out_bytes);
bool amduat_enc_asl_log_decode_v1(amduat_octets_t bytes,
amduat_asl_log_record_t **out_records,
size_t *out_count);
void amduat_enc_asl_log_free(amduat_asl_log_record_t *records,
size_t record_count);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AMDUAT_ENC_ASL_LOG_H */

384
src/near_core/enc/asl_log.c Normal file
View file

@ -0,0 +1,384 @@
#include "amduat/enc/asl_log.h"
#include "amduat/hash/asl1.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
enum {
AMDUAT_ASL_LOG_MAGIC_LEN = 8,
AMDUAT_ASL_LOG_HASH_LEN = 32,
AMDUAT_ASL_LOG_HEADER_LEN = 24,
AMDUAT_ASL_LOG_VERSION = 1
};
typedef struct {
const uint8_t *data;
size_t len;
size_t offset;
} amduat_asl_log_cursor_t;
static const uint8_t k_amduat_asl_log_magic[AMDUAT_ASL_LOG_MAGIC_LEN] = {
'A', 'S', 'L', 'L', 'O', 'G', '0', '1'};
static void amduat_asl_log_store_u32_le(uint8_t *out, uint32_t value) {
out[0] = (uint8_t)(value & 0xffu);
out[1] = (uint8_t)((value >> 8) & 0xffu);
out[2] = (uint8_t)((value >> 16) & 0xffu);
out[3] = (uint8_t)((value >> 24) & 0xffu);
}
static void amduat_asl_log_store_u64_le(uint8_t *out, uint64_t value) {
out[0] = (uint8_t)(value & 0xffu);
out[1] = (uint8_t)((value >> 8) & 0xffu);
out[2] = (uint8_t)((value >> 16) & 0xffu);
out[3] = (uint8_t)((value >> 24) & 0xffu);
out[4] = (uint8_t)((value >> 32) & 0xffu);
out[5] = (uint8_t)((value >> 40) & 0xffu);
out[6] = (uint8_t)((value >> 48) & 0xffu);
out[7] = (uint8_t)((value >> 56) & 0xffu);
}
static bool amduat_asl_log_read_u32_le(amduat_asl_log_cursor_t *cur,
uint32_t *out) {
const uint8_t *data;
if (cur->len - cur->offset < 4) {
return false;
}
data = cur->data + cur->offset;
*out = (uint32_t)data[0] | ((uint32_t)data[1] << 8) |
((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24);
cur->offset += 4;
return true;
}
static bool amduat_asl_log_read_u64_le(amduat_asl_log_cursor_t *cur,
uint64_t *out) {
const uint8_t *data;
if (cur->len - cur->offset < 8) {
return false;
}
data = cur->data + cur->offset;
*out = (uint64_t)data[0] | ((uint64_t)data[1] << 8) |
((uint64_t)data[2] << 16) | ((uint64_t)data[3] << 24) |
((uint64_t)data[4] << 32) | ((uint64_t)data[5] << 40) |
((uint64_t)data[6] << 48) | ((uint64_t)data[7] << 56);
cur->offset += 8;
return true;
}
static bool amduat_asl_log_add_size(size_t *acc, size_t add) {
if (*acc > SIZE_MAX - add) {
return false;
}
*acc += add;
return true;
}
static bool amduat_asl_log_is_known_record_type(uint32_t record_type) {
switch (record_type) {
case AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL:
case AMDUAT_ASL_LOG_RECORD_TOMBSTONE:
case AMDUAT_ASL_LOG_RECORD_TOMBSTONE_LIFT:
case AMDUAT_ASL_LOG_RECORD_SNAPSHOT_ANCHOR:
case AMDUAT_ASL_LOG_RECORD_ARTIFACT_PUBLISH:
case AMDUAT_ASL_LOG_RECORD_ARTIFACT_UNPUBLISH:
return true;
default:
return false;
}
}
static bool amduat_asl_log_hash_record(const uint8_t prev_hash[32],
uint64_t logseq,
uint32_t record_type,
uint32_t payload_len,
const uint8_t *payload,
uint8_t out_hash[32]) {
amduat_hash_asl1_stream_t stream;
uint8_t logseq_bytes[8];
uint8_t type_bytes[4];
uint8_t payload_len_bytes[4];
bool ok;
if (payload_len != 0 && payload == NULL) {
return false;
}
if (!amduat_hash_asl1_stream_init(AMDUAT_HASH_ASL1_ID_SHA256, &stream)) {
return false;
}
amduat_asl_log_store_u64_le(logseq_bytes, logseq);
amduat_asl_log_store_u32_le(type_bytes, record_type);
amduat_asl_log_store_u32_le(payload_len_bytes, payload_len);
ok = amduat_hash_asl1_stream_update(&stream,
amduat_octets(prev_hash,
AMDUAT_ASL_LOG_HASH_LEN)) &&
amduat_hash_asl1_stream_update(&stream,
amduat_octets(logseq_bytes,
sizeof(logseq_bytes))) &&
amduat_hash_asl1_stream_update(&stream,
amduat_octets(type_bytes,
sizeof(type_bytes))) &&
amduat_hash_asl1_stream_update(
&stream,
amduat_octets(payload_len_bytes, sizeof(payload_len_bytes)));
if (ok && payload_len != 0) {
ok = amduat_hash_asl1_stream_update(
&stream, amduat_octets(payload, payload_len));
}
if (ok) {
ok = amduat_hash_asl1_stream_final(&stream, out_hash,
AMDUAT_ASL_LOG_HASH_LEN);
}
amduat_hash_asl1_stream_destroy(&stream);
return ok;
}
void amduat_enc_asl_log_free(amduat_asl_log_record_t *records,
size_t record_count) {
size_t i;
if (records == NULL) {
return;
}
for (i = 0; i < record_count; ++i) {
amduat_octets_free(&records[i].payload);
}
free(records);
}
bool amduat_enc_asl_log_encode_v1(const amduat_asl_log_record_t *records,
size_t record_count,
amduat_octets_t *out_bytes) {
size_t total_len;
size_t i;
size_t offset;
uint8_t *buffer;
uint8_t prev_hash[AMDUAT_ASL_LOG_HASH_LEN];
if (out_bytes == NULL) {
return false;
}
out_bytes->data = NULL;
out_bytes->len = 0;
if (record_count != 0 && records == NULL) {
return false;
}
total_len = AMDUAT_ASL_LOG_HEADER_LEN;
for (i = 0; i < record_count; ++i) {
size_t payload_len = records[i].payload.len;
if (payload_len != 0 && records[i].payload.data == NULL) {
return false;
}
if (payload_len > UINT32_MAX) {
return false;
}
if (!amduat_asl_log_add_size(&total_len, 8 + 4 + 4)) {
return false;
}
if (!amduat_asl_log_add_size(&total_len, payload_len)) {
return false;
}
if (!amduat_asl_log_add_size(&total_len, AMDUAT_ASL_LOG_HASH_LEN)) {
return false;
}
}
buffer = (uint8_t *)malloc(total_len);
if (buffer == NULL) {
return false;
}
offset = 0;
memcpy(buffer + offset, k_amduat_asl_log_magic, AMDUAT_ASL_LOG_MAGIC_LEN);
offset += AMDUAT_ASL_LOG_MAGIC_LEN;
amduat_asl_log_store_u32_le(buffer + offset, AMDUAT_ASL_LOG_VERSION);
offset += 4;
amduat_asl_log_store_u32_le(buffer + offset, AMDUAT_ASL_LOG_HEADER_LEN);
offset += 4;
amduat_asl_log_store_u64_le(buffer + offset, 0);
offset += 8;
memset(prev_hash, 0, sizeof(prev_hash));
for (i = 0; i < record_count; ++i) {
const amduat_asl_log_record_t *record = &records[i];
uint32_t payload_len = (uint32_t)record->payload.len;
uint8_t record_hash[AMDUAT_ASL_LOG_HASH_LEN];
amduat_asl_log_store_u64_le(buffer + offset, record->logseq);
offset += 8;
amduat_asl_log_store_u32_le(buffer + offset, record->record_type);
offset += 4;
amduat_asl_log_store_u32_le(buffer + offset, payload_len);
offset += 4;
if (payload_len != 0) {
memcpy(buffer + offset, record->payload.data, payload_len);
offset += payload_len;
}
if (!amduat_asl_log_hash_record(prev_hash,
record->logseq,
record->record_type,
payload_len,
record->payload.data,
record_hash)) {
free(buffer);
return false;
}
memcpy(buffer + offset, record_hash, AMDUAT_ASL_LOG_HASH_LEN);
offset += AMDUAT_ASL_LOG_HASH_LEN;
memcpy(prev_hash, record_hash, AMDUAT_ASL_LOG_HASH_LEN);
}
out_bytes->data = buffer;
out_bytes->len = total_len;
return true;
}
bool amduat_enc_asl_log_decode_v1(amduat_octets_t bytes,
amduat_asl_log_record_t **out_records,
size_t *out_count) {
amduat_asl_log_cursor_t cur;
uint32_t version;
uint32_t header_size;
uint64_t flags;
uint8_t prev_hash[AMDUAT_ASL_LOG_HASH_LEN];
amduat_asl_log_record_t *records;
size_t record_count;
size_t record_capacity;
if (out_records == NULL || out_count == NULL) {
return false;
}
*out_records = NULL;
*out_count = 0;
if (bytes.len != 0 && bytes.data == NULL) {
return false;
}
if (bytes.len < AMDUAT_ASL_LOG_HEADER_LEN) {
return false;
}
cur.data = bytes.data;
cur.len = bytes.len;
cur.offset = 0;
if (memcmp(cur.data, k_amduat_asl_log_magic, AMDUAT_ASL_LOG_MAGIC_LEN) != 0) {
return false;
}
cur.offset += AMDUAT_ASL_LOG_MAGIC_LEN;
if (!amduat_asl_log_read_u32_le(&cur, &version)) {
return false;
}
if (version != AMDUAT_ASL_LOG_VERSION) {
return false;
}
if (!amduat_asl_log_read_u32_le(&cur, &header_size)) {
return false;
}
if (!amduat_asl_log_read_u64_le(&cur, &flags)) {
return false;
}
if (flags != 0) {
return false;
}
if (header_size < AMDUAT_ASL_LOG_HEADER_LEN) {
return false;
}
if (bytes.len < header_size) {
return false;
}
cur.offset = header_size;
records = NULL;
record_count = 0;
record_capacity = 0;
memset(prev_hash, 0, sizeof(prev_hash));
while (cur.offset < cur.len) {
uint64_t logseq;
uint32_t record_type;
uint32_t payload_len;
const uint8_t *payload;
const uint8_t *record_hash;
uint8_t expected_hash[AMDUAT_ASL_LOG_HASH_LEN];
if (!amduat_asl_log_read_u64_le(&cur, &logseq) ||
!amduat_asl_log_read_u32_le(&cur, &record_type) ||
!amduat_asl_log_read_u32_le(&cur, &payload_len)) {
amduat_enc_asl_log_free(records, record_count);
return false;
}
if (cur.len - cur.offset < payload_len + AMDUAT_ASL_LOG_HASH_LEN) {
amduat_enc_asl_log_free(records, record_count);
return false;
}
payload = cur.data + cur.offset;
cur.offset += payload_len;
record_hash = cur.data + cur.offset;
cur.offset += AMDUAT_ASL_LOG_HASH_LEN;
if (!amduat_asl_log_hash_record(prev_hash,
logseq,
record_type,
payload_len,
payload,
expected_hash)) {
amduat_enc_asl_log_free(records, record_count);
return false;
}
if (memcmp(expected_hash, record_hash, AMDUAT_ASL_LOG_HASH_LEN) != 0) {
amduat_enc_asl_log_free(records, record_count);
return false;
}
memcpy(prev_hash, record_hash, AMDUAT_ASL_LOG_HASH_LEN);
if (amduat_asl_log_is_known_record_type(record_type)) {
amduat_asl_log_record_t *slot;
amduat_octets_t payload_bytes;
if (record_count == record_capacity) {
size_t new_capacity = record_capacity == 0 ? 4 : record_capacity * 2;
amduat_asl_log_record_t *next =
(amduat_asl_log_record_t *)realloc(
records, new_capacity * sizeof(*records));
if (next == NULL) {
amduat_enc_asl_log_free(records, record_count);
return false;
}
records = next;
record_capacity = new_capacity;
}
payload_bytes = amduat_octets(payload, payload_len);
slot = &records[record_count];
slot->logseq = logseq;
slot->record_type = record_type;
slot->payload = amduat_octets(NULL, 0u);
if (!amduat_octets_clone(payload_bytes, &slot->payload)) {
amduat_enc_asl_log_free(records, record_count);
return false;
}
memcpy(slot->record_hash, record_hash, AMDUAT_ASL_LOG_HASH_LEN);
record_count++;
}
}
*out_records = records;
*out_count = record_count;
return true;
}

217
tests/enc/test_asl_log.c Normal file
View file

@ -0,0 +1,217 @@
#include "amduat/enc/asl_log.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static bool octets_equal(amduat_octets_t a, amduat_octets_t b) {
if (a.len != b.len) {
return false;
}
if (a.len == 0) {
return true;
}
return memcmp(a.data, b.data, a.len) == 0;
}
static int test_round_trip(void) {
uint8_t payload_a[] = {0x01, 0x02, 0x03};
uint8_t payload_b[] = {0x10, 0x20};
amduat_asl_log_record_t records[2];
amduat_octets_t encoded;
amduat_asl_log_record_t *decoded = NULL;
size_t decoded_len = 0;
int exit_code = 1;
memset(records, 0, sizeof(records));
records[0].logseq = 1;
records[0].record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL;
records[0].payload = amduat_octets(payload_a, sizeof(payload_a));
records[1].logseq = 2;
records[1].record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE;
records[1].payload = amduat_octets(payload_b, sizeof(payload_b));
if (!amduat_enc_asl_log_encode_v1(records, 2, &encoded)) {
fprintf(stderr, "encode failed\n");
return exit_code;
}
if (!amduat_enc_asl_log_decode_v1(encoded, &decoded, &decoded_len)) {
fprintf(stderr, "decode failed\n");
goto cleanup;
}
if (decoded_len != 2) {
fprintf(stderr, "decoded length mismatch\n");
goto cleanup_decoded;
}
if (decoded[0].logseq != records[0].logseq ||
decoded[1].logseq != records[1].logseq) {
fprintf(stderr, "decoded logseq mismatch\n");
goto cleanup_decoded;
}
if (decoded[0].record_type != records[0].record_type ||
decoded[1].record_type != records[1].record_type) {
fprintf(stderr, "decoded record type mismatch\n");
goto cleanup_decoded;
}
if (!octets_equal(decoded[0].payload, records[0].payload) ||
!octets_equal(decoded[1].payload, records[1].payload)) {
fprintf(stderr, "decoded payload mismatch\n");
goto cleanup_decoded;
}
exit_code = 0;
cleanup_decoded:
amduat_enc_asl_log_free(decoded, decoded_len);
cleanup:
free((void *)encoded.data);
return exit_code;
}
static int test_hash_chain_mutation(void) {
uint8_t payload_a[] = {0xaa, 0xbb, 0xcc};
amduat_asl_log_record_t record;
amduat_octets_t encoded;
amduat_asl_log_record_t *decoded = NULL;
size_t decoded_len = 0;
int exit_code = 1;
size_t payload_offset;
memset(&record, 0, sizeof(record));
record.logseq = 1;
record.record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL;
record.payload = amduat_octets(payload_a, sizeof(payload_a));
if (!amduat_enc_asl_log_encode_v1(&record, 1, &encoded)) {
fprintf(stderr, "encode failed\n");
return exit_code;
}
payload_offset = 24 + 8 + 4 + 4;
if (encoded.len <= payload_offset) {
fprintf(stderr, "encoded payload offset invalid\n");
goto cleanup;
}
((uint8_t *)encoded.data)[payload_offset] ^= 0xffu;
if (amduat_enc_asl_log_decode_v1(encoded, &decoded, &decoded_len)) {
fprintf(stderr, "decode unexpectedly succeeded\n");
amduat_enc_asl_log_free(decoded, decoded_len);
goto cleanup;
}
exit_code = 0;
cleanup:
free((void *)encoded.data);
return exit_code;
}
static int test_unknown_record_type(void) {
uint8_t payload_a[] = {0x11};
uint8_t payload_b[] = {0x22};
uint8_t payload_c[] = {0x33};
amduat_asl_log_record_t records[3];
amduat_octets_t encoded;
amduat_asl_log_record_t *decoded = NULL;
size_t decoded_len = 0;
int exit_code = 1;
memset(records, 0, sizeof(records));
records[0].logseq = 1;
records[0].record_type = AMDUAT_ASL_LOG_RECORD_SEGMENT_SEAL;
records[0].payload = amduat_octets(payload_a, sizeof(payload_a));
records[1].logseq = 2;
records[1].record_type = 0x99;
records[1].payload = amduat_octets(payload_b, sizeof(payload_b));
records[2].logseq = 3;
records[2].record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE;
records[2].payload = amduat_octets(payload_c, sizeof(payload_c));
if (!amduat_enc_asl_log_encode_v1(records, 3, &encoded)) {
fprintf(stderr, "encode failed\n");
return exit_code;
}
if (!amduat_enc_asl_log_decode_v1(encoded, &decoded, &decoded_len)) {
fprintf(stderr, "decode failed\n");
goto cleanup;
}
if (decoded_len != 2) {
fprintf(stderr, "unknown record not skipped\n");
goto cleanup_decoded;
}
if (decoded[0].logseq != records[0].logseq ||
decoded[1].logseq != records[2].logseq) {
fprintf(stderr, "decoded logseq mismatch\n");
goto cleanup_decoded;
}
exit_code = 0;
cleanup_decoded:
amduat_enc_asl_log_free(decoded, decoded_len);
cleanup:
free((void *)encoded.data);
return exit_code;
}
static int test_truncated_payload(void) {
uint8_t payload_a[] = {0xaa, 0xbb, 0xcc, 0xdd};
amduat_asl_log_record_t record;
amduat_octets_t encoded;
amduat_asl_log_record_t *decoded = NULL;
size_t decoded_len = 0;
int exit_code = 1;
memset(&record, 0, sizeof(record));
record.logseq = 42;
record.record_type = AMDUAT_ASL_LOG_RECORD_TOMBSTONE;
record.payload = amduat_octets(payload_a, sizeof(payload_a));
if (!amduat_enc_asl_log_encode_v1(&record, 1, &encoded)) {
fprintf(stderr, "encode failed\n");
return exit_code;
}
if (encoded.len == 0) {
fprintf(stderr, "encoded length invalid\n");
goto cleanup;
}
if (amduat_enc_asl_log_decode_v1(
amduat_octets(encoded.data, encoded.len - 1),
&decoded,
&decoded_len)) {
fprintf(stderr, "decode unexpectedly succeeded\n");
amduat_enc_asl_log_free(decoded, decoded_len);
goto cleanup;
}
exit_code = 0;
cleanup:
free((void *)encoded.data);
return exit_code;
}
int main(void) {
if (test_round_trip() != 0) {
return 1;
}
if (test_hash_chain_mutation() != 0) {
return 1;
}
if (test_unknown_record_type() != 0) {
return 1;
}
if (test_truncated_payload() != 0) {
return 1;
}
return 0;
}