From 48084c348ef74b6b486fe113341cc3f0bd65b4ec Mon Sep 17 00:00:00 2001 From: Bibash Shrestha Date: Mon, 9 Oct 2023 15:32:29 +0545 Subject: [PATCH] feat: Add did:key P256 in menu #1980 --- .../shared/constants/secure_storage_keys.dart | 1 + .../helper_functions/helper_functions.dart | 30 +++- .../drawer/ssi/manage_did/manage_did.dart | 2 + .../drawer/ssi/manage_did/view/did_menu.dart | 6 + .../did_p256/did_p256_private_key_page.dart | 135 ++++++++++++++++++ .../view/did_p256/manage_did_p256_page.dart | 77 ++++++++++ lib/l10n/arb/app_en.arb | 3 +- lib/l10n/untranslated.json | 12 +- 8 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart create mode 100644 lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index 122ed041c..c10a1a5cc 100644 --- a/lib/app/shared/constants/secure_storage_keys.dart +++ b/lib/app/shared/constants/secure_storage_keys.dart @@ -61,6 +61,7 @@ class SecureStorageKeys { static const String ssiKey = 'ssi/key'; static const String p256PrivateKey = 'p256PrivateKey'; + static const String p256PrivateKey2 = 'p256PrivateKey2'; static const String cryptoAccount = 'cryptoAccount'; static const String cryptoAccounTrackingIndex = 'cryptoAccounTrackingIndex'; diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 09797bcbd..4fc45c4d5 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -245,7 +245,7 @@ Future web3RpcMainnetInfuraURL() async { return '$prefixUrl$infuraApiKey'; } -Future getRandomP256PrivateKey( +Future getEBSIV3P256PrivateKey( SecureStorageProvider secureStorage, ) async { final String? p256PrivateKey = await secureStorage.get( @@ -273,6 +273,32 @@ Future getRandomP256PrivateKey( } } +Future getP256PrivateKey(SecureStorageProvider secureStorage) async { + final String? p256PrivateKey = await secureStorage.get( + SecureStorageKeys.p256PrivateKey2, + ); + + if (p256PrivateKey == null) { + final jwk = JsonWebKey.generate('ES256'); + + final json = jwk.toJson(); + + // Sort the keys in ascending order and remove alg + final sortedJwk = Map.fromEntries( + json.entries.toList()..sort((e1, e2) => e1.key.compareTo(e2.key)), + )..remove('keyOperations'); + + await secureStorage.set( + SecureStorageKeys.p256PrivateKey2, + jsonEncode(sortedJwk), + ); + + return jsonEncode(sortedJwk); + } else { + return p256PrivateKey; + } +} + Future fetchPrivateKey({ required OIDC4VC oidc4vc, required SecureStorageProvider secureStorage, @@ -283,7 +309,7 @@ Future fetchPrivateKey({ final index = getIndexValue(isEBSIV3: true); if (isEBSIV3) { - privateKey = await getRandomP256PrivateKey(getSecureStorage); + privateKey = await getEBSIV3P256PrivateKey(getSecureStorage); } else { final OIDC4VC oidc4vc = OIDC4VC(); final mnemonic = await getSecureStorage.get(SecureStorageKeys.ssiMnemonic); diff --git a/lib/dashboard/drawer/ssi/manage_did/manage_did.dart b/lib/dashboard/drawer/ssi/manage_did/manage_did.dart index f7faa908c..45b894fd3 100644 --- a/lib/dashboard/drawer/ssi/manage_did/manage_did.dart +++ b/lib/dashboard/drawer/ssi/manage_did/manage_did.dart @@ -4,6 +4,8 @@ export 'view/did_ebsi_v3/did_ebsi_v3_private_key_page.dart'; export 'view/did_ebsi_v3/manage_did_ebsi_v3_page.dart'; export 'view/did_edDSA/did_ed_dsa_private_key_page.dart'; export 'view/did_edDSA/manage_did_ed_dsa_page.dart'; +export 'view/did_p256/did_p256_private_key_page.dart'; +export 'view/did_p256/manage_did_p256_page.dart'; export 'view/did_polygon_id/manage_did_polygon_id_page.dart'; export 'view/did_secp256k1/did_secp256k1_private_key_page.dart'; export 'view/did_secp256k1/manage_did_secp256k1_page.dart'; diff --git a/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart b/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart index 4c3c57d74..ee3d66b4a 100644 --- a/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart +++ b/lib/dashboard/drawer/ssi/manage_did/view/did_menu.dart @@ -68,6 +68,12 @@ class DidView extends StatelessWidget { .push(ManageDidEbsiV3Page.route()); }, ), + DrawerItem( + title: l10n.keyDecentralizedIDP256, + onTap: () { + Navigator.of(context).push(ManageDidP256Page.route()); + }, + ), DrawerItem( title: l10n.polygonDecentralizedID, onTap: () async { diff --git a/lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart new file mode 100644 index 000000000..f50602290 --- /dev/null +++ b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/did_p256_private_key_page.dart @@ -0,0 +1,135 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:altme/theme/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:oidc4vc/oidc4vc.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class DidP256PrivateKeyPage extends StatefulWidget { + const DidP256PrivateKeyPage({super.key}); + + static Route route() { + return MaterialPageRoute( + builder: (_) => const DidP256PrivateKeyPage(), + settings: const RouteSettings(name: '/DidP256PrivateKeyPage'), + ); + } + + @override + State createState() => _DidP256PrivateKeyPageState(); +} + +class _DidP256PrivateKeyPageState extends State + with SingleTickerProviderStateMixin { + late Animation animation; + late AnimationController animationController; + + Future getKey() async { + final privateKey = await getP256PrivateKey(getSecureStorage); + return privateKey; + } + + @override + void initState() { + super.initState(); + animationController = AnimationController( + vsync: this, + duration: const Duration(seconds: 20), + ); + + final Tween rotationTween = Tween(begin: 20, end: 0); + + animation = rotationTween.animate(animationController) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + Navigator.pop(context); + } + }); + animationController.forward(); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BasePage( + scrollView: false, + title: l10n.decentralizedIDKey, + titleAlignment: Alignment.topCenter, + titleLeading: const BackLeadingButton(), + secureScreen: true, + body: BackgroundCard( + width: double.infinity, + height: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + l10n.didPrivateKey, + style: Theme.of(context).textTheme.title, + ), + const SizedBox( + height: Sizes.spaceNormal, + ), + FutureBuilder( + future: getKey(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + return Column( + children: [ + Text( + snapshot.data!, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: Sizes.spaceXLarge), + CopyButton( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: snapshot.data!), + ); + AlertMessage.showStateMessage( + context: context, + stateMessage: StateMessage.success( + stringMessage: l10n.copiedToClipboard, + ), + ); + }, + ), + ], + ); + case ConnectionState.waiting: + case ConnectionState.none: + case ConnectionState.active: + return const Text(''); + } + }, + ), + Expanded( + child: Center( + child: AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + return Text( + timeFormatter(timeInSecond: animation.value.toInt()), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.displayMedium, + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart new file mode 100644 index 000000000..bb042ff46 --- /dev/null +++ b/lib/dashboard/drawer/ssi/manage_did/view/did_p256/manage_did_p256_page.dart @@ -0,0 +1,77 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:did_kit/did_kit.dart'; +import 'package:flutter/material.dart'; +import 'package:oidc4vc/oidc4vc.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class ManageDidP256Page extends StatefulWidget { + const ManageDidP256Page({super.key}); + + static Route route() { + return MaterialPageRoute( + builder: (_) => const ManageDidP256Page(), + settings: const RouteSettings(name: '/ManageDidP256Page'), + ); + } + + @override + State createState() => _ManageDidP256PageState(); +} + +class _ManageDidP256PageState extends State { + Future getDid() async { + final privateKey = await getP256PrivateKey(getSecureStorage); + + final (did, _) = await getDidAndKid( + isEBSIV3: false, + privateKey: privateKey, + didKitProvider: DIDKitProvider(), + ); + + return did; + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BasePage( + title: l10n.keyDecentralizedIDP256, + titleAlignment: Alignment.topCenter, + scrollView: false, + titleLeading: const BackLeadingButton(), + body: BackgroundCard( + height: double.infinity, + width: double.infinity, + padding: const EdgeInsets.all(Sizes.spaceSmall), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + FutureBuilder( + future: getDid(), + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + final did = snapshot.data!; + return Did(did: did); + case ConnectionState.waiting: + case ConnectionState.none: + case ConnectionState.active: + return const SizedBox(); + } + }, + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: Sizes.spaceNormal), + child: Divider(), + ), + DidPrivateKey(route: DidP256PrivateKeyPage.route()), + ], + ), + ), + ); + } +} diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index dd181737b..3fefa9d29 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -979,6 +979,7 @@ "thisCredentialFormatIsNotSupported": "This credential format is not supported", "moreDetails": "More Details", "theCredentialOfferIsInvalid": "The credential offer is invalid", - "dateOfRequest": "Date of Request" + "dateOfRequest": "Date of Request", + "keyDecentralizedIDP256": "Key Decentralized ID P-256" } diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index b4a71f211..c4e8addf1 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -885,7 +885,8 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ], "es": [ @@ -1774,7 +1775,8 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ], "fr": [ @@ -1966,7 +1968,8 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ], "it": [ @@ -2855,6 +2858,7 @@ "thisCredentialFormatIsNotSupported", "moreDetails", "theCredentialOfferIsInvalid", - "dateOfRequest" + "dateOfRequest", + "keyDecentralizedIDP256" ] }