diff --git a/CHANGELOG.md b/CHANGELOG.md index 62bfbc5..d4c2d10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..06f8f01 --- /dev/null +++ b/analysis_options.yaml @@ -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 diff --git a/js/main.dart b/js/main.dart index 8daf99d..3057a4b 100644 --- a/js/main.dart +++ b/js/main.dart @@ -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('stringify'.toJS, value); - return json.decode(stringified.toDart); + final jsonObject = globalContext['JSON'] as JSObject; + final stringified = jsonObject.callMethod('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; } diff --git a/lib/flutter_ed448_core.dart b/lib/flutter_ed448_core.dart index 0db8bb1..a211dc0 100644 --- a/lib/flutter_ed448_core.dart +++ b/lib/flutter_ed448_core.dart @@ -21,6 +21,7 @@ Uint8List _secretScalarForHdPrivateKey(List privateKey) { return scalar; } +/// Generates a random 57-byte Ed448 private key. Uint8List ed448GenerateKey() { final privateKey = Uint8List(ed448.KEY_LENGTH); final random = Random.secure(); @@ -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'); @@ -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'); @@ -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, @@ -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, @@ -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')); @@ -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, diff --git a/lib/sha3.dart b/lib/sha3.dart index 1f99e1b..b61cc18 100644 --- a/lib/sha3.dart +++ b/lib/sha3.dart @@ -162,50 +162,62 @@ List _Keccak(int rate, int capacity, List inputBytes, return Uint8List.fromList(List.from(outputBytes.map((b) => b.toInt()))); } +/// Computes a SHAKE128 extendable-output digest. List shake_128(List inputBytes, int outputByteLength) { return _Keccak(1344, 256, inputBytes, 0x1F, outputByteLength); } +/// Computes a SHAKE256 extendable-output digest. List shake_256(List inputBytes, int outputByteLength) { return _Keccak(1088, 512, inputBytes, 0x1F, outputByteLength); } +/// Computes a SHA3-224 digest. List sha3_224(List inputBytes) { return _Keccak(1152, 448, inputBytes, 0x06, 224 ~/ 8); } +/// Computes a SHA3-256 digest. List sha3_256(List inputBytes) { return _Keccak(1088, 512, inputBytes, 0x06, 256 ~/ 8); } +/// Computes a SHA3-384 digest. List sha3_384(List inputBytes) { return _Keccak(832, 768, inputBytes, 0x06, 384 ~/ 8); } +/// Computes a SHA3-512 digest. List sha3_512(List inputBytes) { return _Keccak(576, 1024, inputBytes, 0x06, 512 ~/ 8); } +/// Computes a SHAKE128 digest and returns lowercase hexadecimal text. String shake_128Hex(List inputBytes, int outputByteLength) { return asHexString(shake_128(inputBytes, outputByteLength)); } +/// Computes a SHAKE256 digest and returns lowercase hexadecimal text. String shake_256Hex(List inputBytes, int outputByteLength) { return asHexString(shake_256(inputBytes, outputByteLength)); } +/// Computes a SHA3-224 digest and returns lowercase hexadecimal text. String sha3_224Hex(List inputBytes) { return asHexString(sha3_224(inputBytes)); } +/// Computes a SHA3-256 digest and returns lowercase hexadecimal text. String sha3_256Hex(List inputBytes) { return asHexString(sha3_256(inputBytes)); } +/// Computes a SHA3-384 digest and returns lowercase hexadecimal text. String sha3_384Hex(List inputBytes) { return asHexString(sha3_384(inputBytes)); } +/// Computes a SHA3-512 digest and returns lowercase hexadecimal text. String sha3_512Hex(List inputBytes) { return asHexString(sha3_512(inputBytes)); } diff --git a/pubspec.yaml b/pubspec.yaml index 88fb5e3..588d9f7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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' @@ -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