twonly-app-dependencies/pointycastle/lib/signers/ecdsa_signer.dart
2025-12-07 16:10:41 +01:00

402 lines
9.7 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
}
}