| // 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<PrivateKey>; |
| } else if (_kMac != null) { |
| _random = null; |
| pvparams = params as PrivateKeyParameter<PrivateKey>; |
| } else { |
| _random = SecureRandom(); |
| pvparams = params as PrivateKeyParameter<PrivateKey>; |
| } |
| |
| _pvkey = pvparams.key as ECPrivateKey; |
| } else { |
| PublicKeyParameter pbparams; |
| |
| pbparams = params as PublicKeyParameter<PublicKey>; |
| |
| _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; |
| } |
| } |