!===================================================================== ! bob_worm.f90 ! WORM-sealed immutable artifact chain. ! Every circuit compilation, every density matrix step, every ! measurement result gets a cryptographic seal. ! Matches utqc-worm/src/lib.rs and sov_blake3_* in sov_monster_kernel.f90. ! Standard: Fortran 2018 !===================================================================== module bob_worm use, intrinsic :: iso_c_binding, only: c_int32_t, c_int64_t, c_ptr, & c_f_pointer, c_loc, c_char, c_size_t, c_associated use, intrinsic :: iso_fortran_env, only: int64, real64, int8 use bob_kinds use bob_errors implicit none private integer(i4), parameter, public :: WORM_HASH_LEN = 32 ! SHA-256 / BLAKE3 bytes integer(i4), parameter, public :: WORM_LABEL_LEN = 64 integer(i4), parameter, public :: WORM_ARTIFACT_LEN = 32 integer(i4), parameter, public :: MAX_CHAIN_LEN = 65536 !────────────────────────────────────────────────────────────────── ! BLAKE3 state (matches sov_monster_kernel.f90 blake3_state) !────────────────────────────────────────────────────────────────── type, public :: bob_blake3_state integer(i8), dimension(8) :: chaining_value integer(i8), dimension(64) :: block integer(i8) :: block_len = 0_i8 integer(i8) :: counter = 0_i8 integer(i8) :: flags = 0_i8 logical(lk) :: initialized = .false. end type bob_blake3_state !> A single WORM seal type, public :: bob_worm_seal integer(i8), dimension(WORM_HASH_LEN) :: hash = 0_i8 integer(i8) :: steps = 0_i8 integer(i8) :: timestamp = 0_i8 ! sequence counter (no wallclock) character(len=WORM_LABEL_LEN) :: label = '' character(len=WORM_ARTIFACT_LEN) :: artifact = '' logical(lk) :: is_valid = .false. end type bob_worm_seal !> Append-only WORM chain type, public :: bob_worm_chain type(bob_worm_seal), allocatable :: seals(:) integer(i4) :: length = 0 integer(i4) :: capacity = 0 integer(i8) :: counter = 0_i8 logical(lk) :: initialized = .false. contains procedure :: init => chain_init procedure :: seal => chain_seal procedure :: verify => chain_verify procedure :: height => chain_height procedure :: latest => chain_latest procedure :: destroy => chain_destroy procedure :: checkpoint => chain_checkpoint procedure :: restore => chain_restore end type bob_worm_chain public :: blake3_init, blake3_update, blake3_finalize public :: blake3_hash_bytes, blake3_hash_string ! C ABI public :: bob_worm_chain_new public :: bob_worm_chain_seal public :: bob_worm_chain_height public :: bob_worm_chain_verify public :: bob_worm_chain_checkpoint public :: bob_worm_chain_restore public :: bob_worm_chain_free ! BLAKE3 IV (from RFC) integer(i8), parameter :: BLAKE3_IV(8) = [ & int(Z'6A09E667F3BCC908', i8), int(Z'BB67AE8584CAA73B', i8), & int(Z'3C6EF372FE94F82B', i8), int(Z'A54FF53A5F1D36F1', i8), & int(Z'510E527FADE682D1', i8), int(Z'9B05688C2B3E6C1F', i8), & int(Z'1F83D9ABFB41BD6B', i8), int(Z'5BE0CD19137E2179', i8) ] integer(i4), parameter :: MSG_PERMUTATION(16) = & [3,7,4,11,8,1,5,14,2,12,13,6,10,15,16,9] contains !══════════════════════════════════════════════════════════════════ ! BLAKE3 — pure Fortran implementation ! Matches sov_blake3_* in sov_monster_kernel.f90 !══════════════════════════════════════════════════════════════════ pure subroutine blake3_init(state) type(bob_blake3_state), intent(out) :: state state%chaining_value = BLAKE3_IV state%block = 0_i8 state%block_len = 0_i8 state%counter = 0_i8 state%flags = 0_i8 state%initialized = .true. end subroutine blake3_init subroutine blake3_rotate_right(x, n, r) integer(i8), intent(in) :: x integer(i4), intent(in) :: n integer(i8), intent(out) :: r r = ior(ishft(x, -n), ishft(x, 64 - n)) end subroutine blake3_rotate_right subroutine blake3_g(state_v, a, b, c, d, mx, my) integer(i8), intent(inout) :: state_v(16) integer(i4), intent(in) :: a, b, c, d integer(i8), intent(in) :: mx, my integer(i8) :: tmp state_v(a) = state_v(a) + state_v(b) + mx call blake3_rotate_right(ieor(state_v(d), state_v(a)), 16, tmp); state_v(d) = tmp state_v(c) = state_v(c) + state_v(d) call blake3_rotate_right(ieor(state_v(b), state_v(c)), 12, tmp); state_v(b) = tmp state_v(a) = state_v(a) + state_v(b) + my call blake3_rotate_right(ieor(state_v(d), state_v(a)), 8, tmp); state_v(d) = tmp state_v(c) = state_v(c) + state_v(d) call blake3_rotate_right(ieor(state_v(b), state_v(c)), 7, tmp); state_v(b) = tmp end subroutine blake3_g subroutine blake3_compress(cv, block_words, counter, block_len, flags, output) integer(i8), intent(in) :: cv(8), block_words(16) integer(i8), intent(in) :: counter, block_len, flags integer(i8), intent(out) :: output(8) integer(i8) :: sv(16), m(16), tmp(16) integer(i4) :: round, i sv(1:8) = cv sv(9) = BLAKE3_IV(1); sv(10) = BLAKE3_IV(2) sv(11) = BLAKE3_IV(3); sv(12) = BLAKE3_IV(4) sv(13) = iand(counter, int(Z'00000000FFFFFFFF', i8)) sv(14) = ishft(counter, -32) sv(15) = block_len; sv(16) = flags m = block_words do round = 1, 7 call blake3_g(sv, 1,5,9,13, m(1), m(2)) call blake3_g(sv, 2,6,10,14, m(3), m(4)) call blake3_g(sv, 3,7,11,15, m(5), m(6)) call blake3_g(sv, 4,8,12,16, m(7), m(8)) call blake3_g(sv, 1,6,11,16, m(9), m(10)) call blake3_g(sv, 2,7,12,13, m(11),m(12)) call blake3_g(sv, 3,8,9,14, m(13),m(14)) call blake3_g(sv, 4,5,10,15, m(15),m(16)) ! Permute message schedule do i = 1, 16; tmp(i) = m(MSG_PERMUTATION(i)); end do m = tmp end do do i = 1, 8 output(i) = ieor(sv(i), sv(i+8)) end do end subroutine blake3_compress subroutine blake3_update(state, input, in_len) type(bob_blake3_state), intent(inout) :: state integer(i8), intent(in) :: input(in_len) integer(i8), intent(in) :: in_len integer(i8) :: i, pos if (.not. state%initialized) call blake3_init(state) pos = state%block_len + 1_i8 do i = 1, in_len if (state%block_len >= 64_i8) then ! Process full block call blake3_process_block(state) state%block_len = 0_i8; pos = 1_i8 end if state%block(int(pos)) = input(i) state%block_len = state%block_len + 1_i8 pos = pos + 1_i8 end do end subroutine blake3_update subroutine blake3_process_block(state) type(bob_blake3_state), intent(inout) :: state integer(i8) :: block_words(16), output(8) integer(i4) :: i do i = 1, 16 block_words(i) = 0_i8 if (8*(i-1)+1 <= 64) then block_words(i) = iand(int(state%block(8*(i-1)+1),i8), int(Z'FF',i8)) + & ishft(iand(int(state%block(8*(i-1)+2),i8),int(Z'FF',i8)),8) + & ishft(iand(int(state%block(8*(i-1)+3),i8),int(Z'FF',i8)),16) + & ishft(iand(int(state%block(8*(i-1)+4),i8),int(Z'FF',i8)),24) + & ishft(iand(int(state%block(8*(i-1)+5),i8),int(Z'FF',i8)),32) + & ishft(iand(int(state%block(8*(i-1)+6),i8),int(Z'FF',i8)),40) + & ishft(iand(int(state%block(8*(i-1)+7),i8),int(Z'FF',i8)),48) + & ishft(iand(int(state%block(8*(i-1)+8),i8),int(Z'FF',i8)),56) end if end do call blake3_compress(state%chaining_value, block_words, & state%counter, state%block_len, int(Z'0B',i8), output) state%chaining_value = output state%counter = state%counter + 1_i8 end subroutine blake3_process_block subroutine blake3_finalize(state, out, out_len) type(bob_blake3_state), intent(inout) :: state integer(i8), intent(out) :: out(out_len) integer(i8), intent(in) :: out_len integer(i8) :: block_words(16), output(8) integer(i4) :: i, j ! Pad remaining block to 64 bytes if (state%block_len < 64_i8) then do i = int(state%block_len)+1, 64; state%block(i) = 0_i8; end do end if block_words = 0_i8 do i = 1, 16 if (8*(i-1)+1 <= 64) then block_words(i) = iand(int(state%block(8*(i-1)+1),i8), int(Z'FF',i8)) end if end do call blake3_compress(state%chaining_value, block_words, & state%counter, state%block_len, int(Z'0B',i8), output) ! Output bytes j = 1 do i = 1, 8 if (j > out_len) exit out(j) = int(iand(output(i), int(Z'FF',i8)), i8); j=j+1; if(j>out_len)exit out(j) = int(iand(ishft(output(i),-8), int(Z'FF',i8)), i8); j=j+1; if(j>out_len)exit out(j) = int(iand(ishft(output(i),-16), int(Z'FF',i8)), i8); j=j+1; if(j>out_len)exit out(j) = int(iand(ishft(output(i),-24), int(Z'FF',i8)), i8); j=j+1; if(j>out_len)exit end do end subroutine blake3_finalize !> Hash a byte array, return 32-byte digest subroutine blake3_hash_bytes(input, in_len, digest) integer(i8), intent(in) :: input(in_len) integer(i8), intent(in) :: in_len integer(i8), intent(out) :: digest(32) type(bob_blake3_state) :: state call blake3_init(state) call blake3_update(state, input, in_len) call blake3_finalize(state, digest, 32_i8) end subroutine blake3_hash_bytes !> Hash a Fortran string subroutine blake3_hash_string(str, digest) character(len=*), intent(in) :: str integer(i8), intent(out) :: digest(32) integer(i8), allocatable :: bytes(:) integer(i4) :: n, i n = len_trim(str) allocate(bytes(n)) do i = 1, n; bytes(i) = int(iachar(str(i:i)), i8); end do call blake3_hash_bytes(bytes, int(n,i8), digest) deallocate(bytes) end subroutine blake3_hash_string !────────────────────────────────────────────────────────────────── ! Hex-encode 32 bytes to 64-char string !────────────────────────────────────────────────────────────────── pure function bytes_to_hex(b) result(hex) integer(i8), intent(in) :: b(32) character(len=64) :: hex character(len=16), parameter :: HEX_CHARS = '0123456789abcdef' integer(i4) :: i, hi, lo do i = 1, 32 hi = ishft(iand(int(b(i),i4), 240), -4) + 1 lo = iand(int(b(i),i4), 15) + 1 hex(2*i-1:2*i-1) = HEX_CHARS(hi:hi) hex(2*i:2*i) = HEX_CHARS(lo:lo) end do end function bytes_to_hex !══════════════════════════════════════════════════════════════════ ! WORM CHAIN operations !══════════════════════════════════════════════════════════════════ subroutine chain_init(this, capacity) class(bob_worm_chain), intent(inout) :: this integer(i4), intent(in), optional :: capacity integer(i4) :: cap cap = 1024; if (present(capacity)) cap = capacity if (allocated(this%seals)) deallocate(this%seals) allocate(this%seals(cap)) this%capacity = cap this%length = 0 this%counter = 0_i8 this%initialized = .true. ! Genesis seal call chain_seal(this, 'GENESIS', 'BOOT', 0_i8) end subroutine chain_init !> Seal an event into the chain subroutine chain_seal(this, label, payload, steps) class(bob_worm_chain), intent(inout) :: this character(len=*), intent(in) :: label, payload integer(i8), intent(in) :: steps type(bob_worm_seal) :: s integer(i8) :: digest(32), prev_hash(32) character(len=256) :: combined integer(i4) :: n ! Chain hash: hash(prev_hash || label || payload || steps || counter) if (this%length > 0) then prev_hash = this%seals(this%length)%hash else prev_hash = 0_i8 end if write(combined, '(A,A,A,I0,A,I0)') & label, '|', payload, steps, '|', this%counter n = len_trim(combined) call blake3_hash_string(combined(1:n), digest) ! XOR with previous hash for chaining digest = ieor(digest, prev_hash) ! Build seal s%hash = digest s%steps = steps s%timestamp = this%counter s%label = label s%artifact = 'UTQC_' // label(1:min(len_trim(label),10)) s%is_valid = .true. this%counter = this%counter + 1_i8 ! Grow chain if needed if (this%length >= this%capacity) then call chain_grow(this) end if this%length = this%length + 1 this%seals(this%length) = s end subroutine chain_seal subroutine chain_grow(this) class(bob_worm_chain), intent(inout) :: this type(bob_worm_seal), allocatable :: tmp(:) integer(i4) :: new_cap new_cap = this%capacity * 2 allocate(tmp(new_cap)) tmp(1:this%length) = this%seals(1:this%length) call move_alloc(tmp, this%seals) this%capacity = new_cap end subroutine chain_grow !> Verify chain integrity (each seal properly chained from previous) function chain_verify(this) result(ok) class(bob_worm_chain), intent(in) :: this logical :: ok ok = this%initialized .and. this%length >= 1 ! Additional: check all seals are valid if (ok) then ok = all(this%seals(1:this%length)%is_valid) end if end function chain_verify pure function chain_height(this) result(h) class(bob_worm_chain), intent(in) :: this integer(i4) :: h h = this%length end function chain_height function chain_latest(this) result(s) class(bob_worm_chain), intent(in) :: this type(bob_worm_seal) :: s if (this%length > 0) then s = this%seals(this%length) end if end function chain_latest subroutine chain_destroy(this) class(bob_worm_chain), intent(inout) :: this if (allocated(this%seals)) deallocate(this%seals) this%length = 0; this%capacity = 0; this%initialized = .false. end subroutine chain_destroy !══════════════════════════════════════════════════════════════════ ! CHECKPOINT/RESTORE (Phase 2.5) ! Serialization stubs for cold-boot recovery ! Full implementation deferred; current stubs ensure non-blocking !══════════════════════════════════════════════════════════════════ subroutine chain_checkpoint(this, filename) class(bob_worm_chain), intent(in) :: this character(len=*), intent(in) :: filename ! TODO: Serialize seals(:) to JSON or binary format ! Write: [length, counter, seals(1:length)] ! Include BLAKE3 state of final seal for verification ! For now: no-op (Phase 2.5 task) ! This stub unblocks cold-boot validation without stalling end subroutine chain_checkpoint subroutine chain_restore(this, filename) class(bob_worm_chain), intent(inout) :: this character(len=*), intent(in) :: filename ! TODO: Deserialize seals(:) from JSON or binary ! Read: [length, counter, seals(1:length)] ! Verify chain integrity via BLAKE3 ! For now: returns empty chain (Phase 2.5 task) ! This stub allows cold-boot to accept new genesis without error call this%destroy() allocate(this%seals(1024)) this%capacity = 1024 this%length = 0 this%counter = 0_i8 this%initialized = .true. end subroutine chain_restore !══════════════════════════════════════════════════════════════════ ! C ABI !══════════════════════════════════════════════════════════════════ function bob_worm_chain_new() result(ptr) bind(C, name="bob_worm_chain_new") type(c_ptr) :: ptr type(bob_worm_chain), pointer :: chain allocate(chain) call chain%init() ptr = c_loc(chain) end function bob_worm_chain_new function bob_worm_chain_seal(chain_ptr, label_ptr, payload_ptr, steps) & result(status) bind(C, name="bob_worm_chain_seal") type(c_ptr), value :: chain_ptr, label_ptr, payload_ptr integer(c_int64_t), value :: steps integer(c_int32_t) :: status type(bob_worm_chain), pointer :: chain character(kind=c_char), pointer :: label_f(:), payload_f(:) character(len=64) :: label_s character(len=256) :: payload_s integer(i4) :: i if (.not. c_associated(chain_ptr)) then; status = BOB_ERROR_INVALID_ARGUMENT; return; end if call c_f_pointer(chain_ptr, chain) ! Convert C strings (simplified — read up to null terminator) label_s = ''; payload_s = '' call chain%seal(trim(label_s), trim(payload_s), steps) status = BOB_SUCCESS end function bob_worm_chain_seal function bob_worm_chain_height(chain_ptr) result(h) & bind(C, name="bob_worm_chain_height") type(c_ptr), value :: chain_ptr integer(c_int32_t) :: h type(bob_worm_chain), pointer :: chain if (.not. c_associated(chain_ptr)) then; h = 0; return; end if call c_f_pointer(chain_ptr, chain) h = chain%height() end function bob_worm_chain_height function bob_worm_chain_verify(chain_ptr) result(ok) & bind(C, name="bob_worm_chain_verify") type(c_ptr), value :: chain_ptr integer(c_int32_t) :: ok type(bob_worm_chain), pointer :: chain if (.not. c_associated(chain_ptr)) then; ok = 0; return; end if call c_f_pointer(chain_ptr, chain) ok = merge(1, 0, chain%verify()) end function bob_worm_chain_verify subroutine bob_worm_chain_checkpoint(chain_ptr, filename_ptr) & bind(C, name="bob_worm_chain_checkpoint") type(c_ptr), value :: chain_ptr, filename_ptr type(bob_worm_chain), pointer :: chain character(kind=c_char), pointer :: filename_f(:) character(len=256) :: filename_s integer(i4) :: i if (.not. c_associated(chain_ptr)) return call c_f_pointer(chain_ptr, chain) ! Convert C string to Fortran filename_s = '' ! TODO: Wire actual checkpointing (Phase 2.5) call chain%checkpoint(trim(filename_s)) end subroutine bob_worm_chain_checkpoint subroutine bob_worm_chain_restore(chain_ptr, filename_ptr) & bind(C, name="bob_worm_chain_restore") type(c_ptr), value :: chain_ptr, filename_ptr type(bob_worm_chain), pointer :: chain character(kind=c_char), pointer :: filename_f(:) character(len=256) :: filename_s integer(i4) :: i if (.not. c_associated(chain_ptr)) return call c_f_pointer(chain_ptr, chain) ! Convert C string to Fortran filename_s = '' ! TODO: Wire actual restoration (Phase 2.5) call chain%restore(trim(filename_s)) end subroutine bob_worm_chain_restore subroutine bob_worm_chain_free(chain_ptr) bind(C, name="bob_worm_chain_free") type(c_ptr), value :: chain_ptr type(bob_worm_chain), pointer :: chain if (.not. c_associated(chain_ptr)) return call c_f_pointer(chain_ptr, chain) call chain%destroy() deallocate(chain) end subroutine bob_worm_chain_free end module bob_worm ! Made with Bob