From 3458e735c3c03c9260332a746f10e4ab51b2c8e1 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 16 Aug 2022 18:55:30 +0300 Subject: [PATCH 01/29] add scripts that set up didkit and install required dependencies for Android on Debian based GNU/Linux distros --- didkit_setup.sh | 84 +++++++++++++++++++ ...dependencies_for_debian_based_gnu_linux.sh | 51 +++++++++++ 2 files changed, 135 insertions(+) create mode 100755 didkit_setup.sh create mode 100644 install_android_dependencies_for_debian_based_gnu_linux.sh diff --git a/didkit_setup.sh b/didkit_setup.sh new file mode 100755 index 00000000..5ee12692 --- /dev/null +++ b/didkit_setup.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +if [[ "$*" != *-android* ]] && [[ "$*" != *-ios* ]]; then + echo -e "\033[0;31mAt least one of the following arguments are required to build didkit:\033[0m + \033[0;36m-android\033[0m: builds didkit's Android binaries + \033[0;36m-ios\033[0m: builds didkit's iOS binaries +" + exit +fi + +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Checking for brew installation." + if ! command -v brew &>/dev/null; then + echo -e "\033[0;Could not find brew, please install brew or add it to path.\033[0m" + exit + fi +fi + +echo "Checking for rustup installation and setting rust to nightly." +if ! command -v rustup &>/dev/null; then + if ! command -v curl &>/dev/null; then + if [[ "$OSTYPE" == "darwin"* ]]; then + brew install curl + else + sudo apt install curl -yet + fi + fi + + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + rustup default nightly +fi + +if [[ "$*" == *-android* ]]; then + echo "Checking for java installation." + + if ! command -v javac &>/dev/null; then + if [[ "$OSTYPE" == "darwin"* ]]; then + brew install openjdk + else + sudo apt install default-jdk + fi + fi +fi + +echo "Cloning DIDKit repo if not yet on previous directory" +[ ! -d "../didkit" ] && git clone https://github.com/spruceid/didkit.git ../didkit + +echo "Cloning SSI repo if not yet on previous directory" +[ ! -d "../ssi" ] && git clone https://github.com/spruceid/ssi.git --recurse-submodules ../ssi + +if [[ "$*" == *-android* ]]; then + echo "Checking for android sdk." + [ ! -d "$ANDROID_SDK_ROOT" ] && echo -e "\033[0;31mFailed to find Android SDK\033[0m" && exit + [ ! -d "$ANDROID_SDK_ROOT/build-tools" ] && [ ! -d "$ANDROID_TOOLS" ] && echo -e "\033[0;31mFailed to find android-tools\033[0m" && exit + [ ! -d "$ANDROID_SDK_ROOT/ndk" ] && [ ! -d "$ANDROID_NDK_HOME" ] && echo -e "\033[0;31mFailed to find Android NDK\033[0m" && exit +fi + +if ! command -v flutter &>/dev/null; then + echo -e "\033[0;Could not find Flutter, please install flutter or add to path.\033[0m" + exit +fi + +flutter channel dev +flutter upgrade + +if [[ "$*" == *-android* ]]; then + flutter doctor --android-licenses +fi + +cd ../didkit + +if [[ "$*" == *-android* ]]; then + echo "Build didkit for Android" + make -C lib install-rustup-android + make -C lib ../target/test/aar.stamp +fi + +if [[ "$*" == *-ios* ]]; then + echo "Build didkit for iOS" + make -C lib install-rustup-ios + make -C lib ../target/test/ios.stamp +fi + +cargo build diff --git a/install_android_dependencies_for_debian_based_gnu_linux.sh b/install_android_dependencies_for_debian_based_gnu_linux.sh new file mode 100644 index 00000000..c10a1dd2 --- /dev/null +++ b/install_android_dependencies_for_debian_based_gnu_linux.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +## DEPENDENCIES +sudo apt update +sudo apt upgrade +sudo apt install -y git lib32z1 libssl-dev pkg-config build-essential curl cmake openjdk-11-jdk zip unzip +# if there are any error loading the above, re-run apt update and address them before continuing! +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +##CLONE SPRUCE REPOSITORIES +git clone https://github.com/spruceid/didkit +git clone https://github.com/spruceid/credible +git clone --recursive https://github.com/spruceid/ssi + +##FLUTTER +git clone https://github.com/flutter/flutter.git -b dev $HOME/flutter +echo 'export PATH=$HOME/flutter/bin:"$PATH"' >> $HOME/.bashrc + +##ANDROID SDK AND NDK +cd $HOME +wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip +unzip sdk-tools-linux-4333796.zip -d Android +rm sdk-tools-linux-4333796.zip +wget https://dl.google.com/android/repository/commandlinetools-linux-6200805_latest.zip +unzip commandlinetools-linux-6200805_latest.zip -d Android/cmdline-tools +rm commandlinetools-linux-6200805_latest.zip +echo 'export ANDROID_SDK_ROOT=$HOME/Android' >> $HOME/.bashrc +echo 'export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64' >> $HOME/.bashrc +echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/tools/bin:"$PATH"' >> $HOME/.bashrc +echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/tools/lib:"$PATH"' >> $HOME/.bashrc +echo 'export PATH=$ANDROID_SDK_ROOT/tools:"$PATH"' >> $HOME/.bashrc +echo 'export PATH=$JAVA_HOME/bin:"$PATH"' >> $HOME/.bashrc +. $HOME/.bashrc +sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "system-images;android-29;google_apis;x86" "system-images;android-29;google_apis;x86_64" "platform-tools" "platforms;android-29" "build-tools;29.0.3" "ndk;22.0.7026061" "cmdline-tools;latest" +sdkmanager --licenses + +##ANDROID EMULATOR +curl -s "https://get.sdkman.io" | bash +. .sdkman/bin/sdkman-init.sh +sdk install gradle 6.5.1 +echo "no" | avdmanager --verbose create avd --force --name "generic_10" --package "system-images;android-29;google_apis;x86" --tag "google_apis" --abi "x86" + +##BUILD DIDKIT +printf "You are now ready to run the following commands, but re-loading bash/OS may be necessary. +If rustup is inaccessible, reboot/re-load bash. +$ cd didkit +$ make -C lib install-rustup-android +$ make -C lib ../target/test/java.stamp +$ make -C lib ../target/test/aar.stamp +$ make -C lib ../target/test/flutter.stamp +$ cargo build\n\n" From 30c10b41fb3461ed6c5104244c27b8d05fb536d1 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:05:08 +0300 Subject: [PATCH 02/29] add did field into UserState to store the DID --- lib/models/user_state.dart | 1 + lib/models/user_state.freezed.dart | 42 ++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/lib/models/user_state.dart b/lib/models/user_state.dart index 098d0222..f89da2ae 100644 --- a/lib/models/user_state.dart +++ b/lib/models/user_state.dart @@ -54,6 +54,7 @@ class UserState with _$UserState { @JsonKey(fromJson: localeFromJson, toJson: localeToJson) Locale? locale, @JsonKey(ignore: true) @Default([]) List contacts, @JsonKey(ignore: true) PhoneAuthCredential? credentials, + @JsonKey(ignore: true) String? did, }) = _UserState; factory UserState.initial() => UserState( diff --git a/lib/models/user_state.freezed.dart b/lib/models/user_state.freezed.dart index 2eb35715..019f7c4f 100644 --- a/lib/models/user_state.freezed.dart +++ b/lib/models/user_state.freezed.dart @@ -57,6 +57,8 @@ mixin _$UserState { List get contacts => throw _privateConstructorUsedError; @JsonKey(ignore: true) PhoneAuthCredential? get credentials => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + String? get did => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -100,7 +102,8 @@ abstract class $UserStateCopyWith<$Res> { BiometricAuth authType, @JsonKey(fromJson: localeFromJson, toJson: localeToJson) Locale? locale, @JsonKey(ignore: true) List contacts, - @JsonKey(ignore: true) PhoneAuthCredential? credentials}); + @JsonKey(ignore: true) PhoneAuthCredential? credentials, + @JsonKey(ignore: true) String? did}); $WalletModulesCopyWith<$Res>? get walletModules; } @@ -147,6 +150,7 @@ class _$UserStateCopyWithImpl<$Res> implements $UserStateCopyWith<$Res> { Object? locale = freezed, Object? contacts = freezed, Object? credentials = freezed, + Object? did = freezed, }) { return _then(_value.copyWith( wcURI: wcURI == freezed @@ -277,6 +281,10 @@ class _$UserStateCopyWithImpl<$Res> implements $UserStateCopyWith<$Res> { ? _value.credentials : credentials // ignore: cast_nullable_to_non_nullable as PhoneAuthCredential?, + did: did == freezed + ? _value.did + : did // ignore: cast_nullable_to_non_nullable + as String?, )); } @@ -330,7 +338,8 @@ abstract class _$$_UserStateCopyWith<$Res> implements $UserStateCopyWith<$Res> { BiometricAuth authType, @JsonKey(fromJson: localeFromJson, toJson: localeToJson) Locale? locale, @JsonKey(ignore: true) List contacts, - @JsonKey(ignore: true) PhoneAuthCredential? credentials}); + @JsonKey(ignore: true) PhoneAuthCredential? credentials, + @JsonKey(ignore: true) String? did}); @override $WalletModulesCopyWith<$Res>? get walletModules; @@ -380,6 +389,7 @@ class __$$_UserStateCopyWithImpl<$Res> extends _$UserStateCopyWithImpl<$Res> Object? locale = freezed, Object? contacts = freezed, Object? credentials = freezed, + Object? did = freezed, }) { return _then(_$_UserState( wcURI: wcURI == freezed @@ -510,6 +520,10 @@ class __$$_UserStateCopyWithImpl<$Res> extends _$UserStateCopyWithImpl<$Res> ? _value.credentials : credentials // ignore: cast_nullable_to_non_nullable as PhoneAuthCredential?, + did: did == freezed + ? _value.did + : did // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -549,7 +563,8 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { this.authType = BiometricAuth.none, @JsonKey(fromJson: localeFromJson, toJson: localeToJson) this.locale, @JsonKey(ignore: true) this.contacts = const [], - @JsonKey(ignore: true) this.credentials}) + @JsonKey(ignore: true) this.credentials, + @JsonKey(ignore: true) this.did}) : super._(); factory _$_UserState.fromJson(Map json) => @@ -646,10 +661,13 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { @override @JsonKey(ignore: true) final PhoneAuthCredential? credentials; + @override + @JsonKey(ignore: true) + final String? did; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'UserState(wcURI: $wcURI, contractVersion: $contractVersion, walletModules: $walletModules, installedAt: $installedAt, isContactsSynced: $isContactsSynced, isLoggedOut: $isLoggedOut, backup: $backup, scrollToTop: $scrollToTop, walletAddress: $walletAddress, networks: $networks, mnemonic: $mnemonic, privateKey: $privateKey, pincode: $pincode, accountAddress: $accountAddress, countryCode: $countryCode, phoneNumber: $phoneNumber, warnSendDialogShowed: $warnSendDialogShowed, isoCode: $isoCode, jwtToken: $jwtToken, displayName: $displayName, avatarUrl: $avatarUrl, email: $email, verificationId: $verificationId, identifier: $identifier, syncedContacts: $syncedContacts, reverseContacts: $reverseContacts, currency: $currency, hasUpgrade: $hasUpgrade, authType: $authType, locale: $locale, contacts: $contacts, credentials: $credentials)'; + return 'UserState(wcURI: $wcURI, contractVersion: $contractVersion, walletModules: $walletModules, installedAt: $installedAt, isContactsSynced: $isContactsSynced, isLoggedOut: $isLoggedOut, backup: $backup, scrollToTop: $scrollToTop, walletAddress: $walletAddress, networks: $networks, mnemonic: $mnemonic, privateKey: $privateKey, pincode: $pincode, accountAddress: $accountAddress, countryCode: $countryCode, phoneNumber: $phoneNumber, warnSendDialogShowed: $warnSendDialogShowed, isoCode: $isoCode, jwtToken: $jwtToken, displayName: $displayName, avatarUrl: $avatarUrl, email: $email, verificationId: $verificationId, identifier: $identifier, syncedContacts: $syncedContacts, reverseContacts: $reverseContacts, currency: $currency, hasUpgrade: $hasUpgrade, authType: $authType, locale: $locale, contacts: $contacts, credentials: $credentials, did: $did)'; } @override @@ -688,7 +706,8 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { ..add(DiagnosticsProperty('authType', authType)) ..add(DiagnosticsProperty('locale', locale)) ..add(DiagnosticsProperty('contacts', contacts)) - ..add(DiagnosticsProperty('credentials', credentials)); + ..add(DiagnosticsProperty('credentials', credentials)) + ..add(DiagnosticsProperty('did', did)); } @override @@ -746,7 +765,8 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { const DeepCollectionEquality().equals(other.locale, locale) && const DeepCollectionEquality().equals(other.contacts, contacts) && const DeepCollectionEquality() - .equals(other.credentials, credentials)); + .equals(other.credentials, credentials) && + const DeepCollectionEquality().equals(other.did, did)); } @JsonKey(ignore: true) @@ -784,7 +804,8 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { const DeepCollectionEquality().hash(authType), const DeepCollectionEquality().hash(locale), const DeepCollectionEquality().hash(contacts), - const DeepCollectionEquality().hash(credentials) + const DeepCollectionEquality().hash(credentials), + const DeepCollectionEquality().hash(did) ]); @JsonKey(ignore: true) @@ -838,7 +859,9 @@ abstract class _UserState extends UserState { @JsonKey(ignore: true) final List contacts, @JsonKey(ignore: true) - final PhoneAuthCredential? credentials}) = _$_UserState; + final PhoneAuthCredential? credentials, + @JsonKey(ignore: true) + final String? did}) = _$_UserState; _UserState._() : super._(); factory _UserState.fromJson(Map json) = @@ -915,6 +938,9 @@ abstract class _UserState extends UserState { PhoneAuthCredential? get credentials; @override @JsonKey(ignore: true) + String? get did; + @override + @JsonKey(ignore: true) _$$_UserStateCopyWith<_$_UserState> get copyWith => throw _privateConstructorUsedError; } From 25f87a7249b70a66a938440f82f73a13004bca18 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:05:43 +0300 Subject: [PATCH 03/29] add the default DID method --- lib/constants/strings.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/constants/strings.dart b/lib/constants/strings.dart index 89ffa34f..309b778f 100644 --- a/lib/constants/strings.dart +++ b/lib/constants/strings.dart @@ -3,4 +3,5 @@ class Strings { static const String appTitle = 'Fuse Wallet'; static const String appStoreId = '1491783654'; + static const defaultDIDMethod = "key"; } From bbb0a1d5cf20034e0d31290cb41a4fb60e3427c9 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:08:27 +0300 Subject: [PATCH 04/29] add instructions to install android dependencies for Debian based GNU Linux distros --- install_android_dependencies_for_debian_based_gnu_linux.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) mode change 100644 => 100755 install_android_dependencies_for_debian_based_gnu_linux.sh diff --git a/install_android_dependencies_for_debian_based_gnu_linux.sh b/install_android_dependencies_for_debian_based_gnu_linux.sh old mode 100644 new mode 100755 index c10a1dd2..aaf2cf9a --- a/install_android_dependencies_for_debian_based_gnu_linux.sh +++ b/install_android_dependencies_for_debian_based_gnu_linux.sh @@ -13,8 +13,7 @@ git clone https://github.com/spruceid/credible git clone --recursive https://github.com/spruceid/ssi ##FLUTTER -git clone https://github.com/flutter/flutter.git -b dev $HOME/flutter -echo 'export PATH=$HOME/flutter/bin:"$PATH"' >> $HOME/.bashrc +flutter channel stable ##ANDROID SDK AND NDK cd $HOME From 51da4739d8958b2d1efcaf2ac0697d11bfa59baa Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:15:03 +0300 Subject: [PATCH 05/29] add generatePrivateKey method that generates a private key from mnemonic and the generateDID method that generates DID from private key --- lib/utils/did/generate_did.dart | 14 +++++ lib/utils/did/private_key_generation.dart | 76 +++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 lib/utils/did/generate_did.dart create mode 100644 lib/utils/did/private_key_generation.dart diff --git a/lib/utils/did/generate_did.dart b/lib/utils/did/generate_did.dart new file mode 100644 index 00000000..04461a0c --- /dev/null +++ b/lib/utils/did/generate_did.dart @@ -0,0 +1,14 @@ +import 'package:didkit/didkit.dart'; +import 'package:flutter/material.dart'; +import 'package:fusecash/constants/strings.dart'; +import 'package:fusecash/utils/did/private_key_generation.dart'; + +Future generateDID(String mnemonic) async { + debugPrint("Generating DID from mnemonic: $mnemonic"); + final privateKeyGeneration = PrivateKeyGeneration(); + final privateKeyToGenerateDID = + await privateKeyGeneration.generatePrivateKey(mnemonic); + final did = DIDKit.keyToDID(Strings.defaultDIDMethod, privateKeyToGenerateDID); + debugPrint("did: $did"); + return did; +} diff --git a/lib/utils/did/private_key_generation.dart b/lib/utils/did/private_key_generation.dart new file mode 100644 index 00000000..25769ef7 --- /dev/null +++ b/lib/utils/did/private_key_generation.dart @@ -0,0 +1,76 @@ +import 'dart:convert'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:bip32/bip32.dart' as bip32; +import 'package:hex/hex.dart'; +import 'package:secp256k1/secp256k1.dart'; + +class PrivateKeyGeneration { + Future generatePrivateKey(String mnemonic) async { + //mnemonic - notice photo opera keen climb agent soft parrot best joke field devote + final seed = bip39.mnemonicToSeed( + mnemonic); //[105, 104, 114, 235, 191, 74, 81, 25, 186, 14, 224, 98, 187, 127, 45, 150, 115, 57, 174, 200, 238, 175, 36, 200, 142, 171, 91, 50, 40, 188, 126, 59, 73, 165, 227, 3, 92, 110, 15, 220, 157, 233, 140, 87, 195, 12, 91, 90, 165, 113, 52, 220, 139, 101, 206, 246, 2, 182, 24, 189, 73, 225, 195, 72] + + var rootKey = bip32.BIP32.fromSeed(seed); //Instance of 'BIP32' + + // derive path for ethereum '60' see bip 44, first address + final child = rootKey.derivePath("m/44'/60'/0'/0/0"); //Instance of 'BIP32' + + Iterable iterable = child + .privateKey!; //[44, 254, 73, 198, 41, 37, 89, 193, 190, 104, 116, 244, 188, 50, 31, 128, 25, 101, 57, 132, 49, 132, 105, 153, 166, 32, 39, 237, 145, 88, 63, 154] + + final epk = HEX.encode(List.from( + iterable)); //2cfe49c6292559c1be6874f4bc321f801965398431846999a62027ed91583f9a + + final pk = PrivateKey.fromHex(epk); //Instance of 'PrivateKey' + + final pub = pk.publicKey.toHex().substring( + 2); //41a72ac211e17bfa49bde064d4617ed88f08d394dfdbf20f51c367035d0dc9e8901f5ac8c57c64f9306b0ed200e08a1b36a08969dc1755f149eac0e3b652a06c + + final ad = HEX.decode( + epk); //[44, 254, 73, 198, 41, 37, 89, 193, 190, 104, 116, 244, 188, 50, 31, 128, 25, 101, 57, 132, 49, 132, 105, 153, 166, 32, 39, 237, 145, 88, 63, 154] + + final d = base64Url.encode(ad).substring(0, + 43); // remove "=" padding 43/44 // ad -> LP5JxiklWcG-aHT0vDIfgBllOYQxhGmZpiAn7ZFYP5o + + final mx = pub.substring(0, + 64); // first 32 bytes // mx -> 41a72ac211e17bfa49bde064d4617ed88f08d394dfdbf20f51c367035d0dc9e8 + + final ax = HEX.decode( + mx); //[65, 167, 42, 194, 17, 225, 123, 250, 73, 189, 224, 100, 212, 97, 126, 216, 143, 8, 211, 148, 223, 219, 242, 15, 81, 195, 103, 3, 93, 13, 201, 232] + + final x = base64Url.encode(ax).substring(0, + 43); // remove "=" padding 43/44 // x -> QacqwhHhe_pJveBk1GF-2I8I05Tf2_IPUcNnA10Nyeg + + final my = pub.substring( + 64); // last 32 bytes // my -> 901f5ac8c57c64f9306b0ed200e08a1b36a08969dc1755f149eac0e3b652a06c + + final ay = HEX.decode( + my); //[144, 31, 90, 200, 197, 124, 100, 249, 48, 107, 14, 210, 0, 224, 138, 27, 54, 160, 137, 105, 220, 23, 85, 241, 73, 234, 192, 227, 182, 82, 160, 108] + + final y = base64Url + .encode(ay) + .substring(0, 43); //kB9ayMV8ZPkwaw7SAOCKGzagiWncF1XxSerA47ZSoGw + + // ATTENTION !!!!! + // alg "ES256K-R" for did:ethr and did:tz2 "EcdsaSecp256k1RecoverySignature2020" + // use alg "ES256K" for did:key + final key = { + 'kty': 'EC', + 'crv': 'secp256k1', + 'd': d, + 'x': x, + 'y': y, + 'alg': 'ES256K-R' // or 'alg': "ES256K" for did:key + }; + // key = { + // "kty": "EC", + // "crv": "secp256k1", + // "d": "LP5JxiklWcG-aHT0vDIfgBllOYQxhGmZpiAn7ZFYP5o", + // "x": "QacqwhHhe_pJveBk1GF-2I8I05Tf2_IPUcNnA10Nyeg", + // "y": "kB9ayMV8ZPkwaw7SAOCKGzagiWncF1XxSerA47ZSoGw", + // "alg": "ES256K-R" + // } + + return jsonEncode(key); + } +} \ No newline at end of file From a28e2256e56fc280649e61313a3c502dd8e3d9f3 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:16:00 +0300 Subject: [PATCH 06/29] add packages required for DID generation --- pubspec.lock | 16 +++++++++++++++- pubspec.yaml | 5 +++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index 0d5c7af8..b385c47e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -142,7 +142,7 @@ packages: source: hosted version: "2.0.0" bip39: - dependency: transitive + dependency: "direct main" description: name: bip39 url: "https://pub.dartlang.org" @@ -484,6 +484,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + didkit: + dependency: "direct main" + description: + path: "../didkit/lib/flutter" + relative: true + source: path + version: "0.0.2" dio: dependency: "direct main" description: @@ -1668,6 +1675,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.6.0" + secp256k1: + dependency: "direct main" + description: + name: secp256k1 + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" sha3: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 352d0b95..82e8882c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,11 @@ dependencies: flutter_localizations: sdk: flutter darq: ^1.2.1 + didkit: + path: ../didkit/lib/flutter + bip39: ^1.0.6 + secp256k1: ^0.3.0 + dev_dependencies: flutter_native_splash: ^2.2.7 freezed: ^2.1.0+1 From dd8f82b6fc6e88806fae75035f18e3140679dc85 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:17:47 +0300 Subject: [PATCH 07/29] test if the same private key is generated when the same mnemonic is used --- test/did_test/private_key_generation_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/did_test/private_key_generation_test.dart diff --git a/test/did_test/private_key_generation_test.dart b/test/did_test/private_key_generation_test.dart new file mode 100644 index 00000000..b9274771 --- /dev/null +++ b/test/did_test/private_key_generation_test.dart @@ -0,0 +1,18 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:fusecash/utils/did/private_key_generation.dart'; + +void main() { + test( + "Should generate the same private key from the same mnemonic", + () async { + const mnemonic = "husband modify silk must mansion payment jeans reopen " + "connect eagle spirit wink"; + final privateKeyGeneration = PrivateKeyGeneration(); + final generatedPrivateKey = + await privateKeyGeneration.generatePrivateKey(mnemonic); + const privateKey = + '{"kty":"EC","crv":"secp256k1","d":"wTo43nJjOmx9aSeyZc3wAA7y4EHHEzvKcXB2A6t15iA","x":"1lUjK7OPOVgQjCKRAg_rvJTil0xHldjStkkH5OJp13k","y":"OQJFxHdalE1Zr2i02gZBopKUvNeO2LDjitkBzE4U2QI","alg":"ES256K-R"}'; + expect(generatedPrivateKey, equals(privateKey)); + }, + ); +} From 63215720f7f6a37fda217a7fb41732a3d62903ed Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:21:10 +0300 Subject: [PATCH 08/29] when local account is created or a wallet is restored, generate a DID and store it --- lib/redux/actions/user_actions.dart | 68 ++++++++++++++++++++++++++++ lib/redux/reducers/user_reducer.dart | 6 +++ 2 files changed, 74 insertions(+) diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index 7b088268..4b1f59ce 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -1,6 +1,9 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; +import 'package:didkit/didkit.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -11,6 +14,9 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_udid/flutter_udid.dart'; +import 'package:fusecash/constants/strings.dart'; +import 'package:fusecash/utils/did/generate_did.dart'; +import 'package:fusecash/utils/did/private_key_generation.dart'; import 'package:image_picker/image_picker.dart'; import 'package:phone_number/phone_number.dart'; import 'package:redux/redux.dart'; @@ -29,14 +35,17 @@ import 'package:fusecash/utils/contacts.dart'; import 'package:fusecash/utils/crashlytics.dart'; import 'package:fusecash/utils/log/log.dart'; import 'package:fusecash/utils/phone.dart'; +import 'package:bip32/bip32.dart' as bip39; class SetWalletConnectURI { final String wcURI; + SetWalletConnectURI(this.wcURI); } class ScrollToTop { final bool value; + ScrollToTop( this.value, ); @@ -44,6 +53,7 @@ class ScrollToTop { class ToggleUpgrade { final bool value; + ToggleUpgrade({ required this.value, }); @@ -51,16 +61,19 @@ class ToggleUpgrade { class UpdateCurrency { final String currency; + UpdateCurrency({required this.currency}); } class UpdateLocale { final Locale locale; + UpdateLocale({required this.locale}); } class WarnSendDialogShowed { final bool value; + WarnSendDialogShowed( this.value, ); @@ -68,6 +81,7 @@ class WarnSendDialogShowed { class SetSecurityType { BiometricAuth biometricAuth; + SetSecurityType({required this.biometricAuth}); } @@ -75,13 +89,22 @@ class CreateLocalAccountSuccess { final List mnemonic; final String privateKey; final String accountAddress; + final String did; + CreateLocalAccountSuccess( this.mnemonic, this.privateKey, this.accountAddress, + this.did, ); } +class GenerateDIDSuccess { + final String did; + + const GenerateDIDSuccess(this.did); +} + class ReLogin { ReLogin(); } @@ -91,6 +114,7 @@ class LoginRequestSuccess { final String phoneNumber; final String? displayName; final String? email; + LoginRequestSuccess({ required this.countryCode, required this.phoneNumber, @@ -105,12 +129,14 @@ class LogoutRequestSuccess { class LoginVerifySuccess { final String jwtToken; + LoginVerifySuccess(this.jwtToken); } class SyncContactsProgress { List contacts; List> newContacts; + SyncContactsProgress(this.contacts, this.newContacts); } @@ -120,21 +146,25 @@ class SyncContactsRejected { class SaveContacts { List contacts; + SaveContacts(this.contacts); } class SetPincodeSuccess { String pincode; + SetPincodeSuccess(this.pincode); } class SetDisplayName { String displayName; + SetDisplayName(this.displayName); } class SetUserAvatar { String avatarUrl; + SetUserAvatar(this.avatarUrl); } @@ -148,21 +178,25 @@ class BackupSuccess { class SetCredentials { PhoneAuthCredential? credentials; + SetCredentials(this.credentials); } class SetVerificationId { String verificationId; + SetVerificationId(this.verificationId); } class JustInstalled { final DateTime installedAt; + JustInstalled(this.installedAt); } class DeviceIdSuccess { final String identifier; + DeviceIdSuccess(this.identifier); } @@ -292,11 +326,18 @@ ThunkAction restoreWalletCall( EthereumAddress accountAddress = await credentials.extractAddress(); log.info('privateKey: $privateKey'); log.info('accountAddress: ${accountAddress.toString()}'); + + final mnemonicAsString = mnemonic.join(' '); + final did = await generateDID(mnemonicAsString); + + debugPrint("Restored did: $did"); + store.dispatch( CreateLocalAccountSuccess( mnemonic, privateKey, accountAddress.toString(), + did, ), ); successCallback(); @@ -342,11 +383,15 @@ ThunkAction createLocalAccountCall( EthereumAddress accountAddress = await credentials.extractAddress(); log.info('privateKey: $privateKey'); log.info('accountAddress: ${accountAddress.toString()}'); + + final did = await generateDID(mnemonic); + store.dispatch( CreateLocalAccountSuccess( mnemonic.split(' '), privateKey, accountAddress.toString(), + did, ), ); Analytics.track( @@ -561,6 +606,29 @@ ThunkAction loadContacts() { }; } +ThunkAction generateDIDCall({required String mnemonic}) { + assert(mnemonic.isNotEmpty, "Mnemonic must not be empty"); + return (Store store) async { + try { + final did = await generateDID(mnemonic); + final generateDIDSuccess = GenerateDIDSuccess(did); + store.dispatch(generateDIDSuccess); + } catch (exception, stackTrace) { + const errorMessage = "An error occurred while generating DID."; + log.error( + errorMessage, + error: exception, + stackTrace: stackTrace, + ); + Crashlytics.recordError( + Exception("$errorMessage ${exception.toString()}"), + stackTrace, + reason: errorMessage, + ); + } + }; +} + ThunkAction web3Init({ WalletModules? modules, }) { diff --git a/lib/redux/reducers/user_reducer.dart b/lib/redux/reducers/user_reducer.dart index 160da60f..4ba6a756 100644 --- a/lib/redux/reducers/user_reducer.dart +++ b/lib/redux/reducers/user_reducer.dart @@ -11,6 +11,7 @@ final userReducers = combineReducers([ TypedReducer(_scrollToTop), TypedReducer(_toggleUpgrade), TypedReducer(_createNewWalletSuccess), + TypedReducer(_generateDIDSuccess), TypedReducer(_loginSuccess), TypedReducer(_loginVerifySuccess), TypedReducer(_logoutSuccess), @@ -76,6 +77,10 @@ UserState _getWalletDataSuccess(UserState state, GetWalletDataSuccess action) { ); } +UserState _generateDIDSuccess(UserState state, GenerateDIDSuccess action) { + return state.copyWith(did: action.did); +} + UserState _backupSuccess(UserState state, BackupSuccess action) { return state.copyWith(backup: true); } @@ -93,6 +98,7 @@ UserState _createNewWalletSuccess( mnemonic: action.mnemonic, privateKey: action.privateKey, accountAddress: action.accountAddress, + did: action.did, ); } From c1bb5b97f73e7c9595f7036bbe8b15b32a17c429 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 18 Aug 2022 16:22:26 +0300 Subject: [PATCH 09/29] on SplashScreen, if mnemonic exists, generate a DID for user --- lib/features/screens/splash_screen.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/features/screens/splash_screen.dart b/lib/features/screens/splash_screen.dart index 449c3443..d9bb9560 100644 --- a/lib/features/screens/splash_screen.dart +++ b/lib/features/screens/splash_screen.dart @@ -1,9 +1,11 @@ +import 'package:darq/darq.dart'; import 'package:flutter/material.dart'; import 'package:another_flushbar/flushbar.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter_gen/gen_l10n/I10n.dart'; import 'package:flutter_redux/flutter_redux.dart'; +import 'package:fusecash/utils/did/generate_did.dart'; import 'package:redux/redux.dart'; import 'package:fusecash/common/router/routes.dart'; @@ -16,6 +18,7 @@ import 'package:fusecash/utils/biometric_local_auth.dart'; class SplashPage extends StatefulWidget { final void Function(bool isLoggedIn)? onLoginResult; + const SplashPage({ Key? key, this.onLoginResult, @@ -27,11 +30,25 @@ class SplashPage extends StatefulWidget { class _SplashPageState extends State { late Flushbar flush; + void onInit(Store store) async { UserState userState = store.state.userState; String privateKey = userState.privateKey; String jwtToken = userState.jwtToken; bool isLoggedOut = userState.isLoggedOut; + + final mnemonic = userState.mnemonic; + + final did = userState.did; + final didDoesNotExist = did == null || did.isEmpty; + + if (mnemonic.isNotEmpty && didDoesNotExist) { + final mnemonic = userState.mnemonic.join(" "); + store.dispatch( + generateDIDCall(mnemonic: mnemonic), + ); + } + if (privateKey.isEmpty || jwtToken.isEmpty || isLoggedOut) { context.router.replaceAll([const OnBoardingRoute()]); widget.onLoginResult?.call(false); From 2cdbdcd17d0231e18a7db632f043a660272884dc Mon Sep 17 00:00:00 2001 From: = Date: Wed, 24 Aug 2022 13:00:15 +0300 Subject: [PATCH 10/29] create a class called DIDService and put generateDID function into it --- lib/features/screens/splash_screen.dart | 2 -- lib/redux/actions/user_actions.dart | 10 ++++------ lib/redux/reducers/user_reducer.dart | 1 + lib/utils/did/did_service.dart | 19 +++++++++++++++++++ lib/utils/did/generate_did.dart | 14 -------------- 5 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 lib/utils/did/did_service.dart delete mode 100644 lib/utils/did/generate_did.dart diff --git a/lib/features/screens/splash_screen.dart b/lib/features/screens/splash_screen.dart index d9bb9560..85b16b0c 100644 --- a/lib/features/screens/splash_screen.dart +++ b/lib/features/screens/splash_screen.dart @@ -1,11 +1,9 @@ -import 'package:darq/darq.dart'; import 'package:flutter/material.dart'; import 'package:another_flushbar/flushbar.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter_gen/gen_l10n/I10n.dart'; import 'package:flutter_redux/flutter_redux.dart'; -import 'package:fusecash/utils/did/generate_did.dart'; import 'package:redux/redux.dart'; import 'package:fusecash/common/router/routes.dart'; diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index 4b1f59ce..55c0d3f8 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -14,9 +14,7 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_udid/flutter_udid.dart'; -import 'package:fusecash/constants/strings.dart'; -import 'package:fusecash/utils/did/generate_did.dart'; -import 'package:fusecash/utils/did/private_key_generation.dart'; +import 'package:fusecash/utils/did/did_service.dart'; import 'package:image_picker/image_picker.dart'; import 'package:phone_number/phone_number.dart'; import 'package:redux/redux.dart'; @@ -328,7 +326,7 @@ ThunkAction restoreWalletCall( log.info('accountAddress: ${accountAddress.toString()}'); final mnemonicAsString = mnemonic.join(' '); - final did = await generateDID(mnemonicAsString); + final did = await DIDService.generateDID(mnemonicAsString); debugPrint("Restored did: $did"); @@ -384,7 +382,7 @@ ThunkAction createLocalAccountCall( log.info('privateKey: $privateKey'); log.info('accountAddress: ${accountAddress.toString()}'); - final did = await generateDID(mnemonic); + final did = await DIDService.generateDID(mnemonic); store.dispatch( CreateLocalAccountSuccess( @@ -610,7 +608,7 @@ ThunkAction generateDIDCall({required String mnemonic}) { assert(mnemonic.isNotEmpty, "Mnemonic must not be empty"); return (Store store) async { try { - final did = await generateDID(mnemonic); + final did = await DIDService.generateDID(mnemonic); final generateDIDSuccess = GenerateDIDSuccess(did); store.dispatch(generateDIDSuccess); } catch (exception, stackTrace) { diff --git a/lib/redux/reducers/user_reducer.dart b/lib/redux/reducers/user_reducer.dart index 4ba6a756..ad23e308 100644 --- a/lib/redux/reducers/user_reducer.dart +++ b/lib/redux/reducers/user_reducer.dart @@ -78,6 +78,7 @@ UserState _getWalletDataSuccess(UserState state, GetWalletDataSuccess action) { } UserState _generateDIDSuccess(UserState state, GenerateDIDSuccess action) { + // TODO: Issue a VC for the user. return state.copyWith(did: action.did); } diff --git a/lib/utils/did/did_service.dart b/lib/utils/did/did_service.dart new file mode 100644 index 00000000..6669bf13 --- /dev/null +++ b/lib/utils/did/did_service.dart @@ -0,0 +1,19 @@ +import 'package:didkit/didkit.dart'; +import 'package:flutter/material.dart'; +import 'package:fusecash/constants/strings.dart'; +import 'package:fusecash/utils/did/private_key_generation.dart'; + +class DIDService { + static Future generateDID(String mnemonic) async { + debugPrint("Generating DID from mnemonic: $mnemonic"); + final privateKeyGeneration = PrivateKeyGeneration(); + final privateKeyToGenerateDID = + await privateKeyGeneration.generatePrivateKey(mnemonic); + final did = DIDKit.keyToDID( + Strings.defaultDIDMethod, + privateKeyToGenerateDID, + ); + debugPrint("did: $did"); + return did; + } +} diff --git a/lib/utils/did/generate_did.dart b/lib/utils/did/generate_did.dart deleted file mode 100644 index 04461a0c..00000000 --- a/lib/utils/did/generate_did.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:didkit/didkit.dart'; -import 'package:flutter/material.dart'; -import 'package:fusecash/constants/strings.dart'; -import 'package:fusecash/utils/did/private_key_generation.dart'; - -Future generateDID(String mnemonic) async { - debugPrint("Generating DID from mnemonic: $mnemonic"); - final privateKeyGeneration = PrivateKeyGeneration(); - final privateKeyToGenerateDID = - await privateKeyGeneration.generatePrivateKey(mnemonic); - final did = DIDKit.keyToDID(Strings.defaultDIDMethod, privateKeyToGenerateDID); - debugPrint("did: $did"); - return did; -} From 6d5bb80ead9d3b7d4b7b13e31e7decee00f40e3f Mon Sep 17 00:00:00 2001 From: = Date: Wed, 24 Aug 2022 13:49:50 +0300 Subject: [PATCH 11/29] add model classes required to issue a VC for the user --- .../credential_subject.dart | 15 +++++++++++ .../credential_subject.g.dart | 17 ++++++++++++ .../self_issued_credential.dart | 24 +++++++++++++++++ .../self_issued_credential.g.dart | 26 +++++++++++++++++++ .../user_info_credential_subject.dart | 21 +++++++++++++++ .../user_info_credential_subject.g.dart | 23 ++++++++++++++++ 6 files changed, 126 insertions(+) create mode 100644 lib/models/verifiable_credential/credential_subject.dart create mode 100644 lib/models/verifiable_credential/credential_subject.g.dart create mode 100644 lib/models/verifiable_credential/self_issued_credential.dart create mode 100644 lib/models/verifiable_credential/self_issued_credential.g.dart create mode 100644 lib/models/verifiable_credential/user_info_credential_subject.dart create mode 100644 lib/models/verifiable_credential/user_info_credential_subject.g.dart diff --git a/lib/models/verifiable_credential/credential_subject.dart b/lib/models/verifiable_credential/credential_subject.dart new file mode 100644 index 00000000..f0b8aaf3 --- /dev/null +++ b/lib/models/verifiable_credential/credential_subject.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'credential_subject.g.dart'; + +@JsonSerializable() +class CredentialSubject { + final String id; + + const CredentialSubject({required this.id}); + + factory CredentialSubject.fromJson(Map json) => + _$CredentialSubjectFromJson(json); + + Map toJson() => _$CredentialSubjectToJson(this); +} diff --git a/lib/models/verifiable_credential/credential_subject.g.dart b/lib/models/verifiable_credential/credential_subject.g.dart new file mode 100644 index 00000000..0e236a9a --- /dev/null +++ b/lib/models/verifiable_credential/credential_subject.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'credential_subject.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CredentialSubject _$CredentialSubjectFromJson(Map json) => + CredentialSubject( + id: json['id'] as String, + ); + +Map _$CredentialSubjectToJson(CredentialSubject instance) => + { + 'id': instance.id, + }; diff --git a/lib/models/verifiable_credential/self_issued_credential.dart b/lib/models/verifiable_credential/self_issued_credential.dart new file mode 100644 index 00000000..7c4640ae --- /dev/null +++ b/lib/models/verifiable_credential/self_issued_credential.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:fusecash/models/verifiable_credential/user_info_credential_subject.dart'; + +part 'self_issued_credential.g.dart'; + +@JsonSerializable(explicitToJson: true) +class SelfIssuedCredential { + final String id; + final String issuer; + final String issuanceDate; + final UserInfoCredentialSubject userInfoCredentialSubject; + + const SelfIssuedCredential({ + required this.id, + required this.issuer, + required this.issuanceDate, + required this.userInfoCredentialSubject, + }); + + factory SelfIssuedCredential.fromJson(Map json) => + _$SelfIssuedCredentialFromJson(json); + + Map toJson() => _$SelfIssuedCredentialToJson(this); +} diff --git a/lib/models/verifiable_credential/self_issued_credential.g.dart b/lib/models/verifiable_credential/self_issued_credential.g.dart new file mode 100644 index 00000000..8f371424 --- /dev/null +++ b/lib/models/verifiable_credential/self_issued_credential.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'self_issued_credential.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SelfIssuedCredential _$SelfIssuedCredentialFromJson( + Map json) => + SelfIssuedCredential( + id: json['id'] as String, + issuer: json['issuer'] as String, + issuanceDate: json['issuanceDate'] as String, + userInfoCredentialSubject: UserInfoCredentialSubject.fromJson( + json['userInfoCredentialSubject'] as Map), + ); + +Map _$SelfIssuedCredentialToJson( + SelfIssuedCredential instance) => + { + 'id': instance.id, + 'issuer': instance.issuer, + 'issuanceDate': instance.issuanceDate, + 'userInfoCredentialSubject': instance.userInfoCredentialSubject.toJson(), + }; diff --git a/lib/models/verifiable_credential/user_info_credential_subject.dart b/lib/models/verifiable_credential/user_info_credential_subject.dart new file mode 100644 index 00000000..93c16d9b --- /dev/null +++ b/lib/models/verifiable_credential/user_info_credential_subject.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:fusecash/models/verifiable_credential/credential_subject.dart'; + +part 'user_info_credential_subject.g.dart'; + +@JsonSerializable(explicitToJson: true) +class UserInfoCredentialSubject extends CredentialSubject { + final String name, phoneNumber; + + const UserInfoCredentialSubject({ + required String id, + required this.name, + required this.phoneNumber, + }) : super(id: id); + + factory UserInfoCredentialSubject.fromJson(Map json) => + _$UserInfoCredentialSubjectFromJson(json); + + @override + Map toJson() => _$UserInfoCredentialSubjectToJson(this); +} diff --git a/lib/models/verifiable_credential/user_info_credential_subject.g.dart b/lib/models/verifiable_credential/user_info_credential_subject.g.dart new file mode 100644 index 00000000..8992f779 --- /dev/null +++ b/lib/models/verifiable_credential/user_info_credential_subject.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_info_credential_subject.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserInfoCredentialSubject _$UserInfoCredentialSubjectFromJson( + Map json) => + UserInfoCredentialSubject( + id: json['id'] as String, + name: json['name'] as String, + phoneNumber: json['phoneNumber'] as String, + ); + +Map _$UserInfoCredentialSubjectToJson( + UserInfoCredentialSubject instance) => + { + 'id': instance.id, + 'name': instance.name, + 'phoneNumber': instance.phoneNumber, + }; From 78969c75c78d396f83bb3a95b933b3e0110dc10d Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Aug 2022 17:32:56 +0300 Subject: [PATCH 12/29] add uuid package --- .../self_issued_credential.dart | 24 -------- .../self_issued_credential.g.dart | 26 --------- .../verifiable_credential/user_info_vc.dart | 58 +++++++++++++++++++ .../verifiable_credential/user_info_vc.g.dart | 50 ++++++++++++++++ pubspec.lock | 2 +- pubspec.yaml | 1 + 6 files changed, 110 insertions(+), 51 deletions(-) delete mode 100644 lib/models/verifiable_credential/self_issued_credential.dart delete mode 100644 lib/models/verifiable_credential/self_issued_credential.g.dart create mode 100644 lib/models/verifiable_credential/user_info_vc.dart create mode 100644 lib/models/verifiable_credential/user_info_vc.g.dart diff --git a/lib/models/verifiable_credential/self_issued_credential.dart b/lib/models/verifiable_credential/self_issued_credential.dart deleted file mode 100644 index 7c4640ae..00000000 --- a/lib/models/verifiable_credential/self_issued_credential.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:fusecash/models/verifiable_credential/user_info_credential_subject.dart'; - -part 'self_issued_credential.g.dart'; - -@JsonSerializable(explicitToJson: true) -class SelfIssuedCredential { - final String id; - final String issuer; - final String issuanceDate; - final UserInfoCredentialSubject userInfoCredentialSubject; - - const SelfIssuedCredential({ - required this.id, - required this.issuer, - required this.issuanceDate, - required this.userInfoCredentialSubject, - }); - - factory SelfIssuedCredential.fromJson(Map json) => - _$SelfIssuedCredentialFromJson(json); - - Map toJson() => _$SelfIssuedCredentialToJson(this); -} diff --git a/lib/models/verifiable_credential/self_issued_credential.g.dart b/lib/models/verifiable_credential/self_issued_credential.g.dart deleted file mode 100644 index 8f371424..00000000 --- a/lib/models/verifiable_credential/self_issued_credential.g.dart +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'self_issued_credential.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SelfIssuedCredential _$SelfIssuedCredentialFromJson( - Map json) => - SelfIssuedCredential( - id: json['id'] as String, - issuer: json['issuer'] as String, - issuanceDate: json['issuanceDate'] as String, - userInfoCredentialSubject: UserInfoCredentialSubject.fromJson( - json['userInfoCredentialSubject'] as Map), - ); - -Map _$SelfIssuedCredentialToJson( - SelfIssuedCredential instance) => - { - 'id': instance.id, - 'issuer': instance.issuer, - 'issuanceDate': instance.issuanceDate, - 'userInfoCredentialSubject': instance.userInfoCredentialSubject.toJson(), - }; diff --git a/lib/models/verifiable_credential/user_info_vc.dart b/lib/models/verifiable_credential/user_info_vc.dart new file mode 100644 index 00000000..d8ea8d99 --- /dev/null +++ b/lib/models/verifiable_credential/user_info_vc.dart @@ -0,0 +1,58 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:fusecash/models/verifiable_credential/user_info_credential_subject.dart'; + +part 'self_issued_credential.g.dart'; + +@JsonSerializable() +class UserInfoVC { + @JsonKey(name: "@context", defaultValue: _context) + final List context; + + final String id; + + @JsonKey(defaultValue: _type) + final List type; + + final String issuer; + final String issuanceDate; + + @JsonKey(name: "credentialSubject") + final UserInfoCredentialSubject userInfoCredentialSubject; + + const UserInfoVC({ + this.context = _context, + required this.id, + this.type = _type, + required this.issuer, + required this.issuanceDate, + required this.userInfoCredentialSubject, + }); + + factory UserInfoVC.fromJson(Map json) => + _$SelfIssuedCredentialFromJson(json); + + Map toJson() => _$SelfIssuedCredentialToJson(this); + + static const _context = [ + "https://www.w3.org/2018/credentials/v1", + { + 'name': 'https://schema.org/name', + 'description': 'https://schema.org/description', + 'SelfIssued': { + '@context': { + '@protected': true, + '@version': 1.1, + 'id': '@id', + 'schema': 'https://schema.org/', + 'telephone': 'schema:telephone', + 'type': '@type' + }, + // TODO: Create a README file for Fuse + // TODO: and use it instead of the one below. + '@id': 'https://github.com/TalaoDAO/context/blob/main/README.md' + } + }, + ]; + + static const _type = ["VerifiableCredential", "SelfIssued"]; +} diff --git a/lib/models/verifiable_credential/user_info_vc.g.dart b/lib/models/verifiable_credential/user_info_vc.g.dart new file mode 100644 index 00000000..31562300 --- /dev/null +++ b/lib/models/verifiable_credential/user_info_vc.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'self_issued_credential.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SelfIssuedCredential _$SelfIssuedCredentialFromJson( + Map json) => + SelfIssuedCredential( + context: json['@context'] as List? ?? + [ + 'https://www.w3.org/2018/credentials/v1', + { + 'name': 'https://schema.org/name', + 'description': 'https://schema.org/description', + 'SelfIssued': { + '@context': { + '@protected': true, + '@version': 1.1, + 'id': '@id', + 'schema': 'https://schema.org/', + 'telephone': 'schema:telephone', + 'type': '@type' + }, + '@id': 'https://github.com/TalaoDAO/context/blob/main/README.md' + } + } + ], + id: json['id'] as String, + type: + (json['type'] as List?)?.map((e) => e as String).toList() ?? + ['VerifiableCredential', 'SelfIssued'], + issuer: json['issuer'] as String, + issuanceDate: json['issuanceDate'] as String, + userInfoCredentialSubject: UserInfoCredentialSubject.fromJson( + json['credentialSubject'] as Map), + ); + +Map _$SelfIssuedCredentialToJson( + SelfIssuedCredential instance) => + { + '@context': instance.context, + 'id': instance.id, + 'type': instance.type, + 'issuer': instance.issuer, + 'issuanceDate': instance.issuanceDate, + 'credentialSubject': instance.userInfoCredentialSubject.toJson(), + }; diff --git a/pubspec.lock b/pubspec.lock index b385c47e..ae9564e4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1961,7 +1961,7 @@ packages: source: hosted version: "3.0.1" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 82e8882c..cede024f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,6 +98,7 @@ dependencies: path: ../didkit/lib/flutter bip39: ^1.0.6 secp256k1: ^0.3.0 + uuid: ^3.0.6 dev_dependencies: flutter_native_splash: ^2.2.7 From 1da2ab1669220cbb83ff74a78a0f8a6bca17ca9e Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Aug 2022 17:35:28 +0300 Subject: [PATCH 13/29] provide "ES256K" for "alg" since did:key method is used for current DID features --- lib/utils/did/private_key_generation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/did/private_key_generation.dart b/lib/utils/did/private_key_generation.dart index 25769ef7..08a857be 100644 --- a/lib/utils/did/private_key_generation.dart +++ b/lib/utils/did/private_key_generation.dart @@ -60,7 +60,7 @@ class PrivateKeyGeneration { 'd': d, 'x': x, 'y': y, - 'alg': 'ES256K-R' // or 'alg': "ES256K" for did:key + 'alg': 'ES256K' // or 'alg': "ES256K" for did:key }; // key = { // "kty": "EC", From 5c5837a4a1bc59dace31ca5c495e2a142c108e38 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Aug 2022 17:40:22 +0300 Subject: [PATCH 14/29] add privateKeyForDID and userInfoVC fields, remove the "ignore" annotation from did so that the did field is taken into account in toJson and fromJson methods --- lib/models/user_state.dart | 4 +- lib/models/user_state.freezed.dart | 66 ++++++++++++++++++++++++------ lib/models/user_state.g.dart | 6 +++ 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/lib/models/user_state.dart b/lib/models/user_state.dart index f89da2ae..2e343cff 100644 --- a/lib/models/user_state.dart +++ b/lib/models/user_state.dart @@ -54,7 +54,9 @@ class UserState with _$UserState { @JsonKey(fromJson: localeFromJson, toJson: localeToJson) Locale? locale, @JsonKey(ignore: true) @Default([]) List contacts, @JsonKey(ignore: true) PhoneAuthCredential? credentials, - @JsonKey(ignore: true) String? did, + String? did, + String? privateKeyForDID, + String? userInfoVC, }) = _UserState; factory UserState.initial() => UserState( diff --git a/lib/models/user_state.freezed.dart b/lib/models/user_state.freezed.dart index 019f7c4f..6a3966ba 100644 --- a/lib/models/user_state.freezed.dart +++ b/lib/models/user_state.freezed.dart @@ -57,8 +57,9 @@ mixin _$UserState { List get contacts => throw _privateConstructorUsedError; @JsonKey(ignore: true) PhoneAuthCredential? get credentials => throw _privateConstructorUsedError; - @JsonKey(ignore: true) String? get did => throw _privateConstructorUsedError; + String? get privateKeyForDID => throw _privateConstructorUsedError; + String? get userInfoVC => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -103,7 +104,9 @@ abstract class $UserStateCopyWith<$Res> { @JsonKey(fromJson: localeFromJson, toJson: localeToJson) Locale? locale, @JsonKey(ignore: true) List contacts, @JsonKey(ignore: true) PhoneAuthCredential? credentials, - @JsonKey(ignore: true) String? did}); + String? did, + String? privateKeyForDID, + String? userInfoVC}); $WalletModulesCopyWith<$Res>? get walletModules; } @@ -151,6 +154,8 @@ class _$UserStateCopyWithImpl<$Res> implements $UserStateCopyWith<$Res> { Object? contacts = freezed, Object? credentials = freezed, Object? did = freezed, + Object? privateKeyForDID = freezed, + Object? userInfoVC = freezed, }) { return _then(_value.copyWith( wcURI: wcURI == freezed @@ -285,6 +290,14 @@ class _$UserStateCopyWithImpl<$Res> implements $UserStateCopyWith<$Res> { ? _value.did : did // ignore: cast_nullable_to_non_nullable as String?, + privateKeyForDID: privateKeyForDID == freezed + ? _value.privateKeyForDID + : privateKeyForDID // ignore: cast_nullable_to_non_nullable + as String?, + userInfoVC: userInfoVC == freezed + ? _value.userInfoVC + : userInfoVC // ignore: cast_nullable_to_non_nullable + as String?, )); } @@ -339,7 +352,9 @@ abstract class _$$_UserStateCopyWith<$Res> implements $UserStateCopyWith<$Res> { @JsonKey(fromJson: localeFromJson, toJson: localeToJson) Locale? locale, @JsonKey(ignore: true) List contacts, @JsonKey(ignore: true) PhoneAuthCredential? credentials, - @JsonKey(ignore: true) String? did}); + String? did, + String? privateKeyForDID, + String? userInfoVC}); @override $WalletModulesCopyWith<$Res>? get walletModules; @@ -390,6 +405,8 @@ class __$$_UserStateCopyWithImpl<$Res> extends _$UserStateCopyWithImpl<$Res> Object? contacts = freezed, Object? credentials = freezed, Object? did = freezed, + Object? privateKeyForDID = freezed, + Object? userInfoVC = freezed, }) { return _then(_$_UserState( wcURI: wcURI == freezed @@ -524,6 +541,14 @@ class __$$_UserStateCopyWithImpl<$Res> extends _$UserStateCopyWithImpl<$Res> ? _value.did : did // ignore: cast_nullable_to_non_nullable as String?, + privateKeyForDID: privateKeyForDID == freezed + ? _value.privateKeyForDID + : privateKeyForDID // ignore: cast_nullable_to_non_nullable + as String?, + userInfoVC: userInfoVC == freezed + ? _value.userInfoVC + : userInfoVC // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -564,7 +589,9 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { @JsonKey(fromJson: localeFromJson, toJson: localeToJson) this.locale, @JsonKey(ignore: true) this.contacts = const [], @JsonKey(ignore: true) this.credentials, - @JsonKey(ignore: true) this.did}) + this.did, + this.privateKeyForDID, + this.userInfoVC}) : super._(); factory _$_UserState.fromJson(Map json) => @@ -662,12 +689,15 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { @JsonKey(ignore: true) final PhoneAuthCredential? credentials; @override - @JsonKey(ignore: true) final String? did; + @override + final String? privateKeyForDID; + @override + final String? userInfoVC; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'UserState(wcURI: $wcURI, contractVersion: $contractVersion, walletModules: $walletModules, installedAt: $installedAt, isContactsSynced: $isContactsSynced, isLoggedOut: $isLoggedOut, backup: $backup, scrollToTop: $scrollToTop, walletAddress: $walletAddress, networks: $networks, mnemonic: $mnemonic, privateKey: $privateKey, pincode: $pincode, accountAddress: $accountAddress, countryCode: $countryCode, phoneNumber: $phoneNumber, warnSendDialogShowed: $warnSendDialogShowed, isoCode: $isoCode, jwtToken: $jwtToken, displayName: $displayName, avatarUrl: $avatarUrl, email: $email, verificationId: $verificationId, identifier: $identifier, syncedContacts: $syncedContacts, reverseContacts: $reverseContacts, currency: $currency, hasUpgrade: $hasUpgrade, authType: $authType, locale: $locale, contacts: $contacts, credentials: $credentials, did: $did)'; + return 'UserState(wcURI: $wcURI, contractVersion: $contractVersion, walletModules: $walletModules, installedAt: $installedAt, isContactsSynced: $isContactsSynced, isLoggedOut: $isLoggedOut, backup: $backup, scrollToTop: $scrollToTop, walletAddress: $walletAddress, networks: $networks, mnemonic: $mnemonic, privateKey: $privateKey, pincode: $pincode, accountAddress: $accountAddress, countryCode: $countryCode, phoneNumber: $phoneNumber, warnSendDialogShowed: $warnSendDialogShowed, isoCode: $isoCode, jwtToken: $jwtToken, displayName: $displayName, avatarUrl: $avatarUrl, email: $email, verificationId: $verificationId, identifier: $identifier, syncedContacts: $syncedContacts, reverseContacts: $reverseContacts, currency: $currency, hasUpgrade: $hasUpgrade, authType: $authType, locale: $locale, contacts: $contacts, credentials: $credentials, did: $did, privateKeyForDID: $privateKeyForDID, userInfoVC: $userInfoVC)'; } @override @@ -707,7 +737,9 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { ..add(DiagnosticsProperty('locale', locale)) ..add(DiagnosticsProperty('contacts', contacts)) ..add(DiagnosticsProperty('credentials', credentials)) - ..add(DiagnosticsProperty('did', did)); + ..add(DiagnosticsProperty('did', did)) + ..add(DiagnosticsProperty('privateKeyForDID', privateKeyForDID)) + ..add(DiagnosticsProperty('userInfoVC', userInfoVC)); } @override @@ -766,7 +798,11 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { const DeepCollectionEquality().equals(other.contacts, contacts) && const DeepCollectionEquality() .equals(other.credentials, credentials) && - const DeepCollectionEquality().equals(other.did, did)); + const DeepCollectionEquality().equals(other.did, did) && + const DeepCollectionEquality() + .equals(other.privateKeyForDID, privateKeyForDID) && + const DeepCollectionEquality() + .equals(other.userInfoVC, userInfoVC)); } @JsonKey(ignore: true) @@ -805,7 +841,9 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { const DeepCollectionEquality().hash(locale), const DeepCollectionEquality().hash(contacts), const DeepCollectionEquality().hash(credentials), - const DeepCollectionEquality().hash(did) + const DeepCollectionEquality().hash(did), + const DeepCollectionEquality().hash(privateKeyForDID), + const DeepCollectionEquality().hash(userInfoVC) ]); @JsonKey(ignore: true) @@ -860,8 +898,9 @@ abstract class _UserState extends UserState { final List contacts, @JsonKey(ignore: true) final PhoneAuthCredential? credentials, - @JsonKey(ignore: true) - final String? did}) = _$_UserState; + final String? did, + final String? privateKeyForDID, + final String? userInfoVC}) = _$_UserState; _UserState._() : super._(); factory _UserState.fromJson(Map json) = @@ -937,9 +976,12 @@ abstract class _UserState extends UserState { @JsonKey(ignore: true) PhoneAuthCredential? get credentials; @override - @JsonKey(ignore: true) String? get did; @override + String? get privateKeyForDID; + @override + String? get userInfoVC; + @override @JsonKey(ignore: true) _$$_UserStateCopyWith<_$_UserState> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/models/user_state.g.dart b/lib/models/user_state.g.dart index 581031b9..be794ef3 100644 --- a/lib/models/user_state.g.dart +++ b/lib/models/user_state.g.dart @@ -53,6 +53,9 @@ _$_UserState _$$_UserStateFromJson(Map json) => _$_UserState( authType: $enumDecodeNullable(_$BiometricAuthEnumMap, json['authType']) ?? BiometricAuth.none, locale: localeFromJson(json['locale'] as Map), + did: json['did'] as String?, + privateKeyForDID: json['privateKeyForDID'] as String?, + userInfoVC: json['userInfoVC'] as String?, ); Map _$$_UserStateToJson(_$_UserState instance) => @@ -85,6 +88,9 @@ Map _$$_UserStateToJson(_$_UserState instance) => 'currency': instance.currency, 'authType': _$BiometricAuthEnumMap[instance.authType]!, 'locale': localeToJson(instance.locale), + 'did': instance.did, + 'privateKeyForDID': instance.privateKeyForDID, + 'userInfoVC': instance.userInfoVC, }; const _$BiometricAuthEnumMap = { From b38cecfe57ababd70f1b91ef85c040fb0f9f9b0a Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Aug 2022 17:41:49 +0300 Subject: [PATCH 15/29] add test that checks if the DIDKit is available in tests by getting the DIDKit version --- test/did_test/get_didkit_version_test.dart | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/did_test/get_didkit_version_test.dart diff --git a/test/did_test/get_didkit_version_test.dart b/test/did_test/get_didkit_version_test.dart new file mode 100644 index 00000000..ac178071 --- /dev/null +++ b/test/did_test/get_didkit_version_test.dart @@ -0,0 +1,9 @@ +import 'package:didkit/didkit.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test("Should get DIDKit version", () { + final didKitVersion = DIDKit.getVersion(); + expect(didKitVersion, isA()); + }); +} \ No newline at end of file From 7956b640212d58e3f95a2fb4e74b4db406986bbe Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Aug 2022 17:42:45 +0300 Subject: [PATCH 16/29] add model classes required to issue and verify a VC --- .../credential_subject.dart | 3 ++- .../credential_subject.g.dart | 2 ++ .../issue_credential_options.dart | 16 ++++++++++++++ .../issue_credential_options.g.dart | 21 +++++++++++++++++++ .../user_info_credential_subject.dart | 10 ++++++--- .../user_info_credential_subject.g.dart | 6 ++++-- .../verifiable_credential/user_info_vc.dart | 6 +++--- .../verifiable_credential/user_info_vc.g.dart | 9 +++----- .../verify_credential_options.dart | 12 +++++++++++ .../verify_credential_options.g.dart | 19 +++++++++++++++++ 10 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 lib/models/verifiable_credential/issue_credential_options.dart create mode 100644 lib/models/verifiable_credential/issue_credential_options.g.dart create mode 100644 lib/models/verifiable_credential/verify_credential_options.dart create mode 100644 lib/models/verifiable_credential/verify_credential_options.g.dart diff --git a/lib/models/verifiable_credential/credential_subject.dart b/lib/models/verifiable_credential/credential_subject.dart index f0b8aaf3..1eb881a7 100644 --- a/lib/models/verifiable_credential/credential_subject.dart +++ b/lib/models/verifiable_credential/credential_subject.dart @@ -5,8 +5,9 @@ part 'credential_subject.g.dart'; @JsonSerializable() class CredentialSubject { final String id; + final String type; - const CredentialSubject({required this.id}); + const CredentialSubject({required this.id, required this.type}); factory CredentialSubject.fromJson(Map json) => _$CredentialSubjectFromJson(json); diff --git a/lib/models/verifiable_credential/credential_subject.g.dart b/lib/models/verifiable_credential/credential_subject.g.dart index 0e236a9a..a3213bbe 100644 --- a/lib/models/verifiable_credential/credential_subject.g.dart +++ b/lib/models/verifiable_credential/credential_subject.g.dart @@ -9,9 +9,11 @@ part of 'credential_subject.dart'; CredentialSubject _$CredentialSubjectFromJson(Map json) => CredentialSubject( id: json['id'] as String, + type: json['type'] as String, ); Map _$CredentialSubjectToJson(CredentialSubject instance) => { 'id': instance.id, + 'type': instance.type, }; diff --git a/lib/models/verifiable_credential/issue_credential_options.dart b/lib/models/verifiable_credential/issue_credential_options.dart new file mode 100644 index 00000000..efa870b0 --- /dev/null +++ b/lib/models/verifiable_credential/issue_credential_options.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'issue_credential_options.g.dart'; + +@JsonSerializable() +class IssueCredentialOptions { + final String proofPurpose; + final String verificationMethod; + + const IssueCredentialOptions({ + required this.proofPurpose, + required this.verificationMethod, + }); + + Map toJson() => _$IssueCredentialOptionsToJson(this); +} diff --git a/lib/models/verifiable_credential/issue_credential_options.g.dart b/lib/models/verifiable_credential/issue_credential_options.g.dart new file mode 100644 index 00000000..a78cc507 --- /dev/null +++ b/lib/models/verifiable_credential/issue_credential_options.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'issue_credential_options.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +IssueCredentialOptions _$IssueCredentialOptionsFromJson( + Map json) => + IssueCredentialOptions( + proofPurpose: json['proofPurpose'] as String, + verificationMethod: json['verificationMethod'] as String, + ); + +Map _$IssueCredentialOptionsToJson( + IssueCredentialOptions instance) => + { + 'proofPurpose': instance.proofPurpose, + 'verificationMethod': instance.verificationMethod, + }; diff --git a/lib/models/verifiable_credential/user_info_credential_subject.dart b/lib/models/verifiable_credential/user_info_credential_subject.dart index 93c16d9b..f7580c24 100644 --- a/lib/models/verifiable_credential/user_info_credential_subject.dart +++ b/lib/models/verifiable_credential/user_info_credential_subject.dart @@ -3,15 +3,19 @@ import 'package:fusecash/models/verifiable_credential/credential_subject.dart'; part 'user_info_credential_subject.g.dart'; -@JsonSerializable(explicitToJson: true) +@JsonSerializable() class UserInfoCredentialSubject extends CredentialSubject { - final String name, phoneNumber; + final String name; + + @JsonKey(name: "telephone") + final String phoneNumber; const UserInfoCredentialSubject({ required String id, + String type = "SelfIssued", required this.name, required this.phoneNumber, - }) : super(id: id); + }) : super(id: id, type: type); factory UserInfoCredentialSubject.fromJson(Map json) => _$UserInfoCredentialSubjectFromJson(json); diff --git a/lib/models/verifiable_credential/user_info_credential_subject.g.dart b/lib/models/verifiable_credential/user_info_credential_subject.g.dart index 8992f779..8739682d 100644 --- a/lib/models/verifiable_credential/user_info_credential_subject.g.dart +++ b/lib/models/verifiable_credential/user_info_credential_subject.g.dart @@ -10,14 +10,16 @@ UserInfoCredentialSubject _$UserInfoCredentialSubjectFromJson( Map json) => UserInfoCredentialSubject( id: json['id'] as String, + type: json['type'] as String? ?? "SelfIssued", name: json['name'] as String, - phoneNumber: json['phoneNumber'] as String, + phoneNumber: json['telephone'] as String, ); Map _$UserInfoCredentialSubjectToJson( UserInfoCredentialSubject instance) => { 'id': instance.id, + 'type': instance.type, 'name': instance.name, - 'phoneNumber': instance.phoneNumber, + 'telephone': instance.phoneNumber, }; diff --git a/lib/models/verifiable_credential/user_info_vc.dart b/lib/models/verifiable_credential/user_info_vc.dart index d8ea8d99..8e4c7e8f 100644 --- a/lib/models/verifiable_credential/user_info_vc.dart +++ b/lib/models/verifiable_credential/user_info_vc.dart @@ -1,7 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:fusecash/models/verifiable_credential/user_info_credential_subject.dart'; -part 'self_issued_credential.g.dart'; +part 'user_info_vc.g.dart'; @JsonSerializable() class UserInfoVC { @@ -29,9 +29,9 @@ class UserInfoVC { }); factory UserInfoVC.fromJson(Map json) => - _$SelfIssuedCredentialFromJson(json); + _$UserInfoVCFromJson(json); - Map toJson() => _$SelfIssuedCredentialToJson(this); + Map toJson() => _$UserInfoVCToJson(this); static const _context = [ "https://www.w3.org/2018/credentials/v1", diff --git a/lib/models/verifiable_credential/user_info_vc.g.dart b/lib/models/verifiable_credential/user_info_vc.g.dart index 31562300..1b6f95dc 100644 --- a/lib/models/verifiable_credential/user_info_vc.g.dart +++ b/lib/models/verifiable_credential/user_info_vc.g.dart @@ -1,14 +1,12 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'self_issued_credential.dart'; +part of 'user_info_vc.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -SelfIssuedCredential _$SelfIssuedCredentialFromJson( - Map json) => - SelfIssuedCredential( +UserInfoVC _$UserInfoVCFromJson(Map json) => UserInfoVC( context: json['@context'] as List? ?? [ 'https://www.w3.org/2018/credentials/v1', @@ -38,8 +36,7 @@ SelfIssuedCredential _$SelfIssuedCredentialFromJson( json['credentialSubject'] as Map), ); -Map _$SelfIssuedCredentialToJson( - SelfIssuedCredential instance) => +Map _$UserInfoVCToJson(UserInfoVC instance) => { '@context': instance.context, 'id': instance.id, diff --git a/lib/models/verifiable_credential/verify_credential_options.dart b/lib/models/verifiable_credential/verify_credential_options.dart new file mode 100644 index 00000000..6915b0ae --- /dev/null +++ b/lib/models/verifiable_credential/verify_credential_options.dart @@ -0,0 +1,12 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'verify_credential_options.g.dart'; + +@JsonSerializable() +class VerifyCredentialOptions { + final String proofPurpose; + + const VerifyCredentialOptions({required this.proofPurpose}); + + Map toJson() => _$VerifyCredentialOptionsToJson(this); +} diff --git a/lib/models/verifiable_credential/verify_credential_options.g.dart b/lib/models/verifiable_credential/verify_credential_options.g.dart new file mode 100644 index 00000000..96bb70cb --- /dev/null +++ b/lib/models/verifiable_credential/verify_credential_options.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'verify_credential_options.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +VerifyCredentialOptions _$VerifyCredentialOptionsFromJson( + Map json) => + VerifyCredentialOptions( + proofPurpose: json['proofPurpose'] as String, + ); + +Map _$VerifyCredentialOptionsToJson( + VerifyCredentialOptions instance) => + { + 'proofPurpose': instance.proofPurpose, + }; From 84a04000592ad88231ae8b510475d71f67837945 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Aug 2022 17:43:07 +0300 Subject: [PATCH 17/29] issue and verify a VC for the user --- lib/features/screens/splash_screen.dart | 14 +++- lib/redux/actions/user_actions.dart | 83 ++++++++++++++++++++-- lib/redux/reducers/user_reducer.dart | 17 ++++- lib/utils/did/did_service.dart | 94 +++++++++++++++++++++---- 4 files changed, 186 insertions(+), 22 deletions(-) diff --git a/lib/features/screens/splash_screen.dart b/lib/features/screens/splash_screen.dart index 85b16b0c..eab673fa 100644 --- a/lib/features/screens/splash_screen.dart +++ b/lib/features/screens/splash_screen.dart @@ -4,6 +4,7 @@ import 'package:another_flushbar/flushbar.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter_gen/gen_l10n/I10n.dart'; import 'package:flutter_redux/flutter_redux.dart'; +import 'package:fusecash/utils/did/did_service.dart'; import 'package:redux/redux.dart'; import 'package:fusecash/common/router/routes.dart'; @@ -38,9 +39,18 @@ class _SplashPageState extends State { final mnemonic = userState.mnemonic; final did = userState.did; - final didDoesNotExist = did == null || did.isEmpty; + final didExists = did != null && did.isNotEmpty; - if (mnemonic.isNotEmpty && didDoesNotExist) { + if (didExists && userState.userInfoVC == null) { + final privateKeyForDID = userState.privateKeyForDID; + + // If did exists, the private must exist too. + store.dispatch( + issueUserInfoVCCall(privateKeyForDID: privateKeyForDID!), + ); + } + + if (!didExists && mnemonic.isNotEmpty) { final mnemonic = userState.mnemonic.join(" "); store.dispatch( generateDIDCall(mnemonic: mnemonic), diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index 55c0d3f8..501b03ee 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'dart:ui'; import 'package:didkit/didkit.dart'; import 'package:flutter/foundation.dart'; @@ -15,6 +16,7 @@ import 'package:firebase_auth_platform_interface/firebase_auth_platform_interfac import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_udid/flutter_udid.dart'; import 'package:fusecash/utils/did/did_service.dart'; +import 'package:fusecash/utils/did/private_key_generation.dart'; import 'package:image_picker/image_picker.dart'; import 'package:phone_number/phone_number.dart'; import 'package:redux/redux.dart'; @@ -33,7 +35,6 @@ import 'package:fusecash/utils/contacts.dart'; import 'package:fusecash/utils/crashlytics.dart'; import 'package:fusecash/utils/log/log.dart'; import 'package:fusecash/utils/phone.dart'; -import 'package:bip32/bip32.dart' as bip39; class SetWalletConnectURI { final String wcURI; @@ -88,19 +89,31 @@ class CreateLocalAccountSuccess { final String privateKey; final String accountAddress; final String did; + final String privateKeyForDID; CreateLocalAccountSuccess( this.mnemonic, this.privateKey, this.accountAddress, this.did, + this.privateKeyForDID, ); } class GenerateDIDSuccess { final String did; - const GenerateDIDSuccess(this.did); + /// The private key used for DID features. + final String privateKeyForDID; + + const GenerateDIDSuccess(this.did, this.privateKeyForDID); +} + +class IssueUserInfoVCSuccess { + /// The JSON representation of the issued [UserInfoVC]. + final String userInfoVC; + + const IssueUserInfoVCSuccess({required this.userInfoVC}); } class ReLogin { @@ -326,7 +339,13 @@ ThunkAction restoreWalletCall( log.info('accountAddress: ${accountAddress.toString()}'); final mnemonicAsString = mnemonic.join(' '); - final did = await DIDService.generateDID(mnemonicAsString); + + final privateKeyGeneration = PrivateKeyGeneration(); + final privateKeyToForDID = + await privateKeyGeneration.generatePrivateKey(mnemonicAsString); + + final didService = DIDService(privateKey: privateKeyToForDID); + final did = await didService.generateDID(); debugPrint("Restored did: $did"); @@ -336,6 +355,7 @@ ThunkAction restoreWalletCall( privateKey, accountAddress.toString(), did, + privateKeyToForDID, ), ); successCallback(); @@ -382,7 +402,12 @@ ThunkAction createLocalAccountCall( log.info('privateKey: $privateKey'); log.info('accountAddress: ${accountAddress.toString()}'); - final did = await DIDService.generateDID(mnemonic); + final privateKeyGeneration = PrivateKeyGeneration(); + final privateKeyToForDID = + await privateKeyGeneration.generatePrivateKey(mnemonic); + + final didService = DIDService(privateKey: privateKeyToForDID); + final did = await didService.generateDID(); store.dispatch( CreateLocalAccountSuccess( @@ -390,6 +415,7 @@ ThunkAction createLocalAccountCall( privateKey, accountAddress.toString(), did, + privateKeyToForDID, ), ); Analytics.track( @@ -608,8 +634,19 @@ ThunkAction generateDIDCall({required String mnemonic}) { assert(mnemonic.isNotEmpty, "Mnemonic must not be empty"); return (Store store) async { try { - final did = await DIDService.generateDID(mnemonic); - final generateDIDSuccess = GenerateDIDSuccess(did); + final privateKeyGeneration = PrivateKeyGeneration(); + final privateKeyToGenerateDID = + await privateKeyGeneration.generatePrivateKey(mnemonic); + + final didService = DIDService(privateKey: privateKeyToGenerateDID); + final did = didService.generateDID(); + + store.dispatch( + issueUserInfoVCCall(privateKeyForDID: privateKeyToGenerateDID), + ); + + final generateDIDSuccess = + GenerateDIDSuccess(did, privateKeyToGenerateDID); store.dispatch(generateDIDSuccess); } catch (exception, stackTrace) { const errorMessage = "An error occurred while generating DID."; @@ -627,6 +664,40 @@ ThunkAction generateDIDCall({required String mnemonic}) { }; } +ThunkAction issueUserInfoVCCall({required String privateKeyForDID}) { + return (Store store) async { + final userState = store.state.userState; + final didService = DIDService(privateKey: privateKeyForDID); + + final userInfoVC = didService.issueUserInfoVC( + did: userState.did, + name: userState.displayName, + phoneNumber: userState.phoneNumber, + ); + + final verificationResultInJson = didService.verifyVC(userInfoVC); + final verificationResult = jsonDecode(verificationResultInJson); + + debugPrint("Verification result: $verificationResultInJson"); + + final warnings = verificationResult["warnings"] as List; + final errors = verificationResult["errors"] as List; + + if (warnings.isNotEmpty) { + log.warn("Warnings produced while verifying the VC: $warnings"); + } + + if (errors.isNotEmpty) { + log.error("Failed to verify VC. $errors"); + return; + } + + final issueUserInfoVCSuccess = + IssueUserInfoVCSuccess(userInfoVC: userInfoVC); + store.dispatch(issueUserInfoVCSuccess); + }; +} + ThunkAction web3Init({ WalletModules? modules, }) { diff --git a/lib/redux/reducers/user_reducer.dart b/lib/redux/reducers/user_reducer.dart index ad23e308..1dfcc803 100644 --- a/lib/redux/reducers/user_reducer.dart +++ b/lib/redux/reducers/user_reducer.dart @@ -1,6 +1,8 @@ +import 'package:flutter/foundation.dart'; import 'package:fusecash/models/user_state.dart'; import 'package:fusecash/redux/actions/cash_wallet_actions.dart'; import 'package:fusecash/redux/actions/user_actions.dart'; +import 'package:fusecash/utils/did/did_service.dart'; import 'package:redux/redux.dart'; final userReducers = combineReducers([ @@ -12,6 +14,7 @@ final userReducers = combineReducers([ TypedReducer(_toggleUpgrade), TypedReducer(_createNewWalletSuccess), TypedReducer(_generateDIDSuccess), + TypedReducer(_issueUserInfoVCSuccess), TypedReducer(_loginSuccess), TypedReducer(_loginVerifySuccess), TypedReducer(_logoutSuccess), @@ -78,8 +81,17 @@ UserState _getWalletDataSuccess(UserState state, GetWalletDataSuccess action) { } UserState _generateDIDSuccess(UserState state, GenerateDIDSuccess action) { - // TODO: Issue a VC for the user. - return state.copyWith(did: action.did); + return state.copyWith( + did: action.did, + privateKeyForDID: action.privateKeyForDID, + ); +} + +UserState _issueUserInfoVCSuccess( + UserState state, + IssueUserInfoVCSuccess action, +) { + return state.copyWith(userInfoVC: action.userInfoVC); } UserState _backupSuccess(UserState state, BackupSuccess action) { @@ -100,6 +112,7 @@ UserState _createNewWalletSuccess( privateKey: action.privateKey, accountAddress: action.accountAddress, did: action.did, + privateKeyForDID: action.privateKeyForDID, ); } diff --git a/lib/utils/did/did_service.dart b/lib/utils/did/did_service.dart index 6669bf13..b145c5ca 100644 --- a/lib/utils/did/did_service.dart +++ b/lib/utils/did/did_service.dart @@ -1,19 +1,89 @@ +import 'dart:convert'; + import 'package:didkit/didkit.dart'; -import 'package:flutter/material.dart'; import 'package:fusecash/constants/strings.dart'; -import 'package:fusecash/utils/did/private_key_generation.dart'; +import 'package:fusecash/models/verifiable_credential/issue_credential_options.dart'; +import 'package:fusecash/models/verifiable_credential/user_info_vc.dart'; +import 'package:fusecash/models/verifiable_credential/user_info_credential_subject.dart'; +import 'package:fusecash/models/verifiable_credential/verify_credential_options.dart'; +import 'package:intl/intl.dart'; +import 'package:uuid/uuid.dart'; class DIDService { - static Future generateDID(String mnemonic) async { - debugPrint("Generating DID from mnemonic: $mnemonic"); - final privateKeyGeneration = PrivateKeyGeneration(); - final privateKeyToGenerateDID = - await privateKeyGeneration.generatePrivateKey(mnemonic); - final did = DIDKit.keyToDID( - Strings.defaultDIDMethod, - privateKeyToGenerateDID, + final String privateKey; + + const DIDService({required this.privateKey}); + + String generateDID() { + return DIDKit.keyToDID(Strings.defaultDIDMethod, privateKey); + } + + String issueUserInfoVC({ + required String did, + required String name, + required String phoneNumber, + }) { + final userInfoVCModel = _createUserInfoVCModel(did, name, phoneNumber); + final userInfoVCAsJson = jsonEncode(userInfoVCModel); + + final options = _createIssueCredentialOptions(); + final optionsAsJson = jsonEncode(options); + + return DIDKit.issueCredential(userInfoVCAsJson, optionsAsJson, privateKey); + } + + String verifyVC(String credential) { + final optionsAsJson = _createOptionsAsJson(); + return DIDKit.verifyCredential(credential, optionsAsJson); + } + + String _createOptionsAsJson() { + const options = VerifyCredentialOptions(proofPurpose: "assertionMethod"); + return jsonEncode(options); + } + + IssueCredentialOptions _createIssueCredentialOptions() { + final verificationMethod = _getVerificationMethod(); + return IssueCredentialOptions( + proofPurpose: "assertionMethod", + verificationMethod: verificationMethod, ); - debugPrint("did: $did"); - return did; + } + + String _getVerificationMethod() { + return DIDKit.keyToVerificationMethod(Strings.defaultDIDMethod, privateKey); + } + + UserInfoVC _createUserInfoVCModel( + String did, + String name, + String phoneNumber, + ) { + final id = _createCredentialID(); + final issuanceDate = _createIssuanceDate(); + + final userInfoCredentialSubject = UserInfoCredentialSubject( + id: did, + name: name, + phoneNumber: phoneNumber, + ); + + return UserInfoVC( + id: id, + issuer: did, + issuanceDate: issuanceDate, + userInfoCredentialSubject: userInfoCredentialSubject, + ); + } + + String _createCredentialID() { + final uuid = const Uuid().v4(); + return "urn:uuid:$uuid"; + } + + String _createIssuanceDate() { + final dateFormatter = DateFormat("yyyy-MM-ddTHH:mm:ss"); + final now = DateTime.now(); + return "${dateFormatter.format(now)}Z"; } } From bbdb3d4a7ad8e72a8f8efd507c68f7e2e8efb6fc Mon Sep 17 00:00:00 2001 From: = Date: Fri, 26 Aug 2022 18:49:30 +0300 Subject: [PATCH 18/29] change the value of "alg" of the expected private key to "ES256K" --- test/did_test/private_key_generation_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/did_test/private_key_generation_test.dart b/test/did_test/private_key_generation_test.dart index b9274771..5a34778d 100644 --- a/test/did_test/private_key_generation_test.dart +++ b/test/did_test/private_key_generation_test.dart @@ -11,7 +11,7 @@ void main() { final generatedPrivateKey = await privateKeyGeneration.generatePrivateKey(mnemonic); const privateKey = - '{"kty":"EC","crv":"secp256k1","d":"wTo43nJjOmx9aSeyZc3wAA7y4EHHEzvKcXB2A6t15iA","x":"1lUjK7OPOVgQjCKRAg_rvJTil0xHldjStkkH5OJp13k","y":"OQJFxHdalE1Zr2i02gZBopKUvNeO2LDjitkBzE4U2QI","alg":"ES256K-R"}'; + '{"kty":"EC","crv":"secp256k1","d":"wTo43nJjOmx9aSeyZc3wAA7y4EHHEzvKcXB2A6t15iA","x":"1lUjK7OPOVgQjCKRAg_rvJTil0xHldjStkkH5OJp13k","y":"OQJFxHdalE1Zr2i02gZBopKUvNeO2LDjitkBzE4U2QI","alg":"ES256K"}'; expect(generatedPrivateKey, equals(privateKey)); }, ); From d2d2a64d05b187eec5de62d16bb1237e5704065b Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 11:46:29 +0300 Subject: [PATCH 19/29] in issueUserInfoVCCall action function, accept did as argument instead getting it from the UserState instance to prevent bugs related to asynchronousity --- lib/features/screens/splash_screen.dart | 5 ++++- lib/redux/actions/user_actions.dart | 12 +++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/features/screens/splash_screen.dart b/lib/features/screens/splash_screen.dart index eab673fa..678e184d 100644 --- a/lib/features/screens/splash_screen.dart +++ b/lib/features/screens/splash_screen.dart @@ -46,7 +46,10 @@ class _SplashPageState extends State { // If did exists, the private must exist too. store.dispatch( - issueUserInfoVCCall(privateKeyForDID: privateKeyForDID!), + issueUserInfoVCCall( + did: did, + privateKeyForDID: privateKeyForDID!, + ), ); } diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index 501b03ee..ed1b184d 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -642,7 +642,10 @@ ThunkAction generateDIDCall({required String mnemonic}) { final did = didService.generateDID(); store.dispatch( - issueUserInfoVCCall(privateKeyForDID: privateKeyToGenerateDID), + issueUserInfoVCCall( + did: did, + privateKeyForDID: privateKeyToGenerateDID, + ), ); final generateDIDSuccess = @@ -664,13 +667,16 @@ ThunkAction generateDIDCall({required String mnemonic}) { }; } -ThunkAction issueUserInfoVCCall({required String privateKeyForDID}) { +ThunkAction issueUserInfoVCCall({ + required String did, + required String privateKeyForDID, +}) { return (Store store) async { final userState = store.state.userState; final didService = DIDService(privateKey: privateKeyForDID); final userInfoVC = didService.issueUserInfoVC( - did: userState.did, + did: did, name: userState.displayName, phoneNumber: userState.phoneNumber, ); From cb6acad18bce5feecd4da9be571bcbdf463a49e7 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 12:03:15 +0300 Subject: [PATCH 20/29] remove unused import --- lib/features/screens/splash_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/features/screens/splash_screen.dart b/lib/features/screens/splash_screen.dart index 678e184d..d681a0e4 100644 --- a/lib/features/screens/splash_screen.dart +++ b/lib/features/screens/splash_screen.dart @@ -4,7 +4,6 @@ import 'package:another_flushbar/flushbar.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter_gen/gen_l10n/I10n.dart'; import 'package:flutter_redux/flutter_redux.dart'; -import 'package:fusecash/utils/did/did_service.dart'; import 'package:redux/redux.dart'; import 'package:fusecash/common/router/routes.dart'; From 42710f5b8226c025e866f03bb2e459d53e18c249 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 12:04:42 +0300 Subject: [PATCH 21/29] dispatch a generateDIDCall in createLocalAccountCall and restoreWalletCall --- lib/redux/actions/user_actions.dart | 38 ++++++++-------------------- lib/redux/reducers/user_reducer.dart | 4 --- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index ed1b184d..ef7b2433 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -1,10 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; -import 'dart:ui'; -import 'package:didkit/didkit.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -88,15 +85,11 @@ class CreateLocalAccountSuccess { final List mnemonic; final String privateKey; final String accountAddress; - final String did; - final String privateKeyForDID; CreateLocalAccountSuccess( this.mnemonic, this.privateKey, this.accountAddress, - this.did, - this.privateKeyForDID, ); } @@ -329,33 +322,27 @@ ThunkAction restoreWalletCall( log.info('restore wallet'); log.info('mnemonic: $mnemonic'); log.info('compute pk'); + + final mnemonicAsString = mnemonic.join(' '); + String privateKey = await compute( Web3.privateKeyFromMnemonic, - mnemonic.join(' '), + mnemonicAsString, ); Credentials credentials = EthPrivateKey.fromHex(privateKey); EthereumAddress accountAddress = await credentials.extractAddress(); log.info('privateKey: $privateKey'); log.info('accountAddress: ${accountAddress.toString()}'); - final mnemonicAsString = mnemonic.join(' '); - - final privateKeyGeneration = PrivateKeyGeneration(); - final privateKeyToForDID = - await privateKeyGeneration.generatePrivateKey(mnemonicAsString); - - final didService = DIDService(privateKey: privateKeyToForDID); - final did = await didService.generateDID(); - - debugPrint("Restored did: $did"); + store.dispatch( + generateDIDCall(mnemonic: mnemonicAsString), + ); store.dispatch( CreateLocalAccountSuccess( mnemonic, privateKey, accountAddress.toString(), - did, - privateKeyToForDID, ), ); successCallback(); @@ -402,20 +389,15 @@ ThunkAction createLocalAccountCall( log.info('privateKey: $privateKey'); log.info('accountAddress: ${accountAddress.toString()}'); - final privateKeyGeneration = PrivateKeyGeneration(); - final privateKeyToForDID = - await privateKeyGeneration.generatePrivateKey(mnemonic); - - final didService = DIDService(privateKey: privateKeyToForDID); - final did = await didService.generateDID(); + store.dispatch( + generateDIDCall(mnemonic: mnemonic), + ); store.dispatch( CreateLocalAccountSuccess( mnemonic.split(' '), privateKey, accountAddress.toString(), - did, - privateKeyToForDID, ), ); Analytics.track( diff --git a/lib/redux/reducers/user_reducer.dart b/lib/redux/reducers/user_reducer.dart index 1dfcc803..d42c61bf 100644 --- a/lib/redux/reducers/user_reducer.dart +++ b/lib/redux/reducers/user_reducer.dart @@ -1,8 +1,6 @@ -import 'package:flutter/foundation.dart'; import 'package:fusecash/models/user_state.dart'; import 'package:fusecash/redux/actions/cash_wallet_actions.dart'; import 'package:fusecash/redux/actions/user_actions.dart'; -import 'package:fusecash/utils/did/did_service.dart'; import 'package:redux/redux.dart'; final userReducers = combineReducers([ @@ -111,8 +109,6 @@ UserState _createNewWalletSuccess( mnemonic: action.mnemonic, privateKey: action.privateKey, accountAddress: action.accountAddress, - did: action.did, - privateKeyForDID: action.privateKeyForDID, ); } From 5f9e991a9b8164ba21db41db06ecbdfbdb75a7b8 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 12:42:01 +0300 Subject: [PATCH 22/29] generate a route for VerifyVerifiableCredentialPage --- lib/common/router/routes.gr.dart | 124 ++++++++++++++---------- lib/features/account/router/router.dart | 4 + 2 files changed, 76 insertions(+), 52 deletions(-) diff --git a/lib/common/router/routes.gr.dart b/lib/common/router/routes.gr.dart index 8e05ecda..446feed2 100644 --- a/lib/common/router/routes.gr.dart +++ b/lib/common/router/routes.gr.dart @@ -11,9 +11,9 @@ // ignore_for_file: type=lint import 'package:auto_route/auto_route.dart' as _i11; -import 'package:charge_wallet_sdk/charge_wallet_sdk.dart' as _i35; -import 'package:contacts_service/contacts_service.dart' as _i34; -import 'package:flutter/material.dart' as _i31; +import 'package:charge_wallet_sdk/charge_wallet_sdk.dart' as _i36; +import 'package:contacts_service/contacts_service.dart' as _i35; +import 'package:flutter/material.dart' as _i32; import '../../features/account/screens/account_screen.dart' as _i23; import '../../features/account/screens/connected_dapps.dart' as _i30; @@ -23,6 +23,8 @@ import '../../features/account/screens/protect_your_wallet.dart' as _i28; import '../../features/account/screens/settings.dart' as _i27; import '../../features/account/screens/show_mnemonic.dart' as _i24; import '../../features/account/screens/verify_mnemonic.dart' as _i25; +import '../../features/account/screens/verify_verifiable_credential.dart' + as _i31; import '../../features/home/screens/action_details.dart' as _i13; import '../../features/home/screens/home.dart' as _i12; import '../../features/onboard/screens/restore_wallet_screen.dart' as _i4; @@ -44,18 +46,18 @@ import '../../features/wallet/screens/send_review.dart' as _i19; import '../../features/wallet/screens/send_success.dart' as _i20; import '../../features/wallet/screens/token.dart' as _i15; import '../../features/wallet/screens/wallet.dart' as _i14; -import '../../features/wallet/send_amount_arguments.dart' as _i36; -import '../../models/actions/wallet_action.dart' as _i33; -import '../../models/tokens/token.dart' as _i37; -import 'route_guards.dart' as _i32; +import '../../features/wallet/send_amount_arguments.dart' as _i37; +import '../../models/actions/wallet_action.dart' as _i34; +import '../../models/tokens/token.dart' as _i38; +import 'route_guards.dart' as _i33; class RootRouter extends _i11.RootStackRouter { RootRouter( - {_i31.GlobalKey<_i31.NavigatorState>? navigatorKey, + {_i32.GlobalKey<_i32.NavigatorState>? navigatorKey, required this.authGuard}) : super(navigatorKey); - final _i32.AuthGuard authGuard; + final _i33.AuthGuard authGuard; @override final Map pagesMap = { @@ -235,6 +237,11 @@ class RootRouter extends _i11.RootStackRouter { ConnectedDAppsRoute.name: (routeData) { return _i11.MaterialPageX( routeData: routeData, child: const _i30.ConnectedDAppsPage()); + }, + VerifyVerifiableCredentialRoute.name: (routeData) { + return _i11.MaterialPageX( + routeData: routeData, + child: const _i31.VerifyVerifiableCredentialPage()); } }; @@ -312,7 +319,10 @@ class RootRouter extends _i11.RootStackRouter { _i11.RouteConfig(ProfileRoute.name, path: 'profile-page', parent: AccountTab.name), _i11.RouteConfig(ConnectedDAppsRoute.name, - path: 'connected-dapps-page', parent: AccountTab.name) + path: 'connected-dapps-page', parent: AccountTab.name), + _i11.RouteConfig(VerifyVerifiableCredentialRoute.name, + path: 'verify-verifiable-credential-page', + parent: AccountTab.name) ]) ]), _i11.RouteConfig('*#redirect', @@ -323,7 +333,7 @@ class RootRouter extends _i11.RootStackRouter { /// generated route for /// [_i1.SplashPage] class SplashRoute extends _i11.PageRouteInfo { - SplashRoute({_i31.Key? key, void Function(bool)? onLoginResult}) + SplashRoute({_i32.Key? key, void Function(bool)? onLoginResult}) : super(SplashRoute.name, path: '/', args: SplashRouteArgs(key: key, onLoginResult: onLoginResult)); @@ -334,7 +344,7 @@ class SplashRoute extends _i11.PageRouteInfo { class SplashRouteArgs { const SplashRouteArgs({this.key, this.onLoginResult}); - final _i31.Key? key; + final _i32.Key? key; final void Function(bool)? onLoginResult; @@ -391,7 +401,7 @@ class SignUpRoute extends _i11.PageRouteInfo { /// [_i7.VerifyPhoneNumberPage] class VerifyPhoneNumberRoute extends _i11.PageRouteInfo { - VerifyPhoneNumberRoute({String? verificationId, _i31.Key? key}) + VerifyPhoneNumberRoute({String? verificationId, _i32.Key? key}) : super(VerifyPhoneNumberRoute.name, path: '/verify-phone-number-page', args: VerifyPhoneNumberRouteArgs( @@ -405,7 +415,7 @@ class VerifyPhoneNumberRouteArgs { final String? verificationId; - final _i31.Key? key; + final _i32.Key? key; @override String toString() { @@ -428,7 +438,7 @@ class Webview extends _i11.PageRouteInfo { {required String url, required String title, void Function(String)? onPageStarted, - _i31.Key? key}) + _i32.Key? key}) : super(Webview.name, path: '/web-view-page', args: WebviewArgs( @@ -450,7 +460,7 @@ class WebviewArgs { final void Function(String)? onPageStarted; - final _i31.Key? key; + final _i32.Key? key; @override String toString() { @@ -515,13 +525,13 @@ class HomeRoute extends _i11.PageRouteInfo { /// [_i13.ActionDetailsPage] class ActionDetailsRoute extends _i11.PageRouteInfo { ActionDetailsRoute( - {_i31.Key? key, - required _i33.WalletAction action, - _i31.ImageProvider? image, + {_i32.Key? key, + required _i34.WalletAction action, + _i32.ImageProvider? image, required String displayName, String? accountAddress, required String symbol, - _i34.Contact? contact}) + _i35.Contact? contact}) : super(ActionDetailsRoute.name, path: 'action-details', args: ActionDetailsRouteArgs( @@ -546,11 +556,11 @@ class ActionDetailsRouteArgs { required this.symbol, this.contact}); - final _i31.Key? key; + final _i32.Key? key; - final _i33.WalletAction action; + final _i34.WalletAction action; - final _i31.ImageProvider? image; + final _i32.ImageProvider? image; final String displayName; @@ -558,7 +568,7 @@ class ActionDetailsRouteArgs { final String symbol; - final _i34.Contact? contact; + final _i35.Contact? contact; @override String toString() { @@ -577,7 +587,7 @@ class WalletRoute extends _i11.PageRouteInfo { /// generated route for /// [_i15.TokenPage] class TokenRoute extends _i11.PageRouteInfo { - TokenRoute({_i31.Key? key, required String tokenAddress}) + TokenRoute({_i32.Key? key, required String tokenAddress}) : super(TokenRoute.name, path: 'token-page', args: TokenRouteArgs(key: key, tokenAddress: tokenAddress)); @@ -588,7 +598,7 @@ class TokenRoute extends _i11.PageRouteInfo { class TokenRouteArgs { const TokenRouteArgs({this.key, required this.tokenAddress}); - final _i31.Key? key; + final _i32.Key? key; final String tokenAddress; @@ -601,7 +611,7 @@ class TokenRouteArgs { /// generated route for /// [_i16.CollectiblePage] class CollectibleRoute extends _i11.PageRouteInfo { - CollectibleRoute({_i31.Key? key, required _i35.Collectible collectible}) + CollectibleRoute({_i32.Key? key, required _i36.Collectible collectible}) : super(CollectibleRoute.name, path: 'collectible-page', args: CollectibleRouteArgs(key: key, collectible: collectible)); @@ -612,9 +622,9 @@ class CollectibleRoute extends _i11.PageRouteInfo { class CollectibleRouteArgs { const CollectibleRouteArgs({this.key, required this.collectible}); - final _i31.Key? key; + final _i32.Key? key; - final _i35.Collectible collectible; + final _i36.Collectible collectible; @override String toString() { @@ -625,7 +635,7 @@ class CollectibleRouteArgs { /// generated route for /// [_i17.ContactsPage] class ContactsRoute extends _i11.PageRouteInfo { - ContactsRoute({_i31.Key? key, _i36.SendFlowArguments? pageArgs}) + ContactsRoute({_i32.Key? key, _i37.SendFlowArguments? pageArgs}) : super(ContactsRoute.name, path: 'contacts-page', args: ContactsRouteArgs(key: key, pageArgs: pageArgs)); @@ -636,9 +646,9 @@ class ContactsRoute extends _i11.PageRouteInfo { class ContactsRouteArgs { const ContactsRouteArgs({this.key, this.pageArgs}); - final _i31.Key? key; + final _i32.Key? key; - final _i36.SendFlowArguments? pageArgs; + final _i37.SendFlowArguments? pageArgs; @override String toString() { @@ -649,7 +659,7 @@ class ContactsRouteArgs { /// generated route for /// [_i18.SendAmountPage] class SendAmountRoute extends _i11.PageRouteInfo { - SendAmountRoute({required _i36.SendFlowArguments pageArgs, _i31.Key? key}) + SendAmountRoute({required _i37.SendFlowArguments pageArgs, _i32.Key? key}) : super(SendAmountRoute.name, path: 'send-amount', args: SendAmountRouteArgs(pageArgs: pageArgs, key: key)); @@ -660,9 +670,9 @@ class SendAmountRoute extends _i11.PageRouteInfo { class SendAmountRouteArgs { const SendAmountRouteArgs({required this.pageArgs, this.key}); - final _i36.SendFlowArguments pageArgs; + final _i37.SendFlowArguments pageArgs; - final _i31.Key? key; + final _i32.Key? key; @override String toString() { @@ -673,7 +683,7 @@ class SendAmountRouteArgs { /// generated route for /// [_i19.SendReviewPage] class SendReviewRoute extends _i11.PageRouteInfo { - SendReviewRoute({required _i36.SendFlowArguments pageArgs, _i31.Key? key}) + SendReviewRoute({required _i37.SendFlowArguments pageArgs, _i32.Key? key}) : super(SendReviewRoute.name, path: 'send-review', args: SendReviewRouteArgs(pageArgs: pageArgs, key: key)); @@ -684,9 +694,9 @@ class SendReviewRoute extends _i11.PageRouteInfo { class SendReviewRouteArgs { const SendReviewRouteArgs({required this.pageArgs, this.key}); - final _i36.SendFlowArguments pageArgs; + final _i37.SendFlowArguments pageArgs; - final _i31.Key? key; + final _i32.Key? key; @override String toString() { @@ -697,7 +707,7 @@ class SendReviewRouteArgs { /// generated route for /// [_i20.SendSuccessPage] class SendSuccessRoute extends _i11.PageRouteInfo { - SendSuccessRoute({required _i36.SendFlowArguments pageArgs, _i31.Key? key}) + SendSuccessRoute({required _i37.SendFlowArguments pageArgs, _i32.Key? key}) : super(SendSuccessRoute.name, path: 'send-success', args: SendSuccessRouteArgs(pageArgs: pageArgs, key: key)); @@ -708,9 +718,9 @@ class SendSuccessRoute extends _i11.PageRouteInfo { class SendSuccessRouteArgs { const SendSuccessRouteArgs({required this.pageArgs, this.key}); - final _i36.SendFlowArguments pageArgs; + final _i37.SendFlowArguments pageArgs; - final _i31.Key? key; + final _i32.Key? key; @override String toString() { @@ -721,7 +731,7 @@ class SendSuccessRouteArgs { /// generated route for /// [_i21.SwapPage] class SwapRoute extends _i11.PageRouteInfo { - SwapRoute({_i31.Key? key, _i37.Token? primaryToken}) + SwapRoute({_i32.Key? key, _i38.Token? primaryToken}) : super(SwapRoute.name, path: '', args: SwapRouteArgs(key: key, primaryToken: primaryToken)); @@ -732,9 +742,9 @@ class SwapRoute extends _i11.PageRouteInfo { class SwapRouteArgs { const SwapRouteArgs({this.key, this.primaryToken}); - final _i31.Key? key; + final _i32.Key? key; - final _i37.Token? primaryToken; + final _i38.Token? primaryToken; @override String toString() { @@ -746,10 +756,10 @@ class SwapRouteArgs { /// [_i22.ReviewSwapPage] class ReviewSwapRoute extends _i11.PageRouteInfo { ReviewSwapRoute( - {_i35.Trade? rateInfo, - required _i35.Trade tradeInfo, - required _i35.TradeRequestBody swapRequestBody, - _i31.Key? key}) + {_i36.Trade? rateInfo, + required _i36.Trade tradeInfo, + required _i36.TradeRequestBody swapRequestBody, + _i32.Key? key}) : super(ReviewSwapRoute.name, path: 'review-swap-page', args: ReviewSwapRouteArgs( @@ -768,13 +778,13 @@ class ReviewSwapRouteArgs { required this.swapRequestBody, this.key}); - final _i35.Trade? rateInfo; + final _i36.Trade? rateInfo; - final _i35.Trade tradeInfo; + final _i36.Trade tradeInfo; - final _i35.TradeRequestBody swapRequestBody; + final _i36.TradeRequestBody swapRequestBody; - final _i31.Key? key; + final _i32.Key? key; @override String toString() { @@ -850,3 +860,13 @@ class ConnectedDAppsRoute extends _i11.PageRouteInfo { static const String name = 'ConnectedDAppsRoute'; } + +/// generated route for +/// [_i31.VerifyVerifiableCredentialPage] +class VerifyVerifiableCredentialRoute extends _i11.PageRouteInfo { + const VerifyVerifiableCredentialRoute() + : super(VerifyVerifiableCredentialRoute.name, + path: 'verify-verifiable-credential-page'); + + static const String name = 'VerifyVerifiableCredentialRoute'; +} diff --git a/lib/features/account/router/router.dart b/lib/features/account/router/router.dart index e1c21c32..cf56b421 100644 --- a/lib/features/account/router/router.dart +++ b/lib/features/account/router/router.dart @@ -7,6 +7,7 @@ import 'package:fusecash/features/account/screens/protect_your_wallet.dart'; import 'package:fusecash/features/account/screens/settings.dart'; import 'package:fusecash/features/account/screens/show_mnemonic.dart'; import 'package:fusecash/features/account/screens/verify_mnemonic.dart'; +import 'package:fusecash/features/account/screens/verify_verifiable_credential.dart'; const accountTab = AutoRoute( path: 'account', @@ -38,5 +39,8 @@ const accountTab = AutoRoute( AutoRoute( page: ConnectedDAppsPage, ), + AutoRoute( + page: VerifyVerifiableCredentialPage, + ), ], ); From 6950cc3daf914fab492e23dda2e58b476b24e690 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 12:43:03 +0300 Subject: [PATCH 23/29] add a MenuTile to for navigate to VerifyVerifiableCredentialPage --- lib/features/account/screens/account_screen.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/features/account/screens/account_screen.dart b/lib/features/account/screens/account_screen.dart index f3305ac8..5f8d1634 100644 --- a/lib/features/account/screens/account_screen.dart +++ b/lib/features/account/screens/account_screen.dart @@ -119,6 +119,15 @@ class _AccountPageState extends State { ); }, ), + MenuTile( + label: "Verify a Verifiable Credential", + menuIcon: 'legal_icon.svg', + onTap: () { + context.router.push( + const VerifyVerifiableCredentialRoute(), + ); + }, + ) ], ); }, From 4215e5906d294c37fdfd601a6d5de2e9947efd25 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 16:57:50 +0300 Subject: [PATCH 24/29] add riverpod package --- ...info_vc.dart => user_info_credential.dart} | 6 +++--- pubspec.lock | 21 +++++++++++++++++++ pubspec.yaml | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) rename lib/models/verifiable_credential/{user_info_vc.dart => user_info_credential.dart} (92%) diff --git a/lib/models/verifiable_credential/user_info_vc.dart b/lib/models/verifiable_credential/user_info_credential.dart similarity index 92% rename from lib/models/verifiable_credential/user_info_vc.dart rename to lib/models/verifiable_credential/user_info_credential.dart index 8e4c7e8f..85818406 100644 --- a/lib/models/verifiable_credential/user_info_vc.dart +++ b/lib/models/verifiable_credential/user_info_credential.dart @@ -4,7 +4,7 @@ import 'package:fusecash/models/verifiable_credential/user_info_credential_subje part 'user_info_vc.g.dart'; @JsonSerializable() -class UserInfoVC { +class UserInfoCredential { @JsonKey(name: "@context", defaultValue: _context) final List context; @@ -19,7 +19,7 @@ class UserInfoVC { @JsonKey(name: "credentialSubject") final UserInfoCredentialSubject userInfoCredentialSubject; - const UserInfoVC({ + const UserInfoCredential({ this.context = _context, required this.id, this.type = _type, @@ -28,7 +28,7 @@ class UserInfoVC { required this.userInfoCredentialSubject, }); - factory UserInfoVC.fromJson(Map json) => + factory UserInfoCredential.fromJson(Map json) => _$UserInfoVCFromJson(json); Map toJson() => _$UserInfoVCToJson(this); diff --git a/pubspec.lock b/pubspec.lock index ae9564e4..c1afd921 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -804,6 +804,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.10.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" flutter_secure_storage: dependency: "direct main" description: @@ -1661,6 +1668,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1+1" + riverpod: + dependency: transitive + description: + name: riverpod + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" rxdart: dependency: transitive description: @@ -1820,6 +1834,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.10.0" + state_notifier: + dependency: transitive + description: + name: state_notifier + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.2+1" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cede024f..40f0b9cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -99,6 +99,7 @@ dependencies: bip39: ^1.0.6 secp256k1: ^0.3.0 uuid: ^3.0.6 + flutter_riverpod: ^1.0.4 dev_dependencies: flutter_native_splash: ^2.2.7 From 85eaad4b094fbc9d6a2cfe1c3ab11f664b0ad7cc Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 17:04:33 +0300 Subject: [PATCH 25/29] create a separate StatelessWidget for Verify Credential menu tile --- .../account/screens/account_screen.dart | 15 +++++------ .../widgets/verify_credential_menu_tile.dart | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 lib/features/account/widgets/verify_credential_menu_tile.dart diff --git a/lib/features/account/screens/account_screen.dart b/lib/features/account/screens/account_screen.dart index 5f8d1634..8a2c6b69 100644 --- a/lib/features/account/screens/account_screen.dart +++ b/lib/features/account/screens/account_screen.dart @@ -10,6 +10,7 @@ import 'package:fusecash/common/router/routes.gr.dart'; import 'package:fusecash/constants/variables.dart'; import 'package:fusecash/features/account/widgets/avatar.dart'; import 'package:fusecash/features/account/widgets/menu_tile.dart'; +import 'package:fusecash/features/account/widgets/verify_credential_menu_tile.dart'; import 'package:fusecash/features/shared/bottom_sheets/deposit.dart'; import 'package:fusecash/features/shared/widgets/my_new_scaffold.dart'; import 'package:flutter_gen/gen_l10n/I10n.dart'; @@ -119,15 +120,11 @@ class _AccountPageState extends State { ); }, ), - MenuTile( - label: "Verify a Verifiable Credential", - menuIcon: 'legal_icon.svg', - onTap: () { - context.router.push( - const VerifyVerifiableCredentialRoute(), - ); - }, - ) + viewModel.privateKeyForDID != null + ? VerifyCredentialMenuTile( + privateKeyForDID: viewModel.privateKeyForDID!, + ) + : const SizedBox(), ], ); }, diff --git a/lib/features/account/widgets/verify_credential_menu_tile.dart b/lib/features/account/widgets/verify_credential_menu_tile.dart new file mode 100644 index 00000000..cfdb2c9e --- /dev/null +++ b/lib/features/account/widgets/verify_credential_menu_tile.dart @@ -0,0 +1,26 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:fusecash/common/router/routes.dart'; +import 'package:fusecash/features/account/widgets/menu_tile.dart'; + +class VerifyCredentialMenuTile extends StatelessWidget { + final String privateKeyForDID; + + const VerifyCredentialMenuTile({ + Key? key, + required this.privateKeyForDID, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return MenuTile( + label: "Verify Credential", + menuIcon: 'legal_icon.svg', + onTap: () { + context.router.push( + VerifyCredentialRoute(privateKeyForDID: privateKeyForDID), + ); + }, + ); + } +} From 67b922c0cd2ece28915bd10faf54dbb6ecf1b9de Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 17:06:40 +0300 Subject: [PATCH 26/29] rename VerifyVerifiableCredentialPage to VerifyCredentialPage --- lib/common/router/routes.gr.dart | 42 +++++++++++++++++-------- lib/features/account/router/router.dart | 4 +-- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/common/router/routes.gr.dart b/lib/common/router/routes.gr.dart index 446feed2..f3e7e5c4 100644 --- a/lib/common/router/routes.gr.dart +++ b/lib/common/router/routes.gr.dart @@ -22,9 +22,8 @@ import '../../features/account/screens/profile.dart' as _i29; import '../../features/account/screens/protect_your_wallet.dart' as _i28; import '../../features/account/screens/settings.dart' as _i27; import '../../features/account/screens/show_mnemonic.dart' as _i24; +import '../../features/account/screens/verify_credential.dart' as _i31; import '../../features/account/screens/verify_mnemonic.dart' as _i25; -import '../../features/account/screens/verify_verifiable_credential.dart' - as _i31; import '../../features/home/screens/action_details.dart' as _i13; import '../../features/home/screens/home.dart' as _i12; import '../../features/onboard/screens/restore_wallet_screen.dart' as _i4; @@ -238,10 +237,12 @@ class RootRouter extends _i11.RootStackRouter { return _i11.MaterialPageX( routeData: routeData, child: const _i30.ConnectedDAppsPage()); }, - VerifyVerifiableCredentialRoute.name: (routeData) { + VerifyCredentialRoute.name: (routeData) { + final args = routeData.argsAs(); return _i11.MaterialPageX( routeData: routeData, - child: const _i31.VerifyVerifiableCredentialPage()); + child: _i31.VerifyCredentialPage( + key: args.key, privateKeyForDID: args.privateKeyForDID)); } }; @@ -320,9 +321,8 @@ class RootRouter extends _i11.RootStackRouter { path: 'profile-page', parent: AccountTab.name), _i11.RouteConfig(ConnectedDAppsRoute.name, path: 'connected-dapps-page', parent: AccountTab.name), - _i11.RouteConfig(VerifyVerifiableCredentialRoute.name, - path: 'verify-verifiable-credential-page', - parent: AccountTab.name) + _i11.RouteConfig(VerifyCredentialRoute.name, + path: 'verify-credential-page', parent: AccountTab.name) ]) ]), _i11.RouteConfig('*#redirect', @@ -862,11 +862,27 @@ class ConnectedDAppsRoute extends _i11.PageRouteInfo { } /// generated route for -/// [_i31.VerifyVerifiableCredentialPage] -class VerifyVerifiableCredentialRoute extends _i11.PageRouteInfo { - const VerifyVerifiableCredentialRoute() - : super(VerifyVerifiableCredentialRoute.name, - path: 'verify-verifiable-credential-page'); +/// [_i31.VerifyCredentialPage] +class VerifyCredentialRoute + extends _i11.PageRouteInfo { + VerifyCredentialRoute({_i32.Key? key, required String privateKeyForDID}) + : super(VerifyCredentialRoute.name, + path: 'verify-credential-page', + args: VerifyCredentialRouteArgs( + key: key, privateKeyForDID: privateKeyForDID)); - static const String name = 'VerifyVerifiableCredentialRoute'; + static const String name = 'VerifyCredentialRoute'; +} + +class VerifyCredentialRouteArgs { + const VerifyCredentialRouteArgs({this.key, required this.privateKeyForDID}); + + final _i32.Key? key; + + final String privateKeyForDID; + + @override + String toString() { + return 'VerifyCredentialRouteArgs{key: $key, privateKeyForDID: $privateKeyForDID}'; + } } diff --git a/lib/features/account/router/router.dart b/lib/features/account/router/router.dart index cf56b421..1ce196d9 100644 --- a/lib/features/account/router/router.dart +++ b/lib/features/account/router/router.dart @@ -7,7 +7,7 @@ import 'package:fusecash/features/account/screens/protect_your_wallet.dart'; import 'package:fusecash/features/account/screens/settings.dart'; import 'package:fusecash/features/account/screens/show_mnemonic.dart'; import 'package:fusecash/features/account/screens/verify_mnemonic.dart'; -import 'package:fusecash/features/account/screens/verify_verifiable_credential.dart'; +import 'package:fusecash/features/account/screens/verify_credential.dart'; const accountTab = AutoRoute( path: 'account', @@ -40,7 +40,7 @@ const accountTab = AutoRoute( page: ConnectedDAppsPage, ), AutoRoute( - page: VerifyVerifiableCredentialPage, + page: VerifyCredentialPage, ), ], ); From 8521be7a7d0c71cb80005c507d81b5cf27acd343 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 17:11:56 +0300 Subject: [PATCH 27/29] use the word "credential" instead of "VC" --- lib/features/screens/splash_screen.dart | 4 +- lib/models/user_state.dart | 2 +- lib/models/user_state.freezed.dart | 38 +++++++-------- lib/models/user_state.g.dart | 4 +- .../user_info_credential.dart | 6 +-- .../verifiable_credential/user_info_vc.g.dart | 47 ------------------- lib/redux/actions/user_actions.dart | 42 ++++++++++------- lib/redux/reducers/user_reducer.dart | 9 ++-- lib/utils/did/did_service.dart | 21 +++++---- 9 files changed, 70 insertions(+), 103 deletions(-) delete mode 100644 lib/models/verifiable_credential/user_info_vc.g.dart diff --git a/lib/features/screens/splash_screen.dart b/lib/features/screens/splash_screen.dart index d681a0e4..d1419927 100644 --- a/lib/features/screens/splash_screen.dart +++ b/lib/features/screens/splash_screen.dart @@ -40,12 +40,12 @@ class _SplashPageState extends State { final did = userState.did; final didExists = did != null && did.isNotEmpty; - if (didExists && userState.userInfoVC == null) { + if (didExists && userState.userInfoCredential == null) { final privateKeyForDID = userState.privateKeyForDID; // If did exists, the private must exist too. store.dispatch( - issueUserInfoVCCall( + issueUserInfoCredentialCall( did: did, privateKeyForDID: privateKeyForDID!, ), diff --git a/lib/models/user_state.dart b/lib/models/user_state.dart index 2e343cff..3c93fcef 100644 --- a/lib/models/user_state.dart +++ b/lib/models/user_state.dart @@ -56,7 +56,7 @@ class UserState with _$UserState { @JsonKey(ignore: true) PhoneAuthCredential? credentials, String? did, String? privateKeyForDID, - String? userInfoVC, + String? userInfoCredential, }) = _UserState; factory UserState.initial() => UserState( diff --git a/lib/models/user_state.freezed.dart b/lib/models/user_state.freezed.dart index 6a3966ba..89cc56cd 100644 --- a/lib/models/user_state.freezed.dart +++ b/lib/models/user_state.freezed.dart @@ -59,7 +59,7 @@ mixin _$UserState { PhoneAuthCredential? get credentials => throw _privateConstructorUsedError; String? get did => throw _privateConstructorUsedError; String? get privateKeyForDID => throw _privateConstructorUsedError; - String? get userInfoVC => throw _privateConstructorUsedError; + String? get userInfoCredential => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -106,7 +106,7 @@ abstract class $UserStateCopyWith<$Res> { @JsonKey(ignore: true) PhoneAuthCredential? credentials, String? did, String? privateKeyForDID, - String? userInfoVC}); + String? userInfoCredential}); $WalletModulesCopyWith<$Res>? get walletModules; } @@ -155,7 +155,7 @@ class _$UserStateCopyWithImpl<$Res> implements $UserStateCopyWith<$Res> { Object? credentials = freezed, Object? did = freezed, Object? privateKeyForDID = freezed, - Object? userInfoVC = freezed, + Object? userInfoCredential = freezed, }) { return _then(_value.copyWith( wcURI: wcURI == freezed @@ -294,9 +294,9 @@ class _$UserStateCopyWithImpl<$Res> implements $UserStateCopyWith<$Res> { ? _value.privateKeyForDID : privateKeyForDID // ignore: cast_nullable_to_non_nullable as String?, - userInfoVC: userInfoVC == freezed - ? _value.userInfoVC - : userInfoVC // ignore: cast_nullable_to_non_nullable + userInfoCredential: userInfoCredential == freezed + ? _value.userInfoCredential + : userInfoCredential // ignore: cast_nullable_to_non_nullable as String?, )); } @@ -354,7 +354,7 @@ abstract class _$$_UserStateCopyWith<$Res> implements $UserStateCopyWith<$Res> { @JsonKey(ignore: true) PhoneAuthCredential? credentials, String? did, String? privateKeyForDID, - String? userInfoVC}); + String? userInfoCredential}); @override $WalletModulesCopyWith<$Res>? get walletModules; @@ -406,7 +406,7 @@ class __$$_UserStateCopyWithImpl<$Res> extends _$UserStateCopyWithImpl<$Res> Object? credentials = freezed, Object? did = freezed, Object? privateKeyForDID = freezed, - Object? userInfoVC = freezed, + Object? userInfoCredential = freezed, }) { return _then(_$_UserState( wcURI: wcURI == freezed @@ -545,9 +545,9 @@ class __$$_UserStateCopyWithImpl<$Res> extends _$UserStateCopyWithImpl<$Res> ? _value.privateKeyForDID : privateKeyForDID // ignore: cast_nullable_to_non_nullable as String?, - userInfoVC: userInfoVC == freezed - ? _value.userInfoVC - : userInfoVC // ignore: cast_nullable_to_non_nullable + userInfoCredential: userInfoCredential == freezed + ? _value.userInfoCredential + : userInfoCredential // ignore: cast_nullable_to_non_nullable as String?, )); } @@ -591,7 +591,7 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { @JsonKey(ignore: true) this.credentials, this.did, this.privateKeyForDID, - this.userInfoVC}) + this.userInfoCredential}) : super._(); factory _$_UserState.fromJson(Map json) => @@ -693,11 +693,11 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { @override final String? privateKeyForDID; @override - final String? userInfoVC; + final String? userInfoCredential; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'UserState(wcURI: $wcURI, contractVersion: $contractVersion, walletModules: $walletModules, installedAt: $installedAt, isContactsSynced: $isContactsSynced, isLoggedOut: $isLoggedOut, backup: $backup, scrollToTop: $scrollToTop, walletAddress: $walletAddress, networks: $networks, mnemonic: $mnemonic, privateKey: $privateKey, pincode: $pincode, accountAddress: $accountAddress, countryCode: $countryCode, phoneNumber: $phoneNumber, warnSendDialogShowed: $warnSendDialogShowed, isoCode: $isoCode, jwtToken: $jwtToken, displayName: $displayName, avatarUrl: $avatarUrl, email: $email, verificationId: $verificationId, identifier: $identifier, syncedContacts: $syncedContacts, reverseContacts: $reverseContacts, currency: $currency, hasUpgrade: $hasUpgrade, authType: $authType, locale: $locale, contacts: $contacts, credentials: $credentials, did: $did, privateKeyForDID: $privateKeyForDID, userInfoVC: $userInfoVC)'; + return 'UserState(wcURI: $wcURI, contractVersion: $contractVersion, walletModules: $walletModules, installedAt: $installedAt, isContactsSynced: $isContactsSynced, isLoggedOut: $isLoggedOut, backup: $backup, scrollToTop: $scrollToTop, walletAddress: $walletAddress, networks: $networks, mnemonic: $mnemonic, privateKey: $privateKey, pincode: $pincode, accountAddress: $accountAddress, countryCode: $countryCode, phoneNumber: $phoneNumber, warnSendDialogShowed: $warnSendDialogShowed, isoCode: $isoCode, jwtToken: $jwtToken, displayName: $displayName, avatarUrl: $avatarUrl, email: $email, verificationId: $verificationId, identifier: $identifier, syncedContacts: $syncedContacts, reverseContacts: $reverseContacts, currency: $currency, hasUpgrade: $hasUpgrade, authType: $authType, locale: $locale, contacts: $contacts, credentials: $credentials, did: $did, privateKeyForDID: $privateKeyForDID, userInfoCredential: $userInfoCredential)'; } @override @@ -739,7 +739,7 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { ..add(DiagnosticsProperty('credentials', credentials)) ..add(DiagnosticsProperty('did', did)) ..add(DiagnosticsProperty('privateKeyForDID', privateKeyForDID)) - ..add(DiagnosticsProperty('userInfoVC', userInfoVC)); + ..add(DiagnosticsProperty('userInfoCredential', userInfoCredential)); } @override @@ -802,7 +802,7 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { const DeepCollectionEquality() .equals(other.privateKeyForDID, privateKeyForDID) && const DeepCollectionEquality() - .equals(other.userInfoVC, userInfoVC)); + .equals(other.userInfoCredential, userInfoCredential)); } @JsonKey(ignore: true) @@ -843,7 +843,7 @@ class _$_UserState extends _UserState with DiagnosticableTreeMixin { const DeepCollectionEquality().hash(credentials), const DeepCollectionEquality().hash(did), const DeepCollectionEquality().hash(privateKeyForDID), - const DeepCollectionEquality().hash(userInfoVC) + const DeepCollectionEquality().hash(userInfoCredential) ]); @JsonKey(ignore: true) @@ -900,7 +900,7 @@ abstract class _UserState extends UserState { final PhoneAuthCredential? credentials, final String? did, final String? privateKeyForDID, - final String? userInfoVC}) = _$_UserState; + final String? userInfoCredential}) = _$_UserState; _UserState._() : super._(); factory _UserState.fromJson(Map json) = @@ -980,7 +980,7 @@ abstract class _UserState extends UserState { @override String? get privateKeyForDID; @override - String? get userInfoVC; + String? get userInfoCredential; @override @JsonKey(ignore: true) _$$_UserStateCopyWith<_$_UserState> get copyWith => diff --git a/lib/models/user_state.g.dart b/lib/models/user_state.g.dart index be794ef3..05ccf006 100644 --- a/lib/models/user_state.g.dart +++ b/lib/models/user_state.g.dart @@ -55,7 +55,7 @@ _$_UserState _$$_UserStateFromJson(Map json) => _$_UserState( locale: localeFromJson(json['locale'] as Map), did: json['did'] as String?, privateKeyForDID: json['privateKeyForDID'] as String?, - userInfoVC: json['userInfoVC'] as String?, + userInfoCredential: json['userInfoCredential'] as String?, ); Map _$$_UserStateToJson(_$_UserState instance) => @@ -90,7 +90,7 @@ Map _$$_UserStateToJson(_$_UserState instance) => 'locale': localeToJson(instance.locale), 'did': instance.did, 'privateKeyForDID': instance.privateKeyForDID, - 'userInfoVC': instance.userInfoVC, + 'userInfoCredential': instance.userInfoCredential, }; const _$BiometricAuthEnumMap = { diff --git a/lib/models/verifiable_credential/user_info_credential.dart b/lib/models/verifiable_credential/user_info_credential.dart index 85818406..4d297b94 100644 --- a/lib/models/verifiable_credential/user_info_credential.dart +++ b/lib/models/verifiable_credential/user_info_credential.dart @@ -1,7 +1,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:fusecash/models/verifiable_credential/user_info_credential_subject.dart'; -part 'user_info_vc.g.dart'; +part 'user_info_credential.g.dart'; @JsonSerializable() class UserInfoCredential { @@ -29,9 +29,9 @@ class UserInfoCredential { }); factory UserInfoCredential.fromJson(Map json) => - _$UserInfoVCFromJson(json); + _$UserInfoCredentialFromJson(json); - Map toJson() => _$UserInfoVCToJson(this); + Map toJson() => _$UserInfoCredentialToJson(this); static const _context = [ "https://www.w3.org/2018/credentials/v1", diff --git a/lib/models/verifiable_credential/user_info_vc.g.dart b/lib/models/verifiable_credential/user_info_vc.g.dart deleted file mode 100644 index 1b6f95dc..00000000 --- a/lib/models/verifiable_credential/user_info_vc.g.dart +++ /dev/null @@ -1,47 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_info_vc.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UserInfoVC _$UserInfoVCFromJson(Map json) => UserInfoVC( - context: json['@context'] as List? ?? - [ - 'https://www.w3.org/2018/credentials/v1', - { - 'name': 'https://schema.org/name', - 'description': 'https://schema.org/description', - 'SelfIssued': { - '@context': { - '@protected': true, - '@version': 1.1, - 'id': '@id', - 'schema': 'https://schema.org/', - 'telephone': 'schema:telephone', - 'type': '@type' - }, - '@id': 'https://github.com/TalaoDAO/context/blob/main/README.md' - } - } - ], - id: json['id'] as String, - type: - (json['type'] as List?)?.map((e) => e as String).toList() ?? - ['VerifiableCredential', 'SelfIssued'], - issuer: json['issuer'] as String, - issuanceDate: json['issuanceDate'] as String, - userInfoCredentialSubject: UserInfoCredentialSubject.fromJson( - json['credentialSubject'] as Map), - ); - -Map _$UserInfoVCToJson(UserInfoVC instance) => - { - '@context': instance.context, - 'id': instance.id, - 'type': instance.type, - 'issuer': instance.issuer, - 'issuanceDate': instance.issuanceDate, - 'credentialSubject': instance.userInfoCredentialSubject.toJson(), - }; diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index ef7b2433..052b7a81 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -12,6 +12,8 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter_udid/flutter_udid.dart'; +import 'package:fusecash/models/verifiable_credential/user_info_credential.dart'; +import 'package:fusecash/models/verifiable_credential/verification_result.dart'; import 'package:fusecash/utils/did/did_service.dart'; import 'package:fusecash/utils/did/private_key_generation.dart'; import 'package:image_picker/image_picker.dart'; @@ -102,11 +104,11 @@ class GenerateDIDSuccess { const GenerateDIDSuccess(this.did, this.privateKeyForDID); } -class IssueUserInfoVCSuccess { - /// The JSON representation of the issued [UserInfoVC]. - final String userInfoVC; +class IssueUserInfoCredentialSuccess { + /// The JSON representation of the issued [UserInfoCredential]. + final String userInfoCredential; - const IssueUserInfoVCSuccess({required this.userInfoVC}); + const IssueUserInfoCredentialSuccess({required this.userInfoCredential}); } class ReLogin { @@ -623,8 +625,10 @@ ThunkAction generateDIDCall({required String mnemonic}) { final didService = DIDService(privateKey: privateKeyToGenerateDID); final did = didService.generateDID(); + debugPrint("did: $did"); + store.dispatch( - issueUserInfoVCCall( + issueUserInfoCredentialCall( did: did, privateKeyForDID: privateKeyToGenerateDID, ), @@ -649,7 +653,7 @@ ThunkAction generateDIDCall({required String mnemonic}) { }; } -ThunkAction issueUserInfoVCCall({ +ThunkAction issueUserInfoCredentialCall({ required String did, required String privateKeyForDID, }) { @@ -657,32 +661,36 @@ ThunkAction issueUserInfoVCCall({ final userState = store.state.userState; final didService = DIDService(privateKey: privateKeyForDID); - final userInfoVC = didService.issueUserInfoVC( + final userInfoCredential = didService.issueUserInfoCredential( did: did, name: userState.displayName, phoneNumber: userState.phoneNumber, ); - final verificationResultInJson = didService.verifyVC(userInfoVC); - final verificationResult = jsonDecode(verificationResultInJson); + debugPrint("userInfoCredential: $userInfoCredential"); + + final verificationResultInJson = + didService.verifyCredential(userInfoCredential); - debugPrint("Verification result: $verificationResultInJson"); + final verificationResultAsMap = jsonDecode(verificationResultInJson); + final verificationResult = + VerificationResult.fromJson(verificationResultAsMap); - final warnings = verificationResult["warnings"] as List; - final errors = verificationResult["errors"] as List; + final warnings = verificationResult.warnings; + final errors = verificationResult.errors; if (warnings.isNotEmpty) { - log.warn("Warnings produced while verifying the VC: $warnings"); + log.warn("Warnings produced while verifying the credential: $warnings"); } if (errors.isNotEmpty) { - log.error("Failed to verify VC. $errors"); + log.error("Failed to verify credential. $errors"); return; } - final issueUserInfoVCSuccess = - IssueUserInfoVCSuccess(userInfoVC: userInfoVC); - store.dispatch(issueUserInfoVCSuccess); + final issueUserInfoCredentialSuccess = + IssueUserInfoCredentialSuccess(userInfoCredential: userInfoCredential); + store.dispatch(issueUserInfoCredentialSuccess); }; } diff --git a/lib/redux/reducers/user_reducer.dart b/lib/redux/reducers/user_reducer.dart index d42c61bf..1553063a 100644 --- a/lib/redux/reducers/user_reducer.dart +++ b/lib/redux/reducers/user_reducer.dart @@ -12,7 +12,8 @@ final userReducers = combineReducers([ TypedReducer(_toggleUpgrade), TypedReducer(_createNewWalletSuccess), TypedReducer(_generateDIDSuccess), - TypedReducer(_issueUserInfoVCSuccess), + TypedReducer( + _issueUserInfoCredentialSuccess), TypedReducer(_loginSuccess), TypedReducer(_loginVerifySuccess), TypedReducer(_logoutSuccess), @@ -85,11 +86,11 @@ UserState _generateDIDSuccess(UserState state, GenerateDIDSuccess action) { ); } -UserState _issueUserInfoVCSuccess( +UserState _issueUserInfoCredentialSuccess( UserState state, - IssueUserInfoVCSuccess action, + IssueUserInfoCredentialSuccess action, ) { - return state.copyWith(userInfoVC: action.userInfoVC); + return state.copyWith(userInfoCredential: action.userInfoCredential); } UserState _backupSuccess(UserState state, BackupSuccess action) { diff --git a/lib/utils/did/did_service.dart b/lib/utils/did/did_service.dart index b145c5ca..1cdbd328 100644 --- a/lib/utils/did/did_service.dart +++ b/lib/utils/did/did_service.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:didkit/didkit.dart'; import 'package:fusecash/constants/strings.dart'; import 'package:fusecash/models/verifiable_credential/issue_credential_options.dart'; -import 'package:fusecash/models/verifiable_credential/user_info_vc.dart'; +import 'package:fusecash/models/verifiable_credential/user_info_credential.dart'; import 'package:fusecash/models/verifiable_credential/user_info_credential_subject.dart'; import 'package:fusecash/models/verifiable_credential/verify_credential_options.dart'; import 'package:intl/intl.dart'; @@ -18,21 +18,26 @@ class DIDService { return DIDKit.keyToDID(Strings.defaultDIDMethod, privateKey); } - String issueUserInfoVC({ + String issueUserInfoCredential({ required String did, required String name, required String phoneNumber, }) { - final userInfoVCModel = _createUserInfoVCModel(did, name, phoneNumber); - final userInfoVCAsJson = jsonEncode(userInfoVCModel); + final userInfoCredentialModel = + _createUserInfoCredentialModel(did, name, phoneNumber); + final userInfoCredentialAsJson = jsonEncode(userInfoCredentialModel); final options = _createIssueCredentialOptions(); final optionsAsJson = jsonEncode(options); - return DIDKit.issueCredential(userInfoVCAsJson, optionsAsJson, privateKey); + return DIDKit.issueCredential( + userInfoCredentialAsJson, + optionsAsJson, + privateKey, + ); } - String verifyVC(String credential) { + String verifyCredential(String credential) { final optionsAsJson = _createOptionsAsJson(); return DIDKit.verifyCredential(credential, optionsAsJson); } @@ -54,7 +59,7 @@ class DIDService { return DIDKit.keyToVerificationMethod(Strings.defaultDIDMethod, privateKey); } - UserInfoVC _createUserInfoVCModel( + UserInfoCredential _createUserInfoCredentialModel( String did, String name, String phoneNumber, @@ -68,7 +73,7 @@ class DIDService { phoneNumber: phoneNumber, ); - return UserInfoVC( + return UserInfoCredential( id: id, issuer: did, issuanceDate: issuanceDate, From d23b3a6d55fc077b39ec9dc4118955c6f5253ae4 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 17:12:30 +0300 Subject: [PATCH 28/29] add privateKeyForDID field into AccountViewModel --- lib/redux/viewsmodels/account.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/redux/viewsmodels/account.dart b/lib/redux/viewsmodels/account.dart index b06f8b0a..3902bd9e 100644 --- a/lib/redux/viewsmodels/account.dart +++ b/lib/redux/viewsmodels/account.dart @@ -10,6 +10,7 @@ class AccountViewModel extends Equatable { final Function getSwapListBalances; final bool isBackup; final bool hasPreviousSessions; + final String? privateKeyForDID; const AccountViewModel({ required this.walletAddress, @@ -18,6 +19,7 @@ class AccountViewModel extends Equatable { required this.displayName, required this.isBackup, required this.hasPreviousSessions, + required this.privateKeyForDID, }); static AccountViewModel fromStore(Store store) { @@ -28,6 +30,7 @@ class AccountViewModel extends Equatable { displayName: store.state.userState.displayName, avatarUrl: store.state.userState.avatarUrl, walletAddress: store.state.userState.walletAddress, + privateKeyForDID: store.state.userState.privateKeyForDID, getSwapListBalances: () { store.dispatch(fetchSwapBalances()); }, From 4249f1e103aa29212653a0c7a9298025364c419e Mon Sep 17 00:00:00 2001 From: = Date: Sat, 27 Aug 2022 17:14:15 +0300 Subject: [PATCH 29/29] design and implement a page where the JSON representation of a Verifiable Credential could be inserted into a TextField and it's integrity could be verified on a button tap --- lib/features/account/providers.dart | 6 ++ .../account/screens/verify_credential.dart | 38 ++++++++ .../verify_credential_page_model.dart | 9 ++ .../widgets/credential_text_field.dart | 22 +++++ .../widgets/verify_credential_button.dart | 94 +++++++++++++++++++ .../user_info_credential.g.dart | 48 ++++++++++ .../verification_result.dart | 14 +++ .../verification_result.g.dart | 21 +++++ 8 files changed, 252 insertions(+) create mode 100644 lib/features/account/providers.dart create mode 100644 lib/features/account/screens/verify_credential.dart create mode 100644 lib/features/account/widget_models/verify_credential_page_model.dart create mode 100644 lib/features/account/widgets/credential_text_field.dart create mode 100644 lib/features/account/widgets/verify_credential_button.dart create mode 100644 lib/models/verifiable_credential/user_info_credential.g.dart create mode 100644 lib/models/verifiable_credential/verification_result.dart create mode 100644 lib/models/verifiable_credential/verification_result.g.dart diff --git a/lib/features/account/providers.dart b/lib/features/account/providers.dart new file mode 100644 index 00000000..1e40c919 --- /dev/null +++ b/lib/features/account/providers.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fusecash/features/account/widget_models/verify_credential_page_model.dart'; + +final verifyCredentialPageModelProvider = Provider( + (_) => VerifyCredentialPageModel(privateKeyForDID: ""), +); diff --git a/lib/features/account/screens/verify_credential.dart b/lib/features/account/screens/verify_credential.dart new file mode 100644 index 00000000..e9325332 --- /dev/null +++ b/lib/features/account/screens/verify_credential.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fusecash/features/account/providers.dart'; +import 'package:fusecash/features/account/widget_models/verify_credential_page_model.dart'; +import 'package:fusecash/features/account/widgets/credential_text_field.dart'; +import 'package:fusecash/features/account/widgets/verify_credential_button.dart'; +import 'package:fusecash/features/shared/widgets/inner_page.dart'; + +class VerifyCredentialPage extends StatelessWidget { + final String privateKeyForDID; + + const VerifyCredentialPage({ + Key? key, + required this.privateKeyForDID, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ProviderScope( + overrides: [ + verifyCredentialPageModelProvider.overrideWithValue( + VerifyCredentialPageModel(privateKeyForDID: privateKeyForDID), + ), + ], + child: InnerScaffold( + title: "Verify Credential", + padding: const EdgeInsets.symmetric(horizontal: 24), + body: ListView( + children: const [ + CredentialTextField(), + SizedBox(height: 60), + VerifyCredentialButton(), + ], + ), + ), + ); + } +} diff --git a/lib/features/account/widget_models/verify_credential_page_model.dart b/lib/features/account/widget_models/verify_credential_page_model.dart new file mode 100644 index 00000000..efcfc612 --- /dev/null +++ b/lib/features/account/widget_models/verify_credential_page_model.dart @@ -0,0 +1,9 @@ +import 'package:flutter/widgets.dart'; + +class VerifyCredentialPageModel { + final String privateKeyForDID; + + VerifyCredentialPageModel({required this.privateKeyForDID}); + + final credentialTextController = TextEditingController(); +} diff --git a/lib/features/account/widgets/credential_text_field.dart b/lib/features/account/widgets/credential_text_field.dart new file mode 100644 index 00000000..2a780fc7 --- /dev/null +++ b/lib/features/account/widgets/credential_text_field.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fusecash/features/account/providers.dart'; + +class CredentialTextField extends ConsumerWidget { + const CredentialTextField({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final verifyCredentialPageModel = + ref.watch(verifyCredentialPageModelProvider); + final textController = verifyCredentialPageModel.credentialTextController; + return TextField( + controller: textController, + decoration: const InputDecoration( + hintText: "Insert the Verifiable Credential in JSON format here", + hintStyle: TextStyle(color: Colors.grey, fontSize: 12), + ), + maxLines: 10, + ); + } +} diff --git a/lib/features/account/widgets/verify_credential_button.dart b/lib/features/account/widgets/verify_credential_button.dart new file mode 100644 index 00000000..f921d928 --- /dev/null +++ b/lib/features/account/widgets/verify_credential_button.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fusecash/common/di/di.dart'; +import 'package:fusecash/features/account/providers.dart'; +import 'package:fusecash/models/verifiable_credential/verification_result.dart'; +import 'package:fusecash/utils/alerts/alerts.dart'; +import 'package:fusecash/utils/alerts/alerts_model.dart'; +import 'package:fusecash/utils/did/did_service.dart'; + +class VerifyCredentialButton extends ConsumerWidget { + const VerifyCredentialButton({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final verifyCredentialPageModel = + ref.watch(verifyCredentialPageModelProvider); + return ValueListenableBuilder( + valueListenable: verifyCredentialPageModel.credentialTextController, + builder: (_, value, __) { + final credential = value.text; + final primaryColor = Theme.of(context).colorScheme.primary; + final buttonColor = + credential.isEmpty ? primaryColor.withOpacity(0.5) : primaryColor; + return ElevatedButton( + onPressed: () { + _onPressed( + context: context, + credential: credential, + privateKeyForDID: verifyCredentialPageModel.privateKeyForDID, + ); + }, + style: _getStyle(buttonColor), + child: const Text( + "Verify Credential", + style: TextStyle(color: Colors.white, fontSize: 18), + ), + ); + }, + ); + } + + void _onPressed({ + required BuildContext context, + required String credential, + required String privateKeyForDID, + }) { + final alerts = getIt(); + + try { + final didService = DIDService(privateKey: privateKeyForDID); + final result = didService.verifyCredential(credential); + + final resultAsMap = jsonDecode(result); + final verificationResult = VerificationResult.fromJson(resultAsMap); + + _showAlertAccordingToTheResult(context, verificationResult); + } on Exception catch (exception) { + final alertMessage = + "Failed to verify credential. ${exception.toString()}"; + alerts.setAlert(context, alertMessage, type: AlertsTypeEnum.ERROR); + } + } + + void _showAlertAccordingToTheResult( + BuildContext context, + VerificationResult result, + ) { + final warnings = result.warnings; + final errors = result.errors; + + final alerts = getIt(); + + if (errors.isEmpty) { + String alertMessage = "Credential has been verified successfully."; + if (warnings.isNotEmpty) { + alertMessage += " $warnings"; + } + alerts.setAlert(context, alertMessage, type: AlertsTypeEnum.SUCCESS); + } else { + final alertMessage = "Failed to verify credential. $errors"; + alerts.setAlert(context, alertMessage, type: AlertsTypeEnum.ERROR); + } + } + + ButtonStyle _getStyle(Color buttonColor) { + return ElevatedButton.styleFrom( + primary: buttonColor, + elevation: 0.0, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + ); + } +} diff --git a/lib/models/verifiable_credential/user_info_credential.g.dart b/lib/models/verifiable_credential/user_info_credential.g.dart new file mode 100644 index 00000000..4eeaf3f0 --- /dev/null +++ b/lib/models/verifiable_credential/user_info_credential.g.dart @@ -0,0 +1,48 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_info_credential.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserInfoCredential _$UserInfoCredentialFromJson(Map json) => + UserInfoCredential( + context: json['@context'] as List? ?? + [ + 'https://www.w3.org/2018/credentials/v1', + { + 'name': 'https://schema.org/name', + 'description': 'https://schema.org/description', + 'SelfIssued': { + '@context': { + '@protected': true, + '@version': 1.1, + 'id': '@id', + 'schema': 'https://schema.org/', + 'telephone': 'schema:telephone', + 'type': '@type' + }, + '@id': 'https://github.com/TalaoDAO/context/blob/main/README.md' + } + } + ], + id: json['id'] as String, + type: + (json['type'] as List?)?.map((e) => e as String).toList() ?? + ['VerifiableCredential', 'SelfIssued'], + issuer: json['issuer'] as String, + issuanceDate: json['issuanceDate'] as String, + userInfoCredentialSubject: UserInfoCredentialSubject.fromJson( + json['credentialSubject'] as Map), + ); + +Map _$UserInfoCredentialToJson(UserInfoCredential instance) => + { + '@context': instance.context, + 'id': instance.id, + 'type': instance.type, + 'issuer': instance.issuer, + 'issuanceDate': instance.issuanceDate, + 'credentialSubject': instance.userInfoCredentialSubject.toJson(), + }; diff --git a/lib/models/verifiable_credential/verification_result.dart b/lib/models/verifiable_credential/verification_result.dart new file mode 100644 index 00000000..9f63235b --- /dev/null +++ b/lib/models/verifiable_credential/verification_result.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'verification_result.g.dart'; + +@JsonSerializable() +class VerificationResult { + final List warnings; + final List errors; + + const VerificationResult({required this.warnings, required this.errors}); + + factory VerificationResult.fromJson(Map json) => + _$VerificationResultFromJson(json); +} diff --git a/lib/models/verifiable_credential/verification_result.g.dart b/lib/models/verifiable_credential/verification_result.g.dart new file mode 100644 index 00000000..58ccfb52 --- /dev/null +++ b/lib/models/verifiable_credential/verification_result.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'verification_result.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +VerificationResult _$VerificationResultFromJson(Map json) => + VerificationResult( + warnings: + (json['warnings'] as List).map((e) => e as String).toList(), + errors: + (json['errors'] as List).map((e) => e as String).toList(), + ); + +Map _$VerificationResultToJson(VerificationResult instance) => + { + 'warnings': instance.warnings, + 'errors': instance.errors, + };