git » sdk » commit de56f62

Implement get/storeOmemoContactIdentityKey

author Eric Roberts
2026-08-26 18:14:53 UTC
committer Stephen Paul Weber
2026-08-26 18:16:17 UTC
parent 6ecfa5a74becff99649fa24e371c68b187ad334f

Implement get/storeOmemoContactIdentityKey

borogove/OMEMO.hx +5 -4
borogove/Persistence.hx +2 -2
borogove/SignalProtocol.hx +1 -1
borogove/persistence/Dummy.hx +5 -3
borogove/persistence/IDB.js +5 -6
borogove/persistence/Sqlite.hx +27 -3
test/TestSqlite.hx +34 -0
test/persistence-tests.ts +42 -0

diff --git a/borogove/OMEMO.hx b/borogove/OMEMO.hx
index 4fe407a..d372fd8 100644
--- a/borogove/OMEMO.hx
+++ b/borogove/OMEMO.hx
@@ -119,15 +119,16 @@ class OMEMOStore extends SignalProtocolStore {
 	}
 
 	// Load the identity key of a contact (partners with saveIdentity())
-	public function loadIdentityKey(identifier:SignalProtocolAddress):Promise<IdentityPublicKey> {
+	public function loadIdentityKey(identifier:SignalProtocolAddress):Promise<Null<IdentityPublicKey>> {
 		return persistence.getOmemoContactIdentityKey(accountId, identifier.toString());
 	}
 
 	public function saveIdentity(identifier:SignalProtocolAddress, identityKey:IdentityPublicKey):Promise<Bool> {
 		return persistence.getOmemoContactIdentityKey(accountId, identifier.toString()).then((prevKey) -> {
-			persistence.storeOmemoContactIdentityKey(accountId, identifier.toString(), identityKey);
-			// Return true if the key was updated, false if it matches what we already had stored
-			return prevKey != identityKey;
+			return persistence.storeOmemoContactIdentityKey(accountId, identifier.toString(), identityKey).then(_ -> {
+				// Return true if the key was updated, false if it matches what we already had stored
+				return prevKey != identityKey;
+			});
 		});
 	}
 
diff --git a/borogove/Persistence.hx b/borogove/Persistence.hx
index 4f6058c..94ca911 100644
--- a/borogove/Persistence.hx
+++ b/borogove/Persistence.hx
@@ -400,12 +400,12 @@ interface Persistence {
 	/**
 		Store a trusted identity key for a remote OMEMO contact
 	**/
-	public function storeOmemoContactIdentityKey(account:String, address:String, identityKey:IdentityPublicKey):Void;
+	public function storeOmemoContactIdentityKey(account:String, address:String, identityKey:IdentityPublicKey):Promise<IdentityPublicKey>;
 
 	/**
 		Load a stored identity key for a remote OMEMO contact
 	**/
-	public function getOmemoContactIdentityKey(account:String, address:String): Promise<IdentityPublicKey>;
+	public function getOmemoContactIdentityKey(account:String, address:String): Promise<Null<IdentityPublicKey>>;
 
 	/**
 		Load a stored OMEMO session for a remote device
diff --git a/borogove/SignalProtocol.hx b/borogove/SignalProtocol.hx
index a25da43..112bbda 100644
--- a/borogove/SignalProtocol.hx
+++ b/borogove/SignalProtocol.hx
@@ -129,7 +129,7 @@ abstract class SignalProtocolStore {
 	// Return a boolean indicating whether we trust this identity
 	abstract public function isTrustedIdentity(identifier: String, identityKey: IdentityPublicKey, _direction: Int):Promise<Bool>;
 
-	abstract public function loadIdentityKey(identifier: SignalProtocolAddress):Promise<IdentityPublicKey>;
+	abstract public function loadIdentityKey(identifier: SignalProtocolAddress):Promise<Null<IdentityPublicKey>>;
 
 	abstract public function saveIdentity(identifier: SignalProtocolAddress, identityKey:IdentityPublicKey):Promise<Bool>;
 
diff --git a/borogove/persistence/Dummy.hx b/borogove/persistence/Dummy.hx
index edbfcd2..8f04b33 100644
--- a/borogove/persistence/Dummy.hx
+++ b/borogove/persistence/Dummy.hx
@@ -244,11 +244,13 @@ class Dummy implements Persistence {
 	}
 
 	@HaxeCBridge.noemit
-	public function storeOmemoContactIdentityKey(account:String, address:String, identityKey:IdentityPublicKey):Void { }
+	public function storeOmemoContactIdentityKey(account:String, address:String, identityKey:IdentityPublicKey):Promise<IdentityPublicKey> {
+		return Promise.resolve(identityKey);
+	}
 
 	@HaxeCBridge.noemit
-	public function getOmemoContactIdentityKey(account:String, address:String): Promise<IdentityPublicKey> {
-		return Promise.reject("Not found");
+	public function getOmemoContactIdentityKey(account:String, address:String): Promise<Null<IdentityPublicKey>> {
+		return Promise.resolve(null);
 	}
 
 	@HaxeCBridge.noemit
diff --git a/borogove/persistence/IDB.js b/borogove/persistence/IDB.js
index 709608d..98f668e 100644
--- a/borogove/persistence/IDB.js
+++ b/borogove/persistence/IDB.js
@@ -1436,23 +1436,22 @@ tx.onerror = console.error;
 			const tx = db.transaction(["omemo_identities"], "readonly");
 			const store = tx.objectStore("omemo_identities");
 			const result = await promisifyRequest(store.get([account, address]));
-			if(!result) {
+			if (!result) {
 				return null;
 			} else {
 				return base64ToArrayBuffer(result.pubKey);
 			}
 		},
 
-		storeOmemoContactIdentityKey(account, address, identityKey) {
+		async storeOmemoContactIdentityKey(account, address, identityKey) {
 			const tx = db.transaction(["omemo_identities"], "readwrite");
 			const store = tx.objectStore("omemo_identities");
-			promisifyRequest(store.put({
+			await promisifyRequest(store.put({
 				account: account,
 				address: address,
 				pubKey: arrayBufferToBase64(identityKey),
-			})).catch((e) => {
-				console.error("Failed to store contact identity key: " + e);
-			});
+			}));
+			return identityKey;
 		},
 
 		async getOmemoSession(account, address) {
diff --git a/borogove/persistence/Sqlite.hx b/borogove/persistence/Sqlite.hx
index 8717921..3d3886c 100644
--- a/borogove/persistence/Sqlite.hx
+++ b/borogove/persistence/Sqlite.hx
@@ -313,6 +313,17 @@ class Sqlite implements Persistence implements KeyValueStore {
 						"PRAGMA user_version = 17"]);
 					}
 					return Promise.resolve(null);
+				}).then(_ -> {
+					if (version < 18) {
+						return exec(["CREATE TABLE omemo_contact_identity_keys (
+							account_id TEXT NOT NULL,
+							address TEXT NOT NULL,
+							identity_key BLOB NOT NULL,
+							PRIMARY KEY (account_id, address)
+						) STRICT",
+						"PRAGMA user_version = 18"]);
+					}
+					return Promise.resolve(null);
 				});
 			});
 		});
@@ -1560,11 +1571,24 @@ class Sqlite implements Persistence implements KeyValueStore {
 	}
 
 	@HaxeCBridge.noemit
-	public function storeOmemoContactIdentityKey(account:String, address:String, identityKey:IdentityPublicKey):Void { }
+	public function storeOmemoContactIdentityKey(account:String, address:String, identityKey:IdentityPublicKey):Promise<IdentityPublicKey> {
+		return db.exec(
+			"INSERT OR REPLACE INTO omemo_contact_identity_keys VALUES (?,?,?)",
+			[account, address, identityKey],
+		).then(_ -> identityKey);
+	}
 
 	@HaxeCBridge.noemit
-	public function getOmemoContactIdentityKey(account:String, address:String): Promise<IdentityPublicKey> {
-		return Promise.reject("TODO");
+	public function getOmemoContactIdentityKey(account:String, address:String): Promise<Null<IdentityPublicKey>> {
+		return db.exec(
+			"SELECT identity_key FROM omemo_contact_identity_keys WHERE account_id=? AND address=? LIMIT 1",
+			[account, address],
+		).then(result -> {
+			for (row in result) {
+				return row.identity_key;
+			}
+			return null;
+		});
 	}
 
 	@HaxeCBridge.noemit
diff --git a/test/TestSqlite.hx b/test/TestSqlite.hx
index d34f58a..58da533 100644
--- a/test/TestSqlite.hx
+++ b/test/TestSqlite.hx
@@ -1478,6 +1478,40 @@ class TestSqlite extends utest.Test {
 			});
 	}
 
+	public function testGetOmemoContactIdentityKeyNotFound(async: Async) {
+		persistence
+			.getOmemoContactIdentityKey(
+				"contact-notfound@example.com",
+				"contact@example.com/1",
+			)
+			.then(result -> {
+				Assert.equals(null, result);
+				async.done();
+			})
+			.catchError(e -> {
+				Assert.fail(Std.string(e));
+				async.done();
+			});
+	}
+
+	public function testOmemoContactIdentityKey(async: Async) {
+		final account = "contact-existing@example.com";
+		final address = "contact@example.com/1";
+		final identityKey = makeKey();
+
+		persistence
+			.storeOmemoContactIdentityKey(account, address, identityKey)
+			.then(_ -> persistence.getOmemoContactIdentityKey(account, address))
+			.then(result -> {
+				assertKeyMatches(identityKey, result);
+				async.done();
+			})
+			.catchError(e -> {
+				Assert.fail(Std.string(e));
+				async.done();
+			});
+	}
+
 	public function testOmemoSignedPreKey(async: Async) {
 		final login = "signed-prekey@example.com";
 		final signedPreKey = {
diff --git a/test/persistence-tests.ts b/test/persistence-tests.ts
index 14c751e..33cf4b8 100644
--- a/test/persistence-tests.ts
+++ b/test/persistence-tests.ts
@@ -1747,4 +1747,46 @@ export function sharedPersistenceTests(test: PersistenceTest) {
 
 		expect(result).toBeNull();
 	});
+
+	test("getOmemoContactIdentityKey returns null when none is stored", async ({
+		page,
+		persistence,
+	}) => {
+		const result = await page.evaluate(
+			async ({ persistence }) =>
+				persistence.getOmemoContactIdentityKey(
+					"omemo-contact-not-found@example.com",
+					"contact@example.com/1",
+				),
+			{ persistence },
+		);
+
+		expect(result).toBeNull();
+	});
+
+	test("storeOmemoContactIdentityKey and getOmemoContactIdentityKey", async ({
+		page,
+		persistence,
+	}) => {
+		const account = "omemo-contact-existing@example.com";
+		const address = "contact@example.com/1";
+		const identityKey = [0, 1, 2, 127, 128, 255];
+		const result = await page.evaluate(
+			async ({ persistence, account, address, identityKey }) => {
+				await persistence.storeOmemoContactIdentityKey(
+					account,
+					address,
+					new Uint8Array(identityKey).buffer,
+				);
+				const loaded = await persistence.getOmemoContactIdentityKey(
+					account,
+					address,
+				);
+				return [...new Uint8Array(loaded)];
+			},
+			{ persistence, account, address, identityKey },
+		);
+
+		expect(result).toEqual(identityKey);
+	});
 }