UNPKG

nel-neo-thinsdk

Version:
357 lines (325 loc) 13 kB
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; } } }