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

204 lines
6.3 KiB
Dart

/// Package ed25519 implements the Ed25519 signature algorithm. See
/// https://ed25519.cr.yp.to/.
///
/// These functions are also compatible with the “Ed25519” function defined in
/// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
/// representation includes a public key suffix to make multiple signing
/// operations with the same key more efficient. This package refers to the RFC
/// 8032 private key as the “seed”.
library edwards25519;
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:ed25519_edwards/src/edwards25519.dart';
import 'package:ed25519_edwards/src/util.dart';
/// PublicKeySize is the size, in bytes, of public keys as used in this package.
const PublicKeySize = 32;
/// PrivateKeySize is the size, in bytes, of private keys as used in this package.
const PrivateKeySize = 64;
/// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
const SignatureSize = 64;
/// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
const SeedSize = 32;
/// PublicKey is the type of Ed25519 public keys.
class PublicKey {
List<int> bytes;
PublicKey(this.bytes);
}
/// PrivateKey is the type of Ed25519 private keys.
class PrivateKey {
List<int> bytes;
PrivateKey(this.bytes);
}
/// KeyPair is the type of Ed25519 public/private key pair.
class KeyPair {
final PrivateKey privateKey;
final PublicKey publicKey;
KeyPair(this.privateKey, this.publicKey);
@override
int get hashCode => publicKey.hashCode;
@override
bool operator ==(other) =>
other is KeyPair &&
publicKey == other.publicKey &&
privateKey == other.privateKey;
}
/// public returns the PublicKey corresponding to PrivateKey.
PublicKey public(PrivateKey privateKey) {
var publicKey = privateKey.bytes.sublist(32, 32 + PublicKeySize);
return PublicKey(publicKey);
}
/// Seed returns the private key seed corresponding to priv. It is provided for
/// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
/// in this package.
Uint8List seed(PrivateKey privateKey) {
var seed = privateKey.bytes.sublist(0, SeedSize);
return seed as Uint8List;
}
/// GenerateKey generates a public/private key pair using entropy from secure random.
KeyPair generateKey() {
var seed = Uint8List(32);
fillBytesWithSecureRandomNumbers(seed);
var privateKey = newKeyFromSeed(seed);
var publicKey = privateKey.bytes.sublist(32, PrivateKeySize);
return KeyPair(privateKey, PublicKey(publicKey));
}
/// NewKeyFromSeed calculates a private key from a seed. It will throw
/// ArgumentError if seed.length is not SeedSize.
/// This function is provided for interoperability with RFC 8032.
/// RFC 8032's private keys correspond to seeds in this package.
PrivateKey newKeyFromSeed(Uint8List seed) {
if (seed.length != SeedSize) {
throw ArgumentError('ed25519: bad seed length ${seed.length}');
}
var h = sha512.convert(seed);
var digest = h.bytes.sublist(0, 32);
digest[0] &= 248;
digest[31] &= 127;
digest[31] |= 64;
var A = ExtendedGroupElement();
var hBytes = digest.sublist(0);
GeScalarMultBase(A, hBytes as Uint8List);
var publicKeyBytes = Uint8List(32);
A.ToBytes(publicKeyBytes);
var privateKey = Uint8List(PrivateKeySize);
arrayCopy(seed, 0, privateKey, 0, 32);
arrayCopy(publicKeyBytes, 0, privateKey, 32, 32);
return PrivateKey(privateKey);
}
/// Sign signs the message with privateKey and returns a signature. It will
/// throw ArumentError if privateKey.bytes.length is not PrivateKeySize.
Uint8List sign(PrivateKey privateKey, Uint8List message) {
if (privateKey.bytes.length != PrivateKeySize) {
throw ArgumentError(
'ed25519: bad privateKey length ${privateKey.bytes.length}');
}
var h = sha512.convert(privateKey.bytes.sublist(0, 32));
var digest1 = h.bytes;
var expandedSecretKey = digest1.sublist(0, 32);
expandedSecretKey[0] &= 248;
expandedSecretKey[31] &= 63;
expandedSecretKey[31] |= 64;
var output = AccumulatorSink<Digest>();
var input = sha512.startChunkedConversion(output);
input.add(digest1.sublist(32));
input.add(message);
input.close();
var messageDigest = output.events.single.bytes;
var messageDigestReduced = Uint8List(32);
ScReduce(messageDigestReduced, messageDigest as Uint8List);
var R = ExtendedGroupElement();
GeScalarMultBase(R, messageDigestReduced);
var encodedR = Uint8List(32);
R.ToBytes(encodedR);
output = AccumulatorSink<Digest>();
input = sha512.startChunkedConversion(output);
input.add(encodedR);
input.add(privateKey.bytes.sublist(32));
input.add(message);
input.close();
var hramDigest = output.events.single.bytes;
var hramDigestReduced = Uint8List(32);
ScReduce(hramDigestReduced, hramDigest as Uint8List);
var s = Uint8List(32);
ScMulAdd(s, hramDigestReduced, expandedSecretKey as Uint8List,
messageDigestReduced);
var signature = Uint8List(SignatureSize);
arrayCopy(encodedR, 0, signature, 0, 32);
arrayCopy(s, 0, signature, 32, 32);
return signature;
}
/// Verify reports whether sig is a valid signature of message by publicKey. It
/// will throw ArgumentError if publicKey.bytes.length is not PublicKeySize.
bool verify(PublicKey publicKey, Uint8List message, Uint8List sig) {
if (publicKey.bytes.length != PublicKeySize) {
throw ArgumentError(
'ed25519: bad publicKey length ${publicKey.bytes.length}');
}
if (sig.length != SignatureSize || sig[63] & 224 != 0) {
return false;
}
var A = ExtendedGroupElement();
var publicKeyBytes = Uint8List.fromList(publicKey.bytes);
if (!A.FromBytes(publicKeyBytes)) {
return false;
}
FeNeg(A.X, A.X);
FeNeg(A.T, A.T);
var output = AccumulatorSink<Digest>();
var input = sha512.startChunkedConversion(output);
input.add(sig.sublist(0, 32));
input.add(publicKeyBytes);
input.add(message);
input.close();
var digest = output.events.single.bytes;
var hReduced = Uint8List(32);
ScReduce(hReduced, digest as Uint8List);
var R = ProjectiveGroupElement();
var s = sig.sublist(32);
if (!ScMinimal(s)) {
return false;
}
GeDoubleScalarMultVartime(R, hReduced, A, s);
var checkR = Uint8List(32);
R.ToBytes(checkR);
return ListEquality().equals(sig.sublist(0, 32), checkR);
}