@@ -2,6 +2,8 @@ package org.xmtp.android.library.messages
2
2
3
3
import com.google.crypto.tink.subtle.Base64.encodeToString
4
4
import com.google.protobuf.kotlin.toByteString
5
+ import org.xmtp.android.library.Crypto
6
+ import org.xmtp.android.library.toHex
5
7
import org.xmtp.proto.message.contents.Invitation
6
8
import org.xmtp.proto.message.contents.Invitation.InvitationV1.Context
7
9
import java.security.SecureRandom
@@ -12,8 +14,8 @@ class InvitationV1Builder {
12
14
companion object {
13
15
fun buildFromTopic (
14
16
topic : Topic ,
15
- context : Invitation . InvitationV1 . Context ? = null,
16
- aes256GcmHkdfSha256 : Invitation .InvitationV1 .Aes256gcmHkdfsha256
17
+ context : Context ? = null,
18
+ aes256GcmHkdfSha256 : Invitation .InvitationV1 .Aes256gcmHkdfsha256 ,
17
19
): InvitationV1 {
18
20
return InvitationV1 .newBuilder().apply {
19
21
this .topic = topic.description
@@ -26,18 +28,18 @@ class InvitationV1Builder {
26
28
27
29
fun buildContextFromId (
28
30
conversationId : String = "",
29
- metadata : Map <String , String > = mapOf()
30
- ): Invitation . InvitationV1 . Context {
31
- return Invitation . InvitationV1 . Context .newBuilder().apply {
31
+ metadata : Map <String , String > = mapOf(),
32
+ ): Context {
33
+ return Context .newBuilder().apply {
32
34
this .conversationId = conversationId
33
35
this .putAllMetadata(metadata)
34
36
}.build()
35
37
}
36
38
}
37
39
}
38
40
39
- fun InvitationV1.createRandom (context : Invitation . InvitationV1 . Context ? = null): InvitationV1 {
40
- val inviteContext = context ? : Invitation . InvitationV1 . Context .newBuilder().build()
41
+ fun InvitationV1.createRandom (context : Context ? = null): InvitationV1 {
42
+ val inviteContext = context ? : Context .newBuilder().build()
41
43
val randomBytes = SecureRandom ().generateSeed(32 )
42
44
val randomString = encodeToString(randomBytes, 0 ).replace(Regex (" =*$" ), " " )
43
45
.replace(Regex (" [^A-Za-z0-9]" ), " " )
@@ -54,11 +56,50 @@ fun InvitationV1.createRandom(context: Invitation.InvitationV1.Context? = null):
54
56
)
55
57
}
56
58
59
+ fun InvitationV1.createDeterministic (
60
+ sender : PrivateKeyBundleV2 ,
61
+ recipient : SignedPublicKeyBundle ,
62
+ context : Context ? = null,
63
+ ): InvitationV1 {
64
+ val inviteContext = context ? : Context .newBuilder().build()
65
+ val secret = sender.sharedSecret(
66
+ peer = recipient,
67
+ myPreKey = sender.preKeysList[0 ].publicKey,
68
+ isRecipient = false
69
+ )
70
+
71
+ val addresses = arrayOf(sender.toV1().walletAddress, recipient.walletAddress)
72
+ addresses.sort()
73
+
74
+ val msg = if (context != null && ! context.conversationId.isNullOrBlank()) {
75
+ context.conversationId + addresses.joinToString(separator = " ," )
76
+ } else {
77
+ addresses.joinToString(separator = " ," )
78
+ }
79
+
80
+ val topicId = Crypto ().calculateMac(secret = secret, message = msg.toByteArray()).toHex()
81
+ val topic = Topic .directMessageV2(topicId)
82
+ val keyMaterial = Crypto ().deriveKey(
83
+ secret = secret,
84
+ salt = " __XMTP__INVITATION__SALT__XMTP__" .toByteArray(),
85
+ info = listOf (" 0" ).plus(addresses).joinToString(separator = " |" ).toByteArray()
86
+ )
87
+ val aes256GcmHkdfSha256 = Invitation .InvitationV1 .Aes256gcmHkdfsha256 .newBuilder().apply {
88
+ this .keyMaterial = keyMaterial.toByteString()
89
+ }.build()
90
+
91
+ return InvitationV1Builder .buildFromTopic(
92
+ topic = topic,
93
+ context = inviteContext,
94
+ aes256GcmHkdfSha256 = aes256GcmHkdfSha256
95
+ )
96
+ }
97
+
57
98
class InvitationV1ContextBuilder {
58
99
companion object {
59
100
fun buildFromConversation (
60
101
conversationId : String = "",
61
- metadata : Map <String , String > = mapOf()
102
+ metadata : Map <String , String > = mapOf(),
62
103
): Context {
63
104
return Context .newBuilder().also {
64
105
it.conversationId = conversationId
0 commit comments