|
|
|
|
@ -0,0 +1,234 @@
|
|
|
|
|
/// The NIST prime curves P-256, P-384 and P-521 of FIPS 186-5, which the
|
|
|
|
|
/// table of spec v0.12 §29.10 names for ECDSA: the reading of an
|
|
|
|
|
/// uncompressed point as Go's ecdsa.ParseUncompressedPublicKey reads it, and
|
|
|
|
|
/// u1·G + u2·Q, the multiplication of an ECDSA verification (ecdsa.dart).
|
|
|
|
|
///
|
|
|
|
|
/// The arithmetic is on BigInt, exact on the VM and compiled to JavaScript,
|
|
|
|
|
/// where an int is a double. It is not constant-time, and it does not need
|
|
|
|
|
/// to be: a verification works on public values only, the key of a
|
|
|
|
|
/// certificate and a signature. Never use it with a secret.
|
|
|
|
|
///
|
|
|
|
|
/// Internal: lib/datekeys.dart does not export it.
|
|
|
|
|
library;
|
|
|
|
|
|
|
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
|
|
|
|
import 'bytes.dart';
|
|
|
|
|
|
|
|
|
|
/// A curve y² = x³ − 3x + b over GF(p), with a base point G of prime order
|
|
|
|
|
/// n and cofactor 1, as Go's elliptic.CurveParams.
|
|
|
|
|
final class NistCurve {
|
|
|
|
|
NistCurve._(
|
|
|
|
|
this.name,
|
|
|
|
|
this.byteLength,
|
|
|
|
|
String p,
|
|
|
|
|
String n,
|
|
|
|
|
String b,
|
|
|
|
|
String gx,
|
|
|
|
|
String gy,
|
|
|
|
|
) : p = BigInt.parse(p, radix: 16),
|
|
|
|
|
n = BigInt.parse(n, radix: 16),
|
|
|
|
|
b = BigInt.parse(b, radix: 16),
|
|
|
|
|
gx = BigInt.parse(gx, radix: 16),
|
|
|
|
|
gy = BigInt.parse(gy, radix: 16);
|
|
|
|
|
|
|
|
|
|
/// The name of FIPS 186-5, such as `P-256`.
|
|
|
|
|
final String name;
|
|
|
|
|
|
|
|
|
|
/// The bytes of a coordinate, and of the order: 32, 48 or 66.
|
|
|
|
|
final int byteLength;
|
|
|
|
|
|
|
|
|
|
/// The prime of the field, the order of G, the constant b and G.
|
|
|
|
|
final BigInt p, n, b, gx, gy;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String toString() => name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The parameters as Go's elliptic.P256, P384 and P521 give them; a test
|
|
|
|
|
// compares them with those that tool/cms_go_vectors_test.go records.
|
|
|
|
|
|
|
|
|
|
/// P-256, secp256r1 (1.2.840.10045.3.1.7).
|
|
|
|
|
final p256 = NistCurve._(
|
|
|
|
|
'P-256',
|
|
|
|
|
32,
|
|
|
|
|
'ffffffff00000001000000000000000000000000ffffffffffffffffffffffff',
|
|
|
|
|
'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551',
|
|
|
|
|
'5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b',
|
|
|
|
|
'6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296',
|
|
|
|
|
'4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/// P-384, secp384r1 (1.3.132.0.34).
|
|
|
|
|
final p384 = NistCurve._(
|
|
|
|
|
'P-384',
|
|
|
|
|
48,
|
|
|
|
|
'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe'
|
|
|
|
|
'ffffffff0000000000000000ffffffff',
|
|
|
|
|
'ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf'
|
|
|
|
|
'581a0db248b0a77aecec196accc52973',
|
|
|
|
|
'b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875a'
|
|
|
|
|
'c656398d8a2ed19d2a85c8edd3ec2aef',
|
|
|
|
|
'aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a38'
|
|
|
|
|
'5502f25dbf55296c3a545e3872760ab7',
|
|
|
|
|
'3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c0'
|
|
|
|
|
'0a60b1ce1d7e819d7a431d7c90ea0e5f',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/// P-521, secp521r1 (1.3.132.0.35).
|
|
|
|
|
final p521 = NistCurve._(
|
|
|
|
|
'P-521',
|
|
|
|
|
66,
|
|
|
|
|
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
|
|
|
|
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
|
|
|
|
'ffff',
|
|
|
|
|
'01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
|
|
|
|
'fffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e9138'
|
|
|
|
|
'6409',
|
|
|
|
|
'51953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109'
|
|
|
|
|
'e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f'
|
|
|
|
|
'00',
|
|
|
|
|
'c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3d'
|
|
|
|
|
'baa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd'
|
|
|
|
|
'66',
|
|
|
|
|
'011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e'
|
|
|
|
|
'662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd1'
|
|
|
|
|
'6650',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/// A point of a curve in affine coordinates, never the point at infinity.
|
|
|
|
|
final class NistPoint {
|
|
|
|
|
const NistPoint._(this.curve, this.x, this.y);
|
|
|
|
|
|
|
|
|
|
/// The curve of the point.
|
|
|
|
|
final NistCurve curve;
|
|
|
|
|
|
|
|
|
|
/// The coordinates, each below p.
|
|
|
|
|
final BigInt x, y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The unsigned big-endian integer of [bytes], zero when they are empty.
|
|
|
|
|
BigInt bigFromBytes(List<int> bytes) =>
|
|
|
|
|
bytes.isEmpty ? BigInt.zero : BigInt.parse(toHex(bytes), radix: 16);
|
|
|
|
|
|
|
|
|
|
/// The [length] bytes of the unsigned big-endian encoding of the
|
|
|
|
|
/// non-negative [v], which must fit in them.
|
|
|
|
|
Uint8List bytesFromBig(BigInt v, int length) {
|
|
|
|
|
final hex = v.toRadixString(16);
|
|
|
|
|
if (v.isNegative || hex.length > 2 * length) {
|
|
|
|
|
throw ArgumentError.value(v, 'v', 'not an integer of $length bytes');
|
|
|
|
|
}
|
|
|
|
|
return fromHex(hex.padLeft(2 * length, '0'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The point of the uncompressed encoding [data] on [curve], as Go's
|
|
|
|
|
/// ecdsa.ParseUncompressedPublicKey reads it (SEC 1 2.3.3): 0x04 and the two
|
|
|
|
|
/// coordinates in exactly [NistCurve.byteLength] bytes each, both below p,
|
|
|
|
|
/// on the curve. Null for anything else: a compressed point, the encoding of
|
|
|
|
|
/// the infinity, another length, a coordinate of p or more, or a point off
|
|
|
|
|
/// the curve, none of them a key of the table of spec §29.10.
|
|
|
|
|
NistPoint? decodeUncompressed(NistCurve curve, List<int> data) {
|
|
|
|
|
final l = curve.byteLength;
|
|
|
|
|
if (data.length != 1 + 2 * l || data[0] != 4) return null;
|
|
|
|
|
final x = bigFromBytes(data.sublist(1, 1 + l));
|
|
|
|
|
final y = bigFromBytes(data.sublist(1 + l));
|
|
|
|
|
if (x >= curve.p || y >= curve.p) return null;
|
|
|
|
|
final p = curve.p;
|
|
|
|
|
final rhs = ((x * x - BigInt.from(3)) * x + curve.b) % p;
|
|
|
|
|
if (y * y % p != rhs) return null;
|
|
|
|
|
return NistPoint._(curve, x, y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A point in Jacobian coordinates, x = X/Z² and y = Y/Z³; Z = 0 is the point
|
|
|
|
|
// at infinity.
|
|
|
|
|
final class _Jacobian {
|
|
|
|
|
const _Jacobian(this.x, this.y, this.z);
|
|
|
|
|
|
|
|
|
|
final BigInt x, y, z;
|
|
|
|
|
|
|
|
|
|
bool get isInfinity => z == BigInt.zero;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final _infinity = _Jacobian(BigInt.one, BigInt.one, BigInt.zero);
|
|
|
|
|
final _three = BigInt.from(3);
|
|
|
|
|
final _four = BigInt.from(4);
|
|
|
|
|
final _eight = BigInt.from(8);
|
|
|
|
|
|
|
|
|
|
// 2·a, with the formulas dbl-2001-b of the Explicit-Formulas Database for
|
|
|
|
|
// a = −3.
|
|
|
|
|
_Jacobian _double(BigInt p, _Jacobian a) {
|
|
|
|
|
if (a.isInfinity || a.y == BigInt.zero) return _infinity;
|
|
|
|
|
final delta = a.z * a.z % p;
|
|
|
|
|
final gamma = a.y * a.y % p;
|
|
|
|
|
final beta = a.x * gamma % p;
|
|
|
|
|
final alpha = _three * ((a.x - delta) * (a.x + delta) % p) % p;
|
|
|
|
|
final x3 = (alpha * alpha - _eight * beta) % p;
|
|
|
|
|
final yz = a.y + a.z;
|
|
|
|
|
final z3 = (yz * yz - gamma - delta) % p;
|
|
|
|
|
final y3 = (alpha * (_four * beta - x3) - _eight * (gamma * gamma % p)) % p;
|
|
|
|
|
return _Jacobian(x3, y3, z3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// a + (x2, y2), an affine point: the mixed addition of Jacobian coordinates,
|
|
|
|
|
// with the doubling when the two points are the same and the infinity when
|
|
|
|
|
// they are opposite.
|
|
|
|
|
_Jacobian _addAffine(BigInt p, _Jacobian a, BigInt x2, BigInt y2) {
|
|
|
|
|
if (a.isInfinity) return _Jacobian(x2, y2, BigInt.one);
|
|
|
|
|
final z1z1 = a.z * a.z % p;
|
|
|
|
|
final u2 = x2 * z1z1 % p;
|
|
|
|
|
final s2 = y2 * (a.z * z1z1 % p) % p;
|
|
|
|
|
final h = (u2 - a.x) % p;
|
|
|
|
|
final r = (s2 - a.y) % p;
|
|
|
|
|
if (h == BigInt.zero) {
|
|
|
|
|
return r == BigInt.zero ? _double(p, a) : _infinity;
|
|
|
|
|
}
|
|
|
|
|
final hh = h * h % p;
|
|
|
|
|
final hhh = h * hh % p;
|
|
|
|
|
final v = a.x * hh % p;
|
|
|
|
|
final x3 = (r * r - hhh - v - v) % p;
|
|
|
|
|
final y3 = (r * (v - x3) - a.y * hhh) % p;
|
|
|
|
|
final z3 = a.z * h % p;
|
|
|
|
|
return _Jacobian(x3, y3, z3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The affine coordinates of a, which is not the infinity.
|
|
|
|
|
(BigInt, BigInt) _affine(BigInt p, _Jacobian a) {
|
|
|
|
|
final zi = a.z.modInverse(p);
|
|
|
|
|
final zi2 = zi * zi % p;
|
|
|
|
|
return (a.x * zi2 % p, a.y * (zi2 * zi % p) % p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The bits of the non-negative v, the most significant first, in exactly
|
|
|
|
|
// width places.
|
|
|
|
|
List<bool> _bits(BigInt v, int width) {
|
|
|
|
|
final s = v.toRadixString(2).padLeft(width, '0');
|
|
|
|
|
return [for (var i = 0; i < s.length; i++) s.codeUnitAt(i) == 0x31];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The x coordinate of u1·G + u2·Q, both scalars in [0, n), or null when the
|
|
|
|
|
/// sum is the point at infinity: Shamir's trick, one doubling per bit and
|
|
|
|
|
/// one addition of G, Q or G + Q.
|
|
|
|
|
BigInt? mulAddX(NistCurve curve, BigInt u1, BigInt u2, NistPoint q) {
|
|
|
|
|
final p = curve.p;
|
|
|
|
|
final gq = _addAffine(p, _Jacobian(curve.gx, curve.gy, BigInt.one), q.x, q.y);
|
|
|
|
|
// G + Q is the infinity when Q = −G: adding it then adds nothing.
|
|
|
|
|
final (BigInt, BigInt)? gqAffine = gq.isInfinity ? null : _affine(p, gq);
|
|
|
|
|
final width = u1.bitLength > u2.bitLength ? u1.bitLength : u2.bitLength;
|
|
|
|
|
final b1 = _bits(u1, width);
|
|
|
|
|
final b2 = _bits(u2, width);
|
|
|
|
|
var acc = _infinity;
|
|
|
|
|
for (var i = 0; i < width; i++) {
|
|
|
|
|
acc = _double(p, acc);
|
|
|
|
|
if (b1[i] && b2[i]) {
|
|
|
|
|
if (gqAffine != null) {
|
|
|
|
|
acc = _addAffine(p, acc, gqAffine.$1, gqAffine.$2);
|
|
|
|
|
}
|
|
|
|
|
} else if (b1[i]) {
|
|
|
|
|
acc = _addAffine(p, acc, curve.gx, curve.gy);
|
|
|
|
|
} else if (b2[i]) {
|
|
|
|
|
acc = _addAffine(p, acc, q.x, q.y);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (acc.isInfinity) return null;
|
|
|
|
|
return _affine(p, acc).$1;
|
|
|
|
|
}
|