diff --git a/doc/invitations.md b/doc/invitations.md new file mode 100644 index 0000000..b714bb1 --- /dev/null +++ b/doc/invitations.md @@ -0,0 +1,47 @@ +## Sending an invitation +1. Generate writer keypair to share with new contact +2. Encrypt secret with requested encryption type +3. Create Local Chat DHT record (no content yet, will be encrypted with DH of contact identity key) +4. Create ContactRequestPrivate and encrypt with the writer secret +5. Create ContactRequest and embed possibly encrypted ContactRequestPrivate +6. Create DHT unicast inbox for ContactRequest and store ContactRequest in owner subkey +7. Create ContactInvitation and add invitation record to local table +8. Create SignedContactInvitation embedding ContactInvitation +9. Render SignedContactInvitation to shareable encoding (qr code, text blob, etc) +10. Share SignedContactInvitation out of band to desired contact, along with password somehow if used + +## Receiving an invitation +1. Receive SignedContactInvitation from out of band, and the password somehow if used +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 +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 ContactAccept with chat dht record and account master +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 ContactReject with account master +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. Decrypt with writer secret +2. Get DHT record for contact's AccountMaster +3. Validate the SignedContactResponse signature + +If accept == false: + 1. Announce rejection + 2. Delete local invitation from table + +If accept == true: + 1. Add a local contact with the remote chat dht record, updating from the remote profile in it. + 2. Delete local invitation from table + diff --git a/lib/entities/proto/veilidchat.pb.dart b/lib/entities/proto/veilidchat.pb.dart index e012b04..61269e8 100644 --- a/lib/entities/proto/veilidchat.pb.dart +++ b/lib/entities/proto/veilidchat.pb.dart @@ -1122,6 +1122,7 @@ class Account extends $pb.GeneratedMessage { ..aOB(2, _omitFieldNames ? '' : 'invisible') ..a<$core.int>(3, _omitFieldNames ? '' : 'autoAwayTimeoutSec', $pb.PbFieldType.OU3) ..aOM(4, _omitFieldNames ? '' : 'contactList', subBuilder: TypedKey.create) + ..aOM(5, _omitFieldNames ? '' : 'contactRequests', subBuilder: TypedKey.create) ..hasRequiredFields = false ; @@ -1185,6 +1186,497 @@ class Account extends $pb.GeneratedMessage { void clearContactList() => clearField(4); @$pb.TagNumber(4) TypedKey ensureContactList() => $_ensure(3); + + @$pb.TagNumber(5) + TypedKey get contactRequests => $_getN(4); + @$pb.TagNumber(5) + set contactRequests(TypedKey v) { setField(5, v); } + @$pb.TagNumber(5) + $core.bool hasContactRequests() => $_has(4); + @$pb.TagNumber(5) + void clearContactRequests() => clearField(5); + @$pb.TagNumber(5) + TypedKey ensureContactRequests() => $_ensure(4); +} + +class ContactInvitation extends $pb.GeneratedMessage { + factory ContactInvitation() => create(); + ContactInvitation._() : super(); + factory ContactInvitation.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ContactInvitation.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactInvitation', createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'contactRequestRecordKey', subBuilder: TypedKey.create) + ..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'writerSecret', $pb.PbFieldType.OY) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ContactInvitation clone() => ContactInvitation()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ContactInvitation copyWith(void Function(ContactInvitation) updates) => super.copyWith((message) => updates(message as ContactInvitation)) as ContactInvitation; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ContactInvitation create() => ContactInvitation._(); + ContactInvitation createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ContactInvitation getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ContactInvitation? _defaultInstance; + + @$pb.TagNumber(1) + TypedKey get contactRequestRecordKey => $_getN(0); + @$pb.TagNumber(1) + set contactRequestRecordKey(TypedKey v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasContactRequestRecordKey() => $_has(0); + @$pb.TagNumber(1) + void clearContactRequestRecordKey() => clearField(1); + @$pb.TagNumber(1) + TypedKey ensureContactRequestRecordKey() => $_ensure(0); + + @$pb.TagNumber(2) + $core.List<$core.int> get writerSecret => $_getN(1); + @$pb.TagNumber(2) + set writerSecret($core.List<$core.int> v) { $_setBytes(1, v); } + @$pb.TagNumber(2) + $core.bool hasWriterSecret() => $_has(1); + @$pb.TagNumber(2) + void clearWriterSecret() => clearField(2); +} + +class SignedContactInvitation extends $pb.GeneratedMessage { + factory SignedContactInvitation() => create(); + SignedContactInvitation._() : super(); + factory SignedContactInvitation.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory SignedContactInvitation.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SignedContactInvitation', createEmptyInstance: create) + ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'contactInvitation', $pb.PbFieldType.OY) + ..aOM(2, _omitFieldNames ? '' : 'identitySignature', subBuilder: Signature.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + SignedContactInvitation clone() => SignedContactInvitation()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + SignedContactInvitation copyWith(void Function(SignedContactInvitation) updates) => super.copyWith((message) => updates(message as SignedContactInvitation)) as SignedContactInvitation; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SignedContactInvitation create() => SignedContactInvitation._(); + SignedContactInvitation createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static SignedContactInvitation getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static SignedContactInvitation? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get contactInvitation => $_getN(0); + @$pb.TagNumber(1) + set contactInvitation($core.List<$core.int> v) { $_setBytes(0, v); } + @$pb.TagNumber(1) + $core.bool hasContactInvitation() => $_has(0); + @$pb.TagNumber(1) + void clearContactInvitation() => clearField(1); + + @$pb.TagNumber(2) + Signature get identitySignature => $_getN(1); + @$pb.TagNumber(2) + set identitySignature(Signature v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasIdentitySignature() => $_has(1); + @$pb.TagNumber(2) + void clearIdentitySignature() => clearField(2); + @$pb.TagNumber(2) + Signature ensureIdentitySignature() => $_ensure(1); +} + +class ContactRequest extends $pb.GeneratedMessage { + factory ContactRequest() => create(); + ContactRequest._() : super(); + factory ContactRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ContactRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactRequest', createEmptyInstance: create) + ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'writerSalt', $pb.PbFieldType.OY) + ..e(2, _omitFieldNames ? '' : 'encryptionKeyType', $pb.PbFieldType.OE, defaultOrMaker: EncryptionKind.ENCRYPTION_KIND_UNSPECIFIED, valueOf: EncryptionKind.valueOf, enumValues: EncryptionKind.values) + ..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'private', $pb.PbFieldType.OY) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ContactRequest clone() => ContactRequest()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ContactRequest copyWith(void Function(ContactRequest) updates) => super.copyWith((message) => updates(message as ContactRequest)) as ContactRequest; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ContactRequest create() => ContactRequest._(); + ContactRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ContactRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ContactRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get writerSalt => $_getN(0); + @$pb.TagNumber(1) + set writerSalt($core.List<$core.int> v) { $_setBytes(0, v); } + @$pb.TagNumber(1) + $core.bool hasWriterSalt() => $_has(0); + @$pb.TagNumber(1) + void clearWriterSalt() => clearField(1); + + @$pb.TagNumber(2) + EncryptionKind get encryptionKeyType => $_getN(1); + @$pb.TagNumber(2) + set encryptionKeyType(EncryptionKind v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasEncryptionKeyType() => $_has(1); + @$pb.TagNumber(2) + void clearEncryptionKeyType() => clearField(2); + + @$pb.TagNumber(3) + $core.List<$core.int> get private => $_getN(2); + @$pb.TagNumber(3) + set private($core.List<$core.int> v) { $_setBytes(2, v); } + @$pb.TagNumber(3) + $core.bool hasPrivate() => $_has(2); + @$pb.TagNumber(3) + void clearPrivate() => clearField(3); +} + +class ContactRequestPrivate extends $pb.GeneratedMessage { + factory ContactRequestPrivate() => create(); + ContactRequestPrivate._() : super(); + factory ContactRequestPrivate.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ContactRequestPrivate.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactRequestPrivate', createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'writerKey', subBuilder: CryptoKey.create) + ..aOM(2, _omitFieldNames ? '' : 'profile', subBuilder: Profile.create) + ..aOM(3, _omitFieldNames ? '' : 'accountMasterRecordKey', subBuilder: TypedKey.create) + ..aOM(4, _omitFieldNames ? '' : 'chatRecordKey', subBuilder: TypedKey.create) + ..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'expiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ContactRequestPrivate clone() => ContactRequestPrivate()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ContactRequestPrivate copyWith(void Function(ContactRequestPrivate) updates) => super.copyWith((message) => updates(message as ContactRequestPrivate)) as ContactRequestPrivate; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ContactRequestPrivate create() => ContactRequestPrivate._(); + ContactRequestPrivate createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ContactRequestPrivate getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ContactRequestPrivate? _defaultInstance; + + @$pb.TagNumber(1) + CryptoKey get writerKey => $_getN(0); + @$pb.TagNumber(1) + set writerKey(CryptoKey v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasWriterKey() => $_has(0); + @$pb.TagNumber(1) + void clearWriterKey() => clearField(1); + @$pb.TagNumber(1) + CryptoKey ensureWriterKey() => $_ensure(0); + + @$pb.TagNumber(2) + Profile get profile => $_getN(1); + @$pb.TagNumber(2) + set profile(Profile v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasProfile() => $_has(1); + @$pb.TagNumber(2) + void clearProfile() => clearField(2); + @$pb.TagNumber(2) + Profile ensureProfile() => $_ensure(1); + + @$pb.TagNumber(3) + TypedKey get accountMasterRecordKey => $_getN(2); + @$pb.TagNumber(3) + set accountMasterRecordKey(TypedKey v) { setField(3, v); } + @$pb.TagNumber(3) + $core.bool hasAccountMasterRecordKey() => $_has(2); + @$pb.TagNumber(3) + void clearAccountMasterRecordKey() => clearField(3); + @$pb.TagNumber(3) + TypedKey ensureAccountMasterRecordKey() => $_ensure(2); + + @$pb.TagNumber(4) + TypedKey get chatRecordKey => $_getN(3); + @$pb.TagNumber(4) + set chatRecordKey(TypedKey v) { setField(4, v); } + @$pb.TagNumber(4) + $core.bool hasChatRecordKey() => $_has(3); + @$pb.TagNumber(4) + void clearChatRecordKey() => clearField(4); + @$pb.TagNumber(4) + TypedKey ensureChatRecordKey() => $_ensure(3); + + @$pb.TagNumber(5) + $fixnum.Int64 get expiration => $_getI64(4); + @$pb.TagNumber(5) + set expiration($fixnum.Int64 v) { $_setInt64(4, v); } + @$pb.TagNumber(5) + $core.bool hasExpiration() => $_has(4); + @$pb.TagNumber(5) + void clearExpiration() => clearField(5); +} + +class ContactResponse extends $pb.GeneratedMessage { + factory ContactResponse() => create(); + ContactResponse._() : super(); + factory ContactResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ContactResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactResponse', createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'accept') + ..aOM(2, _omitFieldNames ? '' : 'accountMasterRecordKey', subBuilder: TypedKey.create) + ..aOM(3, _omitFieldNames ? '' : 'chatRecordKey', subBuilder: TypedKey.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ContactResponse clone() => ContactResponse()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ContactResponse copyWith(void Function(ContactResponse) updates) => super.copyWith((message) => updates(message as ContactResponse)) as ContactResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ContactResponse create() => ContactResponse._(); + ContactResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ContactResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ContactResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get accept => $_getBF(0); + @$pb.TagNumber(1) + set accept($core.bool v) { $_setBool(0, v); } + @$pb.TagNumber(1) + $core.bool hasAccept() => $_has(0); + @$pb.TagNumber(1) + void clearAccept() => clearField(1); + + @$pb.TagNumber(2) + TypedKey get accountMasterRecordKey => $_getN(1); + @$pb.TagNumber(2) + set accountMasterRecordKey(TypedKey v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasAccountMasterRecordKey() => $_has(1); + @$pb.TagNumber(2) + void clearAccountMasterRecordKey() => clearField(2); + @$pb.TagNumber(2) + TypedKey ensureAccountMasterRecordKey() => $_ensure(1); + + @$pb.TagNumber(3) + TypedKey get chatRecordKey => $_getN(2); + @$pb.TagNumber(3) + set chatRecordKey(TypedKey v) { setField(3, v); } + @$pb.TagNumber(3) + $core.bool hasChatRecordKey() => $_has(2); + @$pb.TagNumber(3) + void clearChatRecordKey() => clearField(3); + @$pb.TagNumber(3) + TypedKey ensureChatRecordKey() => $_ensure(2); +} + +class SignedContactResponse extends $pb.GeneratedMessage { + factory SignedContactResponse() => create(); + SignedContactResponse._() : super(); + factory SignedContactResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory SignedContactResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SignedContactResponse', createEmptyInstance: create) + ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'contactResponse', $pb.PbFieldType.OY) + ..aOM(2, _omitFieldNames ? '' : 'identitySignature', subBuilder: Signature.create) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + SignedContactResponse clone() => SignedContactResponse()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + SignedContactResponse copyWith(void Function(SignedContactResponse) updates) => super.copyWith((message) => updates(message as SignedContactResponse)) as SignedContactResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SignedContactResponse create() => SignedContactResponse._(); + SignedContactResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static SignedContactResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static SignedContactResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.List<$core.int> get contactResponse => $_getN(0); + @$pb.TagNumber(1) + set contactResponse($core.List<$core.int> v) { $_setBytes(0, v); } + @$pb.TagNumber(1) + $core.bool hasContactResponse() => $_has(0); + @$pb.TagNumber(1) + void clearContactResponse() => clearField(1); + + @$pb.TagNumber(2) + Signature get identitySignature => $_getN(1); + @$pb.TagNumber(2) + set identitySignature(Signature v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasIdentitySignature() => $_has(1); + @$pb.TagNumber(2) + void clearIdentitySignature() => clearField(2); + @$pb.TagNumber(2) + Signature ensureIdentitySignature() => $_ensure(1); +} + +class ContactRequestRecord extends $pb.GeneratedMessage { + factory ContactRequestRecord() => create(); + ContactRequestRecord._() : super(); + factory ContactRequestRecord.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory ContactRequestRecord.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ContactRequestRecord', createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'contactRequestRecordKey', subBuilder: TypedKey.create) + ..aOM(2, _omitFieldNames ? '' : 'writerKey', subBuilder: CryptoKey.create) + ..aOM(3, _omitFieldNames ? '' : 'writerSecret', subBuilder: CryptoKey.create) + ..aOM(4, _omitFieldNames ? '' : 'chatRecordKey', subBuilder: TypedKey.create) + ..a<$fixnum.Int64>(5, _omitFieldNames ? '' : 'expiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) + ..a<$core.List<$core.int>>(6, _omitFieldNames ? '' : 'invitation', $pb.PbFieldType.OY) + ..hasRequiredFields = false + ; + + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + ContactRequestRecord clone() => ContactRequestRecord()..mergeFromMessage(this); + @$core.Deprecated( + 'Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + ContactRequestRecord copyWith(void Function(ContactRequestRecord) updates) => super.copyWith((message) => updates(message as ContactRequestRecord)) as ContactRequestRecord; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ContactRequestRecord create() => ContactRequestRecord._(); + ContactRequestRecord createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static ContactRequestRecord getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static ContactRequestRecord? _defaultInstance; + + @$pb.TagNumber(1) + TypedKey get contactRequestRecordKey => $_getN(0); + @$pb.TagNumber(1) + set contactRequestRecordKey(TypedKey v) { setField(1, v); } + @$pb.TagNumber(1) + $core.bool hasContactRequestRecordKey() => $_has(0); + @$pb.TagNumber(1) + void clearContactRequestRecordKey() => clearField(1); + @$pb.TagNumber(1) + TypedKey ensureContactRequestRecordKey() => $_ensure(0); + + @$pb.TagNumber(2) + CryptoKey get writerKey => $_getN(1); + @$pb.TagNumber(2) + set writerKey(CryptoKey v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasWriterKey() => $_has(1); + @$pb.TagNumber(2) + void clearWriterKey() => clearField(2); + @$pb.TagNumber(2) + CryptoKey ensureWriterKey() => $_ensure(1); + + @$pb.TagNumber(3) + CryptoKey get writerSecret => $_getN(2); + @$pb.TagNumber(3) + set writerSecret(CryptoKey v) { setField(3, v); } + @$pb.TagNumber(3) + $core.bool hasWriterSecret() => $_has(2); + @$pb.TagNumber(3) + void clearWriterSecret() => clearField(3); + @$pb.TagNumber(3) + CryptoKey ensureWriterSecret() => $_ensure(2); + + @$pb.TagNumber(4) + TypedKey get chatRecordKey => $_getN(3); + @$pb.TagNumber(4) + set chatRecordKey(TypedKey v) { setField(4, v); } + @$pb.TagNumber(4) + $core.bool hasChatRecordKey() => $_has(3); + @$pb.TagNumber(4) + void clearChatRecordKey() => clearField(4); + @$pb.TagNumber(4) + TypedKey ensureChatRecordKey() => $_ensure(3); + + @$pb.TagNumber(5) + $fixnum.Int64 get expiration => $_getI64(4); + @$pb.TagNumber(5) + set expiration($fixnum.Int64 v) { $_setInt64(4, v); } + @$pb.TagNumber(5) + $core.bool hasExpiration() => $_has(4); + @$pb.TagNumber(5) + void clearExpiration() => clearField(5); + + @$pb.TagNumber(6) + $core.List<$core.int> get invitation => $_getN(5); + @$pb.TagNumber(6) + set invitation($core.List<$core.int> v) { $_setBytes(5, v); } + @$pb.TagNumber(6) + $core.bool hasInvitation() => $_has(5); + @$pb.TagNumber(6) + void clearInvitation() => clearField(6); } diff --git a/lib/entities/proto/veilidchat.pbenum.dart b/lib/entities/proto/veilidchat.pbenum.dart index 32a6e92..41e681f 100644 --- a/lib/entities/proto/veilidchat.pbenum.dart +++ b/lib/entities/proto/veilidchat.pbenum.dart @@ -51,5 +51,22 @@ class Availability extends $pb.ProtobufEnum { const Availability._($core.int v, $core.String n) : super(v, n); } +class EncryptionKind extends $pb.ProtobufEnum { + static const EncryptionKind ENCRYPTION_KIND_UNSPECIFIED = EncryptionKind._(0, _omitEnumNames ? '' : 'ENCRYPTION_KIND_UNSPECIFIED'); + static const EncryptionKind ENCRYPTION_KIND_PIN = EncryptionKind._(1, _omitEnumNames ? '' : 'ENCRYPTION_KIND_PIN'); + static const EncryptionKind ENCRYPTION_KIND_PASSWORD = EncryptionKind._(2, _omitEnumNames ? '' : 'ENCRYPTION_KIND_PASSWORD'); + + static const $core.List values = [ + ENCRYPTION_KIND_UNSPECIFIED, + ENCRYPTION_KIND_PIN, + ENCRYPTION_KIND_PASSWORD, + ]; + + static final $core.Map<$core.int, EncryptionKind> _byValue = $pb.ProtobufEnum.initByValue(values); + static EncryptionKind? valueOf($core.int value) => _byValue[value]; + + const EncryptionKind._($core.int v, $core.String n) : super(v, n); +} + const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/entities/proto/veilidchat.pbjson.dart b/lib/entities/proto/veilidchat.pbjson.dart index 052e8c5..44de007 100644 --- a/lib/entities/proto/veilidchat.pbjson.dart +++ b/lib/entities/proto/veilidchat.pbjson.dart @@ -46,6 +46,21 @@ final $typed_data.Uint8List availabilityDescriptor = $convert.base64Decode( 'lMSVRZX09GRkxJTkUQARIVChFBVkFJTEFCSUxJVFlfRlJFRRACEhUKEUFWQUlMQUJJTElUWV9C' 'VVNZEAMSFQoRQVZBSUxBQklMSVRZX0FXQVkQBA=='); +@$core.Deprecated('Use encryptionKindDescriptor instead') +const EncryptionKind$json = { + '1': 'EncryptionKind', + '2': [ + {'1': 'ENCRYPTION_KIND_UNSPECIFIED', '2': 0}, + {'1': 'ENCRYPTION_KIND_PIN', '2': 1}, + {'1': 'ENCRYPTION_KIND_PASSWORD', '2': 2}, + ], +}; + +/// Descriptor for `EncryptionKind`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List encryptionKindDescriptor = $convert.base64Decode( + 'Cg5FbmNyeXB0aW9uS2luZBIfChtFTkNSWVBUSU9OX0tJTkRfVU5TUEVDSUZJRUQQABIXChNFTk' + 'NSWVBUSU9OX0tJTkRfUElOEAESHAoYRU5DUllQVElPTl9LSU5EX1BBU1NXT1JEEAI='); + @$core.Deprecated('Use cryptoKeyDescriptor instead') const CryptoKey$json = { '1': 'CryptoKey', @@ -301,6 +316,7 @@ const Account$json = { {'1': 'invisible', '3': 2, '4': 1, '5': 8, '10': 'invisible'}, {'1': 'auto_away_timeout_sec', '3': 3, '4': 1, '5': 13, '10': 'autoAwayTimeoutSec'}, {'1': 'contact_list', '3': 4, '4': 1, '5': 11, '6': '.TypedKey', '10': 'contactList'}, + {'1': 'contact_requests', '3': 5, '4': 1, '5': 11, '6': '.TypedKey', '10': 'contactRequests'}, ], }; @@ -309,5 +325,125 @@ final $typed_data.Uint8List accountDescriptor = $convert.base64Decode( 'CgdBY2NvdW50EiIKB3Byb2ZpbGUYASABKAsyCC5Qcm9maWxlUgdwcm9maWxlEhwKCWludmlzaW' 'JsZRgCIAEoCFIJaW52aXNpYmxlEjEKFWF1dG9fYXdheV90aW1lb3V0X3NlYxgDIAEoDVISYXV0' 'b0F3YXlUaW1lb3V0U2VjEiwKDGNvbnRhY3RfbGlzdBgEIAEoCzIJLlR5cGVkS2V5Ugtjb250YW' - 'N0TGlzdA=='); + 'N0TGlzdBI0ChBjb250YWN0X3JlcXVlc3RzGAUgASgLMgkuVHlwZWRLZXlSD2NvbnRhY3RSZXF1' + 'ZXN0cw=='); + +@$core.Deprecated('Use contactInvitationDescriptor instead') +const ContactInvitation$json = { + '1': 'ContactInvitation', + '2': [ + {'1': 'contact_request_record_key', '3': 1, '4': 1, '5': 11, '6': '.TypedKey', '10': 'contactRequestRecordKey'}, + {'1': 'writer_secret', '3': 2, '4': 1, '5': 12, '10': 'writerSecret'}, + ], +}; + +/// Descriptor for `ContactInvitation`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List contactInvitationDescriptor = $convert.base64Decode( + 'ChFDb250YWN0SW52aXRhdGlvbhJGChpjb250YWN0X3JlcXVlc3RfcmVjb3JkX2tleRgBIAEoCz' + 'IJLlR5cGVkS2V5Uhdjb250YWN0UmVxdWVzdFJlY29yZEtleRIjCg13cml0ZXJfc2VjcmV0GAIg' + 'ASgMUgx3cml0ZXJTZWNyZXQ='); + +@$core.Deprecated('Use signedContactInvitationDescriptor instead') +const SignedContactInvitation$json = { + '1': 'SignedContactInvitation', + '2': [ + {'1': 'contact_invitation', '3': 1, '4': 1, '5': 12, '10': 'contactInvitation'}, + {'1': 'identity_signature', '3': 2, '4': 1, '5': 11, '6': '.Signature', '10': 'identitySignature'}, + ], +}; + +/// Descriptor for `SignedContactInvitation`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List signedContactInvitationDescriptor = $convert.base64Decode( + 'ChdTaWduZWRDb250YWN0SW52aXRhdGlvbhItChJjb250YWN0X2ludml0YXRpb24YASABKAxSEW' + 'NvbnRhY3RJbnZpdGF0aW9uEjkKEmlkZW50aXR5X3NpZ25hdHVyZRgCIAEoCzIKLlNpZ25hdHVy' + 'ZVIRaWRlbnRpdHlTaWduYXR1cmU='); + +@$core.Deprecated('Use contactRequestDescriptor instead') +const ContactRequest$json = { + '1': 'ContactRequest', + '2': [ + {'1': 'writer_salt', '3': 1, '4': 1, '5': 12, '10': 'writerSalt'}, + {'1': 'encryption_key_type', '3': 2, '4': 1, '5': 14, '6': '.EncryptionKind', '10': 'encryptionKeyType'}, + {'1': 'private', '3': 3, '4': 1, '5': 12, '10': 'private'}, + ], +}; + +/// Descriptor for `ContactRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List contactRequestDescriptor = $convert.base64Decode( + 'Cg5Db250YWN0UmVxdWVzdBIfCgt3cml0ZXJfc2FsdBgBIAEoDFIKd3JpdGVyU2FsdBI/ChNlbm' + 'NyeXB0aW9uX2tleV90eXBlGAIgASgOMg8uRW5jcnlwdGlvbktpbmRSEWVuY3J5cHRpb25LZXlU' + 'eXBlEhgKB3ByaXZhdGUYAyABKAxSB3ByaXZhdGU='); + +@$core.Deprecated('Use contactRequestPrivateDescriptor instead') +const ContactRequestPrivate$json = { + '1': 'ContactRequestPrivate', + '2': [ + {'1': 'writer_key', '3': 1, '4': 1, '5': 11, '6': '.CryptoKey', '10': 'writerKey'}, + {'1': 'profile', '3': 2, '4': 1, '5': 11, '6': '.Profile', '10': 'profile'}, + {'1': 'account_master_record_key', '3': 3, '4': 1, '5': 11, '6': '.TypedKey', '10': 'accountMasterRecordKey'}, + {'1': 'chat_record_key', '3': 4, '4': 1, '5': 11, '6': '.TypedKey', '10': 'chatRecordKey'}, + {'1': 'expiration', '3': 5, '4': 1, '5': 4, '10': 'expiration'}, + ], +}; + +/// Descriptor for `ContactRequestPrivate`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List contactRequestPrivateDescriptor = $convert.base64Decode( + 'ChVDb250YWN0UmVxdWVzdFByaXZhdGUSKQoKd3JpdGVyX2tleRgBIAEoCzIKLkNyeXB0b0tleV' + 'IJd3JpdGVyS2V5EiIKB3Byb2ZpbGUYAiABKAsyCC5Qcm9maWxlUgdwcm9maWxlEkQKGWFjY291' + 'bnRfbWFzdGVyX3JlY29yZF9rZXkYAyABKAsyCS5UeXBlZEtleVIWYWNjb3VudE1hc3RlclJlY2' + '9yZEtleRIxCg9jaGF0X3JlY29yZF9rZXkYBCABKAsyCS5UeXBlZEtleVINY2hhdFJlY29yZEtl' + 'eRIeCgpleHBpcmF0aW9uGAUgASgEUgpleHBpcmF0aW9u'); + +@$core.Deprecated('Use contactResponseDescriptor instead') +const ContactResponse$json = { + '1': 'ContactResponse', + '2': [ + {'1': 'accept', '3': 1, '4': 1, '5': 8, '10': 'accept'}, + {'1': 'account_master_record_key', '3': 2, '4': 1, '5': 11, '6': '.TypedKey', '10': 'accountMasterRecordKey'}, + {'1': 'chat_record_key', '3': 3, '4': 1, '5': 11, '6': '.TypedKey', '10': 'chatRecordKey'}, + ], +}; + +/// Descriptor for `ContactResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List contactResponseDescriptor = $convert.base64Decode( + 'Cg9Db250YWN0UmVzcG9uc2USFgoGYWNjZXB0GAEgASgIUgZhY2NlcHQSRAoZYWNjb3VudF9tYX' + 'N0ZXJfcmVjb3JkX2tleRgCIAEoCzIJLlR5cGVkS2V5UhZhY2NvdW50TWFzdGVyUmVjb3JkS2V5' + 'EjEKD2NoYXRfcmVjb3JkX2tleRgDIAEoCzIJLlR5cGVkS2V5Ug1jaGF0UmVjb3JkS2V5'); + +@$core.Deprecated('Use signedContactResponseDescriptor instead') +const SignedContactResponse$json = { + '1': 'SignedContactResponse', + '2': [ + {'1': 'contact_response', '3': 1, '4': 1, '5': 12, '10': 'contactResponse'}, + {'1': 'identity_signature', '3': 2, '4': 1, '5': 11, '6': '.Signature', '10': 'identitySignature'}, + ], +}; + +/// Descriptor for `SignedContactResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List signedContactResponseDescriptor = $convert.base64Decode( + 'ChVTaWduZWRDb250YWN0UmVzcG9uc2USKQoQY29udGFjdF9yZXNwb25zZRgBIAEoDFIPY29udG' + 'FjdFJlc3BvbnNlEjkKEmlkZW50aXR5X3NpZ25hdHVyZRgCIAEoCzIKLlNpZ25hdHVyZVIRaWRl' + 'bnRpdHlTaWduYXR1cmU='); + +@$core.Deprecated('Use contactRequestRecordDescriptor instead') +const ContactRequestRecord$json = { + '1': 'ContactRequestRecord', + '2': [ + {'1': 'contact_request_record_key', '3': 1, '4': 1, '5': 11, '6': '.TypedKey', '10': 'contactRequestRecordKey'}, + {'1': 'writer_key', '3': 2, '4': 1, '5': 11, '6': '.CryptoKey', '10': 'writerKey'}, + {'1': 'writer_secret', '3': 3, '4': 1, '5': 11, '6': '.CryptoKey', '10': 'writerSecret'}, + {'1': 'chat_record_key', '3': 4, '4': 1, '5': 11, '6': '.TypedKey', '10': 'chatRecordKey'}, + {'1': 'expiration', '3': 5, '4': 1, '5': 4, '10': 'expiration'}, + {'1': 'invitation', '3': 6, '4': 1, '5': 12, '10': 'invitation'}, + ], +}; + +/// Descriptor for `ContactRequestRecord`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List contactRequestRecordDescriptor = $convert.base64Decode( + 'ChRDb250YWN0UmVxdWVzdFJlY29yZBJGChpjb250YWN0X3JlcXVlc3RfcmVjb3JkX2tleRgBIA' + 'EoCzIJLlR5cGVkS2V5Uhdjb250YWN0UmVxdWVzdFJlY29yZEtleRIpCgp3cml0ZXJfa2V5GAIg' + 'ASgLMgouQ3J5cHRvS2V5Ugl3cml0ZXJLZXkSLwoNd3JpdGVyX3NlY3JldBgDIAEoCzIKLkNyeX' + 'B0b0tleVIMd3JpdGVyU2VjcmV0EjEKD2NoYXRfcmVjb3JkX2tleRgEIAEoCzIJLlR5cGVkS2V5' + 'Ug1jaGF0UmVjb3JkS2V5Eh4KCmV4cGlyYXRpb24YBSABKARSCmV4cGlyYXRpb24SHgoKaW52aX' + 'RhdGlvbhgGIAEoDFIKaW52aXRhdGlvbg=='); diff --git a/lib/entities/veilidchat.proto b/lib/entities/veilidchat.proto index 82ddd7c..2053e7c 100644 --- a/lib/entities/veilidchat.proto +++ b/lib/entities/veilidchat.proto @@ -61,6 +61,7 @@ message TypedKey { message DHTData { // Other keys to concatenate + // Uses the same writer as this DHTList with SMPL schema repeated TypedKey keys = 1; // Hash of reassembled data to verify contents TypedKey hash = 2; @@ -80,6 +81,7 @@ message DHTData { // Keys must use writable schema in order to make this list mutable message DHTList { // Other keys to concatenate + // Uses the same writer as this DHTList with SMPL schema repeated TypedKey keys = 1; // Item position index // Actual item location is: @@ -223,6 +225,16 @@ message Profile { optional TypedKey avatar = 5; } +// A pointer to an owned DHT record +message OwnedDHTRecordPointer { + // DHT Record key + TypedKey record_key = 1; + // DHT record owner key + CryptoKey owner_key = 2; + // DHT record owner secret + CryptoKey owner_secret = 3; +} + // A record of an individual account // Pointed to by the identity account map in the identity key // @@ -236,6 +248,95 @@ message Account { // Auto-away sets 'away' mode after an inactivity time uint32 auto_away_timeout_sec = 3; // The contacts DHTList for this account - // DHT Private: accountSecretKey - TypedKey contact_list = 4; + // DHT Private + OwnedDHTRecordPointer contact_list = 4; + // The contact requests DHTList for this account + // DHT Private + OwnedDHTRecordPointer contact_requests = 5; } + +// EncryptionKind +// Encryption of secret +enum EncryptionKind { + ENCRYPTION_KIND_UNSPECIFIED = 0; + ENCRYPTION_KIND_PIN = 1; + ENCRYPTION_KIND_PASSWORD =2; +} + +// Invitation that is shared for VeilidChat contact connections +// serialized to QR code or data blob, not send over DHT, out of band. +// Writer secret is unique to this invitation +message ContactInvitation { + // Contact request DHT record key + TypedKey contact_request_record_key = 1; + // Writer secret key bytes possibly encrypted + bytes writer_secret = 2; +} + +// Signature of invitation with identity +message SignedContactInvitation { + // The serialized bytes for the contact invitation + bytes contact_invitation = 1; + // The signature of the contact_invitation bytes with the identity + Signature identity_signature = 2; +} + +// Contact request unicastinbox on the DHT +// DHTSchema: SMPL 2 owner key, 1 writer key symmetrically encrypted with writer secret +message ContactRequest { + // The salt for the encryption used on the unicastinbox writer secret + bytes writer_salt = 1; + // The kind of encryption used on the unicastinbox writer key + EncryptionKind encryption_key_type = 2; + // The private part encoded and symmetrically encrypted with the unicastinbox writer secret + bytes private = 3; +} + +// The private part of a possibly encrypted contact request +// Symmetrically encrypted with writer secret +message ContactRequestPrivate { + // Writer public key for signing writes to contact request unicastinbox + CryptoKey writer_key = 1; + // Snapshot of profile + Profile profile = 2; + // Account master dht key + TypedKey account_master_record_key = 3; + // Local chat DHT record key + TypedKey chat_record_key = 4; + // Expiration timestamp + uint64 expiration = 5; +} + +// To accept or reject a contact request, fill this out and send to the ContactRequest unicastinbox +message ContactResponse { + // Accept or reject + bool accept = 1; + // Account master record key + TypedKey account_master_record_key = 2; + // Local chat DHT record key if accepted + TypedKey chat_record_key = 3; +} + +// Signature of response with identity +// Symmetrically encrypted with writer secret +message SignedContactResponse { + // Serialized bytes for ContactResponse + bytes contact_response = 1; + // Signature of the contact_accept bytes with the identity + Signature identity_signature = 2; +} + +// Contact request record kept in Account DHTList to keep track of extant contact invitations +message ContactRequestRecord { + // Contact request unicastinbox DHT record key + TypedKey contact_request_record_key = 1; + // Unencrypted writer key for this request + CryptoKey writer_key = 2; + CryptoKey writer_secret = 3; + // Local chat DHT record key + TypedKey chat_record_key = 4; + // Expiration timestamp + uint64 expiration = 5; + // A copy of the raw invitation bytes post-encryption + bytes invitation = 6; +} \ No newline at end of file diff --git a/lib/providers/contact_request_records.dart b/lib/providers/contact_request_records.dart new file mode 100644 index 0000000..3718e52 --- /dev/null +++ b/lib/providers/contact_request_records.dart @@ -0,0 +1,48 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:veilid/veilid.dart'; + +import '../entities/entities.dart'; +import '../entities/proto.dart' as proto; +import '../tools/tools.dart'; +import '../veilid_support/veilid_support.dart'; +import 'logins.dart'; + +part 'contact_request_records.g.dart'; + +// Contact invitation records stored in Account +class ContactRequestRecords extends DHTList { + // + + Future newContactRequest( + proto.EncryptionKind encryptionKind, + String encryptionKey, + ) async { + // + } +} + +class ContactRequestRecordsParams { + ContactRequestRecordsParams({required this.contactRequestsDHTListKey}); + TypedKey contactRequestsDHTListKey; +} + +@riverpod +Future fetchContactRequestRecords( + FetchContactRequestRecordsRef ref, + {required ContactRequestRecordsParams params}) async { + // final localAccounts = await ref.watch(localAccountsProvider.future); + // try { + // return localAccounts.firstWhere( + // (e) => e.identityMaster.masterRecordKey == accountMasterRecordKey); + // } on Exception catch (e) { + // if (e is StateError) { + // return null; + // } + // rethrow; + // } +} diff --git a/lib/providers/contact_request_records.g.dart b/lib/providers/contact_request_records.g.dart new file mode 100644 index 0000000..af5b142 --- /dev/null +++ b/lib/providers/contact_request_records.g.dart @@ -0,0 +1,117 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'contact_request_records.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$fetchContactRequestRecordsHash() => + r'603c6d81b22d1cb4fd26cf32b98d3206ff6bc38c'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +typedef FetchContactRequestRecordsRef + = AutoDisposeFutureProviderRef; + +/// See also [fetchContactRequestRecords]. +@ProviderFor(fetchContactRequestRecords) +const fetchContactRequestRecordsProvider = FetchContactRequestRecordsFamily(); + +/// See also [fetchContactRequestRecords]. +class FetchContactRequestRecordsFamily + extends Family> { + /// See also [fetchContactRequestRecords]. + const FetchContactRequestRecordsFamily(); + + /// See also [fetchContactRequestRecords]. + FetchContactRequestRecordsProvider call({ + required ContactRequestRecordsParams params, + }) { + return FetchContactRequestRecordsProvider( + params: params, + ); + } + + @override + FetchContactRequestRecordsProvider getProviderOverride( + covariant FetchContactRequestRecordsProvider provider, + ) { + return call( + params: provider.params, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'fetchContactRequestRecordsProvider'; +} + +/// See also [fetchContactRequestRecords]. +class FetchContactRequestRecordsProvider + extends AutoDisposeFutureProvider { + /// See also [fetchContactRequestRecords]. + FetchContactRequestRecordsProvider({ + required this.params, + }) : super.internal( + (ref) => fetchContactRequestRecords( + ref, + params: params, + ), + from: fetchContactRequestRecordsProvider, + name: r'fetchContactRequestRecordsProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$fetchContactRequestRecordsHash, + dependencies: FetchContactRequestRecordsFamily._dependencies, + allTransitiveDependencies: + FetchContactRequestRecordsFamily._allTransitiveDependencies, + ); + + final ContactRequestRecordsParams params; + + @override + bool operator ==(Object other) { + return other is FetchContactRequestRecordsProvider && + other.params == params; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, params.hashCode); + + return _SystemHash.finish(hash); + } +} +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/lib/veilid_support/dht_list.dart b/lib/veilid_support/dht_list.dart new file mode 100644 index 0000000..85f90b1 --- /dev/null +++ b/lib/veilid_support/dht_list.dart @@ -0,0 +1,43 @@ +import 'dart:typed_data'; + +import 'package:protobuf/protobuf.dart'; +import 'package:veilid/veilid.dart'; + +import '../tools/tools.dart'; +import 'veilid_support.dart'; + +class DHTList { + DHTList({required DHTRecord dhtRecord}) : _dhtRecord = dhtRecord; + + final DHTRecord _dhtRecord; + + static Future create(VeilidRoutingContext dhtctx, + {DHTRecordCrypto? crypto}) async { + final dhtRecord = await DHTRecord.create(dhtctx, crypto: crypto); + final dhtList = DHTList(dhtRecord: dhtRecord); + return dhtList; + } + + static Future openRead( + VeilidRoutingContext dhtctx, TypedKey dhtRecordKey, + {DHTRecordCrypto? crypto}) async { + final dhtRecord = + await DHTRecord.openRead(dhtctx, dhtRecordKey, crypto: crypto); + final dhtList = DHTList(dhtRecord: dhtRecord); + return dhtList; + } + + static Future openWrite( + VeilidRoutingContext dhtctx, + TypedKey dhtRecordKey, + KeyPair writer, { + DHTRecordCrypto? crypto, + }) async { + final dhtRecord = + await DHTRecord.openWrite(dhtctx, dhtRecordKey, writer, crypto: crypto); + final dhtList = DHTList(dhtRecord: dhtRecord); + return dhtList; + } + + //////////////////////////////////////////////////////////////// +} diff --git a/lib/veilid_support/dht_record.dart b/lib/veilid_support/dht_record.dart index 7770a6b..67c4259 100644 --- a/lib/veilid_support/dht_record.dart +++ b/lib/veilid_support/dht_record.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:protobuf/protobuf.dart'; import 'package:veilid/veilid.dart'; +import '../entities/proto.dart' as proto; import '../tools/tools.dart'; import 'veilid_support.dart'; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index abfabac..36ab4fb 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import path_provider_foundation import screen_retriever +import share_plus import shared_preferences_foundation import sqflite import url_launcher_macos @@ -16,6 +17,7 @@ import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index f598abe..623afdd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -281,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" + source: hosted + version: "0.3.3+4" crypto: dependency: transitive description: @@ -981,6 +989,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: ed3fcea4f789ed95913328e629c0c53e69e80e08b6c24542f1b3576046c614e8 + url: "https://pub.dev" + source: hosted + version: "7.0.2" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" + url: "https://pub.dev" + source: hosted + version: "3.2.1" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 79b71e3..abec7bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: radix_colors: ^1.0.4 reorderable_grid: ^1.0.7 riverpod_annotation: ^2.1.1 + share_plus: ^7.0.2 shared_preferences: ^2.0.15 signal_strength_indicator: ^0.4.1 split_view: ^3.2.1 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 283c01f..d7c7d25 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -14,6 +15,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { ScreenRetrieverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); VeilidPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index dfb7c19..0124f46 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST screen_retriever + share_plus url_launcher_windows veilid window_manager