|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| integer(i4), parameter, public :: WORM_LABEL_LEN = 64
|
| integer(i4), parameter, public :: WORM_ARTIFACT_LEN = 32
|
| integer(i4), parameter, public :: MAX_CHAIN_LEN = 65536
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
| type, public :: bob_worm_seal
|
| integer(i8), dimension(WORM_HASH_LEN) :: hash = 0_i8
|
| integer(i8) :: steps = 0_i8
|
| integer(i8) :: timestamp = 0_i8
|
| character(len=WORM_LABEL_LEN) :: label = ''
|
| character(len=WORM_ARTIFACT_LEN) :: artifact = ''
|
| logical(lk) :: is_valid = .false.
|
| end type bob_worm_seal
|
|
|
|
|
| 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
|
|
|
|
|
| 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
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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))
|
|
|
| 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
|
|
|
| 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
|
|
|
| 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)
|
|
|
| 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
|
|
|
|
|
| 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
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
|
|
| call chain_seal(this, 'GENESIS', 'BOOT', 0_i8)
|
| end subroutine chain_init
|
|
|
|
|
| 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
|
|
|
| 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)
|
|
|
| digest = ieor(digest, prev_hash)
|
|
|
| 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
|
|
|
| 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
|
|
|
|
|
| function chain_verify(this) result(ok)
|
| class(bob_worm_chain), intent(in) :: this
|
| logical :: ok
|
| ok = this%initialized .and. this%length >= 1
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| subroutine chain_checkpoint(this, filename)
|
| class(bob_worm_chain), intent(in) :: this
|
| character(len=*), intent(in) :: filename
|
|
|
|
|
|
|
|
|
|
|
| end subroutine chain_checkpoint
|
|
|
| subroutine chain_restore(this, filename)
|
| class(bob_worm_chain), intent(inout) :: this
|
| character(len=*), intent(in) :: filename
|
|
|
|
|
|
|
|
|
|
|
| call this%destroy()
|
| allocate(this%seals(1024))
|
| this%capacity = 1024
|
| this%length = 0
|
| this%counter = 0_i8
|
| this%initialized = .true.
|
| end subroutine chain_restore
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
|
|
| 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)
|
|
|
| filename_s = ''
|
|
|
| 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)
|
|
|
| filename_s = ''
|
|
|
| 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
|
|
|
|
|
|
|