mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-02 19:26:16 -04:00
big identity refactor
This commit is contained in:
parent
bae58d5f5c
commit
ddc02f6771
46 changed files with 2143 additions and 1300 deletions
|
@ -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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue