blob: 3e4510f63035a880d32b2c9e6c75e6a328d468c2 [file] [log] [blame]
// 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;
}
}