mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-09-07 23:31:53 +00:00
big identity refactor
This commit is contained in:
parent
bae58d5f5c
commit
ddc02f6771
@ -31,7 +31,8 @@
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"accept": "Accept",
|
||||
"reject": "Reject"
|
||||
"reject": "Reject",
|
||||
"waiting_for_network": "Waiting For Network"
|
||||
},
|
||||
"toast": {
|
||||
"error": "Error",
|
||||
|
@ -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:
|
||||
|
@ -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<VeilidCryptoSystem> get identityCryptoSystem =>
|
||||
localAccount.superIdentity.currentInstance.cryptoSystem;
|
||||
|
||||
Future<VeilidCrypto> makeConversationCrypto(
|
||||
TypedKey remoteIdentityPublicKey) async {
|
||||
|
@ -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;
|
||||
|
@ -20,9 +20,10 @@ LocalAccount _$LocalAccountFromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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;
|
||||
|
@ -8,7 +8,7 @@ part of 'local_account.dart';
|
||||
|
||||
_$LocalAccountImpl _$$LocalAccountImplFromJson(Map<String, dynamic> 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<String, dynamic> json) =>
|
||||
|
||||
Map<String, dynamic> _$$LocalAccountImplToJson(_$LocalAccountImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'identity_master': instance.identityMaster.toJson(),
|
||||
'super_identity': instance.superIdentity.toJson(),
|
||||
'identity_secret_bytes':
|
||||
const Uint8ListJsonConverter().toJson(instance.identitySecretBytes),
|
||||
'encryption_key_type': instance.encryptionKeyType.toJson(),
|
||||
|
@ -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
|
||||
|
@ -20,8 +20,9 @@ UserLogin _$UserLoginFromJson(Map<String, dynamic> json) {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UserLogin {
|
||||
// Master record key for the user used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey =>
|
||||
// SuperIdentity record key for the user
|
||||
// used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get superIdentityRecordKey =>
|
||||
throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table
|
||||
Typed<FixedEncodedString43> 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<FixedEncodedString43> accountMasterRecordKey,
|
||||
{Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
Typed<FixedEncodedString43> 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<FixedEncodedString43>,
|
||||
identitySecret: null == identitySecret
|
||||
? _value.identitySecret
|
||||
@ -105,7 +106,7 @@ abstract class _$$UserLoginImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
{Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
Typed<FixedEncodedString43> 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<FixedEncodedString43>,
|
||||
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<String, dynamic> 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<FixedEncodedString43> accountMasterRecordKey;
|
||||
final Typed<FixedEncodedString43> superIdentityRecordKey;
|
||||
// The identity secret as unlocked from the local accounts table
|
||||
@override
|
||||
final Typed<FixedEncodedString43> 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<FixedEncodedString43> accountMasterRecordKey,
|
||||
{required final Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
required final Typed<FixedEncodedString43> identitySecret,
|
||||
required final AccountRecordInfo accountRecordInfo,
|
||||
required final Timestamp lastActive}) = _$UserLoginImpl;
|
||||
@ -225,8 +227,9 @@ abstract class _UserLogin implements UserLogin {
|
||||
factory _UserLogin.fromJson(Map<String, dynamic> json) =
|
||||
_$UserLoginImpl.fromJson;
|
||||
|
||||
@override // Master record key for the user used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey;
|
||||
@override // SuperIdentity record key for the user
|
||||
// used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get superIdentityRecordKey;
|
||||
@override // The identity secret as unlocked from the local accounts table
|
||||
Typed<FixedEncodedString43> get identitySecret;
|
||||
@override // The account record key, owner key and secret pulled from the identity
|
||||
|
@ -8,8 +8,8 @@ part of 'user_login.dart';
|
||||
|
||||
_$UserLoginImpl _$$UserLoginImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserLoginImpl(
|
||||
accountMasterRecordKey: Typed<FixedEncodedString43>.fromJson(
|
||||
json['account_master_record_key']),
|
||||
superIdentityRecordKey: Typed<FixedEncodedString43>.fromJson(
|
||||
json['super_identity_record_key']),
|
||||
identitySecret:
|
||||
Typed<FixedEncodedString43>.fromJson(json['identity_secret']),
|
||||
accountRecordInfo:
|
||||
@ -19,7 +19,7 @@ _$UserLoginImpl _$$UserLoginImplFromJson(Map<String, dynamic> json) =>
|
||||
|
||||
Map<String, dynamic> _$$UserLoginImplToJson(_$UserLoginImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'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(),
|
||||
|
@ -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<void> 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<void> 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<LocalAccount> _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<bool> deleteLocalAccount(TypedKey accountMasterRecordKey) async {
|
||||
await logout(accountMasterRecordKey);
|
||||
Future<bool> 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<void> switchToAccount(TypedKey? accountMasterRecordKey) async {
|
||||
Future<void> 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<bool> _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<bool> login(TypedKey accountMasterRecordKey,
|
||||
Future<bool> 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<void> 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<DHTRecord> 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;
|
||||
}
|
||||
|
@ -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<NewAccountPage> {
|
||||
}
|
||||
|
||||
Widget _newAccountForm(BuildContext context,
|
||||
{required Future<void> Function(GlobalKey<FormBuilderState>)
|
||||
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<void> Function(GlobalKey<FormBuilderState>) onSubmit}) {
|
||||
final networkReady = context
|
||||
.watch<ConnectionStateCubit>()
|
||||
.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<NewAccountPage> {
|
||||
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'),
|
@ -1,2 +1,2 @@
|
||||
export 'new_account_page/new_account_page.dart';
|
||||
export 'new_account_page.dart';
|
||||
export 'profile_widget.dart';
|
||||
|
35
lib/app.dart
35
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: <Type, Action<Intent>>{
|
||||
ReloadThemeIntent: CallbackAction<ReloadThemeIntent>(
|
||||
onInvoke: (intent) => _reloadTheme(context)),
|
||||
AttachDetachThemeIntent:
|
||||
CallbackAction<AttachDetachThemeIntent>(
|
||||
onInvoke: (intent) => _attachDetachTheme(context)),
|
||||
}, child: Focus(autofocus: true, child: builder(context)))));
|
||||
|
||||
@override
|
||||
|
@ -49,8 +49,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
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,
|
||||
|
@ -114,13 +114,12 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
_conversationCrypto = await _activeAccountInfo
|
||||
.makeConversationCrypto(_remoteIdentityPublicKey);
|
||||
_senderMessageIntegrity = await MessageIntegrity.create(
|
||||
author: _activeAccountInfo.localAccount.identityMaster
|
||||
.identityPublicTypedKey());
|
||||
author: _activeAccountInfo.identityTypedPublicKey);
|
||||
}
|
||||
|
||||
// Open local messages key
|
||||
Future<void> _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<SingleContactMessagesState> {
|
||||
{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<SingleContactMessagesState> {
|
||||
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<SingleContactMessagesState> {
|
||||
|
||||
// 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<SingleContactMessagesState> {
|
||||
|
||||
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<SingleContactMessagesState> {
|
||||
// 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
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -43,23 +43,21 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
if (signedContactResponse == null) {
|
||||
return const AsyncValue.loading();
|
||||
}
|
||||
final pool = DHTRecordPool.instance;
|
||||
final contactResponseBytes =
|
||||
Uint8List.fromList(signedContactResponse.contactResponse);
|
||||
final contactResponse =
|
||||
proto.ContactResponse.fromBuffer(contactResponseBytes);
|
||||
final contactIdentityMasterRecordKey =
|
||||
contactResponse.identityMasterRecordKey.toVeilid();
|
||||
final cs =
|
||||
await pool.veilid.getCryptoSystem(contactIdentityMasterRecordKey.kind);
|
||||
contactResponse.superIdentityRecordKey.toVeilid();
|
||||
|
||||
// Fetch the remote contact's account master
|
||||
final contactIdentityMaster = await openIdentityMaster(
|
||||
identityMasterRecordKey: contactIdentityMasterRecordKey);
|
||||
final contactSuperIdentity = await SuperIdentity.open(
|
||||
superRecordKey: contactIdentityMasterRecordKey);
|
||||
|
||||
// Verify
|
||||
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
|
||||
final signature = signedContactResponse.identitySignature.toVeilid();
|
||||
await cs.verify(contactIdentityMaster.identityPublicKey,
|
||||
await idcs.verify(contactSuperIdentity.currentInstance.publicKey,
|
||||
contactResponseBytes, signature);
|
||||
|
||||
// Check for rejection
|
||||
@ -74,7 +72,8 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
|
||||
final conversation = ConversationCubit(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
remoteIdentityPublicKey: contactIdentityMaster.identityPublicTypedKey(),
|
||||
remoteIdentityPublicKey:
|
||||
contactSuperIdentity.currentInstance.typedPublicKey,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey);
|
||||
|
||||
// wait for remote conversation for up to 20 seconds
|
||||
@ -106,7 +105,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
return AsyncValue.data(InvitationStatus(
|
||||
acceptedContact: AcceptedContact(
|
||||
remoteProfile: remoteProfile,
|
||||
remoteIdentity: contactIdentityMaster,
|
||||
remoteIdentity: contactSuperIdentity,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||
localConversationRecordKey: localConversationRecordKey)));
|
||||
});
|
||||
|
@ -14,7 +14,7 @@ class AcceptedContact extends Equatable {
|
||||
});
|
||||
|
||||
final proto.Profile remoteProfile;
|
||||
final IdentityMaster remoteIdentity;
|
||||
final SuperIdentity remoteIdentity;
|
||||
final TypedKey remoteConversationRecordKey;
|
||||
final TypedKey localConversationRecordKey;
|
||||
|
||||
|
@ -17,13 +17,13 @@ class ValidContactInvitation {
|
||||
required proto.Account account,
|
||||
required TypedKey contactRequestInboxKey,
|
||||
required proto.ContactRequestPrivate contactRequestPrivate,
|
||||
required IdentityMaster contactIdentityMaster,
|
||||
required SuperIdentity contactSuperIdentity,
|
||||
required KeyPair writer})
|
||||
: _activeAccountInfo = activeAccountInfo,
|
||||
_account = account,
|
||||
_contactRequestInboxKey = contactRequestInboxKey,
|
||||
_contactRequestPrivate = contactRequestPrivate,
|
||||
_contactIdentityMaster = contactIdentityMaster,
|
||||
_contactSuperIdentity = contactSuperIdentity,
|
||||
_writer = writer;
|
||||
|
||||
proto.Profile get remoteProfile => _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;
|
||||
}
|
||||
|
@ -86,13 +86,12 @@ class InvitationDialogState extends State<InvitationDialog> {
|
||||
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:
|
||||
|
@ -36,7 +36,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
|
||||
Future<void> 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<proto.Contact> {
|
||||
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;
|
||||
|
@ -47,7 +47,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
// 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<AsyncValue<ConversationState>> {
|
||||
_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<AsyncValue<ConversationState>> {
|
||||
// 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<AsyncValue<ConversationState>> {
|
||||
}) async {
|
||||
final crypto =
|
||||
await activeAccountInfo.makeConversationCrypto(remoteIdentityPublicKey);
|
||||
final writer = activeAccountInfo.conversationWriter;
|
||||
final writer = activeAccountInfo.identityWriter;
|
||||
|
||||
return (await DHTLog.create(
|
||||
debugName: 'ConversationCubit::initLocalMessages::LocalMessages',
|
||||
|
@ -91,7 +91,7 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
|
||||
// Accept
|
||||
await contactListCubit.createContact(
|
||||
remoteProfile: acceptedContact.remoteProfile,
|
||||
remoteIdentity: acceptedContact.remoteIdentity,
|
||||
remoteSuperIdentity: acceptedContact.remoteIdentity,
|
||||
remoteConversationRecordKey:
|
||||
acceptedContact.remoteConversationRecordKey,
|
||||
localConversationRecordKey:
|
||||
|
@ -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<Profile>(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<Profile>(1, _omitFieldNames ? '' : 'editedProfile', subBuilder: Profile.create)
|
||||
..aOM<Profile>(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<Profile>(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);
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<String, dynamic>);
|
||||
}
|
@ -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>(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<String, dynamic> json) {
|
||||
return _AccountRecordInfo.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$AccountRecordInfo {
|
||||
// Top level account keys and secrets
|
||||
OwnedDHTRecordPointer get accountRecord => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$AccountRecordInfoCopyWith<AccountRecordInfo> 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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
return _$$AccountRecordInfoImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _AccountRecordInfo implements AccountRecordInfo {
|
||||
const factory _AccountRecordInfo(
|
||||
{required final OwnedDHTRecordPointer accountRecord}) =
|
||||
_$AccountRecordInfoImpl;
|
||||
|
||||
factory _AccountRecordInfo.fromJson(Map<String, dynamic> json) =
|
||||
_$AccountRecordInfoImpl.fromJson;
|
||||
|
||||
@override // Top level account keys and secrets
|
||||
OwnedDHTRecordPointer get accountRecord;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$AccountRecordInfoImplCopyWith<_$AccountRecordInfoImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'account_record_info.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$AccountRecordInfoImpl _$$AccountRecordInfoImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$AccountRecordInfoImpl(
|
||||
accountRecord: OwnedDHTRecordPointer.fromJson(json['account_record']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AccountRecordInfoImplToJson(
|
||||
_$AccountRecordInfoImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'account_record': instance.accountRecord.toJson(),
|
||||
};
|
13
packages/veilid_support/lib/identity_support/exceptions.dart
Normal file
13
packages/veilid_support/lib/identity_support/exceptions.dart
Normal file
@ -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';
|
||||
}
|
25
packages/veilid_support/lib/identity_support/identity.dart
Normal file
25
packages/veilid_support/lib/identity_support/identity.dart
Normal file
@ -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<String, ISet<AccountRecordInfo>> accountRecords,
|
||||
}) = _Identity;
|
||||
|
||||
factory Identity.fromJson(dynamic json) =>
|
||||
_$IdentityFromJson(json as Map<String, dynamic>);
|
||||
}
|
@ -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>(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<String, dynamic> json) {
|
||||
return _Identity.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Identity {
|
||||
// Top level account keys and secrets
|
||||
IMap<String, ISet<AccountRecordInfo>> get accountRecords =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$IdentityCopyWith<Identity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $IdentityCopyWith<$Res> {
|
||||
factory $IdentityCopyWith(Identity value, $Res Function(Identity) then) =
|
||||
_$IdentityCopyWithImpl<$Res, Identity>;
|
||||
@useResult
|
||||
$Res call({IMap<String, ISet<AccountRecordInfo>> 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<String, ISet<AccountRecordInfo>>,
|
||||
) 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<String, ISet<AccountRecordInfo>> 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<String, ISet<AccountRecordInfo>>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$IdentityImpl implements _Identity {
|
||||
const _$IdentityImpl({required this.accountRecords});
|
||||
|
||||
factory _$IdentityImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$IdentityImplFromJson(json);
|
||||
|
||||
// Top level account keys and secrets
|
||||
@override
|
||||
final IMap<String, ISet<AccountRecordInfo>> 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<String, dynamic> toJson() {
|
||||
return _$$IdentityImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Identity implements Identity {
|
||||
const factory _Identity(
|
||||
{required final IMap<String, ISet<AccountRecordInfo>>
|
||||
accountRecords}) = _$IdentityImpl;
|
||||
|
||||
factory _Identity.fromJson(Map<String, dynamic> json) =
|
||||
_$IdentityImpl.fromJson;
|
||||
|
||||
@override // Top level account keys and secrets
|
||||
IMap<String, ISet<AccountRecordInfo>> get accountRecords;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$IdentityImplCopyWith<_$IdentityImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
26
packages/veilid_support/lib/identity_support/identity.g.dart
Normal file
26
packages/veilid_support/lib/identity_support/identity.g.dart
Normal file
@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'identity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$IdentityImpl _$$IdentityImplFromJson(Map<String, dynamic> json) =>
|
||||
_$IdentityImpl(
|
||||
accountRecords: IMap<String, ISet<AccountRecordInfo>>.fromJson(
|
||||
json['account_records'] as Map<String, dynamic>,
|
||||
(value) => value as String,
|
||||
(value) => ISet<AccountRecordInfo>.fromJson(
|
||||
value, (value) => AccountRecordInfo.fromJson(value))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$IdentityImplToJson(_$IdentityImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'account_records': instance.accountRecords.toJson(
|
||||
(value) => value,
|
||||
(value) => value.toJson(
|
||||
(value) => value.toJson(),
|
||||
),
|
||||
),
|
||||
};
|
@ -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<String, dynamic>);
|
||||
|
||||
const IdentityInstance._();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Public interface
|
||||
|
||||
/// Delete this identity instance record
|
||||
/// Only deletes from the local machine not the DHT
|
||||
Future<void> delete() async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
await pool.deleteRecord(recordKey);
|
||||
}
|
||||
|
||||
Future<VeilidCryptoSystem> get cryptoSystem =>
|
||||
Veilid.instance.getCryptoSystem(recordKey.kind);
|
||||
|
||||
Future<VeilidCrypto> 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<VeilidCryptoSystem> 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<List<AccountRecordInfo>> 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> 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<AccountRecordInfo> addAccount({
|
||||
required TypedKey superRecordKey,
|
||||
required SecretKey secretKey,
|
||||
required String accountKey,
|
||||
required Future<Uint8List> 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<bool> 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<bool> 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<Signature> 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<bool> 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<Signature> 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);
|
||||
}
|
||||
}
|
@ -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>(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<String, dynamic> json) {
|
||||
return _IdentityInstance.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$IdentityInstance {
|
||||
// Private DHT record storing identity account mapping
|
||||
Typed<FixedEncodedString43> 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<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$IdentityInstanceCopyWith<IdentityInstance> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $IdentityInstanceCopyWith<$Res> {
|
||||
factory $IdentityInstanceCopyWith(
|
||||
IdentityInstance value, $Res Function(IdentityInstance) then) =
|
||||
_$IdentityInstanceCopyWithImpl<$Res, IdentityInstance>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> 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<FixedEncodedString43>,
|
||||
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<FixedEncodedString43> 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<FixedEncodedString43>,
|
||||
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<String, dynamic> json) =>
|
||||
_$$IdentityInstanceImplFromJson(json);
|
||||
|
||||
// Private DHT record storing identity account mapping
|
||||
@override
|
||||
final Typed<FixedEncodedString43> 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<String, dynamic> toJson() {
|
||||
return _$$IdentityInstanceImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _IdentityInstance extends IdentityInstance {
|
||||
const factory _IdentityInstance(
|
||||
{required final Typed<FixedEncodedString43> 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<String, dynamic> json) =
|
||||
_$IdentityInstanceImpl.fromJson;
|
||||
|
||||
@override // Private DHT record storing identity account mapping
|
||||
Typed<FixedEncodedString43> 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;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'identity_instance.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$IdentityInstanceImpl _$$IdentityInstanceImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$IdentityInstanceImpl(
|
||||
recordKey: Typed<FixedEncodedString43>.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<String, dynamic> _$$IdentityInstanceImplToJson(
|
||||
_$IdentityInstanceImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'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(),
|
||||
};
|
@ -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';
|
174
packages/veilid_support/lib/identity_support/super_identity.dart
Normal file
174
packages/veilid_support/lib/identity_support/super_identity.dart
Normal file
@ -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<IdentityInstance> 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<TypedKey> 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<String, dynamic>);
|
||||
|
||||
const SuperIdentity._();
|
||||
|
||||
/// Opens an existing super identity and validates it
|
||||
static Future<SuperIdentity> 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<void> delete() async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
await pool.deleteRecord(recordKey);
|
||||
}
|
||||
|
||||
Future<VeilidCryptoSystem> 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<VeilidCryptoSystem> 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<Signature> deprecatedInstancesSignatures,
|
||||
required List<TypedKey> 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<bool> _validateSuperIdentitySignature({
|
||||
required TypedKey recordKey,
|
||||
required Signature currentInstanceSignature,
|
||||
required List<Signature> deprecatedInstancesSignatures,
|
||||
required List<TypedKey> 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);
|
||||
}
|
||||
}
|
@ -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>(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<String, dynamic> 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<FixedEncodedString43> 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<IdentityInstance> 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<Typed<FixedEncodedString43>> get deprecatedSuperRecordKeys =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
/// Signature of recordKey, currentInstance signature,
|
||||
/// signatures of deprecatedInstances, and deprecatedSuperRecordKeys
|
||||
/// by publicKey
|
||||
FixedEncodedString86 get signature => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$SuperIdentityCopyWith<SuperIdentity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $SuperIdentityCopyWith<$Res> {
|
||||
factory $SuperIdentityCopyWith(
|
||||
SuperIdentity value, $Res Function(SuperIdentity) then) =
|
||||
_$SuperIdentityCopyWithImpl<$Res, SuperIdentity>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> recordKey,
|
||||
FixedEncodedString43 publicKey,
|
||||
IdentityInstance currentInstance,
|
||||
List<IdentityInstance> deprecatedInstances,
|
||||
List<Typed<FixedEncodedString43>> 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<FixedEncodedString43>,
|
||||
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<IdentityInstance>,
|
||||
deprecatedSuperRecordKeys: null == deprecatedSuperRecordKeys
|
||||
? _value.deprecatedSuperRecordKeys
|
||||
: deprecatedSuperRecordKeys // ignore: cast_nullable_to_non_nullable
|
||||
as List<Typed<FixedEncodedString43>>,
|
||||
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<FixedEncodedString43> recordKey,
|
||||
FixedEncodedString43 publicKey,
|
||||
IdentityInstance currentInstance,
|
||||
List<IdentityInstance> deprecatedInstances,
|
||||
List<Typed<FixedEncodedString43>> 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<FixedEncodedString43>,
|
||||
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<IdentityInstance>,
|
||||
deprecatedSuperRecordKeys: null == deprecatedSuperRecordKeys
|
||||
? _value._deprecatedSuperRecordKeys
|
||||
: deprecatedSuperRecordKeys // ignore: cast_nullable_to_non_nullable
|
||||
as List<Typed<FixedEncodedString43>>,
|
||||
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<IdentityInstance> deprecatedInstances,
|
||||
required final List<Typed<FixedEncodedString43>>
|
||||
deprecatedSuperRecordKeys,
|
||||
required this.signature})
|
||||
: _deprecatedInstances = deprecatedInstances,
|
||||
_deprecatedSuperRecordKeys = deprecatedSuperRecordKeys,
|
||||
super._();
|
||||
|
||||
factory _$SuperIdentityImpl.fromJson(Map<String, dynamic> 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<FixedEncodedString43> 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<IdentityInstance> _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<IdentityInstance> 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<Typed<FixedEncodedString43>> _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<Typed<FixedEncodedString43>> 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<String, dynamic> toJson() {
|
||||
return _$$SuperIdentityImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _SuperIdentity extends SuperIdentity {
|
||||
const factory _SuperIdentity(
|
||||
{required final Typed<FixedEncodedString43> recordKey,
|
||||
required final FixedEncodedString43 publicKey,
|
||||
required final IdentityInstance currentInstance,
|
||||
required final List<IdentityInstance> deprecatedInstances,
|
||||
required final List<Typed<FixedEncodedString43>>
|
||||
deprecatedSuperRecordKeys,
|
||||
required final FixedEncodedString86 signature}) = _$SuperIdentityImpl;
|
||||
const _SuperIdentity._() : super._();
|
||||
|
||||
factory _SuperIdentity.fromJson(Map<String, dynamic> 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<FixedEncodedString43> 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<IdentityInstance> 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<Typed<FixedEncodedString43>> 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;
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'super_identity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$SuperIdentityImpl _$$SuperIdentityImplFromJson(Map<String, dynamic> json) =>
|
||||
_$SuperIdentityImpl(
|
||||
recordKey: Typed<FixedEncodedString43>.fromJson(json['record_key']),
|
||||
publicKey: FixedEncodedString43.fromJson(json['public_key']),
|
||||
currentInstance: IdentityInstance.fromJson(json['current_instance']),
|
||||
deprecatedInstances: (json['deprecated_instances'] as List<dynamic>)
|
||||
.map(IdentityInstance.fromJson)
|
||||
.toList(),
|
||||
deprecatedSuperRecordKeys:
|
||||
(json['deprecated_super_record_keys'] as List<dynamic>)
|
||||
.map(Typed<FixedEncodedString43>.fromJson)
|
||||
.toList(),
|
||||
signature: FixedEncodedString86.fromJson(json['signature']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$SuperIdentityImplToJson(_$SuperIdentityImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'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(),
|
||||
};
|
@ -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<WritableSuperIdentity> 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<void> delete() async => superIdentity.delete();
|
||||
|
||||
/// xxx: migration support, new identities, reveal identity secret etc
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
/// Private Implementation
|
||||
|
||||
static Future<Signature> _createSuperIdentitySignature({
|
||||
required TypedKey recordKey,
|
||||
required Signature currentInstanceSignature,
|
||||
required List<Signature> deprecatedInstancesSignatures,
|
||||
required List<TypedKey> 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<T> _createIdentityInstance<T>({
|
||||
required TypedKey superRecordKey,
|
||||
required PublicKey superPublicKey,
|
||||
required SecretKey superSecret,
|
||||
required Future<T> 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;
|
||||
}
|
@ -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<String, dynamic>);
|
||||
}
|
||||
|
||||
// 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<String, ISet<AccountRecordInfo>> accountRecords,
|
||||
}) = _Identity;
|
||||
|
||||
factory Identity.fromJson(dynamic json) =>
|
||||
_$IdentityFromJson(json as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
// 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<String, dynamic>);
|
||||
}
|
||||
|
||||
extension IdentityMasterExtension on IdentityMaster {
|
||||
/// Deletes a master identity and the identity record under it
|
||||
Future<void> delete() async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
await pool.deleteRecord(masterRecordKey);
|
||||
}
|
||||
|
||||
Future<VeilidCryptoSystem> get identityCrypto =>
|
||||
Veilid.instance.getCryptoSystem(identityRecordKey.kind);
|
||||
|
||||
Future<VeilidCryptoSystem> 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<VeilidCryptoSystem> validateIdentitySecret(
|
||||
SecretKey identitySecret) async {
|
||||
final cs = await identityCrypto;
|
||||
final keyOk = await cs.validateKeyPair(identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
throw IdentityException.invalid;
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
Future<List<AccountRecordInfo>> 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> 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<AccountRecordInfo> addAccountToIdentity<T extends GeneratedMessage>({
|
||||
required SecretKey identitySecret,
|
||||
required String accountKey,
|
||||
required Future<T> 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<void> delete() async => identityMaster.delete();
|
||||
|
||||
/// Creates a new master identity and returns it with its secrets
|
||||
static Future<IdentityMasterWithSecrets> 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<IdentityMaster> 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;
|
||||
});
|
||||
}
|
@ -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>(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<String, dynamic> json) {
|
||||
return _AccountRecordInfo.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$AccountRecordInfo {
|
||||
// Top level account keys and secrets
|
||||
OwnedDHTRecordPointer get accountRecord => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$AccountRecordInfoCopyWith<AccountRecordInfo> 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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
return _$$AccountRecordInfoImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _AccountRecordInfo implements AccountRecordInfo {
|
||||
const factory _AccountRecordInfo(
|
||||
{required final OwnedDHTRecordPointer accountRecord}) =
|
||||
_$AccountRecordInfoImpl;
|
||||
|
||||
factory _AccountRecordInfo.fromJson(Map<String, dynamic> 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<String, dynamic> json) {
|
||||
return _Identity.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Identity {
|
||||
// Top level account keys and secrets
|
||||
IMap<String, ISet<AccountRecordInfo>> get accountRecords =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$IdentityCopyWith<Identity> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $IdentityCopyWith<$Res> {
|
||||
factory $IdentityCopyWith(Identity value, $Res Function(Identity) then) =
|
||||
_$IdentityCopyWithImpl<$Res, Identity>;
|
||||
@useResult
|
||||
$Res call({IMap<String, ISet<AccountRecordInfo>> 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<String, ISet<AccountRecordInfo>>,
|
||||
) 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<String, ISet<AccountRecordInfo>> 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<String, ISet<AccountRecordInfo>>,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$IdentityImpl implements _Identity {
|
||||
const _$IdentityImpl({required this.accountRecords});
|
||||
|
||||
factory _$IdentityImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$IdentityImplFromJson(json);
|
||||
|
||||
// Top level account keys and secrets
|
||||
@override
|
||||
final IMap<String, ISet<AccountRecordInfo>> 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<String, dynamic> toJson() {
|
||||
return _$$IdentityImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Identity implements Identity {
|
||||
const factory _Identity(
|
||||
{required final IMap<String, ISet<AccountRecordInfo>>
|
||||
accountRecords}) = _$IdentityImpl;
|
||||
|
||||
factory _Identity.fromJson(Map<String, dynamic> json) =
|
||||
_$IdentityImpl.fromJson;
|
||||
|
||||
@override // Top level account keys and secrets
|
||||
IMap<String, ISet<AccountRecordInfo>> get accountRecords;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$IdentityImplCopyWith<_$IdentityImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
IdentityMaster _$IdentityMasterFromJson(Map<String, dynamic> json) {
|
||||
return _IdentityMaster.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$IdentityMaster {
|
||||
// Private DHT record storing identity account mapping
|
||||
Typed<FixedEncodedString43> get identityRecordKey =>
|
||||
throw _privateConstructorUsedError; // Public key of identity
|
||||
FixedEncodedString43 get identityPublicKey =>
|
||||
throw _privateConstructorUsedError; // Public DHT record storing this structure for account recovery
|
||||
Typed<FixedEncodedString43> 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<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$IdentityMasterCopyWith<IdentityMaster> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $IdentityMasterCopyWith<$Res> {
|
||||
factory $IdentityMasterCopyWith(
|
||||
IdentityMaster value, $Res Function(IdentityMaster) then) =
|
||||
_$IdentityMasterCopyWithImpl<$Res, IdentityMaster>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> identityRecordKey,
|
||||
FixedEncodedString43 identityPublicKey,
|
||||
Typed<FixedEncodedString43> 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<FixedEncodedString43>,
|
||||
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<FixedEncodedString43>,
|
||||
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<FixedEncodedString43> identityRecordKey,
|
||||
FixedEncodedString43 identityPublicKey,
|
||||
Typed<FixedEncodedString43> 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<FixedEncodedString43>,
|
||||
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<FixedEncodedString43>,
|
||||
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<String, dynamic> json) =>
|
||||
_$$IdentityMasterImplFromJson(json);
|
||||
|
||||
// Private DHT record storing identity account mapping
|
||||
@override
|
||||
final Typed<FixedEncodedString43> identityRecordKey;
|
||||
// Public key of identity
|
||||
@override
|
||||
final FixedEncodedString43 identityPublicKey;
|
||||
// Public DHT record storing this structure for account recovery
|
||||
@override
|
||||
final Typed<FixedEncodedString43> 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<String, dynamic> toJson() {
|
||||
return _$$IdentityMasterImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _IdentityMaster implements IdentityMaster {
|
||||
const factory _IdentityMaster(
|
||||
{required final Typed<FixedEncodedString43> identityRecordKey,
|
||||
required final FixedEncodedString43 identityPublicKey,
|
||||
required final Typed<FixedEncodedString43> masterRecordKey,
|
||||
required final FixedEncodedString43 masterPublicKey,
|
||||
required final FixedEncodedString86 identitySignature,
|
||||
required final FixedEncodedString86 masterSignature}) =
|
||||
_$IdentityMasterImpl;
|
||||
|
||||
factory _IdentityMaster.fromJson(Map<String, dynamic> json) =
|
||||
_$IdentityMasterImpl.fromJson;
|
||||
|
||||
@override // Private DHT record storing identity account mapping
|
||||
Typed<FixedEncodedString43> get identityRecordKey;
|
||||
@override // Public key of identity
|
||||
FixedEncodedString43 get identityPublicKey;
|
||||
@override // Public DHT record storing this structure for account recovery
|
||||
Typed<FixedEncodedString43> 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;
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'identity.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$AccountRecordInfoImpl _$$AccountRecordInfoImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$AccountRecordInfoImpl(
|
||||
accountRecord: OwnedDHTRecordPointer.fromJson(json['account_record']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AccountRecordInfoImplToJson(
|
||||
_$AccountRecordInfoImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'account_record': instance.accountRecord.toJson(),
|
||||
};
|
||||
|
||||
_$IdentityImpl _$$IdentityImplFromJson(Map<String, dynamic> json) =>
|
||||
_$IdentityImpl(
|
||||
accountRecords: IMap<String, ISet<AccountRecordInfo>>.fromJson(
|
||||
json['account_records'] as Map<String, dynamic>,
|
||||
(value) => value as String,
|
||||
(value) => ISet<AccountRecordInfo>.fromJson(
|
||||
value, (value) => AccountRecordInfo.fromJson(value))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$IdentityImplToJson(_$IdentityImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'account_records': instance.accountRecords.toJson(
|
||||
(value) => value,
|
||||
(value) => value.toJson(
|
||||
(value) => value.toJson(),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
_$IdentityMasterImpl _$$IdentityMasterImplFromJson(Map<String, dynamic> json) =>
|
||||
_$IdentityMasterImpl(
|
||||
identityRecordKey:
|
||||
Typed<FixedEncodedString43>.fromJson(json['identity_record_key']),
|
||||
identityPublicKey:
|
||||
FixedEncodedString43.fromJson(json['identity_public_key']),
|
||||
masterRecordKey:
|
||||
Typed<FixedEncodedString43>.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<String, dynamic> _$$IdentityMasterImplToJson(
|
||||
_$IdentityMasterImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'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(),
|
||||
};
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user