/=  ztd-five  /common/ztd/five
=>  ztd-five
~%  %fri  ..proof-stream  ~
::    fri
|%
+$  fri-input
  $:  offset=belt
      omega=belt
      init-domain-len=@
      expansion-fac=@
      num-spot-checks=@
      folding-deg=@
  ==
::
++  compute-eval-domain
  ~/  %compute-eval-domain
  |=  [domain-len=@ omega=belt offset=@]
  ^-  (list belt)
  ~+
  =-  (flop acc)
  %+  roll  (range domain-len)
  |=  [i=@ acc=(list felt) omega-pow=_1]
  :_  (bmul omega-pow omega)
  [(bmul offset omega-pow) acc]
::
++  fri-door
  =,  merkle
  =,  proof-stream
  ~/  %fri-door
  =|  fri-input
  |%
  ++  log-folding-deg
    ~+
    ^-  @
    (dec (xeb folding-deg))
  ::
  ++  last-codeword-len
    ~+
    ^-  @
    (div init-domain-len (pow folding-deg num-rounds))
  ::
  ++  eval-domain
    ~+
    ^-  (list belt)
    (compute-eval-domain init-domain-len omega offset)
  ::
  ++  num-rounds
    ~+
    ^-  @
    =/  len  init-domain-len
    =/  num  0
    |-
    ?:  &((gth len expansion-fac) (lth (mul 4 num-spot-checks) len))
      $(num +(num), len (div len folding-deg))
    (max 1 (dec num))
  ::
  ::
  ++  prove
    ~/  %prove
    |=  [codeword=fpoly stream=proof]
    ^-  [fri-indices=(list @) stream=proof]
    |^
    ::  commit phase
    =^  codewords=(list codeword-data)  stream
      (commit codeword stream)
    ::
    ::  query phase
    (query codewords stream)
    ::
    ::
    +$  codeword-data  [codeword=mary merk=(unit [depth=@ heap=merk-heap])]
    ::
    ++  query
      |=  [codewords=(list codeword-data) stream=proof]
      ^-  [fri-indices=(list @) stream=proof]
      ::
      ::  Get random indices from the verifier to spot-check the folding
      =/  rng  ~(prover-fiat-shamir proof-stream stream)
      =^  fri-indices=(list @)  rng
        %-  indices:rng
        :+  num-spot-checks
          init-domain-len
        last-codeword-len
      ::
      =-  [fri-indices stream]
      %^  zip-roll  (range num-rounds)  codewords
      |=  [[round=@ data=codeword-data] indices=_fri-indices stream=_stream]
      =/  len  len.array:(~(change-step ave codeword.data) 3)
      =-  [(flop new-indices) stream]
      %+  roll  indices
      |=  [idx=@ new-indices=(list @) stream=_stream]
      ::
      =/  coset-idx  (mod idx (div len folding-deg))
      =/  merk  (need merk.data)
      =/  axis  (index-to-axis depth.merk coset-idx)
      ::  Compute merkle opening to idx in codeword and send to the verifier
      =/  leaf=fpoly
        (~(snag-as-fpoly ave codeword.data) coset-idx)
      =/  opening=merk-proof:merkle
        (build-merk-proof:merkle heap.merk axis)
      :-  [coset-idx new-indices]
      (~(push proof-stream stream) [%m-path leaf path.opening])
    ::
    ++  commit
      |=  [codeword=fpoly stream=proof]
      ^-  [codewords=(list codeword-data) stream=proof]
      =-  [(flop codewords) stream]
      %+  roll  (range +(num-rounds))
      |=  [round=@ codeword=_codeword codewords=(list codeword-data) omega=_(lift omega) round-offset=_(lift offset) stream=_stream]
      ?:  =(round num-rounds)
        ::  If it's the last round, send the raw codeword to the verifier instead
        ::  of a merkle tree
          :*   zero-fpoly
               codewords
               (fpow omega folding-deg)
               (fpow round-offset folding-deg)
               (~(push proof-stream stream) [%codeword codeword])
          ==
      ::
      =/  num  (div len.codeword folding-deg)
      ::
      ::  sort codeword into cosets
      =/  cosets=mary
        %-  zing-fpolys
        %+  turn  (range num)
        |=  k=@
        %-  init-fpoly
        %+  turn  (range folding-deg)
        |=  i=@
        =/  idx  (add (mul i num) k)
        (~(snag fop codeword) idx)
      ::
      ::  send codeword (as cosets) to verifier
      =/  merk=(pair @ merk-heap:merkle)
        (build-merk-heap:merkle cosets)
      =.  stream
        (~(push proof-stream stream) [%m-root h.q.merk])
      ::
      ::  get challenge from verifier
      =/  rng  ~(prover-fiat-shamir proof-stream stream)
      =^  alpha=felt  rng  $:felt:rng
      ::
      ::  compute new codeword
      =/  new-codeword=fpoly
        %-  init-fpoly
        %+  turn  (range len.array.cosets)
        |=  i=@
        =/  coset=fpoly  (~(snag-as-fpoly ave cosets) i)
        =/  eval-point=felt  (fdiv alpha (fmul round-offset (fpow omega i)))
        ::=/  eval-point=felt  (fdiv alpha (fpow omega i))
        (fpeval (fp-ifft coset) eval-point)
      ::
      :*  new-codeword
          [[cosets (some merk)] codewords]
          (fpow omega folding-deg)
          (fpow round-offset folding-deg)
          stream
      ==
    --
  ::
  ++  verify
    ~/  %verify
    |=  [stream=proof root=noun-digest:tip5]
    ^-  [[top-level-indices=(list @) merks=(list merk-data) deep-cosets=(map @ fpoly) res=?] proof]
    ::
    ::  extract roots and alphas values from commit phase
    =^  [roots=(list noun-digest:tip5) alphas=(list felt)]  stream
      =/  roots=(list noun-digest:tip5)  [root]~
      =-  [[(flop roots) (flop alphas)] str]
      %+  roll  (range num-rounds)
      |=  [i=@ str=_stream roots=_roots alphas=(list felt)]
      ?:  =(i 0)
        :+  str  roots
        ^-  (list felt)
        :_  alphas
        =/  rng  ~(verifier-fiat-shamir proof-stream str)
        =^  felt  rng  $:felt:rng
        felt
      =^  root  str
        =^(r str ~(pull proof-stream str) ?>(?=(%m-root -.r) p.r^str))
      :+  str
        [root roots]
      ^-  (list felt)
      :_  alphas
      =/  rng  ~(verifier-fiat-shamir proof-stream str)
      =^  felt  rng  $:felt:rng
      felt
    ?>  =((lent roots) (lent alphas))
    ::
    ::  extract last codeword
    =^  last-codeword=fpoly  stream
      =^(c stream ~(pull proof-stream stream) ?>(?=(%codeword -.c) p.c^stream))
    ::
    ::  Verify that the last codeword is low degree
    ~|  "last codeword len is not correct"
    ?>  =(len.last-codeword last-codeword-len)
    =/  poly  (fp-ifft last-codeword)
    =/  deg  (fdegree ~(to-poly fop poly))
    =/  degree-bound
      %+  div
        (div init-domain-len expansion-fac)
      (pow folding-deg num-rounds)
    ~|  "last codeword is not low degree"
    ?>  (lth deg degree-bound)
    ::
    ::  get indices
    =/  [top-level-indices=(list @) *]
      =/  rng  ~(verifier-fiat-shamir proof-stream stream)
      %-  indices:rng
      :+  num-spot-checks
        init-domain-len
      last-codeword-len
    ::
    ::  Read cosets out of first codeword
    =^  [indices=(list @) deep-cosets=(map @ fpoly) merks=(list merk-data)]  stream
      =-  [[(flop new-indices) cosets merks] stream]
      %+  roll  top-level-indices
      |=  [idx=@ [new-indices=(list @) cosets=(map @ fpoly) merks=(list merk-data)] stream=_stream]
      =/  coset-idx  (mod idx (div init-domain-len folding-deg))
      =/  depth  (xeb (div init-domain-len folding-deg))
      =/  axis  (index-to-axis depth coset-idx)
      ::  read opening for index from proof
      =^  opening=proof-path  stream
        =^(o stream ~(pull proof-stream stream) ?>(?=(%m-path -.o) p.o^stream))
      ::
      :_  stream
      :+  [idx new-indices]
        (~(put by cosets) coset-idx leaf.opening)
      :-  [(hash-hashable:tip5 (hashable-fpoly:tip5 leaf.opening)) axis root path.opening]
      merks
    ::
    ::
    =-  [[top-level-indices merks deep-cosets %.y] stream]
    %+  roll  (range num-rounds)
    |=  $:  round=@
            roots=_(tail roots)
            alphas=_alphas
            prev-indices=_indices
            prev-cosets=_deep-cosets
            prev-len=_init-domain-len
            omega=_(lift omega)
            round-offset=_(lift offset)
            merks=_merks
            stream=_stream
        ==
    ::
    =/  new-len  (div prev-len folding-deg)
    ::  Read all cosets out of the next codeword
    =^  [indices=(list @) cosets=(map @ fpoly) merks=(list merk-data)]  stream
      ?:  =(+(round) num-rounds)
        :: if it's the last round, we use the codeword which was written into
        :: the proof in the clear
        [[~ ~ merks] stream]
      =/  root  (head roots)
      =-  [[(flop new-indices) cosets merks] stream]
      %+  roll  prev-indices
      |=  [prev-idx=@ new-indices=(list @) cosets=(map @ fpoly) merks=_merks stream=_stream]
      =/  new-idx  (mod prev-idx (div prev-len folding-deg))
      =/  coset-idx  (mod new-idx (div new-len folding-deg))
      =/  depth  (xeb (div new-len folding-deg))
      =/  axis  (index-to-axis depth coset-idx)
      ::  read opening for index from proof
      =^  opening=proof-path  stream
        =^(o stream ~(pull proof-stream stream) ?>(?=(%m-path -.o) p.o^stream))
      ::
      :^    [new-idx new-indices]
          (~(put by cosets) coset-idx leaf.opening)
        :-  [(hash-hashable:tip5 (hashable-fpoly:tip5 leaf.opening)) axis root path.opening]
        merks
      stream
    ::
    ::
    ::  Check folds
    =/  alpha  (head alphas)
    =/  res=?
      %+  levy  prev-indices
      |=  prev-idx=@
      =/  prev-coset-idx  (mod prev-idx (div prev-len folding-deg))
      =/  folded-val=felt
        =/  coeffs=fpoly
          (fp-ifft (~(got by prev-cosets) prev-coset-idx))
        =/  eval-point=felt
          (fdiv alpha (fmul round-offset (fpow omega prev-coset-idx)))
        (fpeval coeffs eval-point)
      =/  new-coset-idx  (mod prev-coset-idx (div new-len folding-deg))
      =/  new-codeword-val=felt
        ?:  =(+(round) num-rounds)
          :: for the last round, just read the value directly out of the last codeword
          (~(snag fop last-codeword) prev-coset-idx)
        =/  entry  (div prev-coset-idx (div new-len folding-deg))
        =/  coset  (~(got by cosets) new-coset-idx)
        (~(snag fop coset) entry)
      =(folded-val new-codeword-val)
    ::
    ::  crash if folds were incorrect
    ?>  =(res %.y)
    ::
    :*  ?~(roots roots (tail roots))
        ?~(alphas alphas (tail alphas))
        indices
        cosets
        new-len
        (fpow omega folding-deg)
        (fpow round-offset folding-deg)
        merks
        stream
    ==
  --  ::fri-door
--  ::fri
