Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identity signer #232

Merged
merged 15 commits into from
Feb 16, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import org.json.JSONObject
import org.xmtp.android.library.Client
Expand All @@ -44,6 +45,8 @@ import org.xmtp.android.library.messages.InvitationV1ContextBuilder
import org.xmtp.android.library.messages.Pagination
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.Signature
import org.xmtp.android.library.messages.toPublicKeyBundle
import org.xmtp.android.library.messages.toV2
import org.xmtp.android.library.push.XMTPPush
import org.xmtp.proto.keystore.api.v1.Keystore.TopicMap.TopicData
import org.xmtp.proto.message.api.v1.MessageApiOuterClass
Expand Down Expand Up @@ -224,6 +227,36 @@ class XMTPModule : Module() {
}
}

AsyncFunction("sign") { clientAddress: String, digest: List<Int>, keyType: String, preKeyIndex: Int ->
logV("sign")
val client = clients[clientAddress] ?: throw XMTPException("No client")
val digestBytes =
digest.foldIndexed(ByteArray(digest.size)) { i, a, v ->
a.apply {
set(
i,
v.toByte()
)
}
}
val privateKeyBundle = client.privateKeyBundle.v2
val key = if (keyType == "prekey") {
privateKeyBundle.preKeysList[preKeyIndex]
} else {
privateKeyBundle.identityKey
}
val signature = runBlocking {
PrivateKeyBuilder(key).sign(digestBytes)
}
signature.toByteArray().map { it.toInt() and 0xFF }
}

AsyncFunction("exportPublicKeyBundle") { clientAddress: String ->
logV("exportPublicKeyBundle")
val client = clients[clientAddress] ?: throw XMTPException("No client")
client.privateKeyBundle.toV2().toPublicKeyBundle().toByteArray().map { it.toInt() and 0xFF }
alexrisch marked this conversation as resolved.
Show resolved Hide resolved
}

AsyncFunction("exportKeyBundle") { clientAddress: String ->
logV("exportKeyBundle")
val client = clients[clientAddress] ?: throw XMTPException("No client")
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,6 @@ SPEC CHECKSUMS:
XMTPReactNative: 0a5a691e0e54c7be2e9f276eaac37f9ad6a8e90a
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9

PODFILE CHECKSUM: bed59df1a015d67be871b27fb59f30a782dbf17c
PODFILE CHECKSUM: 95d6ace79946933ecf80684613842ee553dd76a2

COCOAPODS: 1.14.3
COCOAPODS: 1.13.0
14 changes: 14 additions & 0 deletions example/src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,20 @@ test('canMessage', async () => {
return canMessage
})

test('fetch a public key bundle and sign a digest', async () => {
const bob = await Client.createRandom({ env: 'local' })
const bytes = new Uint8Array([1, 2, 3])
const signature = await bob.sign(bytes, { kind: 'identity' })
if (signature.length === 0) {
throw new Error('signature was not returned')
}
const keyBundle = await bob.exportPublicKeyBundle()
if (keyBundle.length === 0) {
throw new Error('key bundle was not returned')
}
return true
})

test('createFromKeyBundle throws error for non string value', async () => {
try {
const bytes = [1, 2, 3]
Expand Down
23 changes: 23 additions & 0 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,29 @@ public class XMTPModule: Module {
throw error
}
}

AsyncFunction("sign") { (clientAddress: String, digest: [UInt8], keyType: String, preKeyIndex: Int) -> [UInt8] in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}

let privateKeyBundle = client.privateKeyBundle
let key = if (keyType == "prekey") {
privateKeyBundle.v2.preKeys[preKeyIndex]
} else {
privateKeyBundle.v2.identityKey
}
let signature = await PrivateKey(key).sign(Data(digest))
return Array(Data(signature))
}

AsyncFunction("exportPublicKeyBundle") { (clientAddress: String) -> [UInt8] in
guard let client = await clientsManager.getClient(key: clientAddress) else {
throw Error.noClient
}
let bundle = try client.publicKeyBundle.serializedData()
return Array(bundle)
}

// Export the client's serialized key bundle.
AsyncFunction("exportKeyBundle") { (clientAddress: String) -> String in
Expand Down
23 changes: 23 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ export async function createFromKeyBundle(
)
}

export async function sign(
clientAddress: string,
digest: Uint8Array,
keyType: string,
preKeyIndex: number = 0
): Promise<Uint8Array> {
const signatureArray = await XMTPModule.sign(
clientAddress,
Array.from(digest),
keyType,
preKeyIndex
)
return new Uint8Array(signatureArray)
}

export async function exportPublicKeyBundle(
clientAddress: string
): Promise<Uint8Array> {
alexrisch marked this conversation as resolved.
Show resolved Hide resolved
const publicBundleArray =
await XMTPModule.exportPublicKeyBundle(clientAddress)
return new Uint8Array(publicBundleArray)
}

export async function exportKeyBundle(clientAddress: string): Promise<string> {
return await XMTPModule.exportKeyBundle(clientAddress)
}
Expand Down
18 changes: 18 additions & 0 deletions src/lib/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,19 @@ export class Client<ContentTypes> {
this.codecRegistry[id] = contentCodec
}

async sign(digest: Uint8Array, keyType: KeyType): Promise<Uint8Array> {
return XMTPModule.sign(
this.address,
digest,
keyType.kind,
keyType.prekeyIndex
)
}

async exportPublicKeyBundle(): Promise<Uint8Array> {
return XMTPModule.exportPublicKeyBundle(this.address)
}

/**
* Exports the key bundle associated with the current XMTP address.
*
Expand Down Expand Up @@ -408,6 +421,11 @@ export type NetworkOptions = {
appVersion?: string
}

export type KeyType = {
alexrisch marked this conversation as resolved.
Show resolved Hide resolved
kind: 'identity' | 'prekey'
prekeyIndex?: number
}

export type CallbackOptions = {
preCreateIdentityCallback?: () => Promise<void> | void
preEnableIdentityCallback?: () => Promise<void> | void
Expand Down
Loading