/// 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 bytes; PublicKey(this.bytes); } /// PrivateKey is the type of Ed25519 private keys. class PrivateKey { List 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(); 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(); 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(); 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); }