/*
	Licensed to the Apache Software Foundation (ASF) under one
	or more contributor license agreements.  See the NOTICE file
	distributed with this work for additional information
	regarding copyright ownership.  The ASF licenses this file
	to you under the Apache License, Version 2.0 (the
	"License"); you may not use this file except in compliance
	with the License.  You may obtain a copy of the License at
	
	http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing,
	software distributed under the License is distributed on an
	"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
	KIND, either express or implied.  See the License for the
	specific language governing permissions and limitations
	under the License.
*/

//
//  fp24.swift
//
//  Created by Michael Scott on 07/07/2015.
//  Copyright (c) 2015 Michael Scott. All rights reserved.
//

/* AMCL Fp^24 functions */
/* FP24 elements are of the form a+i.b+i^2.c */

public struct FP24
{
    static public let ZERO:Int=0
    static public let ONE:Int=1
    static public let SPARSER:Int=2
    static public let SPARSE:Int=3
    static public let DENSE:Int=4

    private var a:FP8
    private var b:FP8
    private var c:FP8
    private var stype:Int    
    
    /* reduce all components of this mod Modulus */
    mutating func reduce()
    {
        a.reduce()
        b.reduce()
        c.reduce()
    }
    /* normalise all components of this */
    mutating func norm()
    {
        a.norm();
        b.norm();
        c.norm();
    }

    mutating func settype(_ t:Int)
    {
        stype=t
    }

    mutating func gettype() -> Int
    {
        return stype
    }

    /* Constructors */
    init(_ d:FP8)
    {
        a=FP8(d)
        b=FP8()
        c=FP8()
        stype=FP24.SPARSER        
    }
    
    init()
    {
        a=FP8()
        b=FP8()
        c=FP8()
        stype=FP24.ZERO      
    }

    init(_ d:Int)
    {
        a=FP8(d)
        b=FP8()
        c=FP8()
        if (d==1) {stype=FP24.ONE}
        else {stype=FP24.SPARSER}        
    }

    init(_ d:FP8,_ e:FP8,_ f:FP8)
    {
        a=FP8(d)
        b=FP8(e)
        c=FP8(f)
        stype=FP24.DENSE        
    }
    
    init(_ x:FP24)
    {
        a=FP8(x.a)
        b=FP8(x.b)
        c=FP8(x.c)
        stype=x.stype        
    }
    /* test x==0 ? */
    func iszilch() -> Bool
    {
        return a.iszilch() && b.iszilch() && c.iszilch()
    }

    mutating func cmove(_ g:FP24,_ d:Int)
    {
        a.cmove(g.a,d)
        b.cmove(g.b,d)
        c.cmove(g.c,d)
        let u = ~(d-1)
        stype^=(stype^g.stype)&u           
    }

    /* return 1 if b==c, no branching */
    private static func teq(_ b:Int32,_ c:Int32) -> Int
    {
        var x=b^c
        x-=1  // if x=0, x now -1
        return Int((x>>31)&1)
    }
    /* Constant time select from pre-computed table */
    mutating func select(_ g:[FP24],_ b:Int32)
    {

        let m=b>>31
        var babs=(b^m)-m
        
        babs=(babs-1)/2
    
        cmove(g[0],FP24.teq(babs,0)) // conditional move
        cmove(g[1],FP24.teq(babs,1))
        cmove(g[2],FP24.teq(babs,2))
        cmove(g[3],FP24.teq(babs,3))
        cmove(g[4],FP24.teq(babs,4))
        cmove(g[5],FP24.teq(babs,5))
        cmove(g[6],FP24.teq(babs,6))
        cmove(g[7],FP24.teq(babs,7))
    
        var invf=FP24(self)
        invf.conj()
        cmove(invf,Int(m&1))
    }

    /* test x==1 ? */
    public func isunity() -> Bool
    {
        let one=FP8(1)
        return a.equals(one) && b.iszilch() && c.iszilch()
    }
    /* return 1 if x==y, else 0 */
    public func equals(_ x:FP24) -> Bool
    {
        return a.equals(x.a) && b.equals(x.b) && c.equals(x.c)
    }
    /* extract a from self */
    func geta() -> FP8
    {
        return a
    }
    /* extract b */
    func getb()  -> FP8
    {
        return b
    }
    /* extract c */
    func getc() -> FP8
    {
        return c
    }
    /* copy self=x */
    public mutating func copy(_ x:FP24)
    {
        a.copy(x.a)
        b.copy(x.b)
        c.copy(x.c)
        stype=x.stype        
    }
    /* set self=1 */
    mutating func one()
    {
        a.one()
        b.zero()
        c.zero()
       stype=FP24.ONE        
    }

    /* set self=0 */
    mutating func zero()
    {
        a.zero()
        b.zero()
        c.zero()
        stype=FP24.ZERO
    }

    /* self=conj(self) */
    mutating func conj()
    {
        a.conj()
        b.nconj()
        c.conj()
    }

    /* Granger-Scott Unitary Squaring */
    mutating func usqr()
    {
        var A=FP8(a)
        var B=FP8(c)
        var C=FP8(b)
        var D=FP8()
    
        a.sqr()
        D.copy(a); D.add(a)
        a.add(D)
    
        a.norm()
        A.nconj()
    
        A.add(A)
        a.add(A)
        B.sqr()
        B.times_i()
    
        D.copy(B); D.add(B)
        B.add(D)
        B.norm()
    
        C.sqr()
        D.copy(C); D.add(C)
        C.add(D)
        C.norm()
    
        b.conj()
        b.add(b)
        c.nconj()
    
        c.add(c)
        b.add(B)
        c.add(C)
        stype=FP24.DENSE        
        reduce()
    
    }

    /* Chung-Hasan SQR2 method from http://cacr.uwaterloo.ca/techreports/2006/cacr2006-24.pdf */
    mutating func sqr()
    {
        if (stype==FP24.ONE) {return}        
        var A=FP8(a)
        var B=FP8(b)
        var C=FP8(c)
        var D=FP8(a)
    
        A.sqr()
        B.mul(c)
        B.add(B); B.norm()
        C.sqr()
        D.mul(b)
        D.add(D)

        c.add(a)
        c.add(b); c.norm()
        c.sqr()
    
        a.copy(A)
    
        A.add(B)
        A.norm()
        A.add(C)
        A.add(D)
        A.norm()
    
        A.neg()
        B.times_i()
        C.times_i()
    
        a.add(B)

        b.copy(C); b.add(D)
        c.add(A)
        if (stype==FP24.SPARSER) {
            stype=FP24.SPARSE
        } else {
            stype=FP24.DENSE
        }    
        norm()
    }

    /* FP24 full multiplication this=this*y */
    mutating func mul(_ y:FP24)
    {
        var z0=FP8(a)
        var z1=FP8()
        var z2=FP8(b)
        var z3=FP8()
        var t0=FP8(a)
        var t1=FP8(y.a)
    
        z0.mul(y.a)
        z2.mul(y.b)
    
        t0.add(b)
        t1.add(y.b)

        t0.norm(); t1.norm()
    
        z1.copy(t0); z1.mul(t1)
        t0.copy(b); t0.add(c)
    
        t1.copy(y.b); t1.add(y.c)

        t0.norm(); t1.norm()

        z3.copy(t0); z3.mul(t1)
    
        t0.copy(z0); t0.neg()
        t1.copy(z2); t1.neg()
    
        z1.add(t0)
        b.copy(z1); b.add(t1)
    
        z3.add(t1)
        z2.add(t0)
    
        t0.copy(a); t0.add(c)
        t1.copy(y.a); t1.add(y.c)

        t0.norm(); t1.norm()

        t0.mul(t1)
        z2.add(t0)
    
        t0.copy(c); t0.mul(y.c)
        t1.copy(t0); t1.neg()
    
        c.copy(z2); c.add(t1)
        z3.add(t1)
        t0.times_i()
        b.add(t0)
    
        z3.norm()
        z3.times_i()
        a.copy(z0); a.add(z3)
        stype=FP24.DENSE    
        norm()
    }

/* FP24 full multiplication w=w*y */
/* Supports sparse multiplicands */
/* Usually w is denser than y */
    mutating func ssmul(_ y:FP24)
    {
        if stype==FP24.ONE {
            copy(y)
            return
        }
        if y.stype==FP24.ONE {
            return
        }
        if y.stype>=FP24.SPARSE {
            var z0=FP8(a)
            var z1=FP8()
            var z2=FP8()
            var z3=FP8()
            z0.mul(y.a)

            if CONFIG_CURVE.SEXTIC_TWIST == CONFIG_CURVE.M_TYPE {  
                if y.stype==FP24.SPARSE || stype==FP24.SPARSE {
                    var ga=FP4()
                    var gb=FP4()

                    gb.copy(b.getb())
                    gb.mul(y.b.getb())
                    ga.zero()
                    if y.stype != FP24.SPARSE {
                        ga.copy(b.getb())
                        ga.mul(y.b.geta())
                    }
                    if stype != FP24.SPARSE {
                        ga.copy(b.geta())
                        ga.mul(y.b.getb())
                    }
                    z2.set_fp4s(ga,gb)
                    z2.times_i()
                } else {
                    z2.copy(b)
                    z2.mul(y.b)
                }
            } else {
                z2.copy(b)
                z2.mul(y.b)
            }
            var t0=FP8(a)
            var t1=FP8(y.a)
            t0.add(b); t0.norm()
            t1.add(y.b); t1.norm()

            z1.copy(t0); z1.mul(t1)
            t0.copy(b); t0.add(c); t0.norm()
            t1.copy(y.b); t1.add(y.c); t1.norm()

            z3.copy(t0); z3.mul(t1)

            t0.copy(z0); t0.neg()
            t1.copy(z2); t1.neg()

            z1.add(t0)
            b.copy(z1); b.add(t1)

            z3.add(t1)
            z2.add(t0)

            t0.copy(a); t0.add(c); t0.norm()
            t1.copy(y.a); t1.add(y.c); t1.norm()
    
            t0.mul(t1)
            z2.add(t0)

            if CONFIG_CURVE.SEXTIC_TWIST == CONFIG_CURVE.D_TYPE {  
                if y.stype==FP24.SPARSE || stype==FP24.SPARSE {
                    var ga=FP4()
                    var gb=FP4()

                    ga.copy(c.geta())
                    ga.mul(y.c.geta())
                    gb.zero()
                    if y.stype != FP24.SPARSE {
                        gb.copy(c.geta())
                        gb.mul(y.c.getb())
                    }
                    if stype != FP24.SPARSE {
                        gb.copy(c.getb())
                        gb.mul(y.c.geta())
                    }
                    t0.set_fp4s(ga,gb)
                } else {
                    t0.copy(c)
                    t0.mul(y.c)
                }
            } else {
                t0.copy(c)
                t0.mul(y.c)
            }
            t1.copy(t0); t1.neg()

            c.copy(z2); c.add(t1)
            z3.add(t1)
            t0.times_i()
            b.add(t0)
            z3.norm()
            z3.times_i()
            a.copy(z0); a.add(z3);
        } else {
            if stype==FP24.SPARSER {
                smul(y)
                return
            }
            if CONFIG_CURVE.SEXTIC_TWIST == CONFIG_CURVE.D_TYPE {  // dense by sparser - 13m 
                var z0=FP8(a)
                var z2=FP8(b)
                var z3=FP8(b)
                var t0=FP8()
                var t1=FP8(y.a)
                z0.mul(y.a)
                z2.pmul(y.b.real())
                b.add(a)
                t1.adds(y.b.real())

                t1.norm()
                b.norm()
                b.mul(t1)
                z3.add(c)
                z3.norm()
                z3.pmul(y.b.real())

                t0.copy(z0); t0.neg()
                t1.copy(z2); t1.neg()

                b.add(t0)

                b.add(t1)
                z3.add(t1)
                z2.add(t0)

                t0.copy(a); t0.add(c); t0.norm()
                z3.norm()
                t0.mul(y.a)
                c.copy(z2); c.add(t0)

                z3.times_i()
                a.copy(z0); a.add(z3)
            }
            if CONFIG_CURVE.SEXTIC_TWIST == CONFIG_CURVE.M_TYPE {
                var z0=FP8(a)
                var z1=FP8()
                var z2=FP8()
                var z3=FP8()
                var t0=FP8(a)
                var t1=FP8()
        
                z0.mul(y.a)
                t0.add(b); t0.norm()

                z1.copy(t0); z1.mul(y.a)
                t0.copy(b); t0.add(c)
                t0.norm()

                z3.copy(t0)
                z3.pmul(y.c.getb())
                z3.times_i()

                t0.copy(z0); t0.neg()
                z1.add(t0)
                b.copy(z1)
                z2.copy(t0)

                t0.copy(a); t0.add(c); t0.norm()
                t1.copy(y.a); t1.add(y.c); t1.norm()

                t0.mul(t1)
                z2.add(t0)
                t0.copy(c)
            
                t0.pmul(y.c.getb())
                t0.times_i()
                t1.copy(t0); t1.neg()

                c.copy(z2); c.add(t1)
                z3.add(t1)
                t0.times_i()
                b.add(t0)
                z3.norm()
                z3.times_i()
                a.copy(z0); a.add(z3)
            }   
        }
        stype=FP24.DENSE
        norm()    
    }

    /* Special case of multiplication arises from special form of ATE pairing line function */
    mutating func smul(_ y:FP24)
    {
        if CONFIG_CURVE.SEXTIC_TWIST == CONFIG_CURVE.D_TYPE {  
            var w1=FP4(a.geta())
            var w2=FP4(a.getb())
            var w3=FP4(b.geta())

            w1.mul(y.a.geta())
            w2.mul(y.a.getb())
            w3.mul(y.b.geta())

            var ta=FP4(a.geta())
            var tb=FP4(y.a.geta())
            ta.add(a.getb()); ta.norm()
            tb.add(y.a.getb()); tb.norm()
            var tc=FP4(ta)
            tc.mul(tb)
            var t=FP4(w1)
            t.add(w2)
            t.neg()
            tc.add(t)

            ta.copy(a.geta()); ta.add(b.geta()); ta.norm()
            tb.copy(y.a.geta()); tb.add(y.b.geta()); tb.norm()
            var td=FP4(ta)
            td.mul(tb)
            t.copy(w1)
            t.add(w3)
            t.neg()
            td.add(t)

            ta.copy(a.getb()); ta.add(b.geta()); ta.norm()
            tb.copy(y.a.getb()); tb.add(y.b.geta()); tb.norm()
            var te=FP4(ta)
            te.mul(tb)
            t.copy(w2)
            t.add(w3)
            t.neg()
            te.add(t)

            w2.times_i()
            w1.add(w2)

            a.set_fp4s(w1,tc)
            b.set_fp4s(td,te)
            c.set_fp4(w3)

            a.norm()
            b.norm()
    } else {
            var w1=FP4(a.geta())
            var w2=FP4(a.getb())
            var w3=FP4(c.getb())

            w1.mul(y.a.geta())
            w2.mul(y.a.getb())
            w3.mul(y.c.getb())

            var ta=FP4(a.geta())
            var tb=FP4(y.a.geta())
            ta.add(a.getb()); ta.norm()
            tb.add(y.a.getb()); tb.norm()
            var tc=FP4(ta)
            tc.mul(tb)
            var t=FP4(w1)
            t.add(w2)
            t.neg()
            tc.add(t)

            ta.copy(a.geta()); ta.add(c.getb()); ta.norm()
            tb.copy(y.a.geta()); tb.add(y.c.getb()); tb.norm()
            var td=FP4(ta)
            td.mul(tb)
            t.copy(w1)
            t.add(w3)
            t.neg()
            td.add(t)

            ta.copy(a.getb()); ta.add(c.getb()); ta.norm()
            tb.copy(y.a.getb()); tb.add(y.c.getb()); tb.norm()
            var te=FP4(ta)
            te.mul(tb)
            t.copy(w2)
            t.add(w3)
            t.neg()
            te.add(t)

            w2.times_i()
            w1.add(w2)
            a.set_fp4s(w1,tc);

            w3.times_i()
            w3.norm()
            b.set_fp4h(w3);

            te.norm()
            te.times_i()
            c.set_fp4s(te,td)

            a.norm()
            c.norm()

        }
        stype=FP24.SPARSE
    }

    /* self=1/self */
    mutating func inverse()
    {
        var f0=FP8(a)
        var f1=FP8(b)
        var f2=FP8(a)
        var f3=FP8()
    
        //norm()
        f0.sqr()
        f1.mul(c)
        f1.times_i()
        f0.sub(f1); f0.norm()
    
        f1.copy(c); f1.sqr()
        f1.times_i()
        f2.mul(b)
        f1.sub(f2); f1.norm()
    
        f2.copy(b); f2.sqr()
        f3.copy(a); f3.mul(c)
        f2.sub(f3); f2.norm()
    
        f3.copy(b); f3.mul(f2)
        f3.times_i()
        a.mul(f0)
        f3.add(a)
        c.mul(f1)
        c.times_i()
    
        f3.add(c); f3.norm()
        f3.inverse()
        a.copy(f0); a.mul(f3)
        b.copy(f1); b.mul(f3)
        c.copy(f2); c.mul(f3)
        stype=FP24.DENSE        
    }

    /* self=self^p using Frobenius */
    mutating func frob(_ f:FP2,_ n:Int)
    {

        var f2=FP2(f)
        var f3=FP2(f)
    
        f2.sqr()
        f3.mul(f2)
        f3.mul_ip(); f3.norm()    

        for _ in 0 ..< n {
            a.frob(f3)
            b.frob(f3)
            c.frob(f3)

            b.qmul(f); b.times_i2()
            c.qmul(f2); c.times_i2(); c.times_i2()
        }        
        stype=FP24.DENSE
    }

    /* trace function */
    func trace() -> FP8
    {
        var t=FP8()
        t.copy(a)
        t.imul(3)
        t.reduce()
        return t
    }

    /* convert from byte array to FP24 */
    static func fromBytes(_ w:[UInt8]) -> FP24
    {
        let RM=Int(CONFIG_BIG.MODBYTES)
        var t=[UInt8](repeating: 0,count: RM)
    
        for i in 0 ..< RM {t[i]=w[i]}
        var a=BIG.fromBytes(t)
        for i in 0 ..< RM {t[i]=w[i+RM]}
        var b=BIG.fromBytes(t)
        var c=FP2(a,b)
    
        for i in 0 ..< RM {t[i]=w[i+2*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+3*RM]}
        b.copy(BIG.fromBytes(t))
        var d=FP2(a,b)
    
        var ea=FP4(c,d)

        for i in 0 ..< RM {t[i]=w[i+4*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+5*RM]}
        b.copy(BIG.fromBytes(t))
        c.copy(FP2(a,b))
    
        for i in 0 ..< RM {t[i]=w[i+6*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+7*RM]}
        b.copy(BIG.fromBytes(t))
        d.copy(FP2(a,b))
    
        var eb=FP4(c,d)

        let e=FP8(ea,eb)
    
        for i in 0 ..< RM {t[i]=w[i+8*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+9*RM]}
        b.copy(BIG.fromBytes(t))
        c.copy(FP2(a,b))
    
        for i in 0 ..< RM {t[i]=w[i+10*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+11*RM]}
        b.copy(BIG.fromBytes(t))
        d.copy(FP2(a,b))

        ea.copy(FP4(c,d))


        for i in 0 ..< RM {t[i]=w[i+12*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+13*RM]}
        b.copy(BIG.fromBytes(t))
        c.copy(FP2(a,b))
    
        for i in 0 ..< RM {t[i]=w[i+14*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+15*RM]}
        b.copy(BIG.fromBytes(t))
        d.copy(FP2(a,b))

        eb.copy(FP4(c,d))

    
        let f=FP8(ea,eb)
    
    
        for i in 0 ..< RM {t[i]=w[i+16*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+17*RM]}
        b.copy(BIG.fromBytes(t))
        c.copy(FP2(a,b))
    
        for i in 0 ..< RM {t[i]=w[i+18*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+19*RM]}
        b.copy(BIG.fromBytes(t))
        d.copy(FP2(a,b))

        ea.copy(FP4(c,d))

    
        for i in 0 ..< RM {t[i]=w[i+20*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+21*RM]}
        b.copy(BIG.fromBytes(t))
        c.copy(FP2(a,b))
    
        for i in 0 ..< RM {t[i]=w[i+22*RM]}
        a.copy(BIG.fromBytes(t))
        for i in 0 ..< RM {t[i]=w[i+23*RM]}
        b.copy(BIG.fromBytes(t))
        d.copy(FP2(a,b))

        eb.copy(FP4(c,d))

        let g=FP8(ea,eb)
    
        return FP24(e,f,g)
    }

    /* convert this to byte array */
    func toBytes(_ w:inout [UInt8])
    {
        let RM=Int(CONFIG_BIG.MODBYTES)
        var t=[UInt8](repeating: 0,count: RM)

        a.geta().geta().getA().toBytes(&t)
        for i in 0 ..< RM {w[i]=t[i]}
        a.geta().geta().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+RM]=t[i]}
        a.geta().getb().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+2*RM]=t[i]}
        a.geta().getb().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+3*RM]=t[i]}
    
        a.getb().geta().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+4*RM]=t[i]}
        a.getb().geta().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+5*RM]=t[i]}
        a.getb().getb().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+6*RM]=t[i]}
        a.getb().getb().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+7*RM]=t[i]}
  


        b.geta().geta().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+8*RM]=t[i]}
        b.geta().geta().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+9*RM]=t[i]}
        b.geta().getb().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+10*RM]=t[i]}
        b.geta().getb().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+11*RM]=t[i]}
    
        b.getb().geta().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+12*RM]=t[i]}
        b.getb().geta().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+13*RM]=t[i]}
        b.getb().getb().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+14*RM]=t[i]}
        b.getb().getb().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+15*RM]=t[i]}



        c.geta().geta().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+16*RM]=t[i]}
        c.geta().geta().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+17*RM]=t[i]}
        c.geta().getb().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+18*RM]=t[i]}
        c.geta().getb().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+19*RM]=t[i]}

        c.getb().geta().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+20*RM]=t[i]}
        c.getb().geta().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+21*RM]=t[i]}
        c.getb().getb().getA().toBytes(&t)
        for i in 0 ..< RM {w[i+22*RM]=t[i]}
        c.getb().getb().getB().toBytes(&t)
        for i in 0 ..< RM {w[i+23*RM]=t[i]}        
    }

    /* convert to hex string */
    public func toString() -> String
    {
        return ("["+a.toString()+","+b.toString()+","+c.toString()+"]")
    }
    
    /* self=self^e */
    /* Note this is simple square and multiply, so not side-channel safe */
    func pow(_ e:BIG) -> FP24
    {
        var sf = FP24(self)
        sf.norm()
 
        var e1=BIG(e)
        e1.norm()
        var e3=BIG(e1)
        e3.pmul(3)
        e3.norm();

        var w=FP24(sf)
        let nb=e3.nbits()
 
        for i in (1...nb-2).reversed()
        {
            w.usqr()
            let bt=e3.bit(UInt(i))-e1.bit(UInt(i))
            if bt == 1 {
                w.mul(sf)
            }
            if bt == -1 {
                sf.conj(); w.mul(sf); sf.conj()
            }            
        }
    
        w.reduce()
        return w
    }

    /* constant time powering by small integer of max length bts */
    mutating func pinpow(_ e:Int32,_ bts:Int32)
    {
        var R=[FP24]()
        R.append(FP24(1))
        R.append(FP24(self))

        for i in (0...bts-1).reversed()
        {
            let b=Int((e>>i)&1)
            R[1-b].mul(R[b])
            R[b].usqr()
        }
        copy(R[0])
    }

    public func compow(_ e :BIG,_ r :BIG) -> FP8
    {
        let f=FP2(BIG(ROM.Fra),BIG(ROM.Frb))
        let q=BIG(ROM.Modulus)       

        var g1=FP24(self)
        var g2=FP24(self)

        var m=BIG(q)
        m.mod(r)
    
        var a=BIG(e)
        a.mod(m)
    
        var b=BIG(e)
        b.div(m);
    
        var c=g1.trace()

        if b.iszilch() 
        {
            c=c.xtr_pow(e)
            return c
        }

        g2.frob(f,1)
        let cp=g2.trace()
        g1.conj()
        g2.mul(g1)
        let cpm1=g2.trace()
        g2.mul(g1)
        let cpm2=g2.trace()
    
        c=c.xtr_pow2(cp,cpm1,cpm2,a,b)

        return c
    }

    /* P=u0.Q0+u1*Q1+u2*Q2+u3*Q3.. */
    // Bos & Costello https://eprint.iacr.org/2013/458.pdf
    // Faz-Hernandez & Longa & Sanchez  https://eprint.iacr.org/2013/158.pdf
    // Side channel attack secure 

    static func pow8(_ q:[FP24],_ u:[BIG]) -> FP24
    {
        var g1=[FP24]()
        var g2=[FP24]()        
        
        for _ in 0 ..< 8 {
            g1.append(FP24())
            g2.append(FP24())            
        }
        
        var r=FP24()
        var p=FP24()
        
        var t=[BIG]()
        for i in 0 ..< 8 {
            t.append(BIG(u[i]))
            t[i].norm()
        }

        var mt=BIG(0);
        var w1=[Int8](repeating: 0,count: CONFIG_BIG.NLEN*Int(CONFIG_BIG.BASEBITS)+1)           
        var s1=[Int8](repeating: 0,count: CONFIG_BIG.NLEN*Int(CONFIG_BIG.BASEBITS)+1)   
        var w2=[Int8](repeating: 0,count: CONFIG_BIG.NLEN*Int(CONFIG_BIG.BASEBITS)+1)           
        var s2=[Int8](repeating: 0,count: CONFIG_BIG.NLEN*Int(CONFIG_BIG.BASEBITS)+1)   


// precompute table 
        g1[0].copy(q[0])   // q[0]
        g1[1].copy(g1[0]); g1[1].mul(q[1])   // q[0].q[1]
        g1[2].copy(g1[0]); g1[2].mul(q[2])   // q[0].q[2]
        g1[3].copy(g1[1]); g1[3].mul(q[2])   // q[0].q[1].q[2]
        g1[4].copy(g1[0]); g1[4].mul(q[3])   // q[0].q[3]
        g1[5].copy(g1[1]); g1[5].mul(q[3])   // q[0].q[1].q[3]
        g1[6].copy(g1[2]); g1[6].mul(q[3])   // q[0].q[2].q[3]
        g1[7].copy(g1[3]); g1[7].mul(q[3])   // q[0].q[1].q[2].q[3]

// Use Frobenius
    let f=FP2(BIG(ROM.Fra),BIG(ROM.Frb))
    for i in 0 ..< 8  {
        g2[i].copy(g1[i]); g2[i].frob(f,4)       
    }

// Make it odd
        let pb1=1-t[0].parity()
        t[0].inc(pb1)
        t[0].norm()  

        let pb2=1-t[4].parity()
        t[4].inc(pb2)
        t[4].norm()          

// Number of bits
        mt.zero();
        for i in 0 ..< 8 {
            mt.or(t[i]); 
        }

        let nb=1+mt.nbits()

// Sign pivot 

        s1[nb-1]=1
        s2[nb-1]=1
        for i in 0 ..< nb-1 {
            t[0].fshr(1)
            s1[i]=2*Int8(t[0].parity())-1
            t[4].fshr(1)
            s2[i]=2*Int8(t[4].parity())-1            
        }

// Recoded exponent
        for i in 0 ..< nb {
            w1[i]=0
            var k=1
            for j in 1 ..< 4 {
                let bt=s1[i]*Int8(t[j].parity())
                t[j].fshr(1)
                t[j].dec(Int(bt>>1))
                t[j].norm()
                w1[i]+=bt*Int8(k)
                k=2*k
            }
            w2[i]=0
            k=1
            for j in 5 ..< 8 {
                let bt=s2[i]*Int8(t[j].parity())
                t[j].fshr(1)
                t[j].dec(Int(bt>>1))
                t[j].norm()
                w2[i]+=bt*Int8(k)
                k=2*k
            }            
        }   

// Main loop
        p.select(g1,Int32(2*w1[nb-1]+1))
        r.select(g2,Int32(2*w2[nb-1]+1))
        p.mul(r)        
        for i in (0 ..< nb-1).reversed() {
            p.usqr()
            r.select(g1,Int32(2*w1[i]+s1[i]))
            p.mul(r)
            r.select(g2,Int32(2*w2[i]+s2[i]))
            p.mul(r)            
        }      

// apply correction
        r.copy(q[0]); r.conj()   
        r.mul(p)
        p.cmove(r,pb1)

        r.copy(q[4]); r.conj()   
        r.mul(p)
        p.cmove(r,pb2)

        p.reduce()
        return p
    }
}
