// See file LICENSE for more information. library impl.signer.ecdsa_signer; import 'dart:math'; import 'dart:typed_data'; import 'package:pointycastle/api.dart'; import 'package:pointycastle/ecc/api.dart'; import 'package:pointycastle/src/registry/registry.dart'; import 'package:pointycastle/src/utils.dart' as utils; bool _testBit(BigInt i, int n) { return (i & (BigInt.one << n)) != BigInt.zero; } class ECDSASigner implements Signer { /// Intended for internal use. // ignore: non_constant_identifier_names static final FactoryConfig factoryConfig = DynamicFactoryConfig.regex( Signer, r'^(.+)/(DET-)?ECDSA$', (_, final Match match) { // ignore: omit_local_variable_types final String? digestName = match.group(1); // ignore: omit_local_variable_types final bool withMac = match.group(2) != null; return () { var underlyingDigest = Digest(digestName!); var mac = withMac ? Mac('$digestName/HMAC') : null; return ECDSASigner(underlyingDigest, mac); }; }); ECPublicKey? _pbkey; ECPrivateKey? _pvkey; SecureRandom? _random; final Digest? _digest; final Mac? _kMac; /// If [_digest] is not null it is used to hash the message before signing and verifying, otherwise, the message needs to be /// hashed by the user of this [ECDSASigner] object. /// If [_kMac] is not null, RFC 6979 is used for k calculation with the given [Mac]. Keep in mind that, to comply with /// RFC 69679, [_kMac] must be HMAC with the same digest used to hash the message. ECDSASigner([this._digest, this._kMac]); @override String get algorithmName => '${_digest!.algorithmName}/${_kMac == null ? '' : 'DET-'}ECDSA'; @override void reset() {} /// Init this [Signer]. The [params] argument can be: /// -A [ParametersWithRandom] containing a [PrivateKeyParameter] or a raw [PrivateKeyParameter] for signing /// -An [PublicKeyParameter] for verifying. @override void init(bool forSigning, CipherParameters params) { _pbkey = _pvkey = null; if (forSigning) { PrivateKeyParameter pvparams; if (params is ParametersWithRandom) { _random = params.random; pvparams = params.parameters as PrivateKeyParameter; } else if (_kMac != null) { _random = null; pvparams = params as PrivateKeyParameter; } else { _random = SecureRandom(); pvparams = params as PrivateKeyParameter; } _pvkey = pvparams.key as ECPrivateKey; } else { PublicKeyParameter pbparams; pbparams = params as PublicKeyParameter; _pbkey = pbparams.key as ECPublicKey; } } @override Signature generateSignature(Uint8List message) { message = _hashMessageIfNeeded(message); var n = _pvkey!.parameters!.n; var e = _calculateE(n, message); BigInt r; BigInt s; dynamic kCalculator; if (_kMac != null) { kCalculator = _RFC6979KCalculator(_kMac, n, _pvkey!.d!, message); } else { kCalculator = _RandomKCalculator(n, _random!); } // 5.3.2 do { // generate s BigInt? k; do { // generate r k = kCalculator.nextK() as BigInt?; var p = (_pvkey!.parameters!.G * k)!; // 5.3.3 var x = p.x!.toBigInteger()!; r = x % n; } while (r == BigInt.zero); var d = _pvkey!.d!; s = (k!.modInverse(n) * (e + (d * r))) % n; } while (s == BigInt.zero); return ECSignature(r, s); } @override bool verifySignature(Uint8List message, covariant ECSignature signature) { message = _hashMessageIfNeeded(message); var n = _pbkey!.parameters!.n; var e = _calculateE(n, message); var r = signature.r; var s = signature.s; // r in the range [1,n-1] if (r.compareTo(BigInt.one) < 0 || r.compareTo(n) >= 0) { return false; } // s in the range [1,n-1] if (s.compareTo(BigInt.one) < 0 || s.compareTo(n) >= 0) { return false; } var c = s.modInverse(n); var u1 = (e * c) % n; var u2 = (r * c) % n; var G = _pbkey!.parameters!.G; var Q = _pbkey!.Q!; var point = _sumOfTwoMultiplies(G, u1, Q, u2)!; // components must be bogus. if (point.isInfinity) { return false; } var v = point.x!.toBigInteger()! % n; return v == r; } Uint8List _hashMessageIfNeeded(Uint8List message) { if (_digest != null) { _digest.reset(); return _digest.process(message); } else { return message; } } BigInt _calculateE(BigInt n, Uint8List message) { var log2n = n.bitLength; var messageBitLength = message.length * 8; if (log2n >= messageBitLength) { return utils.decodeBigIntWithSign(1, message); } else { var trunc = utils.decodeBigIntWithSign(1, message); trunc = trunc >> (messageBitLength - log2n); return trunc; } } ECPoint? _sumOfTwoMultiplies(ECPoint P, BigInt a, ECPoint Q, BigInt b) { var c = P.curve; if (c != Q.curve) { throw ArgumentError('P and Q must be on same curve'); } // Point multiplication for Koblitz curves (using WTNAF) beats Shamir's trick // TODO: uncomment this when F2m available /* if( c is ECCurve.F2m ) { ECCurve.F2m f2mCurve = (ECCurve.F2m)c; if( f2mCurve.isKoblitz() ) { return P.multiply(a).add(Q.multiply(b)); } } */ return _implShamirsTrick(P, a, Q, b); } ECPoint? _implShamirsTrick(ECPoint P, BigInt k, ECPoint Q, BigInt l) { var m = max(k.bitLength, l.bitLength); var Z = P + Q; var R = P.curve.infinity; for (var i = m - 1; i >= 0; --i) { R = R!.twice(); if (_testBit(k, i)) { if (_testBit(l, i)) { R = R! + Z; } else { R = R! + P; } } else { if (_testBit(l, i)) { R = R! + Q; } } } return R; } } class NormalizedECDSASigner implements Signer { final ECDSASigner signer; final bool enforceNormalized; /// Wraps ECDSASigner and enforces normalisation on verify if /// [enforceNormalized] is true. /// /// Always generates normalized signatures. NormalizedECDSASigner(this.signer, {this.enforceNormalized = false}); @override String get algorithmName => signer.algorithmName; @override Signature generateSignature(Uint8List message) { return (signer.generateSignature(message) as ECSignature) .normalize(signer._pvkey!.parameters!); } @override void init(bool forSigning, CipherParameters params) { signer.init(forSigning, params); } @override void reset() { signer.reset(); } @override bool verifySignature(Uint8List message, Signature signature) { var isNormalized = (signature as ECSignature).isNormalized(signer._pbkey!.parameters!); var isVerified = signer.verifySignature(message, signature); // Constant time. return (isNormalized | !enforceNormalized) & isVerified; } } class _RFC6979KCalculator { final Mac _mac; // ignore: non_constant_identifier_names late Uint8List _K; // ignore: non_constant_identifier_names late Uint8List _V; final BigInt _n; _RFC6979KCalculator(this._mac, this._n, BigInt d, Uint8List message) { _V = Uint8List(_mac.macSize); _K = Uint8List(_mac.macSize); _init(d, message); } void _init(BigInt d, Uint8List message) { _V.fillRange(0, _V.length, 0x01); _K.fillRange(0, _K.length, 0x00); var x = Uint8List((_n.bitLength + 7) ~/ 8); var dVal = _asUnsignedByteArray(d); x.setRange(x.length - dVal.length, x.length, dVal); var m = Uint8List((_n.bitLength + 7) ~/ 8); var mInt = _bitsToInt(message); if (mInt > _n) { mInt -= _n; } var mVal = _asUnsignedByteArray(mInt); m.setRange(m.length - mVal.length, m.length, mVal); _mac.init(KeyParameter(_K)); _mac.update(_V, 0, _V.length); _mac.updateByte(0x00); _mac.update(x, 0, x.length); _mac.update(m, 0, m.length); _mac.doFinal(_K, 0); _mac.init(KeyParameter(_K)); _mac.update(_V, 0, _V.length); _mac.doFinal(_V, 0); _mac.update(_V, 0, _V.length); _mac.updateByte(0x01); _mac.update(x, 0, x.length); _mac.update(m, 0, m.length); _mac.doFinal(_K, 0); _mac.init(KeyParameter(_K)); _mac.update(_V, 0, _V.length); _mac.doFinal(_V, 0); } BigInt nextK() { var t = Uint8List((_n.bitLength + 7) ~/ 8); for (;;) { var tOff = 0; while (tOff < t.length) { _mac.update(_V, 0, _V.length); _mac.doFinal(_V, 0); if ((t.length - tOff) < _V.length) { t.setRange(tOff, t.length, _V); tOff += t.length - tOff; } else { t.setRange(tOff, tOff + _V.length, _V); tOff += _V.length; } } var k = _bitsToInt(t); // ignore: unrelated_type_equality_checks if ((k == 0) || (k >= _n)) { _mac.update(_V, 0, _V.length); _mac.updateByte(0x00); _mac.doFinal(_K, 0); _mac.init(KeyParameter(_K)); _mac.update(_V, 0, _V.length); _mac.doFinal(_V, 0); } else { return k; } } } BigInt _bitsToInt(Uint8List t) { var v = utils.decodeBigIntWithSign(1, t); if ((t.length * 8) > _n.bitLength) { v = v >> ((t.length * 8) - _n.bitLength); } return v; } Uint8List _asUnsignedByteArray(BigInt value) { var bytes = utils.encodeBigInt(value); if (bytes[0] == 0) { return Uint8List.fromList(bytes.sublist(1)); } else { return Uint8List.fromList(bytes); } } } class _RandomKCalculator { final BigInt _n; final SecureRandom _random; _RandomKCalculator(this._n, this._random); BigInt nextK() { BigInt k; do { k = _random.nextBigInteger(_n.bitLength); } while (k == BigInt.zero || k >= _n); return k; } }