blob: 94dedc6b0e04fb97e07aa9c34bfbd12ab9707d1c [file] [log] [blame]
/*
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.
*/
/* AMCL FF number class */
/* General purpose Constructor */
var FF = function(n) {
this.v=new Array(n);
this.length=n;
for (var i=0;i<n;i++)
this.v[i]=new BIG(0);
};
FF.prototype={
/* set to zero */
P_EXCESS: function()
{
return ((this.v[this.length-1].get(ROM.NLEN-1)&FF.P_OMASK)>>(FF.P_TBITS));
},
zero: function()
{
for (var i=0;i<this.length;i++) this.v[i].zero();
return this;
},
getlen: function()
{
return this.length;
},
/* set to integer */
set: function(m)
{
this.zero();
this.v[0].set(0,(m&ROM.BMASK));
this.v[0].set(1,(m>>ROM.BASEBITS));
},
/* copy from FF b */
copy: function(b)
{
for (var i=0;i<this.length;i++)
{
this.v[i].copy(b.v[i]);
}
},
/* copy from FF b */
rcopy: function(b)
{
for (var i=0;i<this.length;i++)
{
this.v[i].rcopy(b[i]);
}
},
/* x=y<<n */
dsucopy: function(b)
{
for (var i=0;i<b.length;i++)
{
this.v[b.length+i].copy(b.v[i]);
this.v[i].zero();
}
},
/* x=y */
dscopy: function(b)
{
for (var i=0;i<b.length;i++)
{
this.v[i].copy(b.v[i]);
this.v[b.length+i].zero();
}
},
/* x=y>>n */
sducopy: function(b)
{
for (var i=0;i<this.length;i++)
{
this.v[i].copy(b.v[this.length+i]);
}
},
one: function()
{
this.v[0].one();
for (var i=1;i<this.length;i++)
{
this.v[i].zero();
}
},
/* test equals 0 */
iszilch: function()
{
for (var i=0;i<this.length;i++)
{
if (!this.v[i].iszilch()) return false;
}
return true;
},
/* shift right by BIGBITS-bit words */
shrw: function(n)
{
for (var i=0;i<n;i++)
{
this.v[i].copy(this.v[i+n]);
this.v[i+n].zero();
}
},
/* shift left by BIGBITS-bit words */
shlw: function(n)
{
for (var i=0;i<n;i++)
{
this.v[n+i].copy(this.v[i]);
this.v[i].zero();
}
},
/* extract last bit */
parity: function()
{
return this.v[0].parity();
},
lastbits: function(m)
{
return this.v[0].lastbits(m);
},
/* recursive add */
radd: function(vp,x,xp,y,yp,n)
{
for (var i=0;i<n;i++)
{
this.v[vp+i].copy(x.v[xp+i]);
this.v[vp+i].add(y.v[yp+i]);
}
},
/* recursive inc */
rinc: function(vp,y,yp,n)
{
for (var i=0;i<n;i++)
{
this.v[vp+i].add(y.v[yp+i]);
}
},
/* recursive sub */
rsub: function(vp,x,xp,y,yp,n)
{
for (var i=0;i<n;i++)
{
this.v[vp+i].copy(x.v[xp+i]);
this.v[vp+i].sub(y.v[yp+i]);
}
},
/* recursive dec */
rdec: function(vp,y,yp,n)
{
for (var i=0;i<n;i++)
{
this.v[vp+i].sub(y.v[yp+i]);
}
},
/* simple add */
add: function(b)
{
for (var i=0;i<this.length;i++)
this.v[i].add(b.v[i]);
},
/* simple sub */
sub: function(b)
{
for (var i=0;i<this.length;i++)
this.v[i].sub(b.v[i]);
},
/* reverse sub */
revsub: function(b)
{
for (var i=0;i<this.length;i++)
this.v[i].rsub(b.v[i]);
},
/* increment/decrement by a small integer */
inc: function(m)
{
this.v[0].inc(m);
this.norm();
},
dec: function(m)
{
this.v[0].dec(m);
this.norm();
},
/* normalise - but hold any overflow in top part unless n<0 */
rnorm: function(vp,n)
{
var trunc=false;
var i,carry;
if (n<0)
{ /* -v n signals to do truncation */
n=-n;
trunc=true;
}
for (i=0;i<n-1;i++)
{
carry=this.v[vp+i].norm();
this.v[vp+i].xortop(carry<<FF.P_TBITS);
this.v[vp+i+1].inc(carry);
}
carry=this.v[vp+n-1].norm();
if (trunc)
this.v[vp+n-1].xortop(carry<<FF.P_TBITS);
return this;
},
norm: function()
{
this.rnorm(0,this.length);
},
/* shift left by one bit */
shl: function()
{
var i,carry,delay_carry=0;
for (i=0;i<this.length-1;i++)
{
carry=this.v[i].fshl(1);
this.v[i].inc(delay_carry);
this.v[i].xortop(carry<<FF.P_TBITS);
delay_carry=carry;
}
this.v[this.length-1].fshl(1);
this.v[this.length-1].inc(delay_carry);
},
/* shift right by one bit */
shr: function()
{
var i,carry;
for (i=this.length-1;i>0;i--)
{
carry=this.v[i].fshr(1);
this.v[i-1].ortop(carry<<FF.P_TBITS);
}
this.v[0].fshr(1);
},
/* Convert to Hex String */
toString: function()
{
this.norm();
var s="";
for (var i=this.length-1;i>=0;i--)
{
s+=this.v[i].toString();
}
return s;
},
/* Convert FFs to/from byte arrays */
toBytes: function(b)
{
for (var i=0;i<this.length;i++)
{
this.v[i].tobytearray(b,(this.length-i-1)*ROM.MODBYTES);
}
},
/* z=x*y, t is workspace */
karmul: function(vp,x,xp,y,yp,t,tp,n)
{
var nd2;
if (n==1)
{
var d=BIG.mul(x.v[xp],y.v[yp]);
this.v[vp+1]=d.split(8*ROM.MODBYTES);
this.v[vp].copy(d);
return;
}
nd2=n/2;
this.radd(vp,x,xp,x,xp+nd2,nd2);
this.rnorm(vp,nd2); /* Important - required for 32-bit build */
this.radd(vp+nd2,y,yp,y,yp+nd2,nd2);
this.rnorm(vp+nd2,nd2); /* Important - required for 32-bit build */
t.karmul(tp,this,vp,this,vp+nd2,t,tp+n,nd2);
this.karmul(vp,x,xp,y,yp,t,tp+n,nd2);
this.karmul(vp+n,x,xp+nd2,y,yp+nd2,t,tp+n,nd2);
t.rdec(tp,this,vp,n);
t.rdec(tp,this,vp+n,n);
this.rinc(vp+nd2,t,tp,n);
this.rnorm(vp,2*n);
},
karsqr: function(vp,x,xp,t,tp,n)
{
var nd2;
if (n==1)
{
var d=BIG.sqr(x.v[xp]);
this.v[vp+1].copy(d.split(8*ROM.MODBYTES));
this.v[vp].copy(d);
return;
}
nd2=n/2;
this.karsqr(vp,x,xp,t,tp+n,nd2);
this.karsqr(vp+n,x,xp+nd2,t,tp+n,nd2);
t.karmul(tp,x,xp,x,xp+nd2,t,tp+n,nd2);
this.rinc(vp+nd2,t,tp,n);
this.rinc(vp+nd2,t,tp,n);
this.rnorm(vp+nd2,n);
},
karmul_lower: function(vp,x,xp,y,yp,t,tp,n)
{ /* Calculates Least Significant bottom half of x*y */
var nd2;
if (n==1)
{ /* only calculate bottom half of product */
this.v[vp].copy(BIG.smul(x.v[xp],y.v[yp]));
return;
}
nd2=n/2;
this.karmul(vp,x,xp,y,yp,t,tp+n,nd2);
t.karmul_lower(tp,x,xp+nd2,y,yp,t,tp+n,nd2);
this.rinc(vp+nd2,t,tp,nd2);
t.karmul_lower(tp,x,xp,y,yp+nd2,t,tp+n,nd2);
this.rinc(vp+nd2,t,tp,nd2);
this.rnorm(vp+nd2,-nd2); /* truncate it */
},
karmul_upper: function(x,y,t,n)
{ /* Calculates Most Significant upper half of x*y, given lower part */
var nd2;
nd2=n/2;
this.radd(n,x,0,x,nd2,nd2);
this.radd(n+nd2,y,0,y,nd2,nd2);
this.rnorm(n,nd2);
this.rnorm(n+nd2,nd2);
t.karmul(0,this,n+nd2,this,n,t,n,nd2); /* t = (a0+a1)(b0+b1) */
this.karmul(n,x,nd2,y,nd2,t,n,nd2); /* z[n]= a1*b1 */
/* z[0-nd2]=l(a0b0) z[nd2-n]= h(a0b0)+l(t)-l(a0b0)-l(a1b1) */
t.rdec(0,this,n,n); /* t=t-a1b1 */
this.rinc(nd2,this,0,nd2); /* z[nd2-n]+=l(a0b0) = h(a0b0)+l(t)-l(a1b1) */
this.rdec(nd2,t,0,nd2); /* z[nd2-n]=h(a0b0)+l(t)-l(a1b1)-l(t-a1b1)=h(a0b0) */
this.rnorm(0,-n); /* a0b0 now in z - truncate it */
t.rdec(0,this,0,n); /* (a0+a1)(b0+b1) - a0b0 */
this.rinc(nd2,t,0,n);
this.rnorm(nd2,n);
},
/* return low part of product this*y */
lmul: function(y)
{
var n=this.length;
var t=new FF(2*n);
var x=new FF(n); x.copy(this);
this.karmul_lower(0,x,0,y,0,t,0,n);
},
/* Set b=b mod c */
mod: function(c)
{
var k=0;
this.norm();
if (FF.comp(this,c)<0)
return;
do
{
c.shl();
k++;
} while (FF.comp(this,c)>=0);
while (k>0)
{
c.shr();
if (FF.comp(this,c)>=0)
{
this.sub(c);
this.norm();
}
k--;
}
},
/* return This mod modulus, N is modulus, ND is Montgomery Constant */
reduce: function(N,ND)
{ /* fast karatsuba Montgomery reduction */
var n=N.length;
var t=new FF(2*n);
var r=new FF(n);
var m=new FF(n);
r.sducopy(this);
m.karmul_lower(0,this,0,ND,0,t,0,n);
this.karmul_upper(N,m,t,n);
m.sducopy(this);
r.add(N);
r.sub(m);
r.norm();
return r;
},
/* Set r=this mod b */
/* this is of length - 2*n */
/* r,b is of length - n */
dmod: function(b)
{
var k,n=b.length;
var m=new FF(2*n);
var x=new FF(2*n);
var r=new FF(n);
x.copy(this);
x.norm();
m.dsucopy(b); k=ROM.BIGBITS*n;
while (FF.comp(x,m)>=0)
{
x.sub(m);
x.norm();
}
while (k>0)
{
m.shr();
if (FF.comp(x,m)>=0)
{
x.sub(m);
x.norm();
}
k--;
}
r.copy(x);
r.mod(b);
return r;
},
/* Set return=1/this mod p. Binary method - a<p on entry */
invmodp: function(p)
{
var n=p.length;
var u=new FF(n);
var v=new FF(n);
var x1=new FF(n);
var x2=new FF(n);
var t=new FF(n);
var one=new FF(n);
one.one();
u.copy(this);
v.copy(p);
x1.copy(one);
x2.zero();
// reduce n in here as well!
while (FF.comp(u,one)!==0 && FF.comp(v,one)!==0)
{
while (u.parity()===0)
{
u.shr();
if (x1.parity()!==0)
{
x1.add(p);
x1.norm();
}
x1.shr();
}
while (v.parity()===0)
{
v.shr();
if (x2.parity()!==0)
{
x2.add(p);
x2.norm();
}
x2.shr();
}
if (FF.comp(u,v)>=0)
{
u.sub(v);
u.norm();
if (FF.comp(x1,x2)>=0) x1.sub(x2);
else
{
t.copy(p);
t.sub(x2);
x1.add(t);
}
x1.norm();
}
else
{
v.sub(u);
v.norm();
if (FF.comp(x2,x1)>=0) x2.sub(x1);
else
{
t.copy(p);
t.sub(x1);
x2.add(t);
}
x2.norm();
}
}
if (FF.comp(u,one)===0)
this.copy(x1);
else
this.copy(x2);
},
/* nresidue mod m */
nres: function(m)
{
var n=m.length;
var d=new FF(2*n);
d.dsucopy(this);
this.copy(d.dmod(m));
},
redc: function(m,ND)
{
var n=m.length;
var d=new FF(2*n);
this.mod(m);
d.dscopy(this);
this.copy(d.reduce(m,ND));
this.mod(m);
},
mod2m: function(m)
{
for (var i=m;i<this.length;i++)
this.v[i].zero();
},
/* U=1/a mod 2^m - Arazi & Qi */
invmod2m: function()
{
var i,n=this.length;
var b=new FF(n);
var c=new FF(n);
var U=new FF(n);
var t;
U.zero();
U.v[0].copy(this.v[0]);
U.v[0].invmod2m();
for (i=1;i<n;i<<=1)
{
b.copy(this); b.mod2m(i);
t=FF.mul(U,b); t.shrw(i); b.copy(t);
c.copy(this); c.shrw(i); c.mod2m(i);
c.lmul(U); c.mod2m(i);
b.add(c); b.norm();
b.lmul(U); b.mod2m(i);
c.one(); c.shlw(i); b.revsub(c); b.norm();
b.shlw(i);
U.add(b);
}
U.norm();
return U;
},
random: function(rng)
{
var n=this.length;
for (var i=0;i<n;i++)
{
this.v[i].copy(BIG.random(rng));
}
/* make sure top bit is 1 */
while (this.v[n-1].nbits()<ROM.MODBYTES*8) this.v[n-1].copy(BIG.random(rng));
},
/* generate random x */
randomnum: function(p,rng)
{
var n=this.length;
var d=new FF(2*n);
for (var i=0;i<2*n;i++)
{
d.v[i].copy(BIG.random(rng));
}
this.copy(d.dmod(p));
},
/* this*=y mod p */
modmul: function(y,p,nd)
{
var ex=this.P_EXCESS();
var ey=y.P_EXCESS();
if ((ex+1)>=Math.floor((FF.P_FEXCESS-1)/(ey+1))) this.mod(p);
var d=FF.mul(this,y);
this.copy(d.reduce(p,nd));
},
/* this*=y mod p */
modsqr: function(p,nd)
{
var ex=this.P_EXCESS();
if ((ex+1)>=Math.floor((FF.P_FEXCESS-1)/(ex+1))) this.mod(p);
var d=FF.sqr(this);
this.copy(d.reduce(p,nd));
},
/* this=this^e mod p using side-channel resistant Montgomery Ladder, for large e */
skpow: function(e,p)
{
var i,b,n=p.length;
var R0=new FF(n);
var R1=new FF(n);
var ND=p.invmod2m();
this.mod(p);
R0.one();
R1.copy(this);
R0.nres(p);
R1.nres(p);
for (i=8*ROM.MODBYTES*n-1;i>=0;i--)
{
b=e.v[Math.floor(i/ROM.BIGBITS)].bit(i%ROM.BIGBITS);
this.copy(R0);
this.modmul(R1,p,ND);
FF.cswap(R0,R1,b);
R0.modsqr(p,ND);
R1.copy(this);
FF.cswap(R0,R1,b);
}
this.copy(R0);
this.redc(p,ND);
},
/* this =this^e mod p using side-channel resistant Montgomery Ladder, for short e */
skspow: function(e,p)
{
var i,b,n=p.length;
var R0=new FF(n);
var R1=new FF(n);
var ND=p.invmod2m();
this.mod(p);
R0.one();
R1.copy(this);
R0.nres(p);
R1.nres(p);
for (i=8*ROM.MODBYTES-1;i>=0;i--)
{
b=e.bit(i);
this.copy(R0);
this.modmul(R1,p,ND);
FF.cswap(R0,R1,b);
R0.modsqr(p,ND);
R1.copy(this);
FF.cswap(R0,R1,b);
}
this.copy(R0);
this.redc(p,ND);
},
/* raise to an integer power - right-to-left method */
power: function(e,p)
{
var n=p.length;
var f=true;
var w=new FF(n);
var ND=p.invmod2m();
w.copy(this);
w.nres(p);
if (e==2)
{
this.copy(w);
this.modsqr(p,ND);
}
else for (; ; )
{
if (e%2==1)
{
if (f) this.copy(w);
else
{
ROM.debug=true;
this.modmul(w,p,ND);
ROM.debug=false;
}
f=false;
}
e>>=1;
if (e===0) break;
w.modsqr(p,ND);
}
this.redc(p,ND);
},
/* this=this^e mod p, faster but not side channel resistant */
pow: function(e,p)
{
var i,b,n=p.length;
var w=new FF(n);
var ND=p.invmod2m();
w.copy(this);
this.one();
this.nres(p);
w.nres(p);
for (i=8*ROM.MODBYTES*n-1;i>=0;i--)
{
this.modsqr(p,ND);
b=e.v[Math.floor(i/ROM.BIGBITS)].bit(i%ROM.BIGBITS);
if (b==1) this.modmul(w,p,ND);
}
this.redc(p,ND);
},
/* double exponentiation r=x^e.y^f mod p */
pow2: function(e,y,f,p)
{
var i,eb,fb,n=p.length;
var xn=new FF(n);
var yn=new FF(n);
var xy=new FF(n);
var ND=p.invmod2m();
xn.copy(this);
yn.copy(y);
xn.nres(p);
yn.nres(p);
xy.copy(xn); xy.modmul(yn,p,ND);
this.one();
this.nres(p);
for (i=8*ROM.MODBYTES-1;i>=0;i--)
{
eb=e.bit(i);
fb=f.bit(i);
this.modsqr(p,ND);
if (eb==1)
{
if (fb==1) this.modmul(xy,p,ND);
else this.modmul(xn,p,ND);
}
else
{
if (fb==1) this.modmul(yn,p,ND);
}
}
this.redc(p,ND);
},
/* quick and dirty check for common factor with n */
cfactor: function(s)
{
var r,n=this.length;
var g;
var x=new FF(n);
var y=new FF(n);
y.set(s);
x.copy(this);
x.norm();
do
{
x.sub(y);
x.norm();
while (!x.iszilch() && x.parity()===0) x.shr();
}
while (FF.comp(x,y)>0);
g=x.v[0].get(0);
r=FF.igcd(s,g);
if (r>1) return true;
return false;
}
};
FF.P_MBITS=ROM.MODBYTES*8;
FF.P_OMASK=((-1)<<(FF.P_MBITS%ROM.BASEBITS));
FF.P_FEXCESS=(1<<(ROM.BASEBITS*ROM.NLEN-FF.P_MBITS));
FF.P_TBITS=(FF.P_MBITS%ROM.BASEBITS);
/* compare x and y - must be normalised, and of same length */
FF.comp=function(a,b)
{
var i,j;
for (i=a.length-1;i>=0;i--)
{
j=BIG.comp(a.v[i],b.v[i]);
if (j!==0) return j;
}
return 0;
};
FF.fromBytes=function(x,b)
{
for (var i=0;i<x.length;i++)
{
x.v[i]=BIG.frombytearray(b,(x.length-i-1)*ROM.MODBYTES);
}
};
/* in-place swapping using xor - side channel resistant - lengths must be the same */
FF.cswap=function(a,b,d)
{
for (var i=0;i<a.length;i++)
{
// BIG.cswap(a.v[i],b.v[i],d);
a.v[i].cswap(b.v[i],d);
}
};
/* z=x*y. Assumes x and y are of same length. */
FF.mul=function(x,y)
{
var n=x.length;
var z=new FF(2*n);
var t=new FF(2*n);
z.karmul(0,x,0,y,0,t,0,n);
return z;
};
/* z=x^2 */
FF.sqr=function(x)
{
var n=x.length;
var z=new FF(2*n);
var t=new FF(2*n);
z.karsqr(0,x,0,t,0,n);
return z;
};
FF.igcd=function(x,y)
{ /* integer GCD, returns GCD of x and y */
var r;
if (y===0) return x;
while ((r=x%y)!==0)
{x=y;y=r;}
return y;
};
/* Miller-Rabin test for primality. Slow. */
FF.prime=function(p,rng)
{
var i,j,s=0,n=p.length;
var loop;
var d=new FF(n);
var x=new FF(n);
var unity=new FF(n);
var nm1=new FF(n);
var sf=4849845; /* 3*5*.. *19 */
p.norm();
if (p.cfactor(sf)) return false;
unity.one();
nm1.copy(p);
nm1.sub(unity);
nm1.norm();
d.copy(nm1);
while (d.parity()===0)
{
d.shr();
s++;
}
if (s===0) return false;
for (i=0;i<10;i++)
{
x.randomnum(p,rng);
x.pow(d,p);
if (FF.comp(x,unity)===0 || FF.comp(x,nm1)===0) continue;
loop=false;
for (j=1;j<s;j++)
{
x.power(2,p);
if (FF.comp(x,unity)===0) return false;
if (FF.comp(x,nm1)===0) {loop=true; break;}
}
if (loop) continue;
return false;
}
return true;
};