From b7b4b2f195dc3736eb52c8efaed38dd9301d731a Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Sat, 17 Jan 2026 12:51:32 +0100 Subject: [PATCH] Add ASL log encoding/decoding --- CMakeLists.txt | 11 + include/amduat/enc/asl_log.h | 45 ++++ src/near_core/enc/asl_log.c | 384 +++++++++++++++++++++++++++++++++++ tests/enc/test_asl_log.c | 217 ++++++++++++++++++++ 4 files changed, 657 insertions(+) create mode 100644 include/amduat/enc/asl_log.h create mode 100644 src/near_core/enc/asl_log.c create mode 100644 tests/enc/test_asl_log.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 30ff53e..3bfe9f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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} diff --git a/include/amduat/enc/asl_log.h b/include/amduat/enc/asl_log.h new file mode 100644 index 0000000..ca0a6e0 --- /dev/null +++ b/include/amduat/enc/asl_log.h @@ -0,0 +1,45 @@ +#ifndef AMDUAT_ENC_ASL_LOG_H +#define AMDUAT_ENC_ASL_LOG_H + +#include "amduat/asl/core.h" + +#include +#include +#include + +#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 */ diff --git a/src/near_core/enc/asl_log.c b/src/near_core/enc/asl_log.c new file mode 100644 index 0000000..8185cce --- /dev/null +++ b/src/near_core/enc/asl_log.c @@ -0,0 +1,384 @@ +#include "amduat/enc/asl_log.h" + +#include "amduat/hash/asl1.h" + +#include +#include +#include + +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; +} diff --git a/tests/enc/test_asl_log.c b/tests/enc/test_asl_log.c new file mode 100644 index 0000000..5b1803f --- /dev/null +++ b/tests/enc/test_asl_log.c @@ -0,0 +1,217 @@ +#include "amduat/enc/asl_log.h" + +#include +#include +#include +#include +#include + +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; +}