/= ztd-one /common/ztd/one => ztd-one ~% %ext-field ..belt ~ :: math-ext: arithmetic for elements and polynomials over the extension field. |_ deg=_`@`3 :: field extension degree :: finite field arithmetic +| %felt-math :: :: +deg-to-irp: ++ deg-to-irp ^- (map @ bpoly) %- ~(gas by *(map @ bpoly)) :~ [1 (init-bpoly ~[0 1])] [2 (init-bpoly ~[2 18.446.744.069.414.584.320 1])] [3 (init-bpoly ~[1 18.446.744.069.414.584.320 0 1])] == :: ++ f0 0x1.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000 ++ f1 0x1.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0000.0001 :: :: +lift: the unique lift of a base field element into an extension field ++ lift |= =belt ~+ ^- felt %- frep :- belt (reap (dec deg) 0) :: ++ drop |= =felt ^- belt :: inverse of lift :: (lift 7) => :: (snag 0 ) => 7 (snag 0 (felt-to-list felt)) :: ++ felt-to-list |= fel=felt ^- (list belt) (bpoly-to-list [deg fel]) :: :: :: +fat: is the atom a felt? ++ fat ~/ %fat |= a=@ ^- ? ~+ =/ v (rip 6 a) ?& =((lent v) +(deg)) (levy v based) == :: :: +frip: field rip - rip a felt into a list of belts ++ frip ~/ %frip |= a=felt ^- (list belt) ~+ ?> (fat a) (bpoly-to-list [deg a]) :: :: +frep: inverse of frip; list of belts are rep'd to a felt ++ frep ~/ %frep |= x=(list belt) ^- felt ~+ ?> =((lent x) deg) ?> (levy x based) dat:(init-bpoly x) :: :: +fadd: field addition ++ fadd ~/ %fadd |: [a=`felt`(lift 0) b=`felt`(lift 0)] ~+ ~| a ?> (fat a) ~| b ?> (fat b) %- frep (zip (frip a) (frip b) badd) :: :: +fneg: field negation ++ fneg ~/ %fneg |: a=`felt`(lift 0) ~+ ?> (fat a) %- frep (turn (frip a) bneg) :: :: +fsub: field subtraction ++ fsub ~/ %fsub |: [a=`felt`(lift 0) b=`felt`(lift 0)] ^- felt ~+ (fadd a (fneg b)) :: ++ fmul-naive ~/ %fmul-naive |: [a=`felt`(lift 1) b=`felt`(lift 1)] ^- felt ~+ ~| `@ux`a ?> (fat a) ~| `@ux`b ?> (fat b) =/ result-poly %- bpoly-to-list %+ bpmod (bpmul deg^a deg^b) (~(got by deg-to-irp) deg) %- frep %+ bzero-extend result-poly (sub deg (lent result-poly)) :: :: :: +fmul: field multiplication :: :: Multiply field extension elements and reduce by using the algebraic identities of the basis :: vectors. :: :: We are reducing mod x^3-x+1. So, :: x^3-x+1=0 :: => x^3 = x-1 :: => x^4 = x^2-x :: :: So, :: (a0+a1x+a2x^2)*(b0+b1x+b2x^2) :: = a0b0 + [a0b1+a1b0]x + [a0b2+a1b1+a2b0]x^2 + [a1b2+a2b1]x^3 + a2b2x^4 :: :: Substituting the reductions above we get :: :: (a0+a1x+a2x^2)*(b0+b1x+b2x^2) :: = a0b0 + [a0b1+a1b0]x + [a0b2+a1b1+a2b0]x^2 + [a1b2+a2b1](x-1) + a2b2(x^2-x) :: :: = [a0b0-a1b2-a2b1] + [a0b1+a1b0+a1b2+a2b1-a2b2]x + [a0b2+a1b1+a2b0+a2b2]x^2. :: :: And finally we use the karatsuba trick to reduce multiplications. :: ++ fmul ~/ %fmul |: [a=`felt`(lift 1) b=`felt`(lift 1)] ^- felt ~+ ~| `@ux`a ?> (fat a) ~| `@ux`b ?> (fat b) =/ a [3 a] =/ b [3 b] =/ a0=belt (~(snag bop a) 0) =/ a1=belt (~(snag bop a) 1) =/ a2=belt (~(snag bop a) 2) =/ b0=belt (~(snag bop b) 0) =/ b1=belt (~(snag bop b) 1) =/ b2=belt (~(snag bop b) 2) :: =/ a0b0 (bmul a0 b0) =/ a1b1 (bmul a1 b1) =/ a2b2 (bmul a2 b2) =/ a0b1-a1b0 (bsub (bsub (bmul (badd a0 a1) (badd b0 b1)) a0b0) a1b1) =/ a1b2-a2b1 (bsub (bsub (bmul (badd a1 a2) (badd b1 b2)) a1b1) a2b2) =/ a0b2-a2b0 (bsub (bsub (bmul (badd a0 a2) (badd b0 b2)) a0b0) a2b2) %- frep :~ (bsub a0b0 a1b2-a2b1) (bsub (badd a0b1-a1b0 a1b2-a2b1) a2b2) :(badd a0b2-a2b0 a1b1 a2b2) == :: :: +finv: field inversion ++ finv ~/ %finv |: a=`felt`(lift 1) ^- felt ~+ ?> (fat a) ?< =(a (lift 0)) =/ egcd=[d=bpoly u=bpoly v=bpoly] %+ bpegcd (~(got by deg-to-irp) deg) deg^a =/ d (bpoly-to-list d.egcd) =/ u (bpoly-to-list u.egcd) =/ v (bpoly-to-list v.egcd) ?> =((bdegree d) 0) ?< ?=(~ d) =/ result-poly %- bpoly-to-list (bpscal (binv i.d) v.egcd) %- frep %+ bzero-extend result-poly (sub deg (lent result-poly)) :: :: +mass-inversion: inverts list of elements by cleverly performing only a single inversion ++ mass-inversion ~/ %mass-inversion |= lis=(list felt) ^- (list felt) |^ =/ all-prods (accumulate-products lis) ?< ?=(~ all-prods) =. lis (flop lis) =/ acc (finv i.all-prods) =/ prods t.all-prods =| invs=(list felt) |- ?~ prods [acc invs] ?< ?=(~ lis) %= $ lis t.lis prods t.prods acc (fmul acc i.lis) invs [(fmul acc i.prods) invs] == ++ accumulate-products |= lis=(list felt) ^- (list felt) =| res=(list felt) =/ acc (lift 1) |- ?~ lis res ?: =(i.lis (lift 0)) ~| "Cannot invert 0!" !! =/ new (fmul acc i.lis) $(acc new, res [new res], lis t.lis) -- :: :: +fdiv: division of field elements ++ fdiv ~/ %fdiv |: [a=`felt`(lift 1) b=`felt`(lift 1)] ^- felt ~+ (fmul a (finv b)) :: :: +fpow: field power; computes x^n ++ fpow ~/ %fpow |: [x=`felt`(lift 1) n=`@`0] ^- felt ~+ ?> (fat x) ~? (gte (met 3 n) (met 3 (lift 0))) "fpow: n is wayy too high and is likely a felt by accident." %. [(lift 1) x n] |= [y=felt x=felt n=@] ^- felt ?> (fat y) ?: =(n 0) y :: parity flag =/ f=@ (end 0 n) ?: =(0 f) $(x (fmul x x), n (rsh 0 n)) $(y (fmul y x), x (fmul x x), n (rsh 0 n)) :: :: +bpeval-lift: evaluate a bpoly at a felt ++ bpeval-lift ~/ %bpeval-lift |: [bp=`bpoly`one-bpoly x=`felt`(lift 1)] ^- felt ~+ ?: (bp-is-zero bp) (lift 0) ?: =(len.bp 1) (lift (~(snag bop bp) 0)) =/ p ~(to-poly bop bp) =. p (flop p) =/ res=@ (lift 0) |- ?~ p !! ?~ t.p (fadd (fmul res x) (lift i.p)) :: based on p(x) = (...((a_n)x + a_{n-1})x + a_{n-2})x + ... ) $(res (fadd (fmul res x) (lift i.p)), p t.p) :: :: general field polynomial methods and math +| %fpoly-math :: :: ++ fop ~(ops array-op 3) :: :: +fpadd: field polynomial addition ++ fpadd ~/ %fpadd |: [fp=`fpoly`zero-fpoly fq=`fpoly`zero-fpoly] ^- fpoly ?> &(!=(len.fp 0) !=(len.fq 0)) =/ p ~(to-poly fop fp) =/ q ~(to-poly fop fq) =/ lp (lent p) =/ lq (lent q) =/ m (max lp lq) =: p (weld p (reap (sub m lp) (lift 0))) q (weld q (reap (sub m lq) (lift 0))) == %- init-fpoly (zip p q fadd) :: :: +fpneg: additive inverse of a field polynomial ++ fpneg ~/ %fpneg |: fp=`fpoly`zero-fpoly ^- fpoly ?> !=(len.fp 0) ~+ =/ p ~(to-poly fop fp) %- init-fpoly (turn p fneg) :: :: fpscal: scale a polynomial by a field element ++ fpscal ~/ %fpscal |: [c=`felt`(lift 1) fp=`fpoly`one-fpoly] ^- fpoly ~+ =/ p ~(to-poly fop fp) %- init-fpoly %+ turn p (cury fmul c) :: :: +fpsub: field polynomial subtraction ++ fpsub ~/ %fpsub |: [p=`fpoly`zero-fpoly q=`fpoly`zero-fpoly] ^- fpoly ~+ ?> &(!=(len.p 0) !=(len.q 0)) (fpadd p (fpneg q)) :: ++ fp-is-zero ~/ %fp-is-zero |= p=fpoly ^- ? ~+ =. p (fpcan p) |(=(len.p 0) =(p zero-fpoly)) :: ++ fp-is-one ~/ %fp-is-one |= p=fpoly ^- ? ~+ =. p (fpcan p) &(=(len.p 1) =((~(snag fop p) 0) (lift 1))) :: :: f(x)=0 ++ zero-fpoly ~+ ^- fpoly (init-fpoly ~[(lift 0)]) :: :: f(x)=1 ++ one-fpoly ~+ ^- fpoly (init-fpoly ~[(lift 1)]) :: :: f(x)=x ++ id-fpoly ~+ ^- fpoly (init-fpoly ~[(lift 0) (lift 1)]) :: :: +init-fpoly: transforms a list of felts into its fpoly equivalent ++ init-fpoly ~/ %init-fpoly |= poly=(list felt) ^- fpoly ?~ poly [0 (lift 0)] array:(init-mary poly) :: :: +fcan: gives the canonical leading-zero-stripped representation of p(x) ++ fcan |= p=poly ^- poly =. p (flop p) |- ?~ p ~ ?: =(i.p (lift 0)) $(p t.p) (flop p) :: ++ fpcan |= fp=fpoly ^- fpoly =/ p ~(to-poly fop fp) (init-fpoly (fcan p)) :: :: +fdegree: computes the degree of a polynomial ++ fdegree |= p=poly ^- @ =/ cp=poly (fcan p) ?~ cp 0 (dec (lent cp)) :: :: +fzero-extend: make the zero coefficients for powers of x higher than deg(p) explicit ++ fzero-extend |= [p=poly much=@] ^- poly (weld p (reap much (lift 0))) :: ++ lift-to-fpoly ~/ %lift-to-fpoly |= poly=(list belt) ^- fpoly ?> (levy poly based) (init-fpoly (turn poly lift)) :: ++ fpoly-to-list ~/ %fpoly-to-list |= fp=fpoly ^- (list felt) ~| "len.fp must not be 0" ?> !=(len.fp 0) (mary-to-list [3 fp]) :: :: +bpoly-to-fpoly: lift a bpoly to an fpoly ++ bpoly-to-fpoly ~/ %bpoly-to-fpoly |= bp=bpoly ^- fpoly (lift-to-fpoly ~(to-poly bop bp)) :: :: +fpoly-from-dat :: :: given a dat=@ux, compute the length using met and return an fpoly ++ fpoly-from-dat |= dat=@ux ^- fpoly [(div (dec (met 6 dat)) 3) dat] :: :: +fp-ntt: number theoretic transform for fpolys based on anatomy of a stark ++ fp-ntt ~/ %fp-ntt |= [fp=fpoly root=felt] ^- fpoly ~+ ?: =(len.fp 1) fp =/ half (div len.fp 2) ?> =((fpow root len.fp) (lift 1)) ?< =((fpow root half) (lift 1)) =/ odds %+ fp-ntt %- init-fpoly %+ murn (range len.fp) |= i=@ ?: =(0 (mod i 2)) ~ `(~(snag fop fp) i) (fmul root root) =/ evens %+ fp-ntt %- init-fpoly %+ murn (range len.fp) |= i=@ ?: =(1 (mod i 2)) ~ `(~(snag fop fp) i) (fmul root root) %- init-fpoly %+ turn (range len.fp) |= i=@ %+ fadd (~(snag fop evens) (mod i half)) %+ fmul (fpow root i) (~(snag fop odds) (mod i half)) :: :: +bp-ntt: ntt over base field ++ bp-ntt ~/ %bp-ntt |= [bp=bpoly root=belt] ^- bpoly ~+ ?: =(len.bp 1) bp =/ half (div len.bp 2) ?> =((bpow root len.bp) 1) ?< =((bpow root half) 1) =/ odds %+ bp-ntt %- init-bpoly %+ murn (range len.bp) |= i=@ ?: =(0 (mod i 2)) ~ `(~(snag bop bp) i) (bmul root root) =/ evens %+ bp-ntt %- init-bpoly %+ murn (range len.bp) |= i=@ ?: =(1 (mod i 2)) ~ `(~(snag bop bp) i) (bmul root root) %- init-bpoly %+ turn (range len.bp) |= i=@ %+ badd (~(snag bop evens) (mod i half)) %+ bmul (bpow root i) (~(snag bop odds) (mod i half)) :: :: +fp-fft: Discrete Fourier Transform (DFT) with Fast Fourier Transform (FFT) algorithm ++ fp-fft ~/ %fp-fft |= p=fpoly ^- fpoly ~+ ~| "fft: must have power-of-2-many coefficients." ?> =(0 (dis len.p (dec len.p))) (fp-ntt p (lift (ordered-root len.p))) :: :: +fp-ifft: Inverse DFT with FFT algorithm ++ fp-ifft ~/ %ifft |= p=fpoly ^- fpoly ~+ ~| "ifft: must have power-of-2-many coefficients." ?> =((dis len.p (dec len.p)) 0) %+ fpscal (lift (binv len.p)) (fp-ntt p (lift (binv (ordered-root len.p)))) :: :: +bp-fft: fft over base field ++ bp-fft ~/ %bp-fft |= p=bpoly ^- bpoly ~+ ~| "bp-fft: must have power-of-2-many coefficients." ?> =(0 (dis len.p (dec len.p))) (bp-ntt p (ordered-root len.p)) :: :: +bp-ifft: ifft over base field ++ bp-ifft ~/ %bp-ifft |= p=bpoly ^- bpoly ~+ ~| "bp-ifft: must have power-of-2-many coefficients." ?> =((dis len.p (dec len.p)) 0) %+ bpscal (binv len.p) (bp-ntt p (binv (ordered-root len.p))) :: :: +fpmul-naive: high school polynomial multiplication ++ fpmul-naive ~/ %fpmul-naive |= [fp=fpoly fq=fpoly] ^- fpoly ~+ =/ p ~(to-poly fop fp) =/ q ~(to-poly fop fq) %- init-fpoly ?: ?|(=(~ p) =(~ q)) ~ =/ v=(list felt) %- weld :_ p (reap (dec (lent q)) (lift 0)) =/ w=(list felt) (flop q) =| prod=poly |- ?~ v (flop prod) %= $ v t.v :: prod :_ prod %. [v w] :: computes a "dot product" (actually a bilinear form that just looks like :: one) of v and w by implicitly zero-extending if lengths unequal we :: don't actually zero-extend to save a constant time factor |= [v=(list felt) w=(list felt)] ^- felt =/ dot=felt (lift 0) |- ?: ?|(?=(~ v) ?=(~ w)) dot $(v t.v, w t.w, dot (fadd dot (fmul i.v i.w))) == :: :: +fpmul-fast: polynomial multiplication with fft ++ fpmul-fast ~/ %fpmul-fast |= [fp=fpoly fq=fpoly] ^- fpoly ~+ =: fp (fpcan fp) fq (fpcan fq) == ?: ?|(=(fp zero-fpoly) =(fq zero-fpoly)) zero-fpoly =* deg-p len.fp =* deg-q len.fq =/ deg-prod (bex (xeb (dec (add deg-p deg-q)))) %- fpcan %- fp-ifft %+ %~ zip fop (fp-fft (~(zero-extend fop fp) (sub deg-prod deg-p))) (fp-fft (~(zero-extend fop fq) (sub deg-prod deg-q))) fmul :: :: +fpmul: polynomial multiplication ++ fpmul ~/ %fpmul |: [fp=`fpoly`one-fpoly fq=`fpoly`one-fpoly] ^- fpoly ~+ ?: |(=(len.fp 0) =(len.fq 0)) (init-fpoly ~[(lift 0)]) =/ p ~(to-poly fop fp) =/ q ~(to-poly fop fq) ?: (lth (add (fdegree p) (fdegree q)) 8) (fpmul-naive fp fq) (fpmul-fast fp fq) :: :: fppow: compute (p(x))^k ++ fppow ~/ %fppow |= [fp=fpoly k=@] ^- fpoly ~+ ?> !=(len.fp 0) %. [(init-fpoly ~[(lift 1)]) fp k] |= [q=fpoly p=fpoly k=@] ?: =(k 0) q =/ f=@ (end 0 k) ?: =(f 0) %_ $ p (fpmul p p) k (rsh 0 k) == %_ $ q (fpmul q p) p (fpmul p p) k (rsh 0 k) == :: :: +fp-hadamard :: :: Hadamard product of two fpolys. This is just a fancy name for pointwise multiplication. ++ fp-hadamard ~/ %fp-hadamard |: [fp=`fpoly`one-fpoly fq=`fpoly`one-fpoly] ^- fpoly =: fp (fpcan fp) fq (fpcan fq) == ?: |((fp-is-zero fp) (fp-is-zero fq)) zero-fpoly ?: (fp-is-one fp) fq ?: (fp-is-one fq) fp ?: |(=(len.fp 0) =(len.fq 0)) zero-fpoly (~(zip fop fp) fq fmul) :: :: +fp-hadamard-pow :: :: Hadamard product with itself n times ++ fp-hadamard-pow ~/ %fp-hadamard-pow |: [fa=`fpoly`one-fpoly n=`@`0] ^- fpoly ?: =(n 0) one-fpoly ?: =(n 1) fa %+ roll (range n) |= [i=@ acc=_one-fpoly] ?: =(i 0) fa (fp-hadamard acc fa) :: :: fpdvr ++ fpdvr ~/ %fpdvr |: [fa=`fpoly`one-fpoly fb=`fpoly`one-fpoly] ^- [q=fpoly r=fpoly] ~+ ?> &(!=(len.fa 0) !=(len.fb 0)) =/ a ~(to-poly fop fa) =/ b ~(to-poly fop fb) =^ rem b :- (flop (fcan a)) (flop (fcan b)) ~| "Cannot divide by the zero polynomial." ?< ?=(~ b) =/ db (dec (lent b)) ?: =(db 0) :_ (init-fpoly ~) (fpscal (finv i.b) fa) =| quot=poly |- ?: (lth (fdegree (flop rem)) db) :- (init-fpoly quot) %- init-fpoly (fcan (flop rem)) ?< ?=(~ rem) =/ new-coeff (fdiv i.rem i.b) =/ new-rem %~ to-poly fop %+ fpsub (init-fpoly rem) (fpscal new-coeff (init-fpoly b)) ?< ?=(~ new-rem) %= $ quot [new-coeff quot] rem t.new-rem == :: :: +fpdiv: polynomial division :: :: Quasilinear algo, faster than naive. Based on the formula :: rev(p/q) = rev(q)^{-1} rev(p) mod x^{deg(p) - deg(q) + 1}. :: Why?: we can compute rev(f)^{-1} mod x^l quickly. ++ fpdiv ~/ %fpdiv |: [p=`fpoly`one-fpoly q=`fpoly`one-fpoly] ^- fpoly ~+ ?> &(!=(len.p 0) !=(len.q 0)) |^ =: p (fpcan p) q (fpcan q) == ?: (fp-is-zero q) ~| "Cannot divide by the zero polynomial!" !! ?: (fp-is-zero p) zero-fpoly =/ [c=felt f=fpoly] (con-mon p) =/ [d=felt g=fpoly] (con-mon q) =/ lead=felt (fdiv c d) =/ rf=fpoly ~(flop fop f) =/ rg=fpoly ~(flop fop g) =/ df=@ (fdegree ~(to-poly fop f)) =/ dg=@ (fdegree ~(to-poly fop g)) ?: (lth df dg) zero-fpoly =/ dq=@ (sub df dg) %+ fpscal lead %~ flop fop %. +(dq) %~ scag fop (fpmul (pinv-mod-x-to +(dq) rg) rf) :: :: +pinv-mod-x-to: computes p^{-1} mod x^l ++ pinv-mod-x-to |= [l=@ p=fpoly] ^- fpoly (~(scag fop (hensel-lift-inverse p (xeb l))) l) :: :: +hensel-lift-inverse: if p_0 = 1, compute p^{-1} mod x^{2^l} (l = level parameter below) :: :: Given a(x) such that p(x)a(x) = 1 mod x^{2^i}, then a*p = 1 + x^{2^i}s(x) (see s below). :: Letting t(x) = -a(x)*s(x) mod x^{2^i}, then p's inverse modulo x^{2^{i+1}} is :: a(x) + x^{2^i}t(x) ++ hensel-lift-inverse |= [p=fpoly level=@] ^- fpoly ~| "Polynomial must have constant term equal to 1." ?> =(~(head fop p) (lift 1)) :: since p_0 = 1, 1 is p's inverse mod x (x = x^{2^0}) =/ inv=fpoly one-fpoly :: have solution for level i, i.e. mod x^{2^i}, bootstrapping to next level =/ i=@ 0 |- ?: =(i level) inv =/ bex-i=@ (bex i) =/ s (~(slag fop (fpmul p inv)) bex-i) =/ t (~(scag fop (fpmul (fpscal (lift (bneg 1)) inv) s)) bex-i) $(i +(i), inv (fpadd inv (pmul-by-x-to bex-i t))) :: :: pmul-by-x-to: multiply by x to the power l ++ pmul-by-x-to |= [l=@ p=fpoly] ^- fpoly %. p ~(weld fop (init-fpoly (reap l (lift 0)))) :: :: con-mon: split p(x)!=0 uniquely into c*f(x) where c is constant f monic ++ con-mon |= fp=fpoly ^- [felt fpoly] ~+ =. fp ~(flop fop (fpcan fp)) ~| "Cannot accept the zero polynomial!" ?< =(zero-fpoly fp) :- ~(head fop fp) %~ flop fop (fpscal (finv ~(head fop fp)) fp) -- :: :: fpmod: f(x) mod g(x), gives remainder r of f/g ++ fpmod ~/ %fpmod |: [f=`fpoly`one-fpoly g=`fpoly`one-fpoly] ^- fpoly ~+ :: f - g*q, stripped of leading zeroes %- fpcan (fpsub f (fpmul g (fpdiv f g))) :: :: fpeval: evaluate a polynomial with Horner's method. ++ fpeval ~/ %fpeval |: [fp=`fpoly`one-fpoly x=`felt`(lift 1)] ^- felt ~+ ?: (fp-is-zero fp) (lift 0) ?: =(len.fp 1) (~(snag fop fp) 0) =/ p ~(to-poly fop fp) =. p (flop p) =/ res=@ (lift 0) |- ?~ p !! ?~ t.p (fadd (fmul res x) i.p) :: based on p(x) = (...((a_n)x + a_{n-1})x + a_{n-2})x + ... ) $(res (fadd (fmul res x) i.p), p t.p) :: :: +fpcompose: given fpolys P(X) and Q(X), compute P(Q(X)) ++ fpcompose ~/ %fpcompose |: [p=`fpoly`zero-fpoly q=`fpoly`zero-fpoly] ^- fpoly ~+ =. p (fpcan p) =. q (fpcan q) ?: |((fp-is-zero p) (fp-is-zero q)) zero-fpoly =- -< %+ roll (range len.p) |= [n=@ acc=_zero-fpoly q-pow=_one-fpoly] :_ (fpmul q-pow q) %+ fpadd acc (fpscal (~(snag fop p) n) q-pow) :: :: construct the constant fpoly f(X)=c ++ fp-c |= c=felt ^- fpoly ~+ (init-fpoly ~[c]) :: :: +fp-decompose :: :: given a polynomial f(X) of degree at most D*N, decompose into D polynomials :: {h_i(X) : 0 <= i < D} each of degree at most N such that :: :: f(X) = h_0(X^D) + X*h_1(X^D) + X^2*h_2(X^D) + ... + X^{D-1}*h_{D-1}(X^D) :: :: This is just a generalization of splitting a polynomial into even and odd terms :: as the FFT does. :: h_i(X) is the terms whose degree is congruent to i modulo D. :: :: Passing in d=2 will split into even and odd terms. :: ++ fp-decompose ~/ %fp-decompose |= [p=_one-fpoly d=@] ^- (list fpoly) =/ total-deg=@ (fdegree (fpoly-to-list p)) =/ deg=@ =/ dvr (dvr total-deg d) ?:(=(q.dvr 0) p.dvr (add p.dvr 1)) =/ acc=(list (list felt)) (reap d ~) =- %+ turn - |= poly=(list felt) ?~ poly zero-fpoly (init-fpoly (flop poly)) %+ roll (range (add 1 deg)) |= [i=@ acc=_acc] %+ iturn acc |= [n=@ l=(list felt)] =/ idx (add (mul i d) n) ?: (gth idx total-deg) l [(~(snag fop p) idx) l] :: :: +fp-decomposition-eval :: :: given a decomposition created by +fp-decompose, evaluate it. :: :: input: :: n=number of pieces, :: {h_i(X): 0 <= i < n } :: c=felt (evaluation point) :: :: output: :: h_0(c^n) + X*h_1(c^n) + X^2*h_2(c^n) + ... + x^{n-1)}*h_{n-1}(x^n) :: ++ fp-decomposition-eval |= [n=@ polys=(list fpoly) eval-point=felt] ^- felt =/ c (fpow eval-point n) %^ zip-roll (range n) polys |= [[i=@ poly=fpoly] acc=_(lift 0)] (fadd acc (fmul (fpow eval-point i) (fpeval poly c))) :: :: specialized fpoly manipulations mostly used by the prover +| %prover-math :: codeword: compute a Reed-Solomon codeword, i.e. evaluate a poly on a domain ++ codeword ~/ %codeword |= [fp=fpoly fdomain=fpoly] ^- fpoly ?: =(fdomain zero-fpoly) fdomain ?: =(1 len.fdomain) (init-fpoly (fpeval fp ~(head fop fdomain))^~) =/ half (div len.fdomain 2) =/ lef-zerofier (zerofier (~(scag fop fdomain) half)) =/ rig-zerofier (zerofier (~(slag fop fdomain) half)) =/ lef $(fp (fpmod fp lef-zerofier), fdomain (~(scag fop fdomain) half)) =/ rig $(fp (fpmod fp rig-zerofier), fdomain (~(slag fop fdomain) half)) (~(weld fop lef) rig) :: ++ zerofier ~/ %zerofier |= fdomain=fpoly ^- fpoly ~+ ?: =(fdomain zero-fpoly) fdomain ?: =(1 len.fdomain) %- init-fpoly [(fneg ~(head fop fdomain)) (lift 1) ~] =/ half (div len.fdomain 2) =/ lef $(fdomain (~(scag fop fdomain) half)) =/ rig $(fdomain (~(slag fop fdomain) half)) (fpmul lef rig) :: :: interpolate: compute the poly of minimal degree which evaluates to values on domain ++ interpolate ~/ %interpolate |= [fdomain=fpoly fvalues=fpoly] ^- fpoly ~+ ?> =(len.fdomain len.fvalues) ?: =(fdomain zero-fpoly) fdomain ?: =(1 len.fdomain) fvalues =/ half (div len.fdomain 2) =/ half-1 (~(scag fop fdomain) half) =/ half-2 (~(slag fop fdomain) half) =/ lef-zerofier (zerofier half-1) =/ rig-zerofier (zerofier half-2) =/ lef-offset (codeword rig-zerofier half-1) =/ rig-offset (codeword lef-zerofier half-2) =/ lef-target %+ ~(zip fop (~(scag fop fvalues) half)) lef-offset fdiv =/ rig-target %+ ~(zip fop (~(slag fop fvalues) half)) rig-offset fdiv =/ lef-interpolant $(fdomain half-1, fvalues lef-target) =/ rig-interpolant $(fdomain half-2, fvalues rig-target) %+ fpadd (fpmul lef-interpolant rig-zerofier) (fpmul rig-interpolant lef-zerofier) :: ++ test-colinearity |= points=(list (pair felt felt)) ^- ? ?< ?|(?=(~ points) ?=(~ t.points)) =* x0 p.i.points =* y0 q.i.points =* x1 p.i.t.points =* y1 q.i.t.points ~| "x-coordinates must be distinct" ?< =(x0 x1) =/ line=fpoly (interpolate (init-fpoly ~[x0 x1]) (init-fpoly ~[y0 y1])) =/ bool=? %.y =/ iter t.t.points |- ?~ iter bool %= $ iter t.iter bool ?& bool =((fpeval line p.i.iter) q.i.iter) == == :: +shift: produces the polynomial q(x) such that p(c*x) = q(x), i.e. q_i = (p_i)*(c^i) :: :: Usecase: :: If p is a polynomial you want to evaluate on coset cH of subgroup H, then you can :: instead evaluate q on H. The value of q on h is that of p on ch: q(h) = p(ch). ++ shift ~/ %shift |: [fp=`fpoly`one-fpoly c=`felt`(lift 1)] ^- fpoly =/ p ~(to-poly fop fp) =/ power=felt (lift 1) =| q=poly |- ?~ p (init-fpoly (flop q)) $(q [(fmul i.p power) q], power (fmul power c), p t.p) :: ++ bp-shift ~/ %bp-shift |: [bp=`bpoly`one-bpoly c=`belt`1] ^- bpoly =/ p ~(to-poly bop bp) =/ power=belt 1 =| q=poly |- ?~ p (init-bpoly (flop q)) $(q [(bmul i.p power) q], power (bmul power c), p t.p) :: :: :: +shift-by-unity :: :: compose a polynomial in eval form over a root of unity with a power of that root of unity. It just :: has to shift the vector to the left by pow steps and wrap back to the right. ++ shift-by-unity ~/ %shift-by-unity |= [fp=fpoly n=@] ^- fpoly ?: |(=(len.fp 0) =(len.fp 1)) fp (~(weld fop (~(slag fop fp) n)) (~(scag fop fp) n)) :: ++ bp-shift-by-unity ~/ %bp-shift-by-unity |= [bp=bpoly n=@] ^- bpoly ?: |(=(len.bp 0) =(len.bp 1)) bp (~(weld bop (~(slag bop bp) n)) (~(scag bop bp) n)) :: ++ turn-coseword ~/ %turn-coseword |= [polys=mary offset=belt order=@] ^- mary %- zing-bpolys %+ turn (range len.array.polys) |= i=@ =/ bp=bpoly (~(snag-as-bpoly ave polys) i) (bp-coseword bp offset order) :: :: +coseword: fast evaluation on a coset of a binary subgroup :: :: Portmanteau of coset and codeword. If we want to evaluate a polynomial p on a coset of :: a subgroup H, this is the same as evaluating the shifted polynomial q on H. If H is :: generated by a binary root of unity, this evaluation is the same as an FFT. :: NOTE: the polynomial being evaluated should have length less than the size of H. :: This is because an FFT of a polynomial uses a root of unity of order the power of 2 :: which is larger than the length of the polynomial. :: NOTE: 'order' is the size of H. It suffices for this single number to be our proxy for :: H because there is a unique subgroup of this size. (Follows from the fact that F* is cyclic.) ++ coseword ~/ %coseword |= [p=fpoly offset=felt order=@] ^- fpoly ~| "Order must be a power of 2." ?> =((dis order (dec order)) 0) %- fp-fft %- ~(zero-extend fop (shift p offset)) (sub order len.p) :: :: +bp-coseword: coseword over base field ++ bp-coseword ~/ %bp-coseword |= [p=bpoly offset=belt order=@] ^- bpoly ~| "Order must be a power of 2." ?> =((dis order (dec order)) 0) %- bp-fft %- ~(zero-extend bop (bp-shift p offset)) (sub order len.p) :: :: +intercosate: interpolate a polynomial taking particular values over a binary subgroup coset :: :: Returns a polynomial p satisfying p(c*w^i) = v_i where w generates a cyclic subgroup of :: binary order. This is accomplished by first obtaining q = (ifft values), which satisfies :: q(w^i) = v_i. This is equivalent to q(c^{-1}*(c*w^i)) = v_i so comparing to our desired :: equation we want p(x) = q(c^{-1}*x); i.e. we need to shift q by c^{-1}. ++ intercosate ~/ %intercosate |= [offset=felt order=@ values=fpoly] ^- fpoly ~+ :: order = |H| is a power of 2 ?> =((dis order (dec order)) 0) :: number of values should match the number of points in the coset ?> =(len.values order) (shift (fp-ifft values) (finv offset)) :: ++ bp-intercosate ~/ %bp-intercosate |= [offset=belt order=@ values=bpoly] ^- bpoly ~+ :: order = |H| is a power of 2 ?> =((dis order (dec order)) 0) :: number of values should match the number of points in the coset ?> =(len.values order) =/ ifft (bp-ifft values) (bp-shift (bp-ifft values) (binv offset)) :: :: ++ interpolate-table ~/ %interpolate-table |= [table=mary domain-len=@] ^- mary =/ trace=mary (transpose-bpolys table) %- zing-bpolys %+ turn (range len.array.trace) |= col=@ ^- bpoly =/ values-old=bpoly (~(snag-as-bpoly ave trace) col) =/ values (~(zero-extend bop values-old) (sub domain-len len.values-old)) ?> =(len.values domain-len) ?> =((dis domain-len (dec domain-len)) 0) (bp-intercosate 1 domain-len values) :: :: ++ mp-to-mega ~% %mp-to-mega + ~ |% :: type of a term +$ mega-typ ?(%var %rnd %dyn %con %com) :: :: bit flags for type of term :: constant: just a belt constant term +$ con-id %0 :: variable: index is index of variable +$ var-id %1 :: challenge: index is index in challenge list +$ rnd-id %2 :: dynamic terminal: index is index in dynamic list +$ dyn-id %3 :: composition: index of result of previous evaluation +$ com-id %4 :: :: bit length of type ++ typ-len 3 :: bit length of index ++ idx-len 10 :: bit length of exponent ++ exp-len 30 :: :: one term inside a monomial. fits in a direct atom. +$ mega-term @ux :: ++ mega |_ term=mega-term :: retrieve type of terminal ++ typ ^- mega-typ ?+ (cut 0 [0 typ-len] term) !! con-id %con var-id %var rnd-id %rnd dyn-id %dyn com-id %com == :: :: retrieve index of terminal ++ idx ^- @ud (cut 0 [typ-len idx-len] term) :: :: retrieve exponent of terminal ++ exp ^- @ud (cut 0 [(add typ-len idx-len) exp-len] term) :: -- :: :: assemble a mega term out of a (type, index, exponent) triple ++ form-mega |= [typ=mega-typ idx=@ exp=@ud] ^- mega-term ?> (lte (met 0 idx) idx-len) ?> (lte (met 0 exp) exp-len) =/ t=@ ?- typ %con *con-id %var *var-id %rnd *rnd-id %dyn *dyn-id %com *com-id == (can 0 ~[[typ-len t] [idx-len idx] [exp-len exp]]) :: :: dissable a term into its (type, index, exponent) triple ++ brek |= ter=mega-term ^- [mega-typ @ @ud] :+ ~(typ mega ter) ~(idx mega ter) ~(exp mega ter) :: :: +mp-is-constant: is mp a constant polynomial ++ mp-is-constant |= mp=mp-mega ^- ? |^ %+ levy ~(tap by mp) |= [k=bpoly v=belt] (is-monomial-constant k) :: ++ is-monomial-constant |= monomial=bpoly ^- ? %- levy :_ same %+ turn (range len.monomial) |= i=@ ^- ? =/ ter (~(snag bop monomial) i) !?=(%var ~(typ mega ter)) -- :: :: print out an mp-mega ++ print-mega |= mp=mp-mega ^- tape %- zing =- (flop acc) %+ roll ~(tap by mp) |= [[k=bpoly v=belt] first=? acc=(list tape)] =/ plus ?:(first "" " + ") =/ coeff ?:(=(1 v) "" "{(scow %ud v)}*") =; str=tape :+ %.n :(weld plus coeff str) acc %- zing =- (flop acc) %+ roll (range len.k) |= [i=@ first=? acc=(list tape)] =/ times=tape ?:(first "" "*") =/ ter (~(snag bop k) i) =/ exp ?: =(1 ~(exp mega ter)) "" "^{(scow %ud ~(exp mega ter))}" :- %.n :_ acc ;: weld times (trip ~(typ mega ter)) (scow %ud ~(idx mega ter)) exp == :: :: :: f(x0, x1, ..., xn) = r[i] where r is a result from a previous computation ++ mp-com |= i=@ ^- mp-mega ~+ (my [[(init-bpoly ~[(form-mega %com i 1)]) 1] ~]) :: :: :: f(x0, x1, ..., xn) = c where c is a belt constant ++ mp-c |= c=belt ^- mp-mega ~+ ?> (based c) ?: =(c 0) ~ (my [[zero-bpoly c] ~]) :: :: f(x0, x1, ..., x0)=xi where i is the argument ++ mp-var |= which-variable=@ ^- mp-mega ~+ (my [[(init-bpoly ~[(form-mega %var which-variable 1)]) 1] ~]) :: :: f(x0, x1, ..., x0) = d where d is the value of the dynamic terminal :: with index dyn ++ mp-dyn |= dyn=@ ^- mp-mega ~+ (my [[(init-bpoly ~[(form-mega %dyn dyn 1)]) 1] ~]) :: :: f(x0, x1, ..., x0) = r where r is a random challenge with index rnd ++ mp-chal |= rnd=@ ^- mp-mega ~+ (my [[(init-bpoly ~[(form-mega %rnd rnd 1)]) 1] ~]) :: ++ mpadd ~/ %mpadd |= [f=mp-mega g=mp-mega] ^- mp-mega ?: =(~ f) g ?: =(~ g) f %+ roll ~(tap by g) |= [[gk=bpoly gv=belt] res=_f] =/ fv (~(get by f) gk) ?~ fv (~(put by res) gk gv) (~(put by res) gk (badd u.fv gv)) :: ++ mpsub ~/ %mpsub |= [f=mp-mega g=mp-mega] ^- mp-mega ?: =(~ f) (~(run by g) bneg) ?: =(~ g) f %+ roll ~(tap by g) |= [[gk=bpoly gv=belt] res=_f] =/ fv (~(get by f) gk) ?~ fv (~(put by res) gk (bneg gv)) (~(put by res) gk (bsub u.fv gv)) :: ++ mpscal ~/ %mpsub |= [c=belt f=mp-mega] ^- mp-mega ?: =(0 c) ~ ?: =(~ f) f (~(run by f) (curr bmul c)) :: ++ mpmul ~/ %mpmul |= [f=mp-mega g=mp-mega] ^- mp-mega |^ ?: |(=(~ f) =(~ g)) ~ %+ roll ~(tap by f) |= [[fk=bpoly fv=belt] acc=(map bpoly belt)] %+ roll ~(tap by g) |= [[gk=bpoly gv=belt] acc=_acc] =/ key (mul-key fk gk) =/ val (bmul fv gv) =/ lookup (~(get by acc) key) ?~ lookup (~(put by acc) key val) (~(put by acc) key (badd u.lookup val)) :: ++ mul-key |= [f=bpoly g=bpoly] ^- bpoly ?: (bp-is-zero f) g ?: (bp-is-zero g) f =/ f-map %- ~(gas by *(map [mega-typ @] @ud)) %+ turn (range len.f) |= i=@ =/ ter (~(snag bop f) i) :- [~(typ mega ter) ~(idx mega ter)] ~(exp mega ter) =; acc-map=(map [mega-typ @] @ud) %- init-bpoly %+ turn ~(tap by acc-map) |= [[typ=mega-typ idx=@] exp=@ud] (form-mega typ idx exp) %+ roll (range len.g) |= [j=@ acc=_f-map] =/ ter (~(snag bop g) j) =/ exp (~(get by acc) [~(typ mega ter) ~(idx mega ter)]) ?~ exp (~(put by acc) [~(typ mega ter) ~(idx mega ter)] ~(exp mega ter)) (~(put by acc) [~(typ mega ter) ~(idx mega ter)] (add ~(exp mega ter) u.exp)) :: -- :: ++ mp-degree |= [f=mp-mega com-map=(map @ @)] ^- @ud %- roll :_ max %+ turn ~(tap by f) |= [k=bpoly v=belt] ^- @ud ?: =(v 0) 0 %+ roll (range len.k) |= [i=@ deg=@ud] =/ ter (~(snag bop k) i) =/ [typ=mega-typ idx=@ exp=@ud] (brek ter) ?+ typ deg %var (add deg exp) :: %com =/ dep-deg (~(got by com-map) idx) %+ add deg (mul dep-deg exp) == :: ++ mpeval ~/ %mpeval |= $: field=?(%ext %base) mp=mp-mega args=bpoly :: can be bpoly or fpoly chal-map=(map @ belt) dyns=bpoly com-map=(map @ elt) == ^- elt =/ add-op ?:(=(field %base) badd fadd) =/ mul-op ?:(=(field %base) bmul fmul) =/ pow-op ?:(=(field %base) bpow fpow) =/ aop-door ?:(=(field %base) bop fop) =/ lift-op ?:(=(field %base) |=(v=@ `@ux`v) lift) =/ init-zero=@ux (lift-op 0) =/ init-one=@ux (lift-op 1) ?: =(~ mp) init-zero %+ roll ~(tap by mp) |= [[k=bpoly v=belt] acc=_init-zero] =/ coeff=@ux (lift-op v) ?: =(init-zero coeff) acc %+ add-op acc %+ mul-op coeff %+ roll (range len.k) |= [i=@ res=_init-one] ?: =(init-zero res) init-zero %+ mul-op res =/ ter (~(snag bop k) i) =/ [typ=mega-typ idx=@ exp=@ud] (brek ter) ?- typ %var %+ pow-op (~(snag aop-door args) idx) exp :: %rnd %+ pow-op (lift-op (~(got by chal-map) idx)) exp :: %dyn %+ pow-op (lift-op (~(snag bop dyns) idx)) exp :: %con init-one :: %com %+ pow-op (~(got by com-map) idx) exp == -- :: ++ mp-degree-mega |= [f=mp-mega com-map=(map @ @)] ^- @ (mp-degree:mp-to-mega f com-map) :: ++ mp-degree-ultra |= f=mp-ultra ^- (list @) ?- -.f %mega :~ (mp-degree-mega +.f ~) == :: %comp =; com-degs=(map @ @) %+ turn com.f |= mp=mp-mega (mp-degree-mega mp com-degs) %+ roll (range (lent dep.f)) |= [i=@ acc=(map @ @)] =/ mp (snag i dep.f) %- ~(put by acc) :- i (mp-degree-mega mp ~) == :: :: +mp-to-graph: multipoly arithmetic in the mp-graph representation :: :: you can usually convert mpoly arithmetic into mp-graph arithmetic by :: writing =, mp-to-graph and leaving everything else the same. ++ mp-to-graph ~% %mp-to-graph + ~ |% ++ make-variable |= which-variable=@ ^- mp-graph [%var +<] :: ++ inv |= a=mp-graph ^- mp-graph [%inv a] :: ++ neg |= a=mp-graph ^- mp-graph [%neg a] :: ++ mpadd |= [a=mp-graph b=mp-graph] ^- mp-graph [%addl ~[a b]] :: ++ mpsub |= [a=mp-graph b=mp-graph] ^- mp-graph [%addl ~[a [%scal (bneg 1) b]]] :: ++ mpmul |= [a=mp-graph b=mp-graph] ^- mp-graph [%mul +<] :: ++ mppow |= [a=mp-graph n=@ud] ^- mp-graph [%pow +<] :: ++ mpscal |= [c=belt f=mp-graph] ^- mp-graph [%scal +<] :: ++ mp-c |= a=belt ^- mp-graph [%con a] :: ++ mp-cb |= a=belt ^- mp-graph [%con a] :: ++ mp-dyn |= d=term ^- mp-graph [%dyn d] :: -- :: ++ mpeval-mega ~/ %mpeval-mega |= $: field=?(%ext %base) p=mp-mega args=bpoly :: can be bpoly or fpoly chal-map=(map @ belt) dyns=bpoly com-map=(map @ elt) == ^- elt (mpeval:mp-to-mega +<) :: ++ mpeval-ultra ~/ %mpeval-ultra |= $: field=?(%ext %base) p=mp-ultra args=bpoly :: can be bpoly or fpoly chal-map=(map @ belt) dyns=bpoly == ^- (list elt) ?- -.p %mega :~ (mpeval-mega field +.p args chal-map dyns ~) == :: %comp =; com-map=(map @ elt) %+ turn com.p |= mp=mp-mega (mpeval-mega field mp args chal-map dyns com-map) %+ roll (range (lent dep.p)) |= [i=@ acc=(map @ elt)] =/ mp (snag i dep.p) %- ~(put by acc) :- i (mpeval-mega field mp args chal-map dyns ~) == :: :: :: +mp-substitute-ultra :: :: Handles substitution for %mega and %comp mp-ultra cases. If the multi-poly is a :: single mp-mega constraint, we just call mp-substitute-mega on it. On the other hand :: if it is a composition, we must first evaluate its dependencies, collating the :: indexed results in a map. We then pass the map in as input when we substitute :: the actual computation. :: ++ mp-substitute-ultra ~/ %mp-substitute-ultra |= [p=mp-ultra trace-evals=bpoly height=@ chal-map=(map @ belt) dyns=bpoly] ^- (list bpoly) ?- -.p %mega :~ (mp-substitute-mega +.p trace-evals height chal-map dyns ~) == :: %comp =; com-map=(map @ bpoly) %+ turn com.p |= mp=mp-mega (mp-substitute-mega mp trace-evals height chal-map dyns com-map) :: :: Materialize the dependencies and label them based on order %+ roll (range (lent dep.p)) |= [i=@ acc=(map @ bpoly)] =/ mp=mp-mega (snag i dep.p) %- ~(put by acc) :- i (mp-substitute-mega mp trace-evals height chal-map dyns ~) == :: :: +mp-substitute-mega: Given a multipoly: sub in the chals, dyns, vars, and composition dependencies: :: :: For vars, the trace polys: ~[p0(t) p1(t) ... ] are in eval form and we substitute pi(t) for xi. :: :: The key insight is that multiplication is much faster on polynomials in eval form instead of :: coefficient form. Calling bpmul will do ntt's on the arguments and an ifft on the result :: over and over again. Instead we precompute the ntts for all the polynomials and those :: are the arguments to substitute. Since they're already in the correct form we just compute :: hadamard products on them, sum up all the terms, and do an ifft to get the result. :: :: Another optimization is that the polynomials in eval form must be the length of the degree :: of the final product. Since the max degree of the constraints is 4 (this method has this :: constraint degree hardcoded for optimization purposes and must be changed by hand :: if the constraint degree changes), the vectors must be 4*n where n is the height. :: ++ mp-substitute-mega ~/ %mp-substitute-mega |= [p=mp-mega trace-evals=bpoly height=@ chal-map=(map @ belt) dyns=bpoly com-map=(map @ bpoly)] ^- bpoly %+ roll ~(tap by p) |= [[k=bpoly v=belt] acc=_zero-bpoly] =/ [poly=bpoly len=@] [trace-evals (mul 4 height)] =/ ones=bpoly (init-bpoly (reap len 1)) ?: =(v 0) acc %+ bpadd acc %+ bpscal v %+ roll (range len.k) |= [i=@ acc=_ones] ^- bpoly =/ ter (~(snag bop k) i) =/ [typ=mega-typ:mp-to-mega idx=@ exp=@ud] (brek:mp-to-mega ter) ?- typ %var =/ var=bpoly (~(swag bop poly) (mul idx len) len) %+ roll (range exp) |= [i=@ power=_acc] (bp-hadamard power var) :: %rnd =/ rnd (~(got by chal-map) idx) (bpscal (bpow rnd exp) acc) :: %dyn =/ dyn (~(snag bop dyns) idx) (bpscal (bpow dyn exp) acc) :: %con acc :: %com =/ com=bpoly (~(got by com-map) idx) %+ roll (range exp) |= [i=@ power=_acc] (bp-hadamard power com) == :: :: ++ fpdiv-test :: |= [p=poly q=poly] :: ^- ? :: =(-:(fpdvr p q) (fpdiv p q)) :: :: :: ++ fpdvr-test :: |= [a=poly b=poly] :: ^- ? :: =/ [q=poly r=poly] (fpdvr a b) :: ?& =(a (fpadd (fpmul-naive b q) r)) :: (lth (degree r) (degree b)) :: == :: -- ::ext-field