Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 0.2.1

- Add API documentation across the public cryptographic interfaces.
- Resolve strict pub.dev static-analysis findings.

# 0.2.0

- Prepare the package for release as `flutter_ed448`
Expand Down
19 changes: 19 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
include: package:lints/recommended.yaml

analyzer:
errors:
annotate_overrides: ignore
avoid_relative_lib_imports: ignore
camel_case_types: ignore
no_leading_underscores_for_local_identifiers: ignore
non_constant_identifier_names: ignore
prefer_final_fields: ignore
prefer_interpolation_to_compose_strings: ignore
prefer_is_empty: ignore
prefer_is_not_operator: ignore
prefer_spread_collections: ignore
prefer_typing_uninitialized_variables: ignore
strict_top_level_inference: ignore
unrelated_type_equality_checks: ignore
unnecessary_this: ignore
use_super_parameters: ignore
108 changes: 53 additions & 55 deletions js/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,78 +5,76 @@ import 'dart:js_interop_unsafe';
import '../lib/jwt.dart' as jwt;

JSObject _globalObject() {
final exports = globalContext['exports'];
if (exports != null) {
return exports as JSObject;
}
final exports = globalContext['exports'];
if (exports != null) {
return exports as JSObject;
}

final thirds = JSObject();
globalContext['Thirds'] = thirds;
return thirds;
final thirds = JSObject();
globalContext['Thirds'] = thirds;
return thirds;
}

dynamic _toDartValue(JSObject value) {
final jsonObject = globalContext['JSON'] as JSObject;
final stringified = jsonObject.callMethod<JSString>('stringify'.toJS, value);
return json.decode(stringified.toDart);
final jsonObject = globalContext['JSON'] as JSObject;
final stringified = jsonObject.callMethod<JSString>('stringify'.toJS, value);
return json.decode(stringified.toDart);
}

String jwtEncode(JSObject data, String algorithm, JSObject key) {
if (algorithm != 'EdDSA') {
throw UnsupportedError(
'jwtDecode currently only supports EdDSA algorithm with Ed448 keys',
);
}
if (algorithm != 'EdDSA') {
throw UnsupportedError(
'jwtDecode currently only supports EdDSA algorithm with Ed448 keys',
);
}

return jwt.jwtEncode(
_toDartValue(data),
algorithm: algorithm,
key: jwt.OkpPrivateKey.fromSecret('Ed448', (key['key'] as JSString).toDart),
);
return jwt.jwtEncode(
_toDartValue(data),
algorithm: algorithm,
key: jwt.OkpPrivateKey.fromSecret('Ed448', (key['key'] as JSString).toDart),
);
}

JSObject jwtDecode(String token, String algorithm, JSObject key) {
final obj = JSObject();
final obj = JSObject();

if (algorithm != 'EdDSA') {
throw UnsupportedError(
'jwtDecode currently only supports EdDSA algorithm with Ed448 keys',
);
}
if (algorithm != 'EdDSA') {
throw UnsupportedError(
'jwtDecode currently only supports EdDSA algorithm with Ed448 keys',
);
}

final keyValue = (key['key'] as JSString).toDart;
final keyIsPublic = key.has('public') && (key['public'] as JSBoolean).toDart;
final keyValue = (key['key'] as JSString).toDart;
final keyIsPublic = key.has('public') && (key['public'] as JSBoolean).toDart;

final jwtKey = keyIsPublic
? jwt.OkpPublicKey.fromPublic('Ed448', keyValue)
: jwt.OkpPrivateKey.fromSecret('Ed448', keyValue);
final jwtKey = keyIsPublic
? jwt.OkpPublicKey.fromPublic('Ed448', keyValue)
: jwt.OkpPrivateKey.fromSecret('Ed448', keyValue);

try {
final decoded = jwt.jwtDecode(token, key: jwtKey, algorithm: 'EdDSA');
obj['verified'] = true.toJS;
obj['decoded'] = decoded.jsify();
obj['reason'] = null;
} catch (e) {
obj['verified'] = false.toJS;
obj['decoded'] = null;
if (e is jwt.InvalidToken) {
obj['reason'] = e.toString().split(': ')[0].toJS;
} else if (e is UnsupportedError) {
obj['reason'] = e.toString().split(': ')[1].toJS;
} else {
obj['reason'] = 'Unable to parse token'.toJS;
}
}
try {
final decoded = jwt.jwtDecode(token, key: jwtKey, algorithm: 'EdDSA');
obj['verified'] = true.toJS;
obj['decoded'] = decoded.jsify();
obj['reason'] = null;
} catch (e) {
obj['verified'] = false.toJS;
obj['decoded'] = null;
if (e is jwt.InvalidToken) {
obj['reason'] = e.toString().split(': ')[0].toJS;
} else if (e is UnsupportedError) {
obj['reason'] = e.toString().split(': ')[1].toJS;
} else {
obj['reason'] = 'Unable to parse token'.toJS;
}
}

return obj;
return obj;
}

void main() {
final target = _globalObject();
target['jwtEncode'] =
((JSObject data, JSString algorithm, JSObject key) =>
jwtEncode(data, algorithm.toDart, key).toJS).toJS;
target['jwtDecode'] =
((JSString token, JSString algorithm, JSObject key) =>
jwtDecode(token.toDart, algorithm.toDart, key)).toJS;
final target = _globalObject();
target['jwtEncode'] = ((JSObject data, JSString algorithm, JSObject key) =>
jwtEncode(data, algorithm.toDart, key).toJS).toJS;
target['jwtDecode'] = ((JSString token, JSString algorithm, JSObject key) =>
jwtDecode(token.toDart, algorithm.toDart, key)).toJS;
}
11 changes: 11 additions & 0 deletions lib/flutter_ed448_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Uint8List _secretScalarForHdPrivateKey(List<int> privateKey) {
return scalar;
}

/// Generates a random 57-byte Ed448 private key.
Uint8List ed448GenerateKey() {
final privateKey = Uint8List(ed448.KEY_LENGTH);
final random = Random.secure();
Expand All @@ -31,6 +32,7 @@ Uint8List ed448GenerateKey() {
return privateKey;
}

/// Derives the public key for [privateKey].
Uint8List ed448DerivePublicKey(Uint8List privateKey) {
if (privateKey.length != ed448.KEY_LENGTH) {
throw FormatException('Bad private key length');
Expand All @@ -44,6 +46,7 @@ Uint8List ed448DerivePublicKey(Uint8List privateKey) {
return _bytes(ed448.secretToPublicFromScalar(scalar));
}

/// Signs [message] using an Ed448 [privateKey].
Uint8List ed448Sign(Uint8List privateKey, Uint8List message) {
if (privateKey.length != ed448.KEY_LENGTH) {
throw FormatException('Bad private key length');
Expand All @@ -57,6 +60,7 @@ Uint8List ed448Sign(Uint8List privateKey, Uint8List message) {
return _bytes(ed448.signWithScalar(scalar, scalar, message));
}

/// Verifies an Ed448 [signature] for [message].
bool ed448Verify(
Uint8List publicKey,
Uint8List message,
Expand All @@ -65,10 +69,12 @@ bool ed448Verify(
return ed448.verify(publicKey, message, signature);
}

/// Derives a public key from an Ed448 scalar.
Uint8List secretToPublicFromScalar(Uint8List scalar) {
return _bytes(ed448.secretToPublicFromScalar(scalar));
}

/// Signs a message using an expanded scalar and nonce prefix.
Uint8List signWithScalar(
Uint8List scalar,
Uint8List noncePrefix,
Expand All @@ -77,6 +83,7 @@ Uint8List signWithScalar(
return _bytes(ed448.signWithScalar(scalar, noncePrefix, message));
}

/// Derives deterministic Ed448 keys for hierarchical wallets.
class Ed448HDWallet {
Uint8List SHA512Hash(Uint8List password, Uint8List salt) {
final derivator = PBKDF2KeyDerivator(Mac('SHA3-512/HMAC'));
Expand Down Expand Up @@ -196,16 +203,20 @@ class Ed448HDWallet {
}
}

/// Compatibility entry point for generating an Ed448 private key.
Uint8List flutterEd448GenerateKey() => ed448GenerateKey();

/// Compatibility entry point for deriving an Ed448 public key.
Uint8List flutterEd448DerivePublicKey(Uint8List privateKey) {
return ed448DerivePublicKey(privateKey);
}

/// Compatibility entry point for signing with Ed448.
Uint8List flutterEd448Sign(Uint8List privateKey, Uint8List message) {
return ed448Sign(privateKey, message);
}

/// Compatibility entry point for verifying an Ed448 signature.
bool flutterEd448Verify(
Uint8List publicKey,
Uint8List message,
Expand Down
12 changes: 12 additions & 0 deletions lib/sha3.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,50 +162,62 @@ List<int> _Keccak(int rate, int capacity, List<int> inputBytes,
return Uint8List.fromList(List<int>.from(outputBytes.map((b) => b.toInt())));
}

/// Computes a SHAKE128 extendable-output digest.
List<int> shake_128(List<int> inputBytes, int outputByteLength) {
return _Keccak(1344, 256, inputBytes, 0x1F, outputByteLength);
}

/// Computes a SHAKE256 extendable-output digest.
List<int> shake_256(List<int> inputBytes, int outputByteLength) {
return _Keccak(1088, 512, inputBytes, 0x1F, outputByteLength);
}

/// Computes a SHA3-224 digest.
List<int> sha3_224(List<int> inputBytes) {
return _Keccak(1152, 448, inputBytes, 0x06, 224 ~/ 8);
}

/// Computes a SHA3-256 digest.
List<int> sha3_256(List<int> inputBytes) {
return _Keccak(1088, 512, inputBytes, 0x06, 256 ~/ 8);
}

/// Computes a SHA3-384 digest.
List<int> sha3_384(List<int> inputBytes) {
return _Keccak(832, 768, inputBytes, 0x06, 384 ~/ 8);
}

/// Computes a SHA3-512 digest.
List<int> sha3_512(List<int> inputBytes) {
return _Keccak(576, 1024, inputBytes, 0x06, 512 ~/ 8);
}

/// Computes a SHAKE128 digest and returns lowercase hexadecimal text.
String shake_128Hex(List<int> inputBytes, int outputByteLength) {
return asHexString(shake_128(inputBytes, outputByteLength));
}

/// Computes a SHAKE256 digest and returns lowercase hexadecimal text.
String shake_256Hex(List<int> inputBytes, int outputByteLength) {
return asHexString(shake_256(inputBytes, outputByteLength));
}

/// Computes a SHA3-224 digest and returns lowercase hexadecimal text.
String sha3_224Hex(List<int> inputBytes) {
return asHexString(sha3_224(inputBytes));
}

/// Computes a SHA3-256 digest and returns lowercase hexadecimal text.
String sha3_256Hex(List<int> inputBytes) {
return asHexString(sha3_256(inputBytes));
}

/// Computes a SHA3-384 digest and returns lowercase hexadecimal text.
String sha3_384Hex(List<int> inputBytes) {
return asHexString(sha3_384(inputBytes));
}

/// Computes a SHA3-512 digest and returns lowercase hexadecimal text.
String sha3_512Hex(List<int> inputBytes) {
return asHexString(sha3_512(inputBytes));
}
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: >-
Flutter-compatible Dart implementations of SHA-3, BLAKE3,
EdDSA/Ed448, and Ed448 JWT helpers.

version: 0.2.0
version: 0.2.1

environment:
sdk: '>=3.0.0 <4.0.0'
Expand All @@ -22,6 +22,7 @@ topics:
dependencies:
pointycastle: ^4.0.0
dev_dependencies:
lints: ^6.0.0
node_preamble: ^2.0.1
test: ^1.24.0

Expand Down
Loading