diff --git a/assets/i18n/en.json b/assets/i18n/en.json index eeaa476..d2dcd8c 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -31,7 +31,8 @@ "cancel": "Cancel", "delete": "Delete", "accept": "Accept", - "reject": "Reject" + "reject": "Reject", + "waiting_for_network": "Waiting For Network" }, "toast": { "error": "Error", diff --git a/doc/invitations.md b/doc/invitations.md index a914dc3..f76dee8 100644 --- a/doc/invitations.md +++ b/doc/invitations.md @@ -16,27 +16,27 @@ 2. Get the ContactRequest record unicastinbox DHT record owner subkey from the network 3. Decrypt the writer secret with the password if necessary 4. Decrypt the ContactRequestPrivate chunk with the writer secret -5. Get the contact's AccountMaster record key +5. Get the contact's SuperIdentity record key 6. Verify identity signature on the SignedContactInvitation 7. Verify expiration 8. Display the profile and ask if the user wants to accept or reject the invitation ## Accepting an invitation 1. Create a Local Chat DHT record (no content yet, will be encrypted with DH of contact identity key) -2. Create ContactResponse with chat dht record and account master +2. Create ContactResponse with chat dht record and superidentity 3. Create SignedContactResponse with accept=true signed with identity 4. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret 5. Add a local contact with the remote chat dht record, updating from the remote profile in it ## Rejecting an invitation -1. Create ContactResponse with account master +1. Create ContactResponse with superidentity 2. Create SignedContactResponse with accept=false signed with identity 3. Set ContactRequest unicastinbox DHT record writer subkey with SignedContactResponse, encrypted with writer secret ## Receiving an accept/reject 1. Open and get SignedContactResponse from ContactRequest unicastinbox DHT record 2. Decrypt with writer secret -3. Get DHT record for contact's AccountMaster +3. Get DHT record for contact's SuperIdentity 4. Validate the SignedContactResponse signature If accept == false: diff --git a/lib/account_manager/models/active_account_info.dart b/lib/account_manager/models/active_account_info.dart index 0e1a0ef..5f69e32 100644 --- a/lib/account_manager/models/active_account_info.dart +++ b/lib/account_manager/models/active_account_info.dart @@ -14,14 +14,18 @@ class ActiveAccountInfo { }); // + TypedKey get superIdentityRecordKey => localAccount.superIdentity.recordKey; TypedKey get accountRecordKey => userLogin.accountRecordInfo.accountRecord.recordKey; - - KeyPair get conversationWriter { - final identityKey = localAccount.identityMaster.identityPublicKey; - final identitySecret = userLogin.identitySecret; - return KeyPair(key: identityKey, secret: identitySecret.value); - } + TypedKey get identityTypedPublicKey => + localAccount.superIdentity.currentInstance.typedPublicKey; + PublicKey get identityPublicKey => + localAccount.superIdentity.currentInstance.publicKey; + SecretKey get identitySecretKey => userLogin.identitySecret.value; + KeyPair get identityWriter => + KeyPair(key: identityPublicKey, secret: identitySecretKey); + Future get identityCryptoSystem => + localAccount.superIdentity.currentInstance.cryptoSystem; Future makeConversationCrypto( TypedKey remoteIdentityPublicKey) async { diff --git a/lib/account_manager/models/local_account/local_account.dart b/lib/account_manager/models/local_account/local_account.dart index 1998961..76070ae 100644 --- a/lib/account_manager/models/local_account/local_account.dart +++ b/lib/account_manager/models/local_account/local_account.dart @@ -11,25 +11,31 @@ part 'local_account.freezed.dart'; // Local Accounts are stored in a table locally and not backed by a DHT key // and represents the accounts that have been added/imported // on the current device. -// Stores a copy of the IdentityMaster associated with the account +// Stores a copy of the most recent SuperIdentity associated with the account // and the identitySecretKey optionally encrypted by an unlock code // This is the root of the account information tree for VeilidChat // @freezed class LocalAccount with _$LocalAccount { const factory LocalAccount({ - // The master key record for the account, containing the identityPublicKey - required IdentityMaster identityMaster, - // The encrypted identity secret that goes with + // The super identity key record for the account, + // containing the publicKey in the currentIdentity + required SuperIdentity superIdentity, + + // The encrypted currentIdentity secret that goes with // the identityPublicKey with appended salt @Uint8ListJsonConverter() required Uint8List identitySecretBytes, + // The kind of encryption input used on the account required EncryptionKeyType encryptionKeyType, + // If account is not hidden, password can be retrieved via required bool biometricsEnabled, + // Keep account hidden unless account password is entered // (tries all hidden accounts with auth method (no biometrics)) required bool hiddenAccount, + // Display name for account until it is unlocked required String name, }) = _LocalAccount; diff --git a/lib/account_manager/models/local_account/local_account.freezed.dart b/lib/account_manager/models/local_account/local_account.freezed.dart index 781d1de..92e376f 100644 --- a/lib/account_manager/models/local_account/local_account.freezed.dart +++ b/lib/account_manager/models/local_account/local_account.freezed.dart @@ -20,9 +20,10 @@ LocalAccount _$LocalAccountFromJson(Map json) { /// @nodoc mixin _$LocalAccount { -// The master key record for the account, containing the identityPublicKey - IdentityMaster get identityMaster => - throw _privateConstructorUsedError; // The encrypted identity secret that goes with +// The super identity key record for the account, +// containing the publicKey in the currentIdentity + SuperIdentity get superIdentity => + throw _privateConstructorUsedError; // The encrypted currentIdentity secret that goes with // the identityPublicKey with appended salt @Uint8ListJsonConverter() Uint8List get identitySecretBytes => @@ -49,14 +50,14 @@ abstract class $LocalAccountCopyWith<$Res> { _$LocalAccountCopyWithImpl<$Res, LocalAccount>; @useResult $Res call( - {IdentityMaster identityMaster, + {SuperIdentity superIdentity, @Uint8ListJsonConverter() Uint8List identitySecretBytes, EncryptionKeyType encryptionKeyType, bool biometricsEnabled, bool hiddenAccount, String name}); - $IdentityMasterCopyWith<$Res> get identityMaster; + $SuperIdentityCopyWith<$Res> get superIdentity; } /// @nodoc @@ -72,7 +73,7 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount> @pragma('vm:prefer-inline') @override $Res call({ - Object? identityMaster = null, + Object? superIdentity = null, Object? identitySecretBytes = null, Object? encryptionKeyType = null, Object? biometricsEnabled = null, @@ -80,10 +81,10 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount> Object? name = null, }) { return _then(_value.copyWith( - identityMaster: null == identityMaster - ? _value.identityMaster - : identityMaster // ignore: cast_nullable_to_non_nullable - as IdentityMaster, + superIdentity: null == superIdentity + ? _value.superIdentity + : superIdentity // ignore: cast_nullable_to_non_nullable + as SuperIdentity, identitySecretBytes: null == identitySecretBytes ? _value.identitySecretBytes : identitySecretBytes // ignore: cast_nullable_to_non_nullable @@ -109,9 +110,9 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount> @override @pragma('vm:prefer-inline') - $IdentityMasterCopyWith<$Res> get identityMaster { - return $IdentityMasterCopyWith<$Res>(_value.identityMaster, (value) { - return _then(_value.copyWith(identityMaster: value) as $Val); + $SuperIdentityCopyWith<$Res> get superIdentity { + return $SuperIdentityCopyWith<$Res>(_value.superIdentity, (value) { + return _then(_value.copyWith(superIdentity: value) as $Val); }); } } @@ -125,7 +126,7 @@ abstract class _$$LocalAccountImplCopyWith<$Res> @override @useResult $Res call( - {IdentityMaster identityMaster, + {SuperIdentity superIdentity, @Uint8ListJsonConverter() Uint8List identitySecretBytes, EncryptionKeyType encryptionKeyType, bool biometricsEnabled, @@ -133,7 +134,7 @@ abstract class _$$LocalAccountImplCopyWith<$Res> String name}); @override - $IdentityMasterCopyWith<$Res> get identityMaster; + $SuperIdentityCopyWith<$Res> get superIdentity; } /// @nodoc @@ -147,7 +148,7 @@ class __$$LocalAccountImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? identityMaster = null, + Object? superIdentity = null, Object? identitySecretBytes = null, Object? encryptionKeyType = null, Object? biometricsEnabled = null, @@ -155,10 +156,10 @@ class __$$LocalAccountImplCopyWithImpl<$Res> Object? name = null, }) { return _then(_$LocalAccountImpl( - identityMaster: null == identityMaster - ? _value.identityMaster - : identityMaster // ignore: cast_nullable_to_non_nullable - as IdentityMaster, + superIdentity: null == superIdentity + ? _value.superIdentity + : superIdentity // ignore: cast_nullable_to_non_nullable + as SuperIdentity, identitySecretBytes: null == identitySecretBytes ? _value.identitySecretBytes : identitySecretBytes // ignore: cast_nullable_to_non_nullable @@ -187,7 +188,7 @@ class __$$LocalAccountImplCopyWithImpl<$Res> @JsonSerializable() class _$LocalAccountImpl implements _LocalAccount { const _$LocalAccountImpl( - {required this.identityMaster, + {required this.superIdentity, @Uint8ListJsonConverter() required this.identitySecretBytes, required this.encryptionKeyType, required this.biometricsEnabled, @@ -197,10 +198,11 @@ class _$LocalAccountImpl implements _LocalAccount { factory _$LocalAccountImpl.fromJson(Map json) => _$$LocalAccountImplFromJson(json); -// The master key record for the account, containing the identityPublicKey +// The super identity key record for the account, +// containing the publicKey in the currentIdentity @override - final IdentityMaster identityMaster; -// The encrypted identity secret that goes with + final SuperIdentity superIdentity; +// The encrypted currentIdentity secret that goes with // the identityPublicKey with appended salt @override @Uint8ListJsonConverter() @@ -221,7 +223,7 @@ class _$LocalAccountImpl implements _LocalAccount { @override String toString() { - return 'LocalAccount(identityMaster: $identityMaster, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)'; + return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)'; } @override @@ -229,8 +231,8 @@ class _$LocalAccountImpl implements _LocalAccount { return identical(this, other) || (other.runtimeType == runtimeType && other is _$LocalAccountImpl && - (identical(other.identityMaster, identityMaster) || - other.identityMaster == identityMaster) && + (identical(other.superIdentity, superIdentity) || + other.superIdentity == superIdentity) && const DeepCollectionEquality() .equals(other.identitySecretBytes, identitySecretBytes) && (identical(other.encryptionKeyType, encryptionKeyType) || @@ -246,7 +248,7 @@ class _$LocalAccountImpl implements _LocalAccount { @override int get hashCode => Object.hash( runtimeType, - identityMaster, + superIdentity, const DeepCollectionEquality().hash(identitySecretBytes), encryptionKeyType, biometricsEnabled, @@ -269,7 +271,7 @@ class _$LocalAccountImpl implements _LocalAccount { abstract class _LocalAccount implements LocalAccount { const factory _LocalAccount( - {required final IdentityMaster identityMaster, + {required final SuperIdentity superIdentity, @Uint8ListJsonConverter() required final Uint8List identitySecretBytes, required final EncryptionKeyType encryptionKeyType, required final bool biometricsEnabled, @@ -279,9 +281,10 @@ abstract class _LocalAccount implements LocalAccount { factory _LocalAccount.fromJson(Map json) = _$LocalAccountImpl.fromJson; - @override // The master key record for the account, containing the identityPublicKey - IdentityMaster get identityMaster; - @override // The encrypted identity secret that goes with + @override // The super identity key record for the account, +// containing the publicKey in the currentIdentity + SuperIdentity get superIdentity; + @override // The encrypted currentIdentity secret that goes with // the identityPublicKey with appended salt @Uint8ListJsonConverter() Uint8List get identitySecretBytes; diff --git a/lib/account_manager/models/local_account/local_account.g.dart b/lib/account_manager/models/local_account/local_account.g.dart index 4e8a7b2..b60c226 100644 --- a/lib/account_manager/models/local_account/local_account.g.dart +++ b/lib/account_manager/models/local_account/local_account.g.dart @@ -8,7 +8,7 @@ part of 'local_account.dart'; _$LocalAccountImpl _$$LocalAccountImplFromJson(Map json) => _$LocalAccountImpl( - identityMaster: IdentityMaster.fromJson(json['identity_master']), + superIdentity: SuperIdentity.fromJson(json['super_identity']), identitySecretBytes: const Uint8ListJsonConverter() .fromJson(json['identity_secret_bytes']), encryptionKeyType: @@ -20,7 +20,7 @@ _$LocalAccountImpl _$$LocalAccountImplFromJson(Map json) => Map _$$LocalAccountImplToJson(_$LocalAccountImpl instance) => { - 'identity_master': instance.identityMaster.toJson(), + 'super_identity': instance.superIdentity.toJson(), 'identity_secret_bytes': const Uint8ListJsonConverter().toJson(instance.identitySecretBytes), 'encryption_key_type': instance.encryptionKeyType.toJson(), diff --git a/lib/account_manager/models/user_login/user_login.dart b/lib/account_manager/models/user_login/user_login.dart index 4e23184..7c024cf 100644 --- a/lib/account_manager/models/user_login/user_login.dart +++ b/lib/account_manager/models/user_login/user_login.dart @@ -7,12 +7,13 @@ part 'user_login.g.dart'; // Represents a currently logged in account // User logins are stored in the user_logins tablestore table -// indexed by the accountMasterKey +// indexed by the accountSuperIdentityRecordKey @freezed class UserLogin with _$UserLogin { const factory UserLogin({ - // Master record key for the user used to index the local accounts table - required TypedKey accountMasterRecordKey, + // SuperIdentity record key for the user + // used to index the local accounts table + required TypedKey superIdentityRecordKey, // The identity secret as unlocked from the local accounts table required TypedSecret identitySecret, // The account record key, owner key and secret pulled from the identity diff --git a/lib/account_manager/models/user_login/user_login.freezed.dart b/lib/account_manager/models/user_login/user_login.freezed.dart index a25b4ab..c93ee7b 100644 --- a/lib/account_manager/models/user_login/user_login.freezed.dart +++ b/lib/account_manager/models/user_login/user_login.freezed.dart @@ -20,8 +20,9 @@ UserLogin _$UserLoginFromJson(Map json) { /// @nodoc mixin _$UserLogin { -// Master record key for the user used to index the local accounts table - Typed get accountMasterRecordKey => +// SuperIdentity record key for the user +// used to index the local accounts table + Typed get superIdentityRecordKey => throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table Typed get identitySecret => throw _privateConstructorUsedError; // The account record key, owner key and secret pulled from the identity @@ -41,7 +42,7 @@ abstract class $UserLoginCopyWith<$Res> { _$UserLoginCopyWithImpl<$Res, UserLogin>; @useResult $Res call( - {Typed accountMasterRecordKey, + {Typed superIdentityRecordKey, Typed identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive}); @@ -62,15 +63,15 @@ class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin> @pragma('vm:prefer-inline') @override $Res call({ - Object? accountMasterRecordKey = null, + Object? superIdentityRecordKey = null, Object? identitySecret = null, Object? accountRecordInfo = null, Object? lastActive = null, }) { return _then(_value.copyWith( - accountMasterRecordKey: null == accountMasterRecordKey - ? _value.accountMasterRecordKey - : accountMasterRecordKey // ignore: cast_nullable_to_non_nullable + superIdentityRecordKey: null == superIdentityRecordKey + ? _value.superIdentityRecordKey + : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable as Typed, identitySecret: null == identitySecret ? _value.identitySecret @@ -105,7 +106,7 @@ abstract class _$$UserLoginImplCopyWith<$Res> @override @useResult $Res call( - {Typed accountMasterRecordKey, + {Typed superIdentityRecordKey, Typed identitySecret, AccountRecordInfo accountRecordInfo, Timestamp lastActive}); @@ -125,15 +126,15 @@ class __$$UserLoginImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? accountMasterRecordKey = null, + Object? superIdentityRecordKey = null, Object? identitySecret = null, Object? accountRecordInfo = null, Object? lastActive = null, }) { return _then(_$UserLoginImpl( - accountMasterRecordKey: null == accountMasterRecordKey - ? _value.accountMasterRecordKey - : accountMasterRecordKey // ignore: cast_nullable_to_non_nullable + superIdentityRecordKey: null == superIdentityRecordKey + ? _value.superIdentityRecordKey + : superIdentityRecordKey // ignore: cast_nullable_to_non_nullable as Typed, identitySecret: null == identitySecret ? _value.identitySecret @@ -155,7 +156,7 @@ class __$$UserLoginImplCopyWithImpl<$Res> @JsonSerializable() class _$UserLoginImpl implements _UserLogin { const _$UserLoginImpl( - {required this.accountMasterRecordKey, + {required this.superIdentityRecordKey, required this.identitySecret, required this.accountRecordInfo, required this.lastActive}); @@ -163,9 +164,10 @@ class _$UserLoginImpl implements _UserLogin { factory _$UserLoginImpl.fromJson(Map json) => _$$UserLoginImplFromJson(json); -// Master record key for the user used to index the local accounts table +// SuperIdentity record key for the user +// used to index the local accounts table @override - final Typed accountMasterRecordKey; + final Typed superIdentityRecordKey; // The identity secret as unlocked from the local accounts table @override final Typed identitySecret; @@ -178,7 +180,7 @@ class _$UserLoginImpl implements _UserLogin { @override String toString() { - return 'UserLogin(accountMasterRecordKey: $accountMasterRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)'; + return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)'; } @override @@ -186,8 +188,8 @@ class _$UserLoginImpl implements _UserLogin { return identical(this, other) || (other.runtimeType == runtimeType && other is _$UserLoginImpl && - (identical(other.accountMasterRecordKey, accountMasterRecordKey) || - other.accountMasterRecordKey == accountMasterRecordKey) && + (identical(other.superIdentityRecordKey, superIdentityRecordKey) || + other.superIdentityRecordKey == superIdentityRecordKey) && (identical(other.identitySecret, identitySecret) || other.identitySecret == identitySecret) && (identical(other.accountRecordInfo, accountRecordInfo) || @@ -198,7 +200,7 @@ class _$UserLoginImpl implements _UserLogin { @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, accountMasterRecordKey, + int get hashCode => Object.hash(runtimeType, superIdentityRecordKey, identitySecret, accountRecordInfo, lastActive); @JsonKey(ignore: true) @@ -217,7 +219,7 @@ class _$UserLoginImpl implements _UserLogin { abstract class _UserLogin implements UserLogin { const factory _UserLogin( - {required final Typed accountMasterRecordKey, + {required final Typed superIdentityRecordKey, required final Typed identitySecret, required final AccountRecordInfo accountRecordInfo, required final Timestamp lastActive}) = _$UserLoginImpl; @@ -225,8 +227,9 @@ abstract class _UserLogin implements UserLogin { factory _UserLogin.fromJson(Map json) = _$UserLoginImpl.fromJson; - @override // Master record key for the user used to index the local accounts table - Typed get accountMasterRecordKey; + @override // SuperIdentity record key for the user +// used to index the local accounts table + Typed get superIdentityRecordKey; @override // The identity secret as unlocked from the local accounts table Typed get identitySecret; @override // The account record key, owner key and secret pulled from the identity diff --git a/lib/account_manager/models/user_login/user_login.g.dart b/lib/account_manager/models/user_login/user_login.g.dart index 267fc55..173d853 100644 --- a/lib/account_manager/models/user_login/user_login.g.dart +++ b/lib/account_manager/models/user_login/user_login.g.dart @@ -8,8 +8,8 @@ part of 'user_login.dart'; _$UserLoginImpl _$$UserLoginImplFromJson(Map json) => _$UserLoginImpl( - accountMasterRecordKey: Typed.fromJson( - json['account_master_record_key']), + superIdentityRecordKey: Typed.fromJson( + json['super_identity_record_key']), identitySecret: Typed.fromJson(json['identity_secret']), accountRecordInfo: @@ -19,7 +19,7 @@ _$UserLoginImpl _$$UserLoginImplFromJson(Map json) => Map _$$UserLoginImplToJson(_$UserLoginImpl instance) => { - 'account_master_record_key': instance.accountMasterRecordKey.toJson(), + 'super_identity_record_key': instance.superIdentityRecordKey.toJson(), 'identity_secret': instance.identitySecret.toJson(), 'account_record_info': instance.accountRecordInfo.toJson(), 'last_active': instance.lastActive.toJson(), diff --git a/lib/account_manager/repository/account_repository/account_repository.dart b/lib/account_manager/repository/account_repository/account_repository.dart index 4691d52..ac29913 100644 --- a/lib/account_manager/repository/account_repository/account_repository.dart +++ b/lib/account_manager/repository/account_repository/account_repository.dart @@ -87,30 +87,30 @@ class AccountRepository { : fetchUserLogin(activeLocalAccount); } - LocalAccount? fetchLocalAccount(TypedKey accountMasterRecordKey) { + LocalAccount? fetchLocalAccount(TypedKey accountSuperIdentityRecordKey) { final localAccounts = _localAccounts.value; final idx = localAccounts.indexWhere( - (e) => e.identityMaster.masterRecordKey == accountMasterRecordKey); + (e) => e.superIdentity.recordKey == accountSuperIdentityRecordKey); if (idx == -1) { return null; } return localAccounts[idx]; } - UserLogin? fetchUserLogin(TypedKey accountMasterRecordKey) { + UserLogin? fetchUserLogin(TypedKey superIdentityRecordKey) { final userLogins = _userLogins.value; final idx = userLogins - .indexWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey); + .indexWhere((e) => e.superIdentityRecordKey == superIdentityRecordKey); if (idx == -1) { return null; } return userLogins[idx]; } - AccountInfo getAccountInfo(TypedKey? accountMasterRecordKey) { + AccountInfo getAccountInfo(TypedKey? superIdentityRecordKey) { // Get active account if we have one final activeLocalAccount = getActiveLocalAccount(); - if (accountMasterRecordKey == null) { + if (superIdentityRecordKey == null) { if (activeLocalAccount == null) { // No user logged in return const AccountInfo( @@ -118,12 +118,12 @@ class AccountRepository { active: false, activeAccountInfo: null); } - accountMasterRecordKey = activeLocalAccount; + superIdentityRecordKey = activeLocalAccount; } - final active = accountMasterRecordKey == activeLocalAccount; + final active = superIdentityRecordKey == activeLocalAccount; // Get which local account we want to fetch the profile for - final localAccount = fetchLocalAccount(accountMasterRecordKey); + final localAccount = fetchLocalAccount(superIdentityRecordKey); if (localAccount == null) { // account does not exist return AccountInfo( @@ -133,7 +133,7 @@ class AccountRepository { } // See if we've logged into this account or if it is locked - final userLogin = fetchUserLogin(accountMasterRecordKey); + final userLogin = fetchUserLogin(superIdentityRecordKey); if (userLogin == null) { // Account was locked return AccountInfo( @@ -165,33 +165,32 @@ class AccountRepository { _streamController.add(AccountRepositoryChange.localAccounts); } - /// Creates a new master identity, an account associated with the master - /// identity, stores the account in the identity key and then logs into - /// that account with no password set at this time - Future createWithNewMasterIdentity( - NewProfileSpec newProfileSpec) async { - log.debug('Creating master identity'); - final imws = await IdentityMasterWithSecrets.create(); + /// Creates a new super identity, an identity instance, an account associated + /// with the identity instance, stores the account in the identity key and + /// then logs into that account with no password set at this time + Future createWithNewSuperIdentity(NewProfileSpec newProfileSpec) async { + log.debug('Creating super identity'); + final wsi = await WritableSuperIdentity.create(); try { final localAccount = await _newLocalAccount( - identityMaster: imws.identityMaster, - identitySecret: imws.identitySecret, + superIdentity: wsi.superIdentity, + identitySecret: wsi.identitySecret, newProfileSpec: newProfileSpec); // Log in the new account by default with no pin - final ok = await login(localAccount.identityMaster.masterRecordKey, - EncryptionKeyType.none, ''); + final ok = await login( + localAccount.superIdentity.recordKey, EncryptionKeyType.none, ''); assert(ok, 'login with none should never fail'); } on Exception catch (_) { - await imws.delete(); + await wsi.delete(); rethrow; } } - /// Creates a new Account associated with master identity + /// Creates a new Account associated with the current instance of the identity /// Adds a logged-out LocalAccount to track its existence on this device Future _newLocalAccount( - {required IdentityMaster identityMaster, + {required SuperIdentity superIdentity, required SecretKey identitySecret, required NewProfileSpec newProfileSpec, EncryptionKeyType encryptionKeyType = EncryptionKeyType.none, @@ -201,8 +200,9 @@ class AccountRepository { final localAccounts = await _localAccounts.get(); // Add account with profile to DHT - await identityMaster.addAccountToIdentity( - identitySecret: identitySecret, + await superIdentity.currentInstance.addAccount( + superRecordKey: superIdentity.recordKey, + secretKey: identitySecret, accountKey: veilidChatAccountKey, createAccountCallback: (parent) async { // Make empty contact list @@ -235,13 +235,13 @@ class AccountRepository { ..contactList = contactList.toProto() ..contactInvitationRecords = contactInvitationRecords.toProto() ..chatList = chatRecords.toProto(); - return account; + return account.writeToBuffer(); }); // Encrypt identitySecret with key final identitySecretBytes = await encryptionKeyType.encryptSecretToBytes( secret: identitySecret, - cryptoKind: identityMaster.identityRecordKey.kind, + cryptoKind: superIdentity.currentInstance.recordKey.kind, encryptionKey: encryptionKey, ); @@ -250,7 +250,7 @@ class AccountRepository { // as that is not to be persisted, and only pulled from the identity key // and optionally decrypted with the unlock password final localAccount = LocalAccount( - identityMaster: identityMaster, + superIdentity: superIdentity, identitySecretBytes: identitySecretBytes, encryptionKeyType: encryptionKeyType, biometricsEnabled: false, @@ -269,12 +269,12 @@ class AccountRepository { } /// Remove an account and wipe the messages for this account from this device - Future deleteLocalAccount(TypedKey accountMasterRecordKey) async { - await logout(accountMasterRecordKey); + Future deleteLocalAccount(TypedKey superIdentityRecordKey) async { + await logout(superIdentityRecordKey); final localAccounts = await _localAccounts.get(); final newLocalAccounts = localAccounts.removeWhere( - (la) => la.identityMaster.masterRecordKey == accountMasterRecordKey); + (la) => la.superIdentity.recordKey == superIdentityRecordKey); await _localAccounts.set(newLocalAccounts); _streamController.add(AccountRepositoryChange.localAccounts); @@ -290,31 +290,35 @@ class AccountRepository { /// Delete an account from all devices - Future switchToAccount(TypedKey? accountMasterRecordKey) async { + Future switchToAccount(TypedKey? superIdentityRecordKey) async { final activeLocalAccount = await _activeLocalAccount.get(); - if (activeLocalAccount == accountMasterRecordKey) { + if (activeLocalAccount == superIdentityRecordKey) { // Nothing to do return; } - if (accountMasterRecordKey != null) { + if (superIdentityRecordKey != null) { // Assert the specified record key can be found, will throw if not final _ = _userLogins.value.firstWhere( - (ul) => ul.accountMasterRecordKey == accountMasterRecordKey); + (ul) => ul.superIdentityRecordKey == superIdentityRecordKey); } - await _activeLocalAccount.set(accountMasterRecordKey); + await _activeLocalAccount.set(superIdentityRecordKey); _streamController.add(AccountRepositoryChange.activeLocalAccount); } Future _decryptedLogin( - IdentityMaster identityMaster, SecretKey identitySecret) async { + SuperIdentity superIdentity, SecretKey identitySecret) async { // Verify identity secret works and return the valid cryptosystem - final cs = await identityMaster.validateIdentitySecret(identitySecret); + final cs = await superIdentity.currentInstance + .validateIdentitySecret(identitySecret); // Read the identity key to get the account keys - final accountRecordInfoList = await identityMaster.readAccountsFromIdentity( - identitySecret: identitySecret, accountKey: veilidChatAccountKey); + final accountRecordInfoList = await superIdentity.currentInstance + .readAccount( + superRecordKey: superIdentity.recordKey, + secretKey: identitySecret, + accountKey: veilidChatAccountKey); if (accountRecordInfoList.length > 1) { throw IdentityException.limitExceeded; } else if (accountRecordInfoList.isEmpty) { @@ -326,11 +330,11 @@ class AccountRepository { final userLogins = await _userLogins.get(); final now = Veilid.instance.now(); final newUserLogins = userLogins.replaceFirstWhere( - (ul) => ul.accountMasterRecordKey == identityMaster.masterRecordKey, + (ul) => ul.superIdentityRecordKey == superIdentity.recordKey, (ul) => ul != null ? ul.copyWith(lastActive: now) : UserLogin( - accountMasterRecordKey: identityMaster.masterRecordKey, + superIdentityRecordKey: superIdentity.recordKey, identitySecret: TypedSecret(kind: cs.kind(), value: identitySecret), accountRecordInfo: accountRecordInfo, @@ -338,7 +342,7 @@ class AccountRepository { addIfNotFound: true); await _userLogins.set(newUserLogins); - await _activeLocalAccount.set(identityMaster.masterRecordKey); + await _activeLocalAccount.set(superIdentity.recordKey); _streamController ..add(AccountRepositoryChange.userLogins) @@ -347,13 +351,13 @@ class AccountRepository { return true; } - Future login(TypedKey accountMasterRecordKey, + Future login(TypedKey accountSuperRecordKey, EncryptionKeyType encryptionKeyType, String encryptionKey) async { final localAccounts = await _localAccounts.get(); // Get account, throws if not found final localAccount = localAccounts.firstWhere( - (la) => la.identityMaster.masterRecordKey == accountMasterRecordKey); + (la) => la.superIdentity.recordKey == accountSuperRecordKey); // Log in with this local account @@ -365,12 +369,12 @@ class AccountRepository { final identitySecret = await localAccount.encryptionKeyType.decryptSecretFromBytes( secretBytes: localAccount.identitySecretBytes, - cryptoKind: localAccount.identityMaster.identityRecordKey.kind, + cryptoKind: localAccount.superIdentity.currentInstance.recordKey.kind, encryptionKey: encryptionKey, ); // Validate this secret with the identity public key and log in - return _decryptedLogin(localAccount.identityMaster, identitySecret); + return _decryptedLogin(localAccount.superIdentity, identitySecret); } Future logout(TypedKey? accountMasterRecordKey) async { @@ -390,20 +394,20 @@ class AccountRepository { // Remove user from active logins list final newUserLogins = (await _userLogins.get()) - .removeWhere((ul) => ul.accountMasterRecordKey == logoutUser); + .removeWhere((ul) => ul.superIdentityRecordKey == logoutUser); await _userLogins.set(newUserLogins); _streamController.add(AccountRepositoryChange.userLogins); } Future openAccountRecord(UserLogin userLogin) async { - final localAccount = fetchLocalAccount(userLogin.accountMasterRecordKey)!; + final localAccount = fetchLocalAccount(userLogin.superIdentityRecordKey)!; // Record not yet open, do it final pool = DHTRecordPool.instance; final record = await pool.openRecordOwned( userLogin.accountRecordInfo.accountRecord, debugName: 'AccountRepository::openAccountRecord::AccountRecord', - parent: localAccount.identityMaster.identityRecordKey); + parent: localAccount.superIdentity.currentInstance.recordKey); return record; } diff --git a/lib/account_manager/views/new_account_page/new_account_page.dart b/lib/account_manager/views/new_account_page.dart similarity index 52% rename from lib/account_manager/views/new_account_page/new_account_page.dart rename to lib/account_manager/views/new_account_page.dart index 38664ba..7e15a32 100644 --- a/lib/account_manager/views/new_account_page/new_account_page.dart +++ b/lib/account_manager/views/new_account_page.dart @@ -1,16 +1,17 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:go_router/go_router.dart'; -import '../../../layout/default_app_bar.dart'; -import '../../../theme/theme.dart'; -import '../../../tools/tools.dart'; -import '../../../veilid_processor/veilid_processor.dart'; -import '../../account_manager.dart'; +import '../../layout/default_app_bar.dart'; +import '../../theme/theme.dart'; +import '../../tools/tools.dart'; +import '../../veilid_processor/veilid_processor.dart'; +import '../account_manager.dart'; class NewAccountPage extends StatefulWidget { const NewAccountPage({super.key}); @@ -36,63 +37,76 @@ class NewAccountPageState extends State { } Widget _newAccountForm(BuildContext context, - {required Future Function(GlobalKey) - onSubmit}) => - FormBuilder( - key: _formKey, - child: ListView( - children: [ - Text(translate('new_account_page.header')) - .textStyle(context.headlineSmall) - .paddingSymmetric(vertical: 16), - FormBuilderTextField( - autofocus: true, - name: formFieldName, - decoration: - InputDecoration(labelText: translate('account.form_name')), - maxLength: 64, - // The validator receives the text that the user has entered. - validator: FormBuilderValidators.compose([ - FormBuilderValidators.required(), - ]), - textInputAction: TextInputAction.next, - ), - FormBuilderTextField( - name: formFieldPronouns, - maxLength: 64, - decoration: InputDecoration( - labelText: translate('account.form_pronouns')), - textInputAction: TextInputAction.next, - ), - Row(children: [ - const Spacer(), - Text(translate('new_account_page.instructions')) - .toCenter() - .flexible(flex: 6), - const Spacer(), - ]).paddingSymmetric(vertical: 4), - ElevatedButton( - onPressed: () async { - if (_formKey.currentState?.saveAndValidate() ?? false) { - setState(() { - isInAsyncCall = true; - }); - try { - await onSubmit(_formKey); - } finally { - if (mounted) { + {required Future Function(GlobalKey) onSubmit}) { + final networkReady = context + .watch() + .state + .asData + ?.value + .isPublicInternetReady ?? + false; + final canSubmit = networkReady; + + return FormBuilder( + key: _formKey, + child: ListView( + children: [ + Text(translate('new_account_page.header')) + .textStyle(context.headlineSmall) + .paddingSymmetric(vertical: 16), + FormBuilderTextField( + autofocus: true, + name: formFieldName, + decoration: + InputDecoration(labelText: translate('account.form_name')), + maxLength: 64, + // The validator receives the text that the user has entered. + validator: FormBuilderValidators.compose([ + FormBuilderValidators.required(), + ]), + textInputAction: TextInputAction.next, + ), + FormBuilderTextField( + name: formFieldPronouns, + maxLength: 64, + decoration: + InputDecoration(labelText: translate('account.form_pronouns')), + textInputAction: TextInputAction.next, + ), + Row(children: [ + const Spacer(), + Text(translate('new_account_page.instructions')) + .toCenter() + .flexible(flex: 6), + const Spacer(), + ]).paddingSymmetric(vertical: 4), + ElevatedButton( + onPressed: !canSubmit + ? null + : () async { + if (_formKey.currentState?.saveAndValidate() ?? false) { setState(() { - isInAsyncCall = false; + isInAsyncCall = true; }); + try { + await onSubmit(_formKey); + } finally { + if (mounted) { + setState(() { + isInAsyncCall = false; + }); + } + } } - } - } - }, - child: Text(translate('new_account_page.create')), - ).paddingSymmetric(vertical: 4).alignAtCenterRight(), - ], - ), - ); + }, + child: Text(translate(!networkReady + ? 'button.waiting_for_network' + : 'new_account_page.create')), + ).paddingSymmetric(vertical: 4).alignAtCenterRight(), + ], + ), + ); + } @override Widget build(BuildContext context) { @@ -127,7 +141,7 @@ class NewAccountPageState extends State { NewProfileSpec(name: name, pronouns: pronouns); await AccountRepository.instance - .createWithNewMasterIdentity(newProfileSpec); + .createWithNewSuperIdentity(newProfileSpec); } on Exception catch (e) { if (context.mounted) { await showErrorModal(context, translate('new_account_page.error'), diff --git a/lib/account_manager/views/views.dart b/lib/account_manager/views/views.dart index a10db1b..2acc537 100644 --- a/lib/account_manager/views/views.dart +++ b/lib/account_manager/views/views.dart @@ -1,2 +1,2 @@ -export 'new_account_page/new_account_page.dart'; +export 'new_account_page.dart'; export 'profile_widget.dart'; diff --git a/lib/app.dart b/lib/app.dart index e0c08c5..aaa0190 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,4 +1,5 @@ import 'package:animated_theme_switcher/animated_theme_switcher.dart'; +import 'package:async_tools/async_tools.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,6 +8,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:provider/provider.dart'; +import 'package:veilid_support/veilid_support.dart'; import 'account_manager/account_manager.dart'; import 'init.dart'; @@ -22,6 +24,10 @@ class ReloadThemeIntent extends Intent { const ReloadThemeIntent(); } +class AttachDetachThemeIntent extends Intent { + const AttachDetachThemeIntent(); +} + class VeilidChatApp extends StatelessWidget { const VeilidChatApp({ required this.initialThemeData, @@ -37,6 +43,29 @@ class VeilidChatApp extends StatelessWidget { final theme = PreferencesRepository.instance.value.themePreferences.themeData(); ThemeSwitcher.of(context).changeTheme(theme: theme); + + // Hack to reload translations + final localizationDelegate = LocalizedApp.of(context).delegate; + singleFuture(this, () async { + await LocalizationDelegate.create( + fallbackLocale: localizationDelegate.fallbackLocale.toString(), + supportedLocales: localizationDelegate.supportedLocales + .map((x) => x.toString()) + .toList()); + }); + } + + void _attachDetachTheme(BuildContext context) { + singleFuture(this, () async { + if (ProcessorRepository.instance.processorConnectionState.isAttached) { + log.info('Detaching'); + await Veilid.instance.detach(); + } else if (ProcessorRepository + .instance.processorConnectionState.isDetached) { + log.info('Attaching'); + await Veilid.instance.attach(); + } + }); } Widget _buildShortcuts( @@ -48,10 +77,16 @@ class VeilidChatApp extends StatelessWidget { LogicalKeySet( LogicalKeyboardKey.alt, LogicalKeyboardKey.keyR): const ReloadThemeIntent(), + LogicalKeySet( + LogicalKeyboardKey.alt, LogicalKeyboardKey.keyD): + const AttachDetachThemeIntent(), }, child: Actions(actions: >{ ReloadThemeIntent: CallbackAction( onInvoke: (intent) => _reloadTheme(context)), + AttachDetachThemeIntent: + CallbackAction( + onInvoke: (intent) => _attachDetachTheme(context)), }, child: Focus(autofocus: true, child: builder(context))))); @override diff --git a/lib/chat/cubits/chat_component_cubit.dart b/lib/chat/cubits/chat_component_cubit.dart index 6ac2726..bc7431c 100644 --- a/lib/chat/cubits/chat_component_cubit.dart +++ b/lib/chat/cubits/chat_component_cubit.dart @@ -49,8 +49,7 @@ class ChatComponentCubit extends Cubit { required ActiveConversationState activeConversationState, required SingleContactMessagesCubit messagesCubit}) { // Make local 'User' - final localUserIdentityKey = - activeAccountInfo.localAccount.identityMaster.identityPublicTypedKey(); + final localUserIdentityKey = activeAccountInfo.identityTypedPublicKey; final localUser = types.User( id: localUserIdentityKey.toString(), firstName: accountRecordInfo.profile.name, diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 3fc8e91..b3c6325 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -114,13 +114,12 @@ class SingleContactMessagesCubit extends Cubit { _conversationCrypto = await _activeAccountInfo .makeConversationCrypto(_remoteIdentityPublicKey); _senderMessageIntegrity = await MessageIntegrity.create( - author: _activeAccountInfo.localAccount.identityMaster - .identityPublicTypedKey()); + author: _activeAccountInfo.identityTypedPublicKey); } // Open local messages key Future _initSentMessagesCubit() async { - final writer = _activeAccountInfo.conversationWriter; + final writer = _activeAccountInfo.identityWriter; _sentMessagesCubit = DHTLogCubit( open: () async => DHTLog.openWrite(_localMessagesRecordKey, writer, @@ -190,7 +189,7 @@ class SingleContactMessagesCubit extends Cubit { {int? tail, int? count, bool? follow, bool forceRefresh = false}) async { await _initWait(); - print('setWindow: tail=$tail count=$count, follow=$follow'); + // print('setWindow: tail=$tail count=$count, follow=$follow'); await _reconciledMessagesCubit!.setWindow( tail: tail, count: count, follow: follow, forceRefresh: forceRefresh); @@ -241,10 +240,8 @@ class SingleContactMessagesCubit extends Cubit { return; } - _reconciliation.reconcileMessages( - _activeAccountInfo.localAccount.identityMaster.identityPublicTypedKey(), - sentMessages, - _sentMessagesCubit!); + _reconciliation.reconcileMessages(_activeAccountInfo.identityTypedPublicKey, + sentMessages, _sentMessagesCubit!); // Update the view _renderState(); @@ -281,7 +278,7 @@ class SingleContactMessagesCubit extends Cubit { // Now sign it await _senderMessageIntegrity.signMessage( - message, _activeAccountInfo.userLogin.identitySecret.value); + message, _activeAccountInfo.identitySecretKey); } // Async process to send messages in the background @@ -334,8 +331,7 @@ class SingleContactMessagesCubit extends Cubit { for (final m in reconciledMessages.windowElements) { final isLocal = m.content.author.toVeilid() == - _activeAccountInfo.localAccount.identityMaster - .identityPublicTypedKey(); + _activeAccountInfo.identityTypedPublicKey; final reconciledTimestamp = Timestamp.fromInt64(m.reconciledTime); final sm = isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null; @@ -373,9 +369,7 @@ class SingleContactMessagesCubit extends Cubit { // Add common fields // id and signature will get set by _processMessageToSend message - ..author = _activeAccountInfo.localAccount.identityMaster - .identityPublicTypedKey() - .toProto() + ..author = _activeAccountInfo.identityTypedPublicKey.toProto() ..timestamp = Veilid.instance.now().toInt64(); // Put in the queue diff --git a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart index 640d416..e278c6d 100644 --- a/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart +++ b/lib/contact_invitation/cubits/contact_invitation_list_cubit.dart @@ -68,14 +68,16 @@ class ContactInvitationListCubit final pool = DHTRecordPool.instance; // Generate writer keypair to share with new contact - final cs = await pool.veilid.bestCryptoSystem(); - final contactRequestWriter = await cs.generateKeyPair(); - final conversationWriter = _activeAccountInfo.conversationWriter; + final crcs = await pool.veilid.bestCryptoSystem(); + final contactRequestWriter = await crcs.generateKeyPair(); + + final idcs = await _activeAccountInfo.identityCryptoSystem; + final identityWriter = _activeAccountInfo.identityWriter; // Encrypt the writer secret with the encryption key final encryptedSecret = await encryptionKeyType.encryptSecretToBytes( secret: contactRequestWriter.secret, - cryptoKind: cs.kind(), + cryptoKind: crcs.kind(), encryptionKey: encryptionKey, ); @@ -89,21 +91,21 @@ class ContactInvitationListCubit debugName: 'ContactInvitationListCubit::createInvitation::' 'LocalConversation', parent: _activeAccountInfo.accountRecordKey, - schema: DHTSchema.smpl(oCnt: 0, members: [ - DHTSchemaMember(mKey: conversationWriter.key, mCnt: 1) - ]))) + schema: DHTSchema.smpl( + oCnt: 0, + members: [DHTSchemaMember(mKey: identityWriter.key, mCnt: 1)]))) .deleteScope((localConversation) async { // dont bother reopening localConversation with writer // Make ContactRequestPrivate and encrypt with the writer secret final crpriv = proto.ContactRequestPrivate() ..writerKey = contactRequestWriter.key.toProto() ..profile = _account.profile - ..identityMasterRecordKey = - _activeAccountInfo.userLogin.accountMasterRecordKey.toProto() + ..superIdentityRecordKey = + _activeAccountInfo.userLogin.superIdentityRecordKey.toProto() ..chatRecordKey = localConversation.key.toProto() ..expiration = expiration?.toInt64() ?? Int64.ZERO; final crprivbytes = crpriv.writeToBuffer(); - final encryptedContactRequestPrivate = await cs.encryptAeadWithNonce( + final encryptedContactRequestPrivate = await crcs.encryptAeadWithNonce( crprivbytes, contactRequestWriter.secret); // Create ContactRequest and embed contactrequestprivate @@ -140,9 +142,8 @@ class ContactInvitationListCubit final cinvbytes = cinv.writeToBuffer(); final scinv = proto.SignedContactInvitation() ..contactInvitation = cinvbytes - ..identitySignature = (await cs.sign( - conversationWriter.key, conversationWriter.secret, cinvbytes)) - .toProto(); + ..identitySignature = + (await idcs.signWithKeyPair(identityWriter, cinvbytes)).toProto(); signedContactInvitationBytes = scinv.writeToBuffer(); // Create ContactInvitationRecord @@ -237,8 +238,6 @@ class ContactInvitationListCubit ValidContactInvitation? out; - final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind); - // Compare the invitation's contact request // inbox with our list of extant invitations // If we're chatting to ourselves, @@ -257,6 +256,8 @@ class ContactInvitationListCubit final contactRequest = await contactRequestInbox .getProtobuf(proto.ContactRequest.fromBuffer); + final cs = await pool.veilid.getCryptoSystem(contactRequestInboxKey.kind); + // Decrypt contact request private final encryptionKeyType = EncryptionKeyType.fromProto(contactRequest!.encryptionKeyType); @@ -276,16 +277,17 @@ class ContactInvitationListCubit final contactRequestPrivate = proto.ContactRequestPrivate.fromBuffer(contactRequestPrivateBytes); - final contactIdentityMasterRecordKey = - contactRequestPrivate.identityMasterRecordKey.toVeilid(); + final contactSuperIdentityRecordKey = + contactRequestPrivate.superIdentityRecordKey.toVeilid(); // Fetch the account master - final contactIdentityMaster = await openIdentityMaster( - identityMasterRecordKey: contactIdentityMasterRecordKey); + final contactSuperIdentity = await SuperIdentity.open( + superRecordKey: contactSuperIdentityRecordKey); // Verify + final idcs = await contactSuperIdentity.currentInstance.cryptoSystem; final signature = signedContactInvitation.identitySignature.toVeilid(); - await cs.verify(contactIdentityMaster.identityPublicKey, + await idcs.verify(contactSuperIdentity.currentInstance.publicKey, contactInvitationBytes, signature); final writer = KeyPair( @@ -297,7 +299,7 @@ class ContactInvitationListCubit account: _account, contactRequestInboxKey: contactRequestInboxKey, contactRequestPrivate: contactRequestPrivate, - contactIdentityMaster: contactIdentityMaster, + contactSuperIdentity: contactSuperIdentity, writer: writer); }); diff --git a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart index f8c639f..120a2d7 100644 --- a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart @@ -43,23 +43,21 @@ class WaitingInvitationCubit extends AsyncTransformerCubit _contactRequestPrivate.profile; @@ -33,8 +33,8 @@ class ValidContactInvitation { try { // Ensure we don't delete this if we're trying to chat to self // The initiating side will delete the records in deleteInvitation() - final isSelf = _contactIdentityMaster.identityPublicKey == - _activeAccountInfo.localAccount.identityMaster.identityPublicKey; + final isSelf = _contactSuperIdentity.currentInstance.publicKey == + _activeAccountInfo.identityPublicKey; final accountRecordKey = _activeAccountInfo.accountRecordKey; return (await pool.openRecordWrite(_contactRequestInboxKey, _writer, @@ -48,24 +48,23 @@ class ValidContactInvitation { final conversation = ConversationCubit( activeAccountInfo: _activeAccountInfo, remoteIdentityPublicKey: - _contactIdentityMaster.identityPublicTypedKey()); + _contactSuperIdentity.currentInstance.typedPublicKey); return conversation.initLocalConversation( profile: _account.profile, callback: (localConversation) async { final contactResponse = proto.ContactResponse() ..accept = true ..remoteConversationRecordKey = localConversation.key.toProto() - ..identityMasterRecordKey = _activeAccountInfo - .localAccount.identityMaster.masterRecordKey - .toProto(); + ..superIdentityRecordKey = + _activeAccountInfo.superIdentityRecordKey.toProto(); final contactResponseBytes = contactResponse.writeToBuffer(); final cs = await pool.veilid .getCryptoSystem(_contactRequestInboxKey.kind); final identitySignature = await cs.sign( - _activeAccountInfo.conversationWriter.key, - _activeAccountInfo.conversationWriter.secret, + _activeAccountInfo.identityWriter.key, + _activeAccountInfo.identityWriter.secret, contactResponseBytes); final signedContactResponse = proto.SignedContactResponse() @@ -78,7 +77,7 @@ class ValidContactInvitation { return AcceptedContact( remoteProfile: _contactRequestPrivate.profile, - remoteIdentity: _contactIdentityMaster, + remoteIdentity: _contactSuperIdentity, remoteConversationRecordKey: _contactRequestPrivate.chatRecordKey.toVeilid(), localConversationRecordKey: localConversation.key, @@ -95,10 +94,9 @@ class ValidContactInvitation { final pool = DHTRecordPool.instance; // Ensure we don't delete this if we're trying to chat to self - final isSelf = _contactIdentityMaster.identityPublicKey == - _activeAccountInfo.localAccount.identityMaster.identityPublicKey; - final accountRecordKey = - _activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; + final isSelf = _contactSuperIdentity.currentInstance.publicKey == + _activeAccountInfo.identityPublicKey; + final accountRecordKey = _activeAccountInfo.accountRecordKey; return (await pool.openRecordWrite(_contactRequestInboxKey, _writer, debugName: 'ValidContactInvitation::reject::' @@ -110,14 +108,13 @@ class ValidContactInvitation { final contactResponse = proto.ContactResponse() ..accept = false - ..identityMasterRecordKey = _activeAccountInfo - .localAccount.identityMaster.masterRecordKey - .toProto(); + ..superIdentityRecordKey = + _activeAccountInfo.superIdentityRecordKey.toProto(); final contactResponseBytes = contactResponse.writeToBuffer(); final identitySignature = await cs.sign( - _activeAccountInfo.conversationWriter.key, - _activeAccountInfo.conversationWriter.secret, + _activeAccountInfo.identityWriter.key, + _activeAccountInfo.identityWriter.secret, contactResponseBytes); final signedContactResponse = proto.SignedContactResponse() @@ -135,7 +132,7 @@ class ValidContactInvitation { final ActiveAccountInfo _activeAccountInfo; final proto.Account _account; final TypedKey _contactRequestInboxKey; - final IdentityMaster _contactIdentityMaster; + final SuperIdentity _contactSuperIdentity; final KeyPair _writer; final proto.ContactRequestPrivate _contactRequestPrivate; } diff --git a/lib/contact_invitation/views/invitation_dialog.dart b/lib/contact_invitation/views/invitation_dialog.dart index 2f1bd1c..f97869b 100644 --- a/lib/contact_invitation/views/invitation_dialog.dart +++ b/lib/contact_invitation/views/invitation_dialog.dart @@ -86,13 +86,12 @@ class InvitationDialogState extends State { if (acceptedContact != null) { // initiator when accept is received will create // contact in the case of a 'note to self' - final isSelf = - activeAccountInfo.localAccount.identityMaster.identityPublicKey == - acceptedContact.remoteIdentity.identityPublicKey; + final isSelf = activeAccountInfo.identityPublicKey == + acceptedContact.remoteIdentity.currentInstance.publicKey; if (!isSelf) { await contactList.createContact( remoteProfile: acceptedContact.remoteProfile, - remoteIdentity: acceptedContact.remoteIdentity, + remoteSuperIdentity: acceptedContact.remoteIdentity, remoteConversationRecordKey: acceptedContact.remoteConversationRecordKey, localConversationRecordKey: diff --git a/lib/contacts/cubits/contact_list_cubit.dart b/lib/contacts/cubits/contact_list_cubit.dart index 71669fc..c985392 100644 --- a/lib/contacts/cubits/contact_list_cubit.dart +++ b/lib/contacts/cubits/contact_list_cubit.dart @@ -36,7 +36,7 @@ class ContactListCubit extends DHTShortArrayCubit { Future createContact({ required proto.Profile remoteProfile, - required IdentityMaster remoteIdentity, + required SuperIdentity remoteSuperIdentity, required TypedKey remoteConversationRecordKey, required TypedKey localConversationRecordKey, }) async { @@ -44,11 +44,9 @@ class ContactListCubit extends DHTShortArrayCubit { final contact = proto.Contact() ..editedProfile = remoteProfile ..remoteProfile = remoteProfile - ..identityMasterJson = jsonEncode(remoteIdentity.toJson()) - ..identityPublicKey = TypedKey( - kind: remoteIdentity.identityRecordKey.kind, - value: remoteIdentity.identityPublicKey) - .toProto() + ..superIdentityJson = jsonEncode(remoteSuperIdentity.toJson()) + ..identityPublicKey = + remoteSuperIdentity.currentInstance.typedPublicKey.toProto() ..remoteConversationRecordKey = remoteConversationRecordKey.toProto() ..localConversationRecordKey = localConversationRecordKey.toProto() ..showAvailability = false; diff --git a/lib/contacts/cubits/conversation_cubit.dart b/lib/contacts/cubits/conversation_cubit.dart index ef7e6ec..115ec84 100644 --- a/lib/contacts/cubits/conversation_cubit.dart +++ b/lib/contacts/cubits/conversation_cubit.dart @@ -47,7 +47,7 @@ class ConversationCubit extends Cubit> { // Open local record key if it is specified final pool = DHTRecordPool.instance; final crypto = await _cachedConversationCrypto(); - final writer = _activeAccountInfo.conversationWriter; + final writer = _activeAccountInfo.identityWriter; final record = await pool.openRecordWrite( _localConversationRecordKey!, writer, debugName: 'ConversationCubit::LocalConversation', @@ -221,7 +221,7 @@ class ConversationCubit extends Cubit> { _activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; final crypto = await _cachedConversationCrypto(); - final writer = _activeAccountInfo.conversationWriter; + final writer = _activeAccountInfo.identityWriter; // Open with SMPL scheme for identity writer late final DHTRecord localConversationRecord; @@ -254,8 +254,8 @@ class ConversationCubit extends Cubit> { // Create initial local conversation key contents final conversation = proto.Conversation() ..profile = profile - ..identityMasterJson = jsonEncode( - _activeAccountInfo.localAccount.identityMaster.toJson()) + ..superIdentityJson = jsonEncode( + _activeAccountInfo.localAccount.superIdentity.toJson()) ..messages = messages.recordKey.toProto(); // Write initial conversation to record @@ -289,7 +289,7 @@ class ConversationCubit extends Cubit> { }) async { final crypto = await activeAccountInfo.makeConversationCrypto(remoteIdentityPublicKey); - final writer = activeAccountInfo.conversationWriter; + final writer = activeAccountInfo.identityWriter; return (await DHTLog.create( debugName: 'ConversationCubit::initLocalMessages::LocalMessages', diff --git a/lib/layout/home/home_account_ready/home_account_ready_shell.dart b/lib/layout/home/home_account_ready/home_account_ready_shell.dart index bf98bba..c41185b 100644 --- a/lib/layout/home/home_account_ready/home_account_ready_shell.dart +++ b/lib/layout/home/home_account_ready/home_account_ready_shell.dart @@ -91,7 +91,7 @@ class HomeAccountReadyShellState extends State { // Accept await contactListCubit.createContact( remoteProfile: acceptedContact.remoteProfile, - remoteIdentity: acceptedContact.remoteIdentity, + remoteSuperIdentity: acceptedContact.remoteIdentity, remoteConversationRecordKey: acceptedContact.remoteConversationRecordKey, localConversationRecordKey: diff --git a/lib/proto/veilidchat.pb.dart b/lib/proto/veilidchat.pb.dart index 7770d73..1e0395b 100644 --- a/lib/proto/veilidchat.pb.dart +++ b/lib/proto/veilidchat.pb.dart @@ -1026,7 +1026,7 @@ class Conversation extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Conversation', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create) ..aOM(1, _omitFieldNames ? '' : 'profile', subBuilder: Profile.create) - ..aOS(2, _omitFieldNames ? '' : 'identityMasterJson') + ..aOS(2, _omitFieldNames ? '' : 'superIdentityJson') ..aOM<$0.TypedKey>(3, _omitFieldNames ? '' : 'messages', subBuilder: $0.TypedKey.create) ..hasRequiredFields = false ; @@ -1064,13 +1064,13 @@ class Conversation extends $pb.GeneratedMessage { Profile ensureProfile() => $_ensure(0); @$pb.TagNumber(2) - $core.String get identityMasterJson => $_getSZ(1); + $core.String get superIdentityJson => $_getSZ(1); @$pb.TagNumber(2) - set identityMasterJson($core.String v) { $_setString(1, v); } + set superIdentityJson($core.String v) { $_setString(1, v); } @$pb.TagNumber(2) - $core.bool hasIdentityMasterJson() => $_has(1); + $core.bool hasSuperIdentityJson() => $_has(1); @$pb.TagNumber(2) - void clearIdentityMasterJson() => clearField(2); + void clearSuperIdentityJson() => clearField(2); @$pb.TagNumber(3) $0.TypedKey get messages => $_getN(2); @@ -1427,7 +1427,7 @@ class Contact extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Contact', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create) ..aOM(1, _omitFieldNames ? '' : 'editedProfile', subBuilder: Profile.create) ..aOM(2, _omitFieldNames ? '' : 'remoteProfile', subBuilder: Profile.create) - ..aOS(3, _omitFieldNames ? '' : 'identityMasterJson') + ..aOS(3, _omitFieldNames ? '' : 'superIdentityJson') ..aOM<$0.TypedKey>(4, _omitFieldNames ? '' : 'identityPublicKey', subBuilder: $0.TypedKey.create) ..aOM<$0.TypedKey>(5, _omitFieldNames ? '' : 'remoteConversationRecordKey', subBuilder: $0.TypedKey.create) ..aOM<$0.TypedKey>(6, _omitFieldNames ? '' : 'localConversationRecordKey', subBuilder: $0.TypedKey.create) @@ -1479,13 +1479,13 @@ class Contact extends $pb.GeneratedMessage { Profile ensureRemoteProfile() => $_ensure(1); @$pb.TagNumber(3) - $core.String get identityMasterJson => $_getSZ(2); + $core.String get superIdentityJson => $_getSZ(2); @$pb.TagNumber(3) - set identityMasterJson($core.String v) { $_setString(2, v); } + set superIdentityJson($core.String v) { $_setString(2, v); } @$pb.TagNumber(3) - $core.bool hasIdentityMasterJson() => $_has(2); + $core.bool hasSuperIdentityJson() => $_has(2); @$pb.TagNumber(3) - void clearIdentityMasterJson() => clearField(3); + void clearSuperIdentityJson() => clearField(3); @$pb.TagNumber(4) $0.TypedKey get identityPublicKey => $_getN(3); @@ -1699,7 +1699,7 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactRequestPrivate', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create) ..aOM<$0.CryptoKey>(1, _omitFieldNames ? '' : 'writerKey', subBuilder: $0.CryptoKey.create) ..aOM(2, _omitFieldNames ? '' : 'profile', subBuilder: Profile.create) - ..aOM<$0.TypedKey>(3, _omitFieldNames ? '' : 'identityMasterRecordKey', subBuilder: $0.TypedKey.create) + ..aOM<$0.TypedKey>(3, _omitFieldNames ? '' : 'superIdentityRecordKey', subBuilder: $0.TypedKey.create) ..aOM<$0.TypedKey>(4, _omitFieldNames ? '' : 'chatRecordKey', subBuilder: $0.TypedKey.create) ..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'expiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) ..hasRequiredFields = false @@ -1749,15 +1749,15 @@ class ContactRequestPrivate extends $pb.GeneratedMessage { Profile ensureProfile() => $_ensure(1); @$pb.TagNumber(3) - $0.TypedKey get identityMasterRecordKey => $_getN(2); + $0.TypedKey get superIdentityRecordKey => $_getN(2); @$pb.TagNumber(3) - set identityMasterRecordKey($0.TypedKey v) { setField(3, v); } + set superIdentityRecordKey($0.TypedKey v) { setField(3, v); } @$pb.TagNumber(3) - $core.bool hasIdentityMasterRecordKey() => $_has(2); + $core.bool hasSuperIdentityRecordKey() => $_has(2); @$pb.TagNumber(3) - void clearIdentityMasterRecordKey() => clearField(3); + void clearSuperIdentityRecordKey() => clearField(3); @$pb.TagNumber(3) - $0.TypedKey ensureIdentityMasterRecordKey() => $_ensure(2); + $0.TypedKey ensureSuperIdentityRecordKey() => $_ensure(2); @$pb.TagNumber(4) $0.TypedKey get chatRecordKey => $_getN(3); @@ -1788,7 +1788,7 @@ class ContactResponse extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create) ..aOB(1, _omitFieldNames ? '' : 'accept') - ..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'identityMasterRecordKey', subBuilder: $0.TypedKey.create) + ..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'superIdentityRecordKey', subBuilder: $0.TypedKey.create) ..aOM<$0.TypedKey>(3, _omitFieldNames ? '' : 'remoteConversationRecordKey', subBuilder: $0.TypedKey.create) ..hasRequiredFields = false ; @@ -1824,15 +1824,15 @@ class ContactResponse extends $pb.GeneratedMessage { void clearAccept() => clearField(1); @$pb.TagNumber(2) - $0.TypedKey get identityMasterRecordKey => $_getN(1); + $0.TypedKey get superIdentityRecordKey => $_getN(1); @$pb.TagNumber(2) - set identityMasterRecordKey($0.TypedKey v) { setField(2, v); } + set superIdentityRecordKey($0.TypedKey v) { setField(2, v); } @$pb.TagNumber(2) - $core.bool hasIdentityMasterRecordKey() => $_has(1); + $core.bool hasSuperIdentityRecordKey() => $_has(1); @$pb.TagNumber(2) - void clearIdentityMasterRecordKey() => clearField(2); + void clearSuperIdentityRecordKey() => clearField(2); @$pb.TagNumber(2) - $0.TypedKey ensureIdentityMasterRecordKey() => $_ensure(1); + $0.TypedKey ensureSuperIdentityRecordKey() => $_ensure(1); @$pb.TagNumber(3) $0.TypedKey get remoteConversationRecordKey => $_getN(2); diff --git a/lib/proto/veilidchat.pbjson.dart b/lib/proto/veilidchat.pbjson.dart index 56ebbe6..ed0bda4 100644 --- a/lib/proto/veilidchat.pbjson.dart +++ b/lib/proto/veilidchat.pbjson.dart @@ -309,7 +309,7 @@ const Conversation$json = { '1': 'Conversation', '2': [ {'1': 'profile', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'profile'}, - {'1': 'identity_master_json', '3': 2, '4': 1, '5': 9, '10': 'identityMasterJson'}, + {'1': 'super_identity_json', '3': 2, '4': 1, '5': 9, '10': 'superIdentityJson'}, {'1': 'messages', '3': 3, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'messages'}, ], }; @@ -317,8 +317,8 @@ const Conversation$json = { /// Descriptor for `Conversation`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List conversationDescriptor = $convert.base64Decode( 'CgxDb252ZXJzYXRpb24SLQoHcHJvZmlsZRgBIAEoCzITLnZlaWxpZGNoYXQuUHJvZmlsZVIHcH' - 'JvZmlsZRIwChRpZGVudGl0eV9tYXN0ZXJfanNvbhgCIAEoCVISaWRlbnRpdHlNYXN0ZXJKc29u' - 'EiwKCG1lc3NhZ2VzGAMgASgLMhAudmVpbGlkLlR5cGVkS2V5UghtZXNzYWdlcw=='); + 'JvZmlsZRIuChNzdXBlcl9pZGVudGl0eV9qc29uGAIgASgJUhFzdXBlcklkZW50aXR5SnNvbhIs' + 'CghtZXNzYWdlcxgDIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIIbWVzc2FnZXM='); @$core.Deprecated('Use chatDescriptor instead') const Chat$json = { @@ -411,7 +411,7 @@ const Contact$json = { '2': [ {'1': 'edited_profile', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'editedProfile'}, {'1': 'remote_profile', '3': 2, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'remoteProfile'}, - {'1': 'identity_master_json', '3': 3, '4': 1, '5': 9, '10': 'identityMasterJson'}, + {'1': 'super_identity_json', '3': 3, '4': 1, '5': 9, '10': 'superIdentityJson'}, {'1': 'identity_public_key', '3': 4, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'identityPublicKey'}, {'1': 'remote_conversation_record_key', '3': 5, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKey'}, {'1': 'local_conversation_record_key', '3': 6, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'}, @@ -423,13 +423,13 @@ const Contact$json = { final $typed_data.Uint8List contactDescriptor = $convert.base64Decode( 'CgdDb250YWN0EjoKDmVkaXRlZF9wcm9maWxlGAEgASgLMhMudmVpbGlkY2hhdC5Qcm9maWxlUg' '1lZGl0ZWRQcm9maWxlEjoKDnJlbW90ZV9wcm9maWxlGAIgASgLMhMudmVpbGlkY2hhdC5Qcm9m' - 'aWxlUg1yZW1vdGVQcm9maWxlEjAKFGlkZW50aXR5X21hc3Rlcl9qc29uGAMgASgJUhJpZGVudG' - 'l0eU1hc3Rlckpzb24SQAoTaWRlbnRpdHlfcHVibGljX2tleRgEIAEoCzIQLnZlaWxpZC5UeXBl' - 'ZEtleVIRaWRlbnRpdHlQdWJsaWNLZXkSVQoecmVtb3RlX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2' - 'V5GAUgASgLMhAudmVpbGlkLlR5cGVkS2V5UhtyZW1vdGVDb252ZXJzYXRpb25SZWNvcmRLZXkS' - 'UwodbG9jYWxfY29udmVyc2F0aW9uX3JlY29yZF9rZXkYBiABKAsyEC52ZWlsaWQuVHlwZWRLZX' - 'lSGmxvY2FsQ29udmVyc2F0aW9uUmVjb3JkS2V5EisKEXNob3dfYXZhaWxhYmlsaXR5GAcgASgI' - 'UhBzaG93QXZhaWxhYmlsaXR5'); + 'aWxlUg1yZW1vdGVQcm9maWxlEi4KE3N1cGVyX2lkZW50aXR5X2pzb24YAyABKAlSEXN1cGVySW' + 'RlbnRpdHlKc29uEkAKE2lkZW50aXR5X3B1YmxpY19rZXkYBCABKAsyEC52ZWlsaWQuVHlwZWRL' + 'ZXlSEWlkZW50aXR5UHVibGljS2V5ElUKHnJlbW90ZV9jb252ZXJzYXRpb25fcmVjb3JkX2tleR' + 'gFIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIbcmVtb3RlQ29udmVyc2F0aW9uUmVjb3JkS2V5ElMK' + 'HWxvY2FsX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2V5GAYgASgLMhAudmVpbGlkLlR5cGVkS2V5Uh' + 'psb2NhbENvbnZlcnNhdGlvblJlY29yZEtleRIrChFzaG93X2F2YWlsYWJpbGl0eRgHIAEoCFIQ' + 'c2hvd0F2YWlsYWJpbGl0eQ=='); @$core.Deprecated('Use contactInvitationDescriptor instead') const ContactInvitation$json = { @@ -482,7 +482,7 @@ const ContactRequestPrivate$json = { '2': [ {'1': 'writer_key', '3': 1, '4': 1, '5': 11, '6': '.veilid.CryptoKey', '10': 'writerKey'}, {'1': 'profile', '3': 2, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'profile'}, - {'1': 'identity_master_record_key', '3': 3, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'identityMasterRecordKey'}, + {'1': 'super_identity_record_key', '3': 3, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'superIdentityRecordKey'}, {'1': 'chat_record_key', '3': 4, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'chatRecordKey'}, {'1': 'expiration', '3': 5, '4': 1, '5': 4, '10': 'expiration'}, ], @@ -492,27 +492,27 @@ const ContactRequestPrivate$json = { final $typed_data.Uint8List contactRequestPrivateDescriptor = $convert.base64Decode( 'ChVDb250YWN0UmVxdWVzdFByaXZhdGUSMAoKd3JpdGVyX2tleRgBIAEoCzIRLnZlaWxpZC5Dcn' 'lwdG9LZXlSCXdyaXRlcktleRItCgdwcm9maWxlGAIgASgLMhMudmVpbGlkY2hhdC5Qcm9maWxl' - 'Ugdwcm9maWxlEk0KGmlkZW50aXR5X21hc3Rlcl9yZWNvcmRfa2V5GAMgASgLMhAudmVpbGlkLl' - 'R5cGVkS2V5UhdpZGVudGl0eU1hc3RlclJlY29yZEtleRI4Cg9jaGF0X3JlY29yZF9rZXkYBCAB' - 'KAsyEC52ZWlsaWQuVHlwZWRLZXlSDWNoYXRSZWNvcmRLZXkSHgoKZXhwaXJhdGlvbhgFIAEoBF' - 'IKZXhwaXJhdGlvbg=='); + 'Ugdwcm9maWxlEksKGXN1cGVyX2lkZW50aXR5X3JlY29yZF9rZXkYAyABKAsyEC52ZWlsaWQuVH' + 'lwZWRLZXlSFnN1cGVySWRlbnRpdHlSZWNvcmRLZXkSOAoPY2hhdF9yZWNvcmRfa2V5GAQgASgL' + 'MhAudmVpbGlkLlR5cGVkS2V5Ug1jaGF0UmVjb3JkS2V5Eh4KCmV4cGlyYXRpb24YBSABKARSCm' + 'V4cGlyYXRpb24='); @$core.Deprecated('Use contactResponseDescriptor instead') const ContactResponse$json = { '1': 'ContactResponse', '2': [ {'1': 'accept', '3': 1, '4': 1, '5': 8, '10': 'accept'}, - {'1': 'identity_master_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'identityMasterRecordKey'}, + {'1': 'super_identity_record_key', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'superIdentityRecordKey'}, {'1': 'remote_conversation_record_key', '3': 3, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKey'}, ], }; /// Descriptor for `ContactResponse`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List contactResponseDescriptor = $convert.base64Decode( - 'Cg9Db250YWN0UmVzcG9uc2USFgoGYWNjZXB0GAEgASgIUgZhY2NlcHQSTQoaaWRlbnRpdHlfbW' - 'FzdGVyX3JlY29yZF9rZXkYAiABKAsyEC52ZWlsaWQuVHlwZWRLZXlSF2lkZW50aXR5TWFzdGVy' - 'UmVjb3JkS2V5ElUKHnJlbW90ZV9jb252ZXJzYXRpb25fcmVjb3JkX2tleRgDIAEoCzIQLnZlaW' - 'xpZC5UeXBlZEtleVIbcmVtb3RlQ29udmVyc2F0aW9uUmVjb3JkS2V5'); + 'Cg9Db250YWN0UmVzcG9uc2USFgoGYWNjZXB0GAEgASgIUgZhY2NlcHQSSwoZc3VwZXJfaWRlbn' + 'RpdHlfcmVjb3JkX2tleRgCIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIWc3VwZXJJZGVudGl0eVJl' + 'Y29yZEtleRJVCh5yZW1vdGVfY29udmVyc2F0aW9uX3JlY29yZF9rZXkYAyABKAsyEC52ZWlsaW' + 'QuVHlwZWRLZXlSG3JlbW90ZUNvbnZlcnNhdGlvblJlY29yZEtleQ=='); @$core.Deprecated('Use signedContactResponseDescriptor instead') const SignedContactResponse$json = { diff --git a/lib/proto/veilidchat.proto b/lib/proto/veilidchat.proto index fa701fb..dd2de0b 100644 --- a/lib/proto/veilidchat.proto +++ b/lib/proto/veilidchat.proto @@ -237,8 +237,8 @@ message ReconciledMessage { message Conversation { // Profile to publish to friend Profile profile = 1; - // Identity master (JSON) to publish to friend or chat room - string identity_master_json = 2; + // SuperIdentity (JSON) to publish to friend or chat room + string super_identity_json = 2; // Messages DHTLog veilid.TypedKey messages = 3; } @@ -327,8 +327,8 @@ message Contact { Profile edited_profile = 1; // Copy of friend's profile from remote conversation Profile remote_profile = 2; - // Copy of friend's IdentityMaster in JSON from remote conversation - string identity_master_json = 3; + // Copy of friend's SuperIdentity in JSON from remote conversation + string super_identity_json = 3; // Copy of friend's most recent identity public key from their identityMaster veilid.TypedKey identity_public_key = 4; // Remote conversation key to sync from friend @@ -378,8 +378,8 @@ message ContactRequestPrivate { veilid.CryptoKey writer_key = 1; // Snapshot of profile Profile profile = 2; - // Identity master DHT record key - veilid.TypedKey identity_master_record_key = 3; + // SuperIdentity DHT record key + veilid.TypedKey super_identity_record_key = 3; // Local chat DHT record key veilid.TypedKey chat_record_key = 4; // Expiration timestamp @@ -390,8 +390,8 @@ message ContactRequestPrivate { message ContactResponse { // Accept or reject bool accept = 1; - // Remote identity master DHT record key - veilid.TypedKey identity_master_record_key = 2; + // Remote SuperIdentity DHT record key + veilid.TypedKey super_identity_record_key = 2; // Remote chat DHT record key if accepted veilid.TypedKey remote_conversation_record_key = 3; } diff --git a/lib/veilid_processor/models/processor_connection_state.dart b/lib/veilid_processor/models/processor_connection_state.dart index c5220fb..e92ebdc 100644 --- a/lib/veilid_processor/models/processor_connection_state.dart +++ b/lib/veilid_processor/models/processor_connection_state.dart @@ -15,5 +15,7 @@ class ProcessorConnectionState with _$ProcessorConnectionState { attachment.state == AttachmentState.detaching || attachment.state == AttachmentState.attaching); + bool get isDetached => attachment.state == AttachmentState.detached; + bool get isPublicInternetReady => attachment.publicInternetReady; } diff --git a/packages/veilid_support/lib/identity_support/account_record_info.dart b/packages/veilid_support/lib/identity_support/account_record_info.dart new file mode 100644 index 0000000..60accf9 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/account_record_info.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../veilid_support.dart'; + +part 'account_record_info.freezed.dart'; +part 'account_record_info.g.dart'; + +/// AccountRecordInfo is the key and owner info for the account dht record that +/// is stored in the identity instance record +@freezed +class AccountRecordInfo with _$AccountRecordInfo { + const factory AccountRecordInfo({ + // Top level account keys and secrets + required OwnedDHTRecordPointer accountRecord, + }) = _AccountRecordInfo; + + factory AccountRecordInfo.fromJson(dynamic json) => + _$AccountRecordInfoFromJson(json as Map); +} diff --git a/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart b/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart new file mode 100644 index 0000000..0d5b327 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/account_record_info.freezed.dart @@ -0,0 +1,170 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'account_record_info.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +AccountRecordInfo _$AccountRecordInfoFromJson(Map json) { + return _AccountRecordInfo.fromJson(json); +} + +/// @nodoc +mixin _$AccountRecordInfo { +// Top level account keys and secrets + OwnedDHTRecordPointer get accountRecord => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $AccountRecordInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AccountRecordInfoCopyWith<$Res> { + factory $AccountRecordInfoCopyWith( + AccountRecordInfo value, $Res Function(AccountRecordInfo) then) = + _$AccountRecordInfoCopyWithImpl<$Res, AccountRecordInfo>; + @useResult + $Res call({OwnedDHTRecordPointer accountRecord}); + + $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; +} + +/// @nodoc +class _$AccountRecordInfoCopyWithImpl<$Res, $Val extends AccountRecordInfo> + implements $AccountRecordInfoCopyWith<$Res> { + _$AccountRecordInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountRecord = null, + }) { + return _then(_value.copyWith( + accountRecord: null == accountRecord + ? _value.accountRecord + : accountRecord // ignore: cast_nullable_to_non_nullable + as OwnedDHTRecordPointer, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord { + return $OwnedDHTRecordPointerCopyWith<$Res>(_value.accountRecord, (value) { + return _then(_value.copyWith(accountRecord: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$AccountRecordInfoImplCopyWith<$Res> + implements $AccountRecordInfoCopyWith<$Res> { + factory _$$AccountRecordInfoImplCopyWith(_$AccountRecordInfoImpl value, + $Res Function(_$AccountRecordInfoImpl) then) = + __$$AccountRecordInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({OwnedDHTRecordPointer accountRecord}); + + @override + $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; +} + +/// @nodoc +class __$$AccountRecordInfoImplCopyWithImpl<$Res> + extends _$AccountRecordInfoCopyWithImpl<$Res, _$AccountRecordInfoImpl> + implements _$$AccountRecordInfoImplCopyWith<$Res> { + __$$AccountRecordInfoImplCopyWithImpl(_$AccountRecordInfoImpl _value, + $Res Function(_$AccountRecordInfoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountRecord = null, + }) { + return _then(_$AccountRecordInfoImpl( + accountRecord: null == accountRecord + ? _value.accountRecord + : accountRecord // ignore: cast_nullable_to_non_nullable + as OwnedDHTRecordPointer, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AccountRecordInfoImpl implements _AccountRecordInfo { + const _$AccountRecordInfoImpl({required this.accountRecord}); + + factory _$AccountRecordInfoImpl.fromJson(Map json) => + _$$AccountRecordInfoImplFromJson(json); + +// Top level account keys and secrets + @override + final OwnedDHTRecordPointer accountRecord; + + @override + String toString() { + return 'AccountRecordInfo(accountRecord: $accountRecord)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AccountRecordInfoImpl && + (identical(other.accountRecord, accountRecord) || + other.accountRecord == accountRecord)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, accountRecord); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => + __$$AccountRecordInfoImplCopyWithImpl<_$AccountRecordInfoImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$AccountRecordInfoImplToJson( + this, + ); + } +} + +abstract class _AccountRecordInfo implements AccountRecordInfo { + const factory _AccountRecordInfo( + {required final OwnedDHTRecordPointer accountRecord}) = + _$AccountRecordInfoImpl; + + factory _AccountRecordInfo.fromJson(Map json) = + _$AccountRecordInfoImpl.fromJson; + + @override // Top level account keys and secrets + OwnedDHTRecordPointer get accountRecord; + @override + @JsonKey(ignore: true) + _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/veilid_support/lib/identity_support/account_record_info.g.dart b/packages/veilid_support/lib/identity_support/account_record_info.g.dart new file mode 100644 index 0000000..ad9318c --- /dev/null +++ b/packages/veilid_support/lib/identity_support/account_record_info.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'account_record_info.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AccountRecordInfoImpl _$$AccountRecordInfoImplFromJson( + Map json) => + _$AccountRecordInfoImpl( + accountRecord: OwnedDHTRecordPointer.fromJson(json['account_record']), + ); + +Map _$$AccountRecordInfoImplToJson( + _$AccountRecordInfoImpl instance) => + { + 'account_record': instance.accountRecord.toJson(), + }; diff --git a/packages/veilid_support/lib/identity_support/exceptions.dart b/packages/veilid_support/lib/identity_support/exceptions.dart new file mode 100644 index 0000000..f0774b4 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/exceptions.dart @@ -0,0 +1,13 @@ +/// Identity errors +enum IdentityException implements Exception { + readError('identity could not be read'), + noAccount('no account record info'), + limitExceeded('too many items for the limit'), + invalid('identity is corrupted or secret is invalid'); + + const IdentityException(this.message); + final String message; + + @override + String toString() => 'IdentityException($name): $message'; +} diff --git a/packages/veilid_support/lib/identity_support/identity.dart b/packages/veilid_support/lib/identity_support/identity.dart new file mode 100644 index 0000000..ea9c38c --- /dev/null +++ b/packages/veilid_support/lib/identity_support/identity.dart @@ -0,0 +1,25 @@ +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'account_record_info.dart'; + +part 'identity.freezed.dart'; +part 'identity.g.dart'; + +/// Identity points to accounts associated with this IdentityInstance +/// accountRecords field has a map of bundle id or uuid to account key pairs +/// DHT Schema: DFLT(1) +/// DHT Key (Private): IdentityInstance.recordKey +/// DHT Owner Key: IdentityInstance.publicKey +/// DHT Secret: IdentityInstance Secret Key (stored encrypted with unlock code +/// in local table store) +@freezed +class Identity with _$Identity { + const factory Identity({ + // Top level account keys and secrets + required IMap> accountRecords, + }) = _Identity; + + factory Identity.fromJson(dynamic json) => + _$IdentityFromJson(json as Map); +} diff --git a/packages/veilid_support/lib/identity_support/identity.freezed.dart b/packages/veilid_support/lib/identity_support/identity.freezed.dart new file mode 100644 index 0000000..5977a26 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/identity.freezed.dart @@ -0,0 +1,156 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'identity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Identity _$IdentityFromJson(Map json) { + return _Identity.fromJson(json); +} + +/// @nodoc +mixin _$Identity { +// Top level account keys and secrets + IMap> get accountRecords => + throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IdentityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IdentityCopyWith<$Res> { + factory $IdentityCopyWith(Identity value, $Res Function(Identity) then) = + _$IdentityCopyWithImpl<$Res, Identity>; + @useResult + $Res call({IMap> accountRecords}); +} + +/// @nodoc +class _$IdentityCopyWithImpl<$Res, $Val extends Identity> + implements $IdentityCopyWith<$Res> { + _$IdentityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountRecords = null, + }) { + return _then(_value.copyWith( + accountRecords: null == accountRecords + ? _value.accountRecords + : accountRecords // ignore: cast_nullable_to_non_nullable + as IMap>, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IdentityImplCopyWith<$Res> + implements $IdentityCopyWith<$Res> { + factory _$$IdentityImplCopyWith( + _$IdentityImpl value, $Res Function(_$IdentityImpl) then) = + __$$IdentityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({IMap> accountRecords}); +} + +/// @nodoc +class __$$IdentityImplCopyWithImpl<$Res> + extends _$IdentityCopyWithImpl<$Res, _$IdentityImpl> + implements _$$IdentityImplCopyWith<$Res> { + __$$IdentityImplCopyWithImpl( + _$IdentityImpl _value, $Res Function(_$IdentityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? accountRecords = null, + }) { + return _then(_$IdentityImpl( + accountRecords: null == accountRecords + ? _value.accountRecords + : accountRecords // ignore: cast_nullable_to_non_nullable + as IMap>, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IdentityImpl implements _Identity { + const _$IdentityImpl({required this.accountRecords}); + + factory _$IdentityImpl.fromJson(Map json) => + _$$IdentityImplFromJson(json); + +// Top level account keys and secrets + @override + final IMap> accountRecords; + + @override + String toString() { + return 'Identity(accountRecords: $accountRecords)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IdentityImpl && + (identical(other.accountRecords, accountRecords) || + other.accountRecords == accountRecords)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, accountRecords); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => + __$$IdentityImplCopyWithImpl<_$IdentityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$IdentityImplToJson( + this, + ); + } +} + +abstract class _Identity implements Identity { + const factory _Identity( + {required final IMap> + accountRecords}) = _$IdentityImpl; + + factory _Identity.fromJson(Map json) = + _$IdentityImpl.fromJson; + + @override // Top level account keys and secrets + IMap> get accountRecords; + @override + @JsonKey(ignore: true) + _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/veilid_support/lib/identity_support/identity.g.dart b/packages/veilid_support/lib/identity_support/identity.g.dart new file mode 100644 index 0000000..afc9088 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/identity.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'identity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$IdentityImpl _$$IdentityImplFromJson(Map json) => + _$IdentityImpl( + accountRecords: IMap>.fromJson( + json['account_records'] as Map, + (value) => value as String, + (value) => ISet.fromJson( + value, (value) => AccountRecordInfo.fromJson(value))), + ); + +Map _$$IdentityImplToJson(_$IdentityImpl instance) => + { + 'account_records': instance.accountRecords.toJson( + (value) => value, + (value) => value.toJson( + (value) => value.toJson(), + ), + ), + }; diff --git a/packages/veilid_support/lib/identity_support/identity_instance.dart b/packages/veilid_support/lib/identity_support/identity_instance.dart new file mode 100644 index 0000000..b11e223 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/identity_instance.dart @@ -0,0 +1,274 @@ +import 'dart:typed_data'; + +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../src/veilid_log.dart'; +import '../veilid_support.dart'; +import 'exceptions.dart'; + +part 'identity_instance.freezed.dart'; +part 'identity_instance.g.dart'; + +@freezed +class IdentityInstance with _$IdentityInstance { + const factory IdentityInstance({ + // Private DHT record storing identity account mapping + required TypedKey recordKey, + + // Public key of identity instance + required PublicKey publicKey, + + // Secret key of identity instance + // Encrypted with DH(publicKey, SuperIdentity.secret) with appended salt + // Used to recover accounts without generating a new instance + @Uint8ListJsonConverter() required Uint8List encryptedSecretKey, + + // Signature of SuperInstance recordKey and SuperInstance publicKey + // by publicKey + required Signature superSignature, + + // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature + // by SuperIdentity publicKey + required Signature signature, + }) = _IdentityInstance; + + factory IdentityInstance.fromJson(dynamic json) => + _$IdentityInstanceFromJson(json as Map); + + const IdentityInstance._(); + + //////////////////////////////////////////////////////////////////////////// + // Public interface + + /// Delete this identity instance record + /// Only deletes from the local machine not the DHT + Future delete() async { + final pool = DHTRecordPool.instance; + await pool.deleteRecord(recordKey); + } + + Future get cryptoSystem => + Veilid.instance.getCryptoSystem(recordKey.kind); + + Future getPrivateCrypto(SecretKey secretKey) async => + DHTRecordPool.privateCryptoFromTypedSecret( + TypedKey(kind: recordKey.kind, value: secretKey)); + + KeyPair writer(SecretKey secret) => KeyPair(key: publicKey, secret: secret); + + TypedKey get typedPublicKey => + TypedKey(kind: recordKey.kind, value: publicKey); + + Future validateIdentitySecret(SecretKey secretKey) async { + final cs = await cryptoSystem; + final keyOk = await cs.validateKeyPair(publicKey, secretKey); + if (!keyOk) { + throw IdentityException.invalid; + } + return cs; + } + + /// Read the account record info for a specific accountKey from the identity + /// instance record using the identity instance secret key to decrypt + Future> readAccount( + {required TypedKey superRecordKey, + required SecretKey secretKey, + required String accountKey}) async { + // Read the identity key to get the account keys + final pool = DHTRecordPool.instance; + + final identityRecordCrypto = await getPrivateCrypto(secretKey); + + late final List accountRecordInfo; + await (await pool.openRecordRead(recordKey, + debugName: 'IdentityInstance::readAccounts::IdentityRecord', + parent: superRecordKey, + crypto: identityRecordCrypto)) + .scope((identityRec) async { + final identity = await identityRec.getJson(Identity.fromJson); + if (identity == null) { + // Identity could not be read or decrypted from DHT + throw IdentityException.readError; + } + final accountRecords = IMapOfSets.from(identity.accountRecords); + final vcAccounts = accountRecords.get(accountKey); + + accountRecordInfo = vcAccounts.toList(); + }); + + return accountRecordInfo; + } + + /// Creates a new Account associated with super identity and store it in the + /// identity instance record. + Future addAccount({ + required TypedKey superRecordKey, + required SecretKey secretKey, + required String accountKey, + required Future Function(TypedKey parent) createAccountCallback, + int maxAccounts = 1, + }) async { + final pool = DHTRecordPool.instance; + + /////// Add account with profile to DHT + + // Open identity key for writing + veilidLoggy.debug('Opening identity record'); + return (await pool.openRecordWrite(recordKey, writer(secretKey), + debugName: 'IdentityInstance::addAccount::IdentityRecord', + parent: superRecordKey)) + .scope((identityRec) async { + // Create new account to insert into identity + veilidLoggy.debug('Creating new account'); + return (await pool.createRecord( + debugName: + 'IdentityInstance::addAccount::IdentityRecord::AccountRecord', + parent: identityRec.key)) + .deleteScope((accountRec) async { + final account = await createAccountCallback(accountRec.key); + // Write account key + veilidLoggy.debug('Writing account record'); + await accountRec.eventualWriteBytes(account); + + // Update identity key to include account + final newAccountRecordInfo = AccountRecordInfo( + accountRecord: OwnedDHTRecordPointer( + recordKey: accountRec.key, owner: accountRec.ownerKeyPair!)); + + veilidLoggy.debug('Updating identity with new account'); + await identityRec.eventualUpdateJson(Identity.fromJson, + (oldIdentity) async { + if (oldIdentity == null) { + throw IdentityException.readError; + } + final oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords); + + if (oldAccountRecords.get(accountKey).length >= maxAccounts) { + throw IdentityException.limitExceeded; + } + final accountRecords = + oldAccountRecords.add(accountKey, newAccountRecordInfo).asIMap(); + return oldIdentity.copyWith(accountRecords: accountRecords); + }); + + return newAccountRecordInfo; + }); + }); + } + + //////////////////////////////////////////////////////////////////////////// + // Internal implementation + + Future validateIdentityInstance( + {required TypedKey superRecordKey, + required PublicKey superPublicKey}) async { + final sigValid = await IdentityInstance.validateIdentitySignature( + recordKey: recordKey, + publicKey: publicKey, + encryptedSecretKey: encryptedSecretKey, + superSignature: superSignature, + superPublicKey: superPublicKey, + signature: signature); + if (!sigValid) { + return false; + } + + final superSigValid = await IdentityInstance.validateSuperSignature( + superRecordKey: superRecordKey, + superPublicKey: superPublicKey, + publicKey: publicKey, + superSignature: superSignature); + if (!superSigValid) { + return false; + } + + return true; + } + + static Uint8List signatureBytes({ + required TypedKey recordKey, + required PublicKey publicKey, + required Uint8List encryptedSecretKey, + required Signature superSignature, + }) { + final sigBuf = BytesBuilder() + ..add(recordKey.decode()) + ..add(publicKey.decode()) + ..add(encryptedSecretKey) + ..add(superSignature.decode()); + return sigBuf.toBytes(); + } + + static Future validateIdentitySignature({ + required TypedKey recordKey, + required PublicKey publicKey, + required Uint8List encryptedSecretKey, + required Signature superSignature, + required PublicKey superPublicKey, + required Signature signature, + }) async { + final cs = await Veilid.instance.getCryptoSystem(recordKey.kind); + final identitySigBytes = IdentityInstance.signatureBytes( + recordKey: recordKey, + publicKey: publicKey, + encryptedSecretKey: encryptedSecretKey, + superSignature: superSignature); + return cs.verify(superPublicKey, identitySigBytes, signature); + } + + static Future createIdentitySignature({ + required TypedKey recordKey, + required PublicKey publicKey, + required Uint8List encryptedSecretKey, + required Signature superSignature, + required PublicKey superPublicKey, + required SecretKey superSecret, + }) async { + final cs = await Veilid.instance.getCryptoSystem(recordKey.kind); + final identitySigBytes = IdentityInstance.signatureBytes( + recordKey: recordKey, + publicKey: publicKey, + encryptedSecretKey: encryptedSecretKey, + superSignature: superSignature); + return cs.sign(superPublicKey, superSecret, identitySigBytes); + } + + static Uint8List superSignatureBytes({ + required TypedKey superRecordKey, + required PublicKey superPublicKey, + }) { + final superSigBuf = BytesBuilder() + ..add(superRecordKey.decode()) + ..add(superPublicKey.decode()); + return superSigBuf.toBytes(); + } + + static Future validateSuperSignature({ + required TypedKey superRecordKey, + required PublicKey superPublicKey, + required PublicKey publicKey, + required Signature superSignature, + }) async { + final cs = await Veilid.instance.getCryptoSystem(superRecordKey.kind); + final superSigBytes = IdentityInstance.superSignatureBytes( + superRecordKey: superRecordKey, + superPublicKey: superPublicKey, + ); + return cs.verify(publicKey, superSigBytes, superSignature); + } + + static Future createSuperSignature({ + required TypedKey superRecordKey, + required PublicKey superPublicKey, + required PublicKey publicKey, + required SecretKey secretKey, + }) async { + final cs = await Veilid.instance.getCryptoSystem(superRecordKey.kind); + final superSigBytes = IdentityInstance.superSignatureBytes( + superRecordKey: superRecordKey, + superPublicKey: superPublicKey, + ); + return cs.sign(publicKey, secretKey, superSigBytes); + } +} diff --git a/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart b/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart new file mode 100644 index 0000000..4d6c4ad --- /dev/null +++ b/packages/veilid_support/lib/identity_support/identity_instance.freezed.dart @@ -0,0 +1,274 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'identity_instance.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +IdentityInstance _$IdentityInstanceFromJson(Map json) { + return _IdentityInstance.fromJson(json); +} + +/// @nodoc +mixin _$IdentityInstance { +// Private DHT record storing identity account mapping + Typed get recordKey => + throw _privateConstructorUsedError; // Public key of identity instance + FixedEncodedString43 get publicKey => + throw _privateConstructorUsedError; // Secret key of identity instance +// Encrypted with DH(publicKey, SuperIdentity.secret) with appended salt +// Used to recover accounts without generating a new instance + @Uint8ListJsonConverter() + Uint8List get encryptedSecretKey => + throw _privateConstructorUsedError; // Signature of SuperInstance recordKey and SuperInstance publicKey +// by publicKey + FixedEncodedString86 get superSignature => + throw _privateConstructorUsedError; // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature +// by SuperIdentity publicKey + FixedEncodedString86 get signature => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IdentityInstanceCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IdentityInstanceCopyWith<$Res> { + factory $IdentityInstanceCopyWith( + IdentityInstance value, $Res Function(IdentityInstance) then) = + _$IdentityInstanceCopyWithImpl<$Res, IdentityInstance>; + @useResult + $Res call( + {Typed recordKey, + FixedEncodedString43 publicKey, + @Uint8ListJsonConverter() Uint8List encryptedSecretKey, + FixedEncodedString86 superSignature, + FixedEncodedString86 signature}); +} + +/// @nodoc +class _$IdentityInstanceCopyWithImpl<$Res, $Val extends IdentityInstance> + implements $IdentityInstanceCopyWith<$Res> { + _$IdentityInstanceCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recordKey = null, + Object? publicKey = null, + Object? encryptedSecretKey = null, + Object? superSignature = null, + Object? signature = null, + }) { + return _then(_value.copyWith( + recordKey: null == recordKey + ? _value.recordKey + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as FixedEncodedString43, + encryptedSecretKey: null == encryptedSecretKey + ? _value.encryptedSecretKey + : encryptedSecretKey // ignore: cast_nullable_to_non_nullable + as Uint8List, + superSignature: null == superSignature + ? _value.superSignature + : superSignature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IdentityInstanceImplCopyWith<$Res> + implements $IdentityInstanceCopyWith<$Res> { + factory _$$IdentityInstanceImplCopyWith(_$IdentityInstanceImpl value, + $Res Function(_$IdentityInstanceImpl) then) = + __$$IdentityInstanceImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Typed recordKey, + FixedEncodedString43 publicKey, + @Uint8ListJsonConverter() Uint8List encryptedSecretKey, + FixedEncodedString86 superSignature, + FixedEncodedString86 signature}); +} + +/// @nodoc +class __$$IdentityInstanceImplCopyWithImpl<$Res> + extends _$IdentityInstanceCopyWithImpl<$Res, _$IdentityInstanceImpl> + implements _$$IdentityInstanceImplCopyWith<$Res> { + __$$IdentityInstanceImplCopyWithImpl(_$IdentityInstanceImpl _value, + $Res Function(_$IdentityInstanceImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recordKey = null, + Object? publicKey = null, + Object? encryptedSecretKey = null, + Object? superSignature = null, + Object? signature = null, + }) { + return _then(_$IdentityInstanceImpl( + recordKey: null == recordKey + ? _value.recordKey + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as FixedEncodedString43, + encryptedSecretKey: null == encryptedSecretKey + ? _value.encryptedSecretKey + : encryptedSecretKey // ignore: cast_nullable_to_non_nullable + as Uint8List, + superSignature: null == superSignature + ? _value.superSignature + : superSignature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IdentityInstanceImpl extends _IdentityInstance { + const _$IdentityInstanceImpl( + {required this.recordKey, + required this.publicKey, + @Uint8ListJsonConverter() required this.encryptedSecretKey, + required this.superSignature, + required this.signature}) + : super._(); + + factory _$IdentityInstanceImpl.fromJson(Map json) => + _$$IdentityInstanceImplFromJson(json); + +// Private DHT record storing identity account mapping + @override + final Typed recordKey; +// Public key of identity instance + @override + final FixedEncodedString43 publicKey; +// Secret key of identity instance +// Encrypted with DH(publicKey, SuperIdentity.secret) with appended salt +// Used to recover accounts without generating a new instance + @override + @Uint8ListJsonConverter() + final Uint8List encryptedSecretKey; +// Signature of SuperInstance recordKey and SuperInstance publicKey +// by publicKey + @override + final FixedEncodedString86 superSignature; +// Signature of recordKey, publicKey, encryptedSecretKey, and superSignature +// by SuperIdentity publicKey + @override + final FixedEncodedString86 signature; + + @override + String toString() { + return 'IdentityInstance(recordKey: $recordKey, publicKey: $publicKey, encryptedSecretKey: $encryptedSecretKey, superSignature: $superSignature, signature: $signature)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IdentityInstanceImpl && + (identical(other.recordKey, recordKey) || + other.recordKey == recordKey) && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey) && + const DeepCollectionEquality() + .equals(other.encryptedSecretKey, encryptedSecretKey) && + (identical(other.superSignature, superSignature) || + other.superSignature == superSignature) && + (identical(other.signature, signature) || + other.signature == signature)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + recordKey, + publicKey, + const DeepCollectionEquality().hash(encryptedSecretKey), + superSignature, + signature); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IdentityInstanceImplCopyWith<_$IdentityInstanceImpl> get copyWith => + __$$IdentityInstanceImplCopyWithImpl<_$IdentityInstanceImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$IdentityInstanceImplToJson( + this, + ); + } +} + +abstract class _IdentityInstance extends IdentityInstance { + const factory _IdentityInstance( + {required final Typed recordKey, + required final FixedEncodedString43 publicKey, + @Uint8ListJsonConverter() required final Uint8List encryptedSecretKey, + required final FixedEncodedString86 superSignature, + required final FixedEncodedString86 signature}) = _$IdentityInstanceImpl; + const _IdentityInstance._() : super._(); + + factory _IdentityInstance.fromJson(Map json) = + _$IdentityInstanceImpl.fromJson; + + @override // Private DHT record storing identity account mapping + Typed get recordKey; + @override // Public key of identity instance + FixedEncodedString43 get publicKey; + @override // Secret key of identity instance +// Encrypted with DH(publicKey, SuperIdentity.secret) with appended salt +// Used to recover accounts without generating a new instance + @Uint8ListJsonConverter() + Uint8List get encryptedSecretKey; + @override // Signature of SuperInstance recordKey and SuperInstance publicKey +// by publicKey + FixedEncodedString86 get superSignature; + @override // Signature of recordKey, publicKey, encryptedSecretKey, and superSignature +// by SuperIdentity publicKey + FixedEncodedString86 get signature; + @override + @JsonKey(ignore: true) + _$$IdentityInstanceImplCopyWith<_$IdentityInstanceImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/veilid_support/lib/identity_support/identity_instance.g.dart b/packages/veilid_support/lib/identity_support/identity_instance.g.dart new file mode 100644 index 0000000..cb228e6 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/identity_instance.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'identity_instance.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$IdentityInstanceImpl _$$IdentityInstanceImplFromJson( + Map json) => + _$IdentityInstanceImpl( + recordKey: Typed.fromJson(json['record_key']), + publicKey: FixedEncodedString43.fromJson(json['public_key']), + encryptedSecretKey: + const Uint8ListJsonConverter().fromJson(json['encrypted_secret_key']), + superSignature: FixedEncodedString86.fromJson(json['super_signature']), + signature: FixedEncodedString86.fromJson(json['signature']), + ); + +Map _$$IdentityInstanceImplToJson( + _$IdentityInstanceImpl instance) => + { + 'record_key': instance.recordKey.toJson(), + 'public_key': instance.publicKey.toJson(), + 'encrypted_secret_key': + const Uint8ListJsonConverter().toJson(instance.encryptedSecretKey), + 'super_signature': instance.superSignature.toJson(), + 'signature': instance.signature.toJson(), + }; diff --git a/packages/veilid_support/lib/identity_support/identity_support.dart b/packages/veilid_support/lib/identity_support/identity_support.dart new file mode 100644 index 0000000..463be9a --- /dev/null +++ b/packages/veilid_support/lib/identity_support/identity_support.dart @@ -0,0 +1,6 @@ +export 'account_record_info.dart'; +export 'exceptions.dart'; +export 'identity.dart'; +export 'identity_instance.dart'; +export 'super_identity.dart'; +export 'writable_super_identity.dart'; diff --git a/packages/veilid_support/lib/identity_support/super_identity.dart b/packages/veilid_support/lib/identity_support/super_identity.dart new file mode 100644 index 0000000..e4ec8fc --- /dev/null +++ b/packages/veilid_support/lib/identity_support/super_identity.dart @@ -0,0 +1,174 @@ +import 'dart:typed_data'; + +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../veilid_support.dart'; + +part 'super_identity.freezed.dart'; +part 'super_identity.g.dart'; + +/// SuperIdentity key structure for created account +/// +/// SuperIdentity key allows for regeneration of identity DHT record +/// Bidirectional Super<->Instance signature allows for +/// chain of identity ownership for account recovery process +/// +/// Backed by a DHT key at superRecordKey, the secret is kept +/// completely offline and only written to upon account recovery +/// +/// DHT Schema: DFLT(1) +/// DHT Record Key (Public): SuperIdentity.recordKey +/// DHT Owner Key: SuperIdentity.publicKey +/// DHT Owner Secret: SuperIdentity Secret Key (kept offline) +/// Encryption: None +@freezed +class SuperIdentity with _$SuperIdentity { + const factory SuperIdentity({ + /// Public DHT record storing this structure for account recovery + /// changing this can migrate/forward the SuperIdentity to a new DHT record + /// Instances should not hash this recordKey, rather the actual record + /// key used to store the superIdentity, as this may change. + required TypedKey recordKey, + + /// Public key of the SuperIdentity used to sign identity keys for recovery + /// This must match the owner of the superRecord DHT record and can not be + /// changed without changing the record + required PublicKey publicKey, + + /// Current identity instance + /// The most recently generated identity instance for this SuperIdentity + required IdentityInstance currentInstance, + + /// Deprecated identity instances + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + required List deprecatedInstances, + + /// Deprecated superRecords + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + required List deprecatedSuperRecordKeys, + + /// Signature of recordKey, currentInstance signature, + /// signatures of deprecatedInstances, and deprecatedSuperRecordKeys + /// by publicKey + required Signature signature, + }) = _SuperIdentity; + + //////////////////////////////////////////////////////////////////////////// + // Constructors + + factory SuperIdentity.fromJson(dynamic json) => + _$SuperIdentityFromJson(json as Map); + + const SuperIdentity._(); + + /// Opens an existing super identity and validates it + static Future open({required TypedKey superRecordKey}) async { + final pool = DHTRecordPool.instance; + + // SuperIdentity DHT record is public/unencrypted + return (await pool.openRecordRead(superRecordKey, + debugName: 'SuperIdentity::openSuperIdentity::SuperIdentityRecord')) + .deleteScope((superRec) async { + final superIdentity = (await superRec.getJson(SuperIdentity.fromJson, + refreshMode: DHTRecordRefreshMode.network))!; + + // Validate current IdentityInstance + if (!await superIdentity.currentInstance.validateIdentityInstance( + superRecordKey: superRecordKey, + superPublicKey: superIdentity.publicKey)) { + // Invalid current IdentityInstance signature(s) + throw IdentityException.invalid; + } + + // Validate deprecated IdentityInstances + for (final deprecatedInstance in superIdentity.deprecatedInstances) { + if (!await deprecatedInstance.validateIdentityInstance( + superRecordKey: superRecordKey, + superPublicKey: superIdentity.publicKey)) { + // Invalid deprecated IdentityInstance signature(s) + throw IdentityException.invalid; + } + } + + // Validate SuperIdentity + final deprecatedInstancesSignatures = + superIdentity.deprecatedInstances.map((x) => x.signature).toList(); + if (!await _validateSuperIdentitySignature( + recordKey: superIdentity.recordKey, + currentInstanceSignature: superIdentity.currentInstance.signature, + deprecatedInstancesSignatures: deprecatedInstancesSignatures, + deprecatedSuperRecordKeys: superIdentity.deprecatedSuperRecordKeys, + publicKey: superIdentity.publicKey, + signature: superIdentity.signature)) { + // Invalid SuperIdentity signature + throw IdentityException.invalid; + } + + return superIdentity; + }); + } + + //////////////////////////////////////////////////////////////////////////// + // Public Interface + + /// Deletes a super identity and the identity instance records under it + /// Only deletes from the local machine not the DHT + Future delete() async { + final pool = DHTRecordPool.instance; + await pool.deleteRecord(recordKey); + } + + Future get cryptoSystem => + Veilid.instance.getCryptoSystem(recordKey.kind); + + KeyPair writer(SecretKey secretKey) => + KeyPair(key: publicKey, secret: secretKey); + + TypedKey get typedPublicKey => + TypedKey(kind: recordKey.kind, value: publicKey); + + Future validateSecret(SecretKey secretKey) async { + final cs = await cryptoSystem; + final keyOk = await cs.validateKeyPair(publicKey, secretKey); + if (!keyOk) { + throw IdentityException.invalid; + } + return cs; + } + + //////////////////////////////////////////////////////////////////////////// + // Internal implementation + + static Uint8List signatureBytes({ + required TypedKey recordKey, + required Signature currentInstanceSignature, + required List deprecatedInstancesSignatures, + required List deprecatedSuperRecordKeys, + }) { + final sigBuf = BytesBuilder() + ..add(recordKey.decode()) + ..add(currentInstanceSignature.decode()) + ..add(deprecatedInstancesSignatures.expand((s) => s.decode()).toList()) + ..add(deprecatedSuperRecordKeys.expand((s) => s.decode()).toList()); + return sigBuf.toBytes(); + } + + static Future _validateSuperIdentitySignature({ + required TypedKey recordKey, + required Signature currentInstanceSignature, + required List deprecatedInstancesSignatures, + required List deprecatedSuperRecordKeys, + required PublicKey publicKey, + required Signature signature, + }) async { + final cs = await Veilid.instance.getCryptoSystem(recordKey.kind); + final sigBytes = SuperIdentity.signatureBytes( + recordKey: recordKey, + currentInstanceSignature: currentInstanceSignature, + deprecatedInstancesSignatures: deprecatedInstancesSignatures, + deprecatedSuperRecordKeys: deprecatedSuperRecordKeys); + return cs.verify(publicKey, sigBytes, signature); + } +} diff --git a/packages/veilid_support/lib/identity_support/super_identity.freezed.dart b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart new file mode 100644 index 0000000..dc1c69a --- /dev/null +++ b/packages/veilid_support/lib/identity_support/super_identity.freezed.dart @@ -0,0 +1,380 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'super_identity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +SuperIdentity _$SuperIdentityFromJson(Map json) { + return _SuperIdentity.fromJson(json); +} + +/// @nodoc +mixin _$SuperIdentity { + /// Public DHT record storing this structure for account recovery + /// changing this can migrate/forward the SuperIdentity to a new DHT record + /// Instances should not hash this recordKey, rather the actual record + /// key used to store the superIdentity, as this may change. + Typed get recordKey => + throw _privateConstructorUsedError; + + /// Public key of the SuperIdentity used to sign identity keys for recovery + /// This must match the owner of the superRecord DHT record and can not be + /// changed without changing the record + FixedEncodedString43 get publicKey => throw _privateConstructorUsedError; + + /// Current identity instance + /// The most recently generated identity instance for this SuperIdentity + IdentityInstance get currentInstance => throw _privateConstructorUsedError; + + /// Deprecated identity instances + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + List get deprecatedInstances => + throw _privateConstructorUsedError; + + /// Deprecated superRecords + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + List> get deprecatedSuperRecordKeys => + throw _privateConstructorUsedError; + + /// Signature of recordKey, currentInstance signature, + /// signatures of deprecatedInstances, and deprecatedSuperRecordKeys + /// by publicKey + FixedEncodedString86 get signature => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SuperIdentityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SuperIdentityCopyWith<$Res> { + factory $SuperIdentityCopyWith( + SuperIdentity value, $Res Function(SuperIdentity) then) = + _$SuperIdentityCopyWithImpl<$Res, SuperIdentity>; + @useResult + $Res call( + {Typed recordKey, + FixedEncodedString43 publicKey, + IdentityInstance currentInstance, + List deprecatedInstances, + List> deprecatedSuperRecordKeys, + FixedEncodedString86 signature}); + + $IdentityInstanceCopyWith<$Res> get currentInstance; +} + +/// @nodoc +class _$SuperIdentityCopyWithImpl<$Res, $Val extends SuperIdentity> + implements $SuperIdentityCopyWith<$Res> { + _$SuperIdentityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recordKey = null, + Object? publicKey = null, + Object? currentInstance = null, + Object? deprecatedInstances = null, + Object? deprecatedSuperRecordKeys = null, + Object? signature = null, + }) { + return _then(_value.copyWith( + recordKey: null == recordKey + ? _value.recordKey + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as FixedEncodedString43, + currentInstance: null == currentInstance + ? _value.currentInstance + : currentInstance // ignore: cast_nullable_to_non_nullable + as IdentityInstance, + deprecatedInstances: null == deprecatedInstances + ? _value.deprecatedInstances + : deprecatedInstances // ignore: cast_nullable_to_non_nullable + as List, + deprecatedSuperRecordKeys: null == deprecatedSuperRecordKeys + ? _value.deprecatedSuperRecordKeys + : deprecatedSuperRecordKeys // ignore: cast_nullable_to_non_nullable + as List>, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $IdentityInstanceCopyWith<$Res> get currentInstance { + return $IdentityInstanceCopyWith<$Res>(_value.currentInstance, (value) { + return _then(_value.copyWith(currentInstance: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SuperIdentityImplCopyWith<$Res> + implements $SuperIdentityCopyWith<$Res> { + factory _$$SuperIdentityImplCopyWith( + _$SuperIdentityImpl value, $Res Function(_$SuperIdentityImpl) then) = + __$$SuperIdentityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Typed recordKey, + FixedEncodedString43 publicKey, + IdentityInstance currentInstance, + List deprecatedInstances, + List> deprecatedSuperRecordKeys, + FixedEncodedString86 signature}); + + @override + $IdentityInstanceCopyWith<$Res> get currentInstance; +} + +/// @nodoc +class __$$SuperIdentityImplCopyWithImpl<$Res> + extends _$SuperIdentityCopyWithImpl<$Res, _$SuperIdentityImpl> + implements _$$SuperIdentityImplCopyWith<$Res> { + __$$SuperIdentityImplCopyWithImpl( + _$SuperIdentityImpl _value, $Res Function(_$SuperIdentityImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recordKey = null, + Object? publicKey = null, + Object? currentInstance = null, + Object? deprecatedInstances = null, + Object? deprecatedSuperRecordKeys = null, + Object? signature = null, + }) { + return _then(_$SuperIdentityImpl( + recordKey: null == recordKey + ? _value.recordKey + : recordKey // ignore: cast_nullable_to_non_nullable + as Typed, + publicKey: null == publicKey + ? _value.publicKey + : publicKey // ignore: cast_nullable_to_non_nullable + as FixedEncodedString43, + currentInstance: null == currentInstance + ? _value.currentInstance + : currentInstance // ignore: cast_nullable_to_non_nullable + as IdentityInstance, + deprecatedInstances: null == deprecatedInstances + ? _value._deprecatedInstances + : deprecatedInstances // ignore: cast_nullable_to_non_nullable + as List, + deprecatedSuperRecordKeys: null == deprecatedSuperRecordKeys + ? _value._deprecatedSuperRecordKeys + : deprecatedSuperRecordKeys // ignore: cast_nullable_to_non_nullable + as List>, + signature: null == signature + ? _value.signature + : signature // ignore: cast_nullable_to_non_nullable + as FixedEncodedString86, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SuperIdentityImpl extends _SuperIdentity { + const _$SuperIdentityImpl( + {required this.recordKey, + required this.publicKey, + required this.currentInstance, + required final List deprecatedInstances, + required final List> + deprecatedSuperRecordKeys, + required this.signature}) + : _deprecatedInstances = deprecatedInstances, + _deprecatedSuperRecordKeys = deprecatedSuperRecordKeys, + super._(); + + factory _$SuperIdentityImpl.fromJson(Map json) => + _$$SuperIdentityImplFromJson(json); + + /// Public DHT record storing this structure for account recovery + /// changing this can migrate/forward the SuperIdentity to a new DHT record + /// Instances should not hash this recordKey, rather the actual record + /// key used to store the superIdentity, as this may change. + @override + final Typed recordKey; + + /// Public key of the SuperIdentity used to sign identity keys for recovery + /// This must match the owner of the superRecord DHT record and can not be + /// changed without changing the record + @override + final FixedEncodedString43 publicKey; + + /// Current identity instance + /// The most recently generated identity instance for this SuperIdentity + @override + final IdentityInstance currentInstance; + + /// Deprecated identity instances + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + final List _deprecatedInstances; + + /// Deprecated identity instances + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + @override + List get deprecatedInstances { + if (_deprecatedInstances is EqualUnmodifiableListView) + return _deprecatedInstances; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_deprecatedInstances); + } + + /// Deprecated superRecords + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + final List> _deprecatedSuperRecordKeys; + + /// Deprecated superRecords + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + @override + List> get deprecatedSuperRecordKeys { + if (_deprecatedSuperRecordKeys is EqualUnmodifiableListView) + return _deprecatedSuperRecordKeys; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_deprecatedSuperRecordKeys); + } + + /// Signature of recordKey, currentInstance signature, + /// signatures of deprecatedInstances, and deprecatedSuperRecordKeys + /// by publicKey + @override + final FixedEncodedString86 signature; + + @override + String toString() { + return 'SuperIdentity(recordKey: $recordKey, publicKey: $publicKey, currentInstance: $currentInstance, deprecatedInstances: $deprecatedInstances, deprecatedSuperRecordKeys: $deprecatedSuperRecordKeys, signature: $signature)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SuperIdentityImpl && + (identical(other.recordKey, recordKey) || + other.recordKey == recordKey) && + (identical(other.publicKey, publicKey) || + other.publicKey == publicKey) && + (identical(other.currentInstance, currentInstance) || + other.currentInstance == currentInstance) && + const DeepCollectionEquality() + .equals(other._deprecatedInstances, _deprecatedInstances) && + const DeepCollectionEquality().equals( + other._deprecatedSuperRecordKeys, _deprecatedSuperRecordKeys) && + (identical(other.signature, signature) || + other.signature == signature)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + recordKey, + publicKey, + currentInstance, + const DeepCollectionEquality().hash(_deprecatedInstances), + const DeepCollectionEquality().hash(_deprecatedSuperRecordKeys), + signature); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SuperIdentityImplCopyWith<_$SuperIdentityImpl> get copyWith => + __$$SuperIdentityImplCopyWithImpl<_$SuperIdentityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SuperIdentityImplToJson( + this, + ); + } +} + +abstract class _SuperIdentity extends SuperIdentity { + const factory _SuperIdentity( + {required final Typed recordKey, + required final FixedEncodedString43 publicKey, + required final IdentityInstance currentInstance, + required final List deprecatedInstances, + required final List> + deprecatedSuperRecordKeys, + required final FixedEncodedString86 signature}) = _$SuperIdentityImpl; + const _SuperIdentity._() : super._(); + + factory _SuperIdentity.fromJson(Map json) = + _$SuperIdentityImpl.fromJson; + + @override + + /// Public DHT record storing this structure for account recovery + /// changing this can migrate/forward the SuperIdentity to a new DHT record + /// Instances should not hash this recordKey, rather the actual record + /// key used to store the superIdentity, as this may change. + Typed get recordKey; + @override + + /// Public key of the SuperIdentity used to sign identity keys for recovery + /// This must match the owner of the superRecord DHT record and can not be + /// changed without changing the record + FixedEncodedString43 get publicKey; + @override + + /// Current identity instance + /// The most recently generated identity instance for this SuperIdentity + IdentityInstance get currentInstance; + @override + + /// Deprecated identity instances + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + List get deprecatedInstances; + @override + + /// Deprecated superRecords + /// These may be compromised and should not be considered valid for + /// new signatures, but may be used to validate old signatures + List> get deprecatedSuperRecordKeys; + @override + + /// Signature of recordKey, currentInstance signature, + /// signatures of deprecatedInstances, and deprecatedSuperRecordKeys + /// by publicKey + FixedEncodedString86 get signature; + @override + @JsonKey(ignore: true) + _$$SuperIdentityImplCopyWith<_$SuperIdentityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/veilid_support/lib/identity_support/super_identity.g.dart b/packages/veilid_support/lib/identity_support/super_identity.g.dart new file mode 100644 index 0000000..4c4f4f3 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/super_identity.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'super_identity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SuperIdentityImpl _$$SuperIdentityImplFromJson(Map json) => + _$SuperIdentityImpl( + recordKey: Typed.fromJson(json['record_key']), + publicKey: FixedEncodedString43.fromJson(json['public_key']), + currentInstance: IdentityInstance.fromJson(json['current_instance']), + deprecatedInstances: (json['deprecated_instances'] as List) + .map(IdentityInstance.fromJson) + .toList(), + deprecatedSuperRecordKeys: + (json['deprecated_super_record_keys'] as List) + .map(Typed.fromJson) + .toList(), + signature: FixedEncodedString86.fromJson(json['signature']), + ); + +Map _$$SuperIdentityImplToJson(_$SuperIdentityImpl instance) => + { + 'record_key': instance.recordKey.toJson(), + 'public_key': instance.publicKey.toJson(), + 'current_instance': instance.currentInstance.toJson(), + 'deprecated_instances': + instance.deprecatedInstances.map((e) => e.toJson()).toList(), + 'deprecated_super_record_keys': + instance.deprecatedSuperRecordKeys.map((e) => e.toJson()).toList(), + 'signature': instance.signature.toJson(), + }; diff --git a/packages/veilid_support/lib/identity_support/writable_super_identity.dart b/packages/veilid_support/lib/identity_support/writable_super_identity.dart new file mode 100644 index 0000000..3d88742 --- /dev/null +++ b/packages/veilid_support/lib/identity_support/writable_super_identity.dart @@ -0,0 +1,158 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; + +import '../src/veilid_log.dart'; +import '../veilid_support.dart'; + +Uint8List identityCryptoDomain = utf8.encode('identity'); + +/// SuperIdentity creator with secret +/// Not freezed because we never persist this class in its entirety. +class WritableSuperIdentity { + WritableSuperIdentity._({ + required this.superIdentity, + required this.superSecret, + required this.identitySecret, + }); + + static Future create() async { + final pool = DHTRecordPool.instance; + + // SuperIdentity DHT record is public/unencrypted + veilidLoggy.debug('Creating super identity record'); + return (await pool.createRecord( + debugName: 'WritableSuperIdentity::create::SuperIdentityRecord', + crypto: const VeilidCryptoPublic())) + .deleteScope((superRec) async { + final superRecordKey = superRec.key; + final superPublicKey = superRec.ownerKeyPair!.key; + final superSecret = superRec.ownerKeyPair!.secret; + + return _createIdentityInstance( + superRecordKey: superRecordKey, + superPublicKey: superPublicKey, + superSecret: superSecret, + closure: (identityInstance, identitySecret) async { + final signature = await _createSuperIdentitySignature( + recordKey: superRecordKey, + publicKey: superPublicKey, + secretKey: superSecret, + currentInstanceSignature: identityInstance.signature, + deprecatedInstancesSignatures: [], + deprecatedSuperRecordKeys: [], + ); + + final superIdentity = SuperIdentity( + recordKey: superRecordKey, + publicKey: superPublicKey, + currentInstance: identityInstance, + deprecatedInstances: [], + deprecatedSuperRecordKeys: [], + signature: signature); + + // Write superidentity to dht record + await superRec.eventualWriteJson(superIdentity); + + return WritableSuperIdentity._( + superIdentity: superIdentity, + superSecret: superSecret, + identitySecret: identitySecret); + }); + }); + } + + //////////////////////////////////////////////////////////////////////////// + // Public Interface + + /// Delete a super identity with secrets + Future delete() async => superIdentity.delete(); + + /// xxx: migration support, new identities, reveal identity secret etc + + //////////////////////////////////////////////////////////////////////////// + /// Private Implementation + + static Future _createSuperIdentitySignature({ + required TypedKey recordKey, + required Signature currentInstanceSignature, + required List deprecatedInstancesSignatures, + required List deprecatedSuperRecordKeys, + required PublicKey publicKey, + required SecretKey secretKey, + }) async { + final cs = await Veilid.instance.getCryptoSystem(recordKey.kind); + final sigBytes = SuperIdentity.signatureBytes( + recordKey: recordKey, + currentInstanceSignature: currentInstanceSignature, + deprecatedInstancesSignatures: deprecatedInstancesSignatures, + deprecatedSuperRecordKeys: deprecatedSuperRecordKeys); + return cs.sign(publicKey, secretKey, sigBytes); + } + + static Future _createIdentityInstance({ + required TypedKey superRecordKey, + required PublicKey superPublicKey, + required SecretKey superSecret, + required Future Function(IdentityInstance, SecretKey) closure, + }) async { + final pool = DHTRecordPool.instance; + veilidLoggy.debug('Creating identity instance record'); + // Identity record is private + return (await pool.createRecord( + debugName: 'SuperIdentityWithSecrets::create::IdentityRecord', + parent: superRecordKey)) + .deleteScope((identityRec) async { + final identityRecordKey = identityRec.key; + assert(superRecordKey.kind == identityRecordKey.kind, + 'new super and identity should have same cryptosystem'); + final identityPublicKey = identityRec.ownerKeyPair!.key; + final identitySecretKey = identityRec.ownerKeyPair!.secret; + + // Make encrypted secret key + final cs = await Veilid.instance.getCryptoSystem(identityRecordKey.kind); + + final encryptionKey = await cs.generateSharedSecret( + identityPublicKey, superSecret, identityCryptoDomain); + final encryptedSecretKey = await cs.encryptNoAuthWithNonce( + identitySecretKey.decode(), encryptionKey); + + // Make supersignature + final superSigBuf = BytesBuilder() + ..add(superRecordKey.decode()) + ..add(superPublicKey.decode()); + + final superSignature = await cs.signWithKeyPair( + identityRec.ownerKeyPair!, superSigBuf.toBytes()); + + // Make signature + final signature = await IdentityInstance.createIdentitySignature( + recordKey: identityRecordKey, + publicKey: identityPublicKey, + encryptedSecretKey: encryptedSecretKey, + superSignature: superSignature, + superPublicKey: superPublicKey, + superSecret: superSecret); + + // Make empty identity + const identity = Identity(accountRecords: IMapConst({})); + + // Write empty identity to identity dht key + await identityRec.eventualWriteJson(identity); + + final identityInstance = IdentityInstance( + recordKey: identityRecordKey, + publicKey: identityPublicKey, + encryptedSecretKey: encryptedSecretKey, + superSignature: superSignature, + signature: signature); + + return closure(identityInstance, identitySecretKey); + }); + } + + SuperIdentity superIdentity; + SecretKey superSecret; + SecretKey identitySecret; +} diff --git a/packages/veilid_support/lib/src/identity.dart b/packages/veilid_support/lib/src/identity.dart deleted file mode 100644 index 4666487..0000000 --- a/packages/veilid_support/lib/src/identity.dart +++ /dev/null @@ -1,333 +0,0 @@ -import 'dart:typed_data'; - -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:protobuf/protobuf.dart'; - -import '../veilid_support.dart'; -import 'veilid_log.dart'; - -part 'identity.freezed.dart'; -part 'identity.g.dart'; - -// Identity errors -enum IdentityException implements Exception { - readError('identity could not be read'), - noAccount('no account record info'), - limitExceeded('too many items for the limit'), - invalid('identity is corrupted or secret is invalid'); - - const IdentityException(this.message); - final String message; - - @override - String toString() => 'IdentityException($name): $message'; -} - -// AccountOwnerInfo is the key and owner info for the account dht key that is -// stored in the identity key -@freezed -class AccountRecordInfo with _$AccountRecordInfo { - const factory AccountRecordInfo({ - // Top level account keys and secrets - required OwnedDHTRecordPointer accountRecord, - }) = _AccountRecordInfo; - - factory AccountRecordInfo.fromJson(dynamic json) => - _$AccountRecordInfoFromJson(json as Map); -} - -// Identity Key points to accounts associated with this identity -// accounts field has a map of bundle id or uuid to account key pairs -// DHT Schema: DFLT(1) -// DHT Key (Private): identityRecordKey -// DHT Owner Key: identityPublicKey -// DHT Secret: identitySecretKey (stored encrypted -// with unlock code in local table store) -@freezed -class Identity with _$Identity { - const factory Identity({ - // Top level account keys and secrets - required IMap> accountRecords, - }) = _Identity; - - factory Identity.fromJson(dynamic json) => - _$IdentityFromJson(json as Map); -} - -// Identity Master key structure for created account -// Master key allows for regeneration of identity DHT record -// Bidirectional Master<->Identity signature allows for -// chain of identity ownership for account recovery process -// -// Backed by a DHT key at masterRecordKey, the secret is kept -// completely offline and only written to upon account recovery -// -// DHT Schema: DFLT(1) -// DHT Record Key (Public): masterRecordKey -// DHT Owner Key: masterPublicKey -// DHT Owner Secret: masterSecretKey (kept offline) -// Encryption: None -@freezed -class IdentityMaster with _$IdentityMaster { - const factory IdentityMaster( - { - // Private DHT record storing identity account mapping - required TypedKey identityRecordKey, - // Public key of identity - required PublicKey identityPublicKey, - // Public DHT record storing this structure for account recovery - required TypedKey masterRecordKey, - // Public key of master identity used to sign identity keys for recovery - required PublicKey masterPublicKey, - // Signature of identityRecordKey and identityPublicKey by masterPublicKey - required Signature identitySignature, - // Signature of masterRecordKey and masterPublicKey by identityPublicKey - required Signature masterSignature}) = _IdentityMaster; - - factory IdentityMaster.fromJson(dynamic json) => - _$IdentityMasterFromJson(json as Map); -} - -extension IdentityMasterExtension on IdentityMaster { - /// Deletes a master identity and the identity record under it - Future delete() async { - final pool = DHTRecordPool.instance; - await pool.deleteRecord(masterRecordKey); - } - - Future get identityCrypto => - Veilid.instance.getCryptoSystem(identityRecordKey.kind); - - Future get masterCrypto => - Veilid.instance.getCryptoSystem(masterRecordKey.kind); - - KeyPair identityWriter(SecretKey secret) => - KeyPair(key: identityPublicKey, secret: secret); - - KeyPair masterWriter(SecretKey secret) => - KeyPair(key: masterPublicKey, secret: secret); - - TypedKey identityPublicTypedKey() => - TypedKey(kind: identityRecordKey.kind, value: identityPublicKey); - - TypedKey masterPublicTypedKey() => - TypedKey(kind: identityRecordKey.kind, value: masterPublicKey); - - Future validateIdentitySecret( - SecretKey identitySecret) async { - final cs = await identityCrypto; - final keyOk = await cs.validateKeyPair(identityPublicKey, identitySecret); - if (!keyOk) { - throw IdentityException.invalid; - } - return cs; - } - - Future> readAccountsFromIdentity( - {required SecretKey identitySecret, required String accountKey}) async { - // Read the identity key to get the account keys - final pool = DHTRecordPool.instance; - - final identityRecordCrypto = - await DHTRecordPool.privateCryptoFromTypedSecret( - TypedKey(kind: identityRecordKey.kind, value: identitySecret), - ); - - late final List accountRecordInfo; - await (await pool.openRecordRead(identityRecordKey, - debugName: - 'IdentityMaster::readAccountsFromIdentity::IdentityRecord', - parent: masterRecordKey, - crypto: identityRecordCrypto)) - .scope((identityRec) async { - final identity = await identityRec.getJson(Identity.fromJson); - if (identity == null) { - // Identity could not be read or decrypted from DHT - throw IdentityException.readError; - } - final accountRecords = IMapOfSets.from(identity.accountRecords); - final vcAccounts = accountRecords.get(accountKey); - - accountRecordInfo = vcAccounts.toList(); - }); - - return accountRecordInfo; - } - - /// Creates a new Account associated with master identity and store it in the - /// identity key. - Future addAccountToIdentity({ - required SecretKey identitySecret, - required String accountKey, - required Future Function(TypedKey parent) createAccountCallback, - int maxAccounts = 1, - }) async { - final pool = DHTRecordPool.instance; - - /////// Add account with profile to DHT - - // Open identity key for writing - veilidLoggy.debug('Opening identity record'); - return (await pool.openRecordWrite( - identityRecordKey, identityWriter(identitySecret), - debugName: 'IdentityMaster::addAccountToIdentity::IdentityRecord', - parent: masterRecordKey)) - .scope((identityRec) async { - // Create new account to insert into identity - veilidLoggy.debug('Creating new account'); - return (await pool.createRecord( - debugName: 'IdentityMaster::addAccountToIdentity::AccountRecord', - parent: identityRec.key)) - .deleteScope((accountRec) async { - final account = await createAccountCallback(accountRec.key); - // Write account key - veilidLoggy.debug('Writing account record'); - await accountRec.eventualWriteProtobuf(account); - - // Update identity key to include account - final newAccountRecordInfo = AccountRecordInfo( - accountRecord: OwnedDHTRecordPointer( - recordKey: accountRec.key, owner: accountRec.ownerKeyPair!)); - - veilidLoggy.debug('Updating identity with new account'); - await identityRec.eventualUpdateJson(Identity.fromJson, - (oldIdentity) async { - if (oldIdentity == null) { - throw IdentityException.readError; - } - final oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords); - - if (oldAccountRecords.get(accountKey).length >= maxAccounts) { - throw IdentityException.limitExceeded; - } - final accountRecords = - oldAccountRecords.add(accountKey, newAccountRecordInfo).asIMap(); - return oldIdentity.copyWith(accountRecords: accountRecords); - }); - - return newAccountRecordInfo; - }); - }); - } -} - -// Identity Master with secrets -// Not freezed because we never persist this class in its entirety -class IdentityMasterWithSecrets { - IdentityMasterWithSecrets._( - {required this.identityMaster, - required this.masterSecret, - required this.identitySecret}); - IdentityMaster identityMaster; - SecretKey masterSecret; - SecretKey identitySecret; - - /// Delete a master identity with secrets - Future delete() async => identityMaster.delete(); - - /// Creates a new master identity and returns it with its secrets - static Future create() async { - final pool = DHTRecordPool.instance; - - // IdentityMaster DHT record is public/unencrypted - veilidLoggy.debug('Creating master identity record'); - return (await pool.createRecord( - debugName: - 'IdentityMasterWithSecrets::create::IdentityMasterRecord', - crypto: const VeilidCryptoPublic())) - .deleteScope((masterRec) async { - veilidLoggy.debug('Creating identity record'); - // Identity record is private - return (await pool.createRecord( - debugName: 'IdentityMasterWithSecrets::create::IdentityRecord', - parent: masterRec.key)) - .scope((identityRec) async { - // Make IdentityMaster - final masterRecordKey = masterRec.key; - final masterOwner = masterRec.ownerKeyPair!; - final masterSigBuf = BytesBuilder() - ..add(masterRecordKey.decode()) - ..add(masterOwner.key.decode()); - - final identityRecordKey = identityRec.key; - final identityOwner = identityRec.ownerKeyPair!; - final identitySigBuf = BytesBuilder() - ..add(identityRecordKey.decode()) - ..add(identityOwner.key.decode()); - - assert(masterRecordKey.kind == identityRecordKey.kind, - 'new master and identity should have same cryptosystem'); - final crypto = await pool.veilid.getCryptoSystem(masterRecordKey.kind); - - final identitySignature = - await crypto.signWithKeyPair(masterOwner, identitySigBuf.toBytes()); - final masterSignature = - await crypto.signWithKeyPair(identityOwner, masterSigBuf.toBytes()); - - final identityMaster = IdentityMaster( - identityRecordKey: identityRecordKey, - identityPublicKey: identityOwner.key, - masterRecordKey: masterRecordKey, - masterPublicKey: masterOwner.key, - identitySignature: identitySignature, - masterSignature: masterSignature); - - // Write identity master to master dht key - await masterRec.eventualWriteJson(identityMaster); - - // Make empty identity - const identity = Identity(accountRecords: IMapConst({})); - - // Write empty identity to identity dht key - await identityRec.eventualWriteJson(identity); - - return IdentityMasterWithSecrets._( - identityMaster: identityMaster, - masterSecret: masterOwner.secret, - identitySecret: identityOwner.secret); - }); - }); - } -} - -/// Opens an existing master identity and validates it -Future openIdentityMaster( - {required TypedKey identityMasterRecordKey}) async { - final pool = DHTRecordPool.instance; - - // IdentityMaster DHT record is public/unencrypted - return (await pool.openRecordRead(identityMasterRecordKey, - debugName: - 'IdentityMaster::openIdentityMaster::IdentityMasterRecord')) - .deleteScope((masterRec) async { - final identityMaster = (await masterRec.getJson(IdentityMaster.fromJson, - refreshMode: DHTRecordRefreshMode.network))!; - - // Validate IdentityMaster - final masterRecordKey = masterRec.key; - final masterOwnerKey = masterRec.owner; - final masterSigBuf = BytesBuilder() - ..add(masterRecordKey.decode()) - ..add(masterOwnerKey.decode()); - final masterSignature = identityMaster.masterSignature; - - final identityRecordKey = identityMaster.identityRecordKey; - final identityOwnerKey = identityMaster.identityPublicKey; - final identitySigBuf = BytesBuilder() - ..add(identityRecordKey.decode()) - ..add(identityOwnerKey.decode()); - final identitySignature = identityMaster.identitySignature; - - assert(masterRecordKey.kind == identityRecordKey.kind, - 'new master and identity should have same cryptosystem'); - final crypto = await pool.veilid.getCryptoSystem(masterRecordKey.kind); - - await crypto.verify( - masterOwnerKey, identitySigBuf.toBytes(), identitySignature); - await crypto.verify( - identityOwnerKey, masterSigBuf.toBytes(), masterSignature); - - return identityMaster; - }); -} diff --git a/packages/veilid_support/lib/src/identity.freezed.dart b/packages/veilid_support/lib/src/identity.freezed.dart deleted file mode 100644 index 27f34ea..0000000 --- a/packages/veilid_support/lib/src/identity.freezed.dart +++ /dev/null @@ -1,579 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'identity.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -AccountRecordInfo _$AccountRecordInfoFromJson(Map json) { - return _AccountRecordInfo.fromJson(json); -} - -/// @nodoc -mixin _$AccountRecordInfo { -// Top level account keys and secrets - OwnedDHTRecordPointer get accountRecord => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $AccountRecordInfoCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $AccountRecordInfoCopyWith<$Res> { - factory $AccountRecordInfoCopyWith( - AccountRecordInfo value, $Res Function(AccountRecordInfo) then) = - _$AccountRecordInfoCopyWithImpl<$Res, AccountRecordInfo>; - @useResult - $Res call({OwnedDHTRecordPointer accountRecord}); - - $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; -} - -/// @nodoc -class _$AccountRecordInfoCopyWithImpl<$Res, $Val extends AccountRecordInfo> - implements $AccountRecordInfoCopyWith<$Res> { - _$AccountRecordInfoCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecord = null, - }) { - return _then(_value.copyWith( - accountRecord: null == accountRecord - ? _value.accountRecord - : accountRecord // ignore: cast_nullable_to_non_nullable - as OwnedDHTRecordPointer, - ) as $Val); - } - - @override - @pragma('vm:prefer-inline') - $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord { - return $OwnedDHTRecordPointerCopyWith<$Res>(_value.accountRecord, (value) { - return _then(_value.copyWith(accountRecord: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$AccountRecordInfoImplCopyWith<$Res> - implements $AccountRecordInfoCopyWith<$Res> { - factory _$$AccountRecordInfoImplCopyWith(_$AccountRecordInfoImpl value, - $Res Function(_$AccountRecordInfoImpl) then) = - __$$AccountRecordInfoImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({OwnedDHTRecordPointer accountRecord}); - - @override - $OwnedDHTRecordPointerCopyWith<$Res> get accountRecord; -} - -/// @nodoc -class __$$AccountRecordInfoImplCopyWithImpl<$Res> - extends _$AccountRecordInfoCopyWithImpl<$Res, _$AccountRecordInfoImpl> - implements _$$AccountRecordInfoImplCopyWith<$Res> { - __$$AccountRecordInfoImplCopyWithImpl(_$AccountRecordInfoImpl _value, - $Res Function(_$AccountRecordInfoImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecord = null, - }) { - return _then(_$AccountRecordInfoImpl( - accountRecord: null == accountRecord - ? _value.accountRecord - : accountRecord // ignore: cast_nullable_to_non_nullable - as OwnedDHTRecordPointer, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$AccountRecordInfoImpl implements _AccountRecordInfo { - const _$AccountRecordInfoImpl({required this.accountRecord}); - - factory _$AccountRecordInfoImpl.fromJson(Map json) => - _$$AccountRecordInfoImplFromJson(json); - -// Top level account keys and secrets - @override - final OwnedDHTRecordPointer accountRecord; - - @override - String toString() { - return 'AccountRecordInfo(accountRecord: $accountRecord)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$AccountRecordInfoImpl && - (identical(other.accountRecord, accountRecord) || - other.accountRecord == accountRecord)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, accountRecord); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => - __$$AccountRecordInfoImplCopyWithImpl<_$AccountRecordInfoImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$AccountRecordInfoImplToJson( - this, - ); - } -} - -abstract class _AccountRecordInfo implements AccountRecordInfo { - const factory _AccountRecordInfo( - {required final OwnedDHTRecordPointer accountRecord}) = - _$AccountRecordInfoImpl; - - factory _AccountRecordInfo.fromJson(Map json) = - _$AccountRecordInfoImpl.fromJson; - - @override // Top level account keys and secrets - OwnedDHTRecordPointer get accountRecord; - @override - @JsonKey(ignore: true) - _$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith => - throw _privateConstructorUsedError; -} - -Identity _$IdentityFromJson(Map json) { - return _Identity.fromJson(json); -} - -/// @nodoc -mixin _$Identity { -// Top level account keys and secrets - IMap> get accountRecords => - throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $IdentityCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $IdentityCopyWith<$Res> { - factory $IdentityCopyWith(Identity value, $Res Function(Identity) then) = - _$IdentityCopyWithImpl<$Res, Identity>; - @useResult - $Res call({IMap> accountRecords}); -} - -/// @nodoc -class _$IdentityCopyWithImpl<$Res, $Val extends Identity> - implements $IdentityCopyWith<$Res> { - _$IdentityCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecords = null, - }) { - return _then(_value.copyWith( - accountRecords: null == accountRecords - ? _value.accountRecords - : accountRecords // ignore: cast_nullable_to_non_nullable - as IMap>, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$IdentityImplCopyWith<$Res> - implements $IdentityCopyWith<$Res> { - factory _$$IdentityImplCopyWith( - _$IdentityImpl value, $Res Function(_$IdentityImpl) then) = - __$$IdentityImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({IMap> accountRecords}); -} - -/// @nodoc -class __$$IdentityImplCopyWithImpl<$Res> - extends _$IdentityCopyWithImpl<$Res, _$IdentityImpl> - implements _$$IdentityImplCopyWith<$Res> { - __$$IdentityImplCopyWithImpl( - _$IdentityImpl _value, $Res Function(_$IdentityImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? accountRecords = null, - }) { - return _then(_$IdentityImpl( - accountRecords: null == accountRecords - ? _value.accountRecords - : accountRecords // ignore: cast_nullable_to_non_nullable - as IMap>, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$IdentityImpl implements _Identity { - const _$IdentityImpl({required this.accountRecords}); - - factory _$IdentityImpl.fromJson(Map json) => - _$$IdentityImplFromJson(json); - -// Top level account keys and secrets - @override - final IMap> accountRecords; - - @override - String toString() { - return 'Identity(accountRecords: $accountRecords)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$IdentityImpl && - (identical(other.accountRecords, accountRecords) || - other.accountRecords == accountRecords)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, accountRecords); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => - __$$IdentityImplCopyWithImpl<_$IdentityImpl>(this, _$identity); - - @override - Map toJson() { - return _$$IdentityImplToJson( - this, - ); - } -} - -abstract class _Identity implements Identity { - const factory _Identity( - {required final IMap> - accountRecords}) = _$IdentityImpl; - - factory _Identity.fromJson(Map json) = - _$IdentityImpl.fromJson; - - @override // Top level account keys and secrets - IMap> get accountRecords; - @override - @JsonKey(ignore: true) - _$$IdentityImplCopyWith<_$IdentityImpl> get copyWith => - throw _privateConstructorUsedError; -} - -IdentityMaster _$IdentityMasterFromJson(Map json) { - return _IdentityMaster.fromJson(json); -} - -/// @nodoc -mixin _$IdentityMaster { -// Private DHT record storing identity account mapping - Typed get identityRecordKey => - throw _privateConstructorUsedError; // Public key of identity - FixedEncodedString43 get identityPublicKey => - throw _privateConstructorUsedError; // Public DHT record storing this structure for account recovery - Typed get masterRecordKey => - throw _privateConstructorUsedError; // Public key of master identity used to sign identity keys for recovery - FixedEncodedString43 get masterPublicKey => - throw _privateConstructorUsedError; // Signature of identityRecordKey and identityPublicKey by masterPublicKey - FixedEncodedString86 get identitySignature => - throw _privateConstructorUsedError; // Signature of masterRecordKey and masterPublicKey by identityPublicKey - FixedEncodedString86 get masterSignature => - throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $IdentityMasterCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $IdentityMasterCopyWith<$Res> { - factory $IdentityMasterCopyWith( - IdentityMaster value, $Res Function(IdentityMaster) then) = - _$IdentityMasterCopyWithImpl<$Res, IdentityMaster>; - @useResult - $Res call( - {Typed identityRecordKey, - FixedEncodedString43 identityPublicKey, - Typed masterRecordKey, - FixedEncodedString43 masterPublicKey, - FixedEncodedString86 identitySignature, - FixedEncodedString86 masterSignature}); -} - -/// @nodoc -class _$IdentityMasterCopyWithImpl<$Res, $Val extends IdentityMaster> - implements $IdentityMasterCopyWith<$Res> { - _$IdentityMasterCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? identityRecordKey = null, - Object? identityPublicKey = null, - Object? masterRecordKey = null, - Object? masterPublicKey = null, - Object? identitySignature = null, - Object? masterSignature = null, - }) { - return _then(_value.copyWith( - identityRecordKey: null == identityRecordKey - ? _value.identityRecordKey - : identityRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, - identityPublicKey: null == identityPublicKey - ? _value.identityPublicKey - : identityPublicKey // ignore: cast_nullable_to_non_nullable - as FixedEncodedString43, - masterRecordKey: null == masterRecordKey - ? _value.masterRecordKey - : masterRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, - masterPublicKey: null == masterPublicKey - ? _value.masterPublicKey - : masterPublicKey // ignore: cast_nullable_to_non_nullable - as FixedEncodedString43, - identitySignature: null == identitySignature - ? _value.identitySignature - : identitySignature // ignore: cast_nullable_to_non_nullable - as FixedEncodedString86, - masterSignature: null == masterSignature - ? _value.masterSignature - : masterSignature // ignore: cast_nullable_to_non_nullable - as FixedEncodedString86, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$IdentityMasterImplCopyWith<$Res> - implements $IdentityMasterCopyWith<$Res> { - factory _$$IdentityMasterImplCopyWith(_$IdentityMasterImpl value, - $Res Function(_$IdentityMasterImpl) then) = - __$$IdentityMasterImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {Typed identityRecordKey, - FixedEncodedString43 identityPublicKey, - Typed masterRecordKey, - FixedEncodedString43 masterPublicKey, - FixedEncodedString86 identitySignature, - FixedEncodedString86 masterSignature}); -} - -/// @nodoc -class __$$IdentityMasterImplCopyWithImpl<$Res> - extends _$IdentityMasterCopyWithImpl<$Res, _$IdentityMasterImpl> - implements _$$IdentityMasterImplCopyWith<$Res> { - __$$IdentityMasterImplCopyWithImpl( - _$IdentityMasterImpl _value, $Res Function(_$IdentityMasterImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? identityRecordKey = null, - Object? identityPublicKey = null, - Object? masterRecordKey = null, - Object? masterPublicKey = null, - Object? identitySignature = null, - Object? masterSignature = null, - }) { - return _then(_$IdentityMasterImpl( - identityRecordKey: null == identityRecordKey - ? _value.identityRecordKey - : identityRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, - identityPublicKey: null == identityPublicKey - ? _value.identityPublicKey - : identityPublicKey // ignore: cast_nullable_to_non_nullable - as FixedEncodedString43, - masterRecordKey: null == masterRecordKey - ? _value.masterRecordKey - : masterRecordKey // ignore: cast_nullable_to_non_nullable - as Typed, - masterPublicKey: null == masterPublicKey - ? _value.masterPublicKey - : masterPublicKey // ignore: cast_nullable_to_non_nullable - as FixedEncodedString43, - identitySignature: null == identitySignature - ? _value.identitySignature - : identitySignature // ignore: cast_nullable_to_non_nullable - as FixedEncodedString86, - masterSignature: null == masterSignature - ? _value.masterSignature - : masterSignature // ignore: cast_nullable_to_non_nullable - as FixedEncodedString86, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$IdentityMasterImpl implements _IdentityMaster { - const _$IdentityMasterImpl( - {required this.identityRecordKey, - required this.identityPublicKey, - required this.masterRecordKey, - required this.masterPublicKey, - required this.identitySignature, - required this.masterSignature}); - - factory _$IdentityMasterImpl.fromJson(Map json) => - _$$IdentityMasterImplFromJson(json); - -// Private DHT record storing identity account mapping - @override - final Typed identityRecordKey; -// Public key of identity - @override - final FixedEncodedString43 identityPublicKey; -// Public DHT record storing this structure for account recovery - @override - final Typed masterRecordKey; -// Public key of master identity used to sign identity keys for recovery - @override - final FixedEncodedString43 masterPublicKey; -// Signature of identityRecordKey and identityPublicKey by masterPublicKey - @override - final FixedEncodedString86 identitySignature; -// Signature of masterRecordKey and masterPublicKey by identityPublicKey - @override - final FixedEncodedString86 masterSignature; - - @override - String toString() { - return 'IdentityMaster(identityRecordKey: $identityRecordKey, identityPublicKey: $identityPublicKey, masterRecordKey: $masterRecordKey, masterPublicKey: $masterPublicKey, identitySignature: $identitySignature, masterSignature: $masterSignature)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$IdentityMasterImpl && - (identical(other.identityRecordKey, identityRecordKey) || - other.identityRecordKey == identityRecordKey) && - (identical(other.identityPublicKey, identityPublicKey) || - other.identityPublicKey == identityPublicKey) && - (identical(other.masterRecordKey, masterRecordKey) || - other.masterRecordKey == masterRecordKey) && - (identical(other.masterPublicKey, masterPublicKey) || - other.masterPublicKey == masterPublicKey) && - (identical(other.identitySignature, identitySignature) || - other.identitySignature == identitySignature) && - (identical(other.masterSignature, masterSignature) || - other.masterSignature == masterSignature)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, - identityRecordKey, - identityPublicKey, - masterRecordKey, - masterPublicKey, - identitySignature, - masterSignature); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$IdentityMasterImplCopyWith<_$IdentityMasterImpl> get copyWith => - __$$IdentityMasterImplCopyWithImpl<_$IdentityMasterImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$IdentityMasterImplToJson( - this, - ); - } -} - -abstract class _IdentityMaster implements IdentityMaster { - const factory _IdentityMaster( - {required final Typed identityRecordKey, - required final FixedEncodedString43 identityPublicKey, - required final Typed masterRecordKey, - required final FixedEncodedString43 masterPublicKey, - required final FixedEncodedString86 identitySignature, - required final FixedEncodedString86 masterSignature}) = - _$IdentityMasterImpl; - - factory _IdentityMaster.fromJson(Map json) = - _$IdentityMasterImpl.fromJson; - - @override // Private DHT record storing identity account mapping - Typed get identityRecordKey; - @override // Public key of identity - FixedEncodedString43 get identityPublicKey; - @override // Public DHT record storing this structure for account recovery - Typed get masterRecordKey; - @override // Public key of master identity used to sign identity keys for recovery - FixedEncodedString43 get masterPublicKey; - @override // Signature of identityRecordKey and identityPublicKey by masterPublicKey - FixedEncodedString86 get identitySignature; - @override // Signature of masterRecordKey and masterPublicKey by identityPublicKey - FixedEncodedString86 get masterSignature; - @override - @JsonKey(ignore: true) - _$$IdentityMasterImplCopyWith<_$IdentityMasterImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/packages/veilid_support/lib/src/identity.g.dart b/packages/veilid_support/lib/src/identity.g.dart deleted file mode 100644 index 616477a..0000000 --- a/packages/veilid_support/lib/src/identity.g.dart +++ /dev/null @@ -1,63 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'identity.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$AccountRecordInfoImpl _$$AccountRecordInfoImplFromJson( - Map json) => - _$AccountRecordInfoImpl( - accountRecord: OwnedDHTRecordPointer.fromJson(json['account_record']), - ); - -Map _$$AccountRecordInfoImplToJson( - _$AccountRecordInfoImpl instance) => - { - 'account_record': instance.accountRecord.toJson(), - }; - -_$IdentityImpl _$$IdentityImplFromJson(Map json) => - _$IdentityImpl( - accountRecords: IMap>.fromJson( - json['account_records'] as Map, - (value) => value as String, - (value) => ISet.fromJson( - value, (value) => AccountRecordInfo.fromJson(value))), - ); - -Map _$$IdentityImplToJson(_$IdentityImpl instance) => - { - 'account_records': instance.accountRecords.toJson( - (value) => value, - (value) => value.toJson( - (value) => value.toJson(), - ), - ), - }; - -_$IdentityMasterImpl _$$IdentityMasterImplFromJson(Map json) => - _$IdentityMasterImpl( - identityRecordKey: - Typed.fromJson(json['identity_record_key']), - identityPublicKey: - FixedEncodedString43.fromJson(json['identity_public_key']), - masterRecordKey: - Typed.fromJson(json['master_record_key']), - masterPublicKey: FixedEncodedString43.fromJson(json['master_public_key']), - identitySignature: - FixedEncodedString86.fromJson(json['identity_signature']), - masterSignature: FixedEncodedString86.fromJson(json['master_signature']), - ); - -Map _$$IdentityMasterImplToJson( - _$IdentityMasterImpl instance) => - { - 'identity_record_key': instance.identityRecordKey.toJson(), - 'identity_public_key': instance.identityPublicKey.toJson(), - 'master_record_key': instance.masterRecordKey.toJson(), - 'master_public_key': instance.masterPublicKey.toJson(), - 'identity_signature': instance.identitySignature.toJson(), - 'master_signature': instance.masterSignature.toJson(), - }; diff --git a/packages/veilid_support/lib/veilid_support.dart b/packages/veilid_support/lib/veilid_support.dart index 6d10049..f48376f 100644 --- a/packages/veilid_support/lib/veilid_support.dart +++ b/packages/veilid_support/lib/veilid_support.dart @@ -6,8 +6,8 @@ library veilid_support; export 'package:veilid/veilid.dart'; export 'dht_support/dht_support.dart'; +export 'identity_support/identity_support.dart'; export 'src/config.dart'; -export 'src/identity.dart'; export 'src/json_tools.dart'; export 'src/memory_tools.dart'; export 'src/online_element_state.dart';