// See file LICENSE for more information. library impl.ecc.ecc_fp; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/ecc/ecc_base.dart' hide ECCurveBase, ECFieldElementBase, ECPointBase; import 'package:pointycastle/ecc/ecc_base.dart' as ecc; import 'package:pointycastle/src/utils.dart' as utils; /// return index of lowest 1-bit in x, x < 2^31 */ int _lbit(BigInt x) { // Implementation borrowed from bignum.BigIntegerDartvm. if (x == BigInt.zero) return -1; var r = 0; while ((x & BigInt.from(0xffffffff)) == BigInt.zero) { x >>= 32; r += 32; } if ((x & BigInt.from(0xffff)) == BigInt.zero) { x >>= 16; r += 16; } if ((x & BigInt.from(0xff)) == BigInt.zero) { x >>= 8; r += 8; } if ((x & BigInt.from(0xf)) == BigInt.zero) { x >>= 4; r += 4; } if ((x & BigInt.from(3)) == BigInt.zero) { x >>= 2; r += 2; } if ((x & BigInt.one) == BigInt.zero) ++r; return r; } bool _testBit(BigInt i, int n) { return i & (BigInt.one << n) != BigInt.zero; } class ECFieldElement extends ecc.ECFieldElementBase { final BigInt? q; final BigInt? x; ECFieldElement(this.q, this.x) { if (x! >= q!) { throw ArgumentError('Value x must be smaller than q'); } } @override String get fieldName => 'Fp'; @override int get fieldSize => q!.bitLength; @override BigInt? toBigInteger() => x; @override ECFieldElement operator +(ECFieldElement b) => ECFieldElement(q, (x! + b.toBigInteger()!) % q!); @override ECFieldElement operator -(ECFieldElement b) => ECFieldElement(q, (x! - b.toBigInteger()!) % q!); @override ECFieldElement operator *(ECFieldElement b) => ECFieldElement(q, (x! * b.toBigInteger()!) % q!); @override ECFieldElement operator /(ECFieldElement b) => ECFieldElement(q, (x! * b.toBigInteger()!.modInverse(q!)) % q!); @override ECFieldElement operator -() => ECFieldElement(q, -x! % q!); @override ECFieldElement invert() => ECFieldElement(q, x!.modInverse(q!)); @override ECFieldElement square() => ECFieldElement(q, x!.modPow(BigInt.two, q!)); // D.1.4 91 /// return a sqrt root - the routine verifies that the calculation /// returns the right value - if none exists it returns null. @override ECFieldElement? sqrt() { if (!_testBit(q!, 0)) { throw UnimplementedError('Not implemented yet'); } // p % 4 == 3 if (_testBit(q!, 1)) { // z = g^(u+1) + p, p = 4u + 3 var z = ECFieldElement(q, x!.modPow((q! >> 2) + BigInt.one, q!)); return z.square() == this ? z : null; } // p % 4 == 1 var qMinusOne = q! - BigInt.one; var legendreExponent = qMinusOne >> 1; if (x!.modPow(legendreExponent, q!) != BigInt.one) { return null; } var u = qMinusOne >> 2; var k = (u << 1) + BigInt.one; var Q = x!; var fourQ = (Q >> 2) % q!; BigInt U, V; var rand = SecureRandom(); do { BigInt? P; do { P = rand.nextBigInteger(q!.bitLength); } while ((P >= q!) || (((P * P) - fourQ).modPow(legendreExponent, q!) != qMinusOne)); var result = _lucasSequence(q!, P, Q, k); U = result[0]; V = result[1]; if (((V * V) % q!) == fourQ) { // Integer division by 2, mod q if (_testBit(V, 0)) { V = V + q!; } V = V >> 1; //assert V.multiply(V).mod(q).equals(x); return ECFieldElement(q, V); } } while ((U == BigInt.one) || (U == qMinusOne)); return null; } List _lucasSequence(BigInt p, BigInt P, BigInt Q, BigInt k) { var n = k.bitLength; var s = _lbit(k); var uh = BigInt.one; var vl = BigInt.two; var vh = P; var ql = BigInt.one; var qh = BigInt.one; for (var j = n - 1; j >= (s + 1); j--) { ql = (ql * qh) % p; if (_testBit(k, j)) { qh = (ql * Q) % p; uh = (uh * vh) % p; vl = ((vh * vl) - (P * ql)) % p; vh = ((vh * vh) - (qh << 1)) % p; } else { qh = ql; uh = ((uh * vl) - ql) % p; vh = ((vh * vl) - (P * ql)) % p; vl = ((vl * vl) - (ql << 1)) % p; } } ql = (ql * qh) % p; qh = (ql * Q) % p; uh = ((uh * vl) - ql) % p; vl = ((vh * vl) - (P * ql)) % p; ql = (ql * qh) % p; for (var j = 1; j <= s; j++) { uh = (uh * vl) % p; vl = ((vl * vl) - (ql << 1)) % p; ql = (ql * ql) % p; } return [uh, vl]; } @override bool operator ==(Object other) { if (other is ECFieldElement) { return (q == other.q) && (x == other.x); } return false; } @override int get hashCode => q.hashCode ^ x.hashCode; } /// Elliptic curve points over Fp class ECPoint extends ecc.ECPointBase { /// Create a point that encodes with or without point compression. /// /// @param curve the curve to use /// @param x affine x co-ordinate /// @param y affine y co-ordinate /// @param withCompression if true encode with point compression ECPoint(ECCurve curve, ECFieldElement? x, ECFieldElement? y, [bool withCompression = false]) : super(curve, x, y, withCompression, _wNafMultiplier) { if ((x != null && y == null) || (x == null && y != null)) { throw ArgumentError('Exactly one of the field elements is null'); } } /// return the field element encoded with point compression. (S 4.3.6) @override Uint8List getEncoded([bool compressed = true]) { if (isInfinity) { return Uint8List.fromList([1]); } var qLength = x!.byteLength; if (compressed) { int pc; if (_testBit(y!.toBigInteger()!, 0)) { pc = 0x03; } else { pc = 0x02; } var X = _x9IntegerToBytes(x!.toBigInteger(), qLength); var po = Uint8List(X.length + 1); po[0] = pc.toInt(); po.setAll(1, X); return po; } else { var X = _x9IntegerToBytes(x!.toBigInteger(), qLength); var Y = _x9IntegerToBytes(y!.toBigInteger(), qLength); var po = Uint8List(X.length + Y.length + 1); po[0] = 0x04; po.setAll(1, X); po.setAll(X.length + 1, Y); return po; } } // B.3 pg 62 @override ECPoint? operator +(ECPoint? b) { if (isInfinity) { return b; } if (b!.isInfinity) { return this; } // Check if b = this or b = -this if (x == b.x) { if (y == b.y) { // this = b, i.e. this must be doubled return twice(); } // this = -b, i.e. the result is the point at infinity return curve.infinity as ECPoint?; } var gamma = (b.y! - y!) / (b.x! - x!); var x3 = (gamma.square() - x!) - b.x!; var y3 = (gamma * (x! - x3)) - y!; return ECPoint(curve as ECCurve, x3 as ECFieldElement?, y3 as ECFieldElement?, isCompressed); } // B.3 pg 62 @override ECPoint? twice() { if (isInfinity) { // Twice identity element (point at infinity) is identity return this; } if (y!.toBigInteger() == BigInt.zero) { // if y1 == 0, then (x1, y1) == (x1, -y1) // and hence this = -this and thus 2(x1, y1) == infinity return curve.infinity as ECPoint?; } var two = curve.fromBigInteger(BigInt.two); var three = curve.fromBigInteger(BigInt.from(3)); var gamma = ((x!.square() * three) + curve.a!) / (y! * two); var x3 = gamma.square() - (x! * two); var y3 = (gamma * (x! - x3)) - y!; return ECPoint(curve as ECCurve, x3 as ECFieldElement?, y3 as ECFieldElement?, isCompressed); } // D.3.2 pg 102 (see Note:) @override ECPoint? operator -(ECPoint b) { if (b.isInfinity) { return this; } // Add -b return this + (-b); } @override ECPoint operator -() { return ECPoint(curve as ECCurve, x as ECFieldElement?, -y! as ECFieldElement?, isCompressed); } } /// Elliptic curve over Fp class ECCurve extends ecc.ECCurveBase { final BigInt? q; ECPoint? _infinity; ECCurve(this.q, BigInt? a, BigInt? b) : super(a, b) { _infinity = ECPoint(this, null, null); } @override int get fieldSize => q!.bitLength; @override ECPoint? get infinity => _infinity; @override ECFieldElement fromBigInteger(BigInt? x) => ECFieldElement(q, x); @override ECPoint createPoint(BigInt x, BigInt y, [bool withCompression = false]) => ECPoint(this, fromBigInteger(x), fromBigInteger(y), withCompression); @override ECPoint decompressPoint(int yTilde, BigInt x1) { var x = fromBigInteger(x1); var alpha = (x * ((x * x) + (a as ECFieldElement))) + (b as ECFieldElement); var beta = alpha.sqrt(); // // if we can't find a sqrt we haven't got a point on the // curve - run! // if (beta == null) { throw ArgumentError('Invalid point compression'); } var betaValue = beta.toBigInteger()!; var bit0 = _testBit(betaValue, 0) ? 1 : 0; if (bit0 != yTilde) { // Use the other root beta = fromBigInteger(q! - betaValue); } return ECPoint(this, x, beta, true); } @override bool operator ==(Object other) { if (other is ECCurve) { return q == other.q && a == other.a && b == other.b; } return false; } @override int get hashCode => a.hashCode ^ b.hashCode ^ q.hashCode; } /// Class holding precomputation data for the WNAF (Window Non-Adjacent Form) /// algorithm. class _WNafPreCompInfo implements PreCompInfo { /// Array holding the precomputed [ECPoint]s used for the Window NAF multiplication. List? preComp; /// Holds an [ECPoint] representing twice(this). Used for the Window NAF multiplication. ECPoint? twiceP; } /// Function implementing the WNAF (Window Non-Adjacent Form) multiplication algorithm. Multiplies [p]] by an integer [k] using /// the Window NAF method. ecc.ECPointBase? _wNafMultiplier( ecc.ECPointBase p, BigInt? k, PreCompInfo? preCompInfo) { // Ignore empty PreCompInfo or PreCompInfo of incorrect type _WNafPreCompInfo wnafPreCompInfo; if (preCompInfo is! _WNafPreCompInfo) { wnafPreCompInfo = _WNafPreCompInfo(); } else { wnafPreCompInfo = preCompInfo; } // floor(log2(k)) var m = k!.bitLength; // width of the Window NAF int width; // Required length of precomputation array int reqPreCompLen; // 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 var preCompLen = 1; List? preComp = wnafPreCompInfo.preComp; var twiceP = wnafPreCompInfo.twiceP; // Check if the precomputed ECPoints already exist if (preComp == null) { // Precomputation must be performed from scratch, create an empty // precomputation array of desired length preComp = List.filled(1, p as ECPoint); } else { // Take the already precomputed ECPoints to start with preCompLen = preComp.length; } twiceP ??= p.twice() as ECPoint?; if (preCompLen < reqPreCompLen) { // Precomputation array must be made bigger, copy existing preComp // array into the larger preComp array var oldPreComp = preComp as List; preComp = List.filled(reqPreCompLen, null, growable: false); preComp.setAll(0, oldPreComp); for (var i = preCompLen; i < reqPreCompLen; i++) { // Compute the ECPoints for the precomputation array. // The values 1, 3, 5, ..., 2^(width-1)-1 times p are // computed preComp[i] = twiceP! + (preComp[i - 1]); } } // Compute the Window NAF of the desired width var wnaf = _windowNaf(width, k); var l = wnaf.length; // Apply the Window NAF to p using the precomputed ECPoint values. var q = p.curve.infinity; for (var i = l - 1; i >= 0; i--) { q = q!.twice(); if (wnaf[i] != 0) { if (wnaf[i]! > 0) { q = q! + preComp[(wnaf[i]! - 1) ~/ 2]; } else { // wnaf[i] < 0 q = q! - preComp[(-wnaf[i]! - 1) ~/ 2]!; } } } // Set PreCompInfo in ECPoint, such that it is available for next // multiplication. wnafPreCompInfo.preComp = preComp.map((e) => e as ECPoint).toList(); wnafPreCompInfo.twiceP = twiceP; p.preCompInfo = wnafPreCompInfo; return q; } /// Computes the Window NAF (non-adjacent Form) of an integer. /// @param width The width w of the Window NAF. The width is /// defined as the minimal number w, such that for any /// w consecutive digits in the resulting representation, at /// most one is non-zero. /// @param k The integer of which the Window NAF is computed. /// @return The Window NAF of the given width, such that the following holds: /// k = ∑i=0l-1 ki2i /// , where the ki denote the elements of the /// returned byte[]. List _windowNaf(int width, BigInt k) { // The window NAF is at most 1 element longer than the binary // representation of the integer k. byte can be used instead of short or // int unless the window width is larger than 8. For larger width use // short or int. However, a width of more than 8 is not efficient for // m = log2(q) smaller than 2305 Bits. Note: Values for m larger than // 1000 Bits are currently not used in practice. var wnaf = List.filled(k.bitLength + 1, null, growable: false); // 2^width as short and BigInt var pow2wB = 1 << width; var pow2wBI = BigInt.from(pow2wB); var i = 0; // The actual length of the WNAF var length = 0; // while k >= 1 while (k.sign > 0) { // if k is odd if (_testBit(k, 0)) { // k mod 2^width var remainder = k % pow2wBI; // if remainder > 2^(width - 1) - 1 if (_testBit(remainder, width - 1)) { wnaf[i] = remainder.toInt() - pow2wB; } else { wnaf[i] = remainder.toInt(); } // convert to 'Java byte' wnaf[i] = wnaf[i]! % 0x100; if ((wnaf[i]! & 0x80) != 0) { wnaf[i] = wnaf[i]! - 256; } // wnaf[i] is now in [-2^(width-1), 2^(width-1)-1] k = k - BigInt.from(wnaf[i]!); length = i; } else { wnaf[i] = 0; } // k = k/2 k = k >> 1; i++; } length++; // Reduce the WNAF array to its actual length var wnafShort = List.filled(length, null, growable: false); wnafShort.setAll(0, wnaf.sublist(0, length)); return wnafShort; } Uint8List _x9IntegerToBytes(BigInt? s, int qLength) { var bytes = Uint8List.fromList(utils.encodeBigInt(s)); if (qLength < bytes.length) { return bytes.sublist(bytes.length - qLength); } else if (qLength > bytes.length) { return Uint8List(qLength)..setAll(qLength - bytes.length, bytes); } return bytes; }