204 lines
6.3 KiB
Dart
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);
|
|
}
|