nel-neo-thinsdk
Version:
357 lines (325 loc) • 13 kB
text/typescript
namespace Neo.Cryptography
{
export class ECPoint
{
constructor(public x: ECFieldElement, public y: ECFieldElement, public curve: ECCurve)
{
if ((x == null) != (y == null))
throw new RangeError("Exactly one of the field elements is null");
}
public static add(x: ECPoint, y: ECPoint): ECPoint
{
if (x.isInfinity()) return y;
if (y.isInfinity()) return x;
if (x.x.equals(y.x))
{
if (x.y.equals(y.y)) return x.twice();
console.assert(x.y.equals(y.y.negate()));
return x.curve.Infinity;
}
let gamma = y.y.subtract(x.y).divide(y.x.subtract(x.x));
let x3 = gamma.square().subtract(x.x).subtract(y.x);
let y3 = gamma.multiply(x.x.subtract(x3)).subtract(x.y);
return new ECPoint(x3, y3, x.curve);
}
public compareTo(other: ECPoint): number
{
if (this === other) return 0;
let result = this.x.compareTo(other.x);
if (result != 0) return result;
return this.y.compareTo(other.y);
}
public static decodePoint(encoded: Uint8Array, curve: ECCurve): ECPoint
{
let p: ECPoint;
let expectedLength = Math.ceil(curve.Q.bitLength() / 8);
switch (encoded[0])
{
case 0x00: // infinity
{
if (encoded.length != 1)
throw new RangeError("Incorrect length for infinity encoding");
p = curve.Infinity;
break;
}
case 0x02: // compressed
case 0x03: // compressed
{
if (encoded.length != (expectedLength + 1))
throw new RangeError("Incorrect length for compressed encoding");
let yTilde = encoded[0] & 1;
let X1 = BigInteger.fromUint8Array(encoded.subarray(1), 1, false);
p = ECPoint.decompressPoint(yTilde, X1, curve);
break;
}
case 0x04: // uncompressed
case 0x06: // hybrid
case 0x07: // hybrid
{
if (encoded.length != (2 * expectedLength + 1))
throw new RangeError("Incorrect length for uncompressed/hybrid encoding");
let X1 = BigInteger.fromUint8Array(encoded.subarray(1, 1 + expectedLength), 1, false);
let Y1 = BigInteger.fromUint8Array(encoded.subarray(1 + expectedLength), 1, false);
p = new ECPoint(new ECFieldElement(X1, curve), new ECFieldElement(Y1, curve), curve);
break;
}
default:
throw new RangeError("Invalid point encoding " + encoded[0]);
}
return p;
}
private static decompressPoint(yTilde: number, X1: BigInteger, curve: ECCurve): ECPoint
{
let x = new ECFieldElement(X1, curve);
let alpha = x.multiply(x.square().add(curve.A)).add(curve.B);
let beta = alpha.sqrt();
//
// if we can't find a sqrt we haven't got a point on the
// curve - run!
//
if (beta == null)
throw new RangeError("Invalid point compression");
let betaValue = beta.value;
let bit0 = betaValue.isEven() ? 0 : 1;
if (bit0 != yTilde)
{
// Use the other root
beta = new ECFieldElement(curve.Q.subtract(betaValue), curve);
}
return new ECPoint(x, beta, curve);
}
public static deserializeFrom(reader: IO.BinaryReader, curve: ECCurve): ECPoint
{
let expectedLength = Math.floor((curve.Q.bitLength() + 7) / 8);
let array = new Uint8Array(1 + expectedLength * 2);
array[0] = reader.readByte();
switch (array[0])
{
case 0x00:
return curve.Infinity;
case 0x02:
case 0x03:
reader.read(array.buffer, 1, expectedLength);
return ECPoint.decodePoint(new Uint8Array(array.buffer, 0, 33), curve);
case 0x04:
case 0x06:
case 0x07:
reader.read(array.buffer, 1, expectedLength * 2);
return ECPoint.decodePoint(array, curve);
default:
throw new Error("Invalid point encoding " + array[0]);
}
}
public encodePoint(commpressed: boolean): Uint8Array
{
if (this.isInfinity()) return new Uint8Array(1);
let data: Uint8Array;
if (commpressed)
{
data = new Uint8Array(33);
}
else
{
data = new Uint8Array(65);
let yBytes = this.y.value.toUint8Array();
for (let i = 0; i < yBytes.length; i++)
data[65 - yBytes.length + i] = yBytes[yBytes.length - 1 - i];
}
let xBytes = this.x.value.toUint8Array();
for (let i = 0; i < xBytes.length; i++)
data[33 - xBytes.length + i] = xBytes[xBytes.length - 1 - i];
data[0] = commpressed ? this.y.value.isEven() ? 0x02 : 0x03 : 0x04;
return data;
}
public equals(other: ECPoint): boolean
{
if (this === other) return true;
if (null === other) return false;
if (this.isInfinity && other.isInfinity) return true;
if (this.isInfinity || other.isInfinity) return false;
return this.x.equals(other.x) && this.y.equals(other.y);
}
public static fromUint8Array(arr: Uint8Array, curve: ECCurve): ECPoint
{
switch (arr.length)
{
case 33:
case 65:
return ECPoint.decodePoint(arr, curve);
case 64:
case 72:
{
let arr_new = new Uint8Array(65);
arr_new[0] = 0x04;
arr_new.set(arr.subarray(arr.length - 64), 1);
return ECPoint.decodePoint(arr_new, curve);
}
case 96:
case 104:
{
let arr_new = new Uint8Array(65);
arr_new[0] = 0x04;
arr_new.set(arr.subarray(arr.length - 96, arr.length - 32), 1);
return ECPoint.decodePoint(arr_new, curve);
}
default:
throw new RangeError();
}
}
public isInfinity(): boolean
{
return this.x == null && this.y == null;
}
public static multiply(p: ECPoint, n: Uint8Array | BigInteger): ECPoint
{
let k = n instanceof Uint8Array ? BigInteger.fromUint8Array(n, 1, false) : n as BigInteger;
if (p.isInfinity()) return p;
if (k.isZero()) return p.curve.Infinity;
// floor(log2(k))
let m = k.bitLength();
// width of the Window NAF
let width: number;
// Required length of precomputation array
let reqPreCompLen: number;
// Determine optimal width and corresponding length of precomputation
// array based on literature values
if (m < 13)
{
width = 2;
reqPreCompLen = 1;
}
else if (m < 41)
{
width = 3;
reqPreCompLen = 2;
}
else if (m < 121)
{
width = 4;
reqPreCompLen = 4;
}
else if (m < 337)
{
width = 5;
reqPreCompLen = 8;
}
else if (m < 897)
{
width = 6;
reqPreCompLen = 16;
}
else if (m < 2305)
{
width = 7;
reqPreCompLen = 32;
}
else
{
width = 8;
reqPreCompLen = 127;
}
// The length of the precomputation array
let preCompLen = 1;
let preComp = [p];
let twiceP = p.twice();
if (preCompLen < reqPreCompLen)
{
// Precomputation array must be made bigger, copy existing preComp
// array into the larger new preComp array
let oldPreComp = preComp;
preComp = new Array<ECPoint>(reqPreCompLen);
for (let i = 0; i < preCompLen; i++)
preComp[i] = oldPreComp[i];
for (let i = preCompLen; i < reqPreCompLen; i++)
{
// Compute the new ECPoints for the precomputation array.
// The values 1, 3, 5, ..., 2^(width-1)-1 times p are
// computed
preComp[i] = ECPoint.add(twiceP, preComp[i - 1]);
}
}
// Compute the Window NAF of the desired width
let wnaf = ECPoint.windowNaf(width, k);
let l = wnaf.length;
// Apply the Window NAF to p using the precomputed ECPoint values.
let q = p.curve.Infinity;
for (let i = l - 1; i >= 0; i--)
{
q = q.twice();
if (wnaf[i] != 0)
{
if (wnaf[i] > 0)
{
q = ECPoint.add(q, preComp[Math.floor((wnaf[i] - 1) / 2)]);
}
else
{
// wnaf[i] < 0
q = ECPoint.subtract(q, preComp[Math.floor((-wnaf[i] - 1) / 2)]);
}
}
}
return q;
}
public negate(): ECPoint
{
return new ECPoint(this.x, this.y.negate(), this.curve);
}
public static parse(str: string, curve: ECCurve): ECPoint
{
return ECPoint.decodePoint(str.hexToBytes(), curve);
}
public static subtract(x: ECPoint, y: ECPoint): ECPoint
{
if (y.isInfinity()) return x;
return ECPoint.add(x, y.negate());
}
public toString(): string
{
return this.encodePoint(true).toHexString();
}
public twice(): ECPoint
{
if (this.isInfinity()) return this;
if (this.y.value.sign() == 0) return this.curve.Infinity;
let TWO = new ECFieldElement(new BigInteger(2), this.curve);
let THREE = new ECFieldElement(new BigInteger(3), this.curve);
let gamma = this.x.square().multiply(THREE).add(this.curve.A).divide(this.y.multiply(TWO));
let x3 = gamma.square().subtract(this.x.multiply(TWO));
let y3 = gamma.multiply(this.x.subtract(x3)).subtract(this.y);
return new ECPoint(x3, y3, this.curve);
}
private static windowNaf(width: number, k: BigInteger): number[]
{
let wnaf = new Array<number>(k.bitLength() + 1);
let pow2wB = 1 << width;
let i = 0;
let length = 0;
while (k.sign() > 0)
{
if (!k.isEven())
{
let remainder = BigInteger.remainder(k, pow2wB);
if (remainder.testBit(width - 1))
{
wnaf[i] = BigInteger.subtract(remainder, pow2wB).toInt32();
}
else
{
wnaf[i] = remainder.toInt32();
}
k = k.subtract(wnaf[i]);
length = i;
}
else
{
wnaf[i] = 0;
}
k = k.rightShift(1);
i++;
}
wnaf.length = length + 1;
return wnaf;
}
}
}