402 lines
9.7 KiB
Dart
402 lines
9.7 KiB
Dart
// 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;
|
||
}
|
||
}
|