UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

860 lines (717 loc) 21.3 kB
import type App from '../../App'; import setup from '../../../test/setup'; import FHEClient from '.'; describe('services/fhe', () => { let client: FHEClient; let app: App; beforeAll(async () => { app = await setup.build(); client = new FHEClient({}); await client.connect(); }); afterAll(async () => { client.disconnect(); jest.resetAllMocks(); await setup.teardownDb(app.services.mongodb); }); describe('#connect / #init / #end', () => { it('binds the seal implementation on connect', async () => { const client = new FHEClient({}); let error; try { client.seal; } catch (err) { error = err; } expect(error.message).toEqual( 'Node seal must be initialized first with `.connect()`', ); }); it('binds the seal implementation on connect', async () => { const client = new FHEClient({}); // @ts-expect-error For tests only expect(client._seal).toEqual(null); await client.connect(); // @ts-expect-error For tests only expect(client._seal).not.toEqual(null); }); it('binds the seal implementation on connect only once', async () => { const client = new FHEClient({}); // @ts-expect-error For tests only expect(client._seal).toEqual(null); await client.connect(); await client.connect(); // @ts-expect-error For tests only expect(client._seal).not.toEqual(null); }); it('noops on disconnect of seal on not connected instance', async () => { const client = new FHEClient({}); // @ts-expect-error For tests only expect(client._seal).toEqual(null); await client.disconnect(); // @ts-expect-error For tests only expect(client._seal).toEqual(null); }); it('removes the seal library from the instance', async () => { const client = new FHEClient({}); // @ts-expect-error For tests only expect(client._seal).toEqual(null); await client.connect(); // @ts-expect-error For tests only expect(client._seal).not.toEqual(null); await client.disconnect(); // @ts-expect-error For tests only expect(client._seal).toEqual(null); }); }); describe('#createCypher', () => { it('cyphers are not comparable each others', async () => { // Given const a = client.createCypher([1]); const b = client.createCypher([1]); // When // // Then expect(a.save()).not.toEqual(b.save()); }); it('cyphers can be safely loaded after save', async () => { // Given const a = client.createCypher([1]); // When const savedResult = a.save(); const b = client.seal.CipherText(); b.load(client.context!, savedResult); // Then expect(a.save()).toEqual(b.save()); }); }); describe('#computeFromAnyToCiphers', () => { it('results of the exact same computation are the exactly the same', async () => { // Given const a = client.createCypher([1]); const b = client.createCypher([1]); // When const c1 = await client.computeFromAnyToCiphers( async function handler(a, b) { return a - b; }, a, b, ); const c2 = await client.computeFromAnyToCiphers( async function handler(a, b) { return a - b; }, a, b, ); // Then expect(c1.save()).toEqual(c2.save()); }); it('results of the exact same computation but with different cyphers are different', async () => { // Given const a = client.createCypher([1]); const b = client.createCypher([1]); const c = client.createCypher([1]); // When const c1 = await client.computeFromAnyToCiphers( async function handler(a, b) { return a - b; }, a, b, ); const c2 = await client.computeFromAnyToCiphers( async function handler(a, b) { return a - b; }, a, c, ); // Then expect(c1.save()).not.toEqual(c2.save()); }); it('computes the sum of 2 integers', async () => { // Given const a = client.createCypher([1]); const b = client.createCypher([2]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([3]); }); it('computes the sum of 2 array of integers', async () => { // Given const a = client.createCypher([1, 2]); const b = client.createCypher([3, 4]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then expect(client.fromCypher(c).slice(0, 2)).toEqual([4, 6]); }); it('computes the sum of 2 array of integers', async () => { // Given const a = client.createCypher([1, 2, 3]); const b = client.createCypher([2, 3, 4]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then expect(client.fromCypher(c).slice(0, 3)).toEqual([3, 5, 7]); }); it('computes the multiplication of 2 integers', async () => { // Given const a = client.createCypher([2]); const b = client.createCypher([3]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a * b; }, a, b, ); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([6]); }); it('computes the sum of an integer with the number 1', async () => { // Given const a = client.createCypher([1]); // When const c = await client.computeFromAnyToCiphers(async function handler(a) { return a + 1; }, a); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([2]); }); it('computes the sum of an array of integers with the number 1 scalar', async () => { // Given const a = client.createCypher([1, 2, 3]); // When const c = await client.computeFromAnyToCiphers(async function handler(a) { return a + 1; }, a); // Then expect(client.fromCypher(c).slice(0, 3)).toEqual([2, 3, 4]); }); it('computes an operation with priorities', async () => { // Given const a = client.createCypher([1]); // When const c = await client.computeFromAnyToCiphers(async function handler(a) { const b = 3 - a; return (-a + 3) * b - 2; }, a); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([2]); }); it('computes an operation with self negation', async () => { // Given const a = client.createCypher([1]); // When const c = await client.computeFromAnyToCiphers(async function handler(a) { return -a; }, a); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([-1]); }); it('computes an operation with assignment', async () => { // Given const a = client.createCypher([1]); // When const c = await client.computeFromAnyToCiphers(async function handler(a) { const b = a + 2; return a - b; }, a); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([-2]); }); it('computes an operation with function expression', async () => { // Given const a = client.createCypher([1]); // When const c = await client.computeFromAnyToCiphers(async function handler(a) { function b(c) { return c + 2; } return a - b(a); }, a); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([-2]); }); it.skip('computes a Math.max operation', async () => { // Given const a = client.createCypher([1]); const b = client.createCypher([2]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return Math.max.call(this, a, b); }, a, b, ); // Then expect(client.fromCypher(c).slice(0, 1)).toEqual([-2]); }); it('computes the multiplication of 2 floats', async () => { // Given const client = new FHEClient({ scheme: 'ckks', }); await client.connect(); const a = client.createCypher([2.5]); const b = client.createCypher([3.3]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a * b; }, a, b, ); // Then const target = [2.5 * 3.3]; expect( client .fromCypher(c) .slice(0, 1) .map((v, i) => Math.abs(v - target[i]) / target[i]) .reduce((s, c) => s + c, 0), ).toBeLessThanOrEqual(0.005); }); it('computes the multiplication of 2 high floats', async () => { // Given const client = new FHEClient({ scheme: 'ckks', }); await client.connect(); const a = client.createCypher([123456.789]); const b = client.createCypher([987654.321]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a * b; }, a, b, ); // Then const target = [123456.789 * 987654.321]; expect( client .fromCypher(c) .slice(0, 1) .map((v, i) => Math.abs(v - target[i]) / target[i]) .reduce((s, c) => s + c, 0), ).toBeLessThanOrEqual(0.005); }); it('computes the addition of 2 integers from different clients', async () => { // Given const alice = new FHEClient({}); await alice.connect(); const bernard = new FHEClient({}); await bernard.connect(); // Create new public from the one given by Alice: const UploadedPublicKey = bernard.seal.PublicKey(); UploadedPublicKey.load(bernard.context!, alice.keys!.public.save()); bernard.keys!.public = UploadedPublicKey; const a = alice.createCypher([2]); // The cypher is then created for Alice or Bernard computation const b = bernard.createCypher([3]); // When const aliceComputationResult = await alice.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); const bernardComputationResult = await alice.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then // But only Alice can read the result because of the secret key expect(alice.fromCypher(aliceComputationResult).slice(0, 1)).toEqual([ 2 + 3, ]); expect( bernard.fromCypher(aliceComputationResult).slice(0, 1), ).not.toEqual([2 + 3]); expect(alice.fromCypher(bernardComputationResult).slice(0, 1)).toEqual([ 2 + 3, ]); }); it('computes the addition of 2 integers given by Alice to Bernard', async () => { // Given const alice = new FHEClient({}); await alice.connect(); const a = alice.createCypher([2]); const b = alice.createCypher([3]); const alicePublicKey = alice.keys!.public.save(); const bernard = new FHEClient({}); await bernard.connect(); // Create new public from the one given by Alice: const UploadedPublicKey = bernard.seal.PublicKey(); UploadedPublicKey.load(bernard.context!, alicePublicKey); bernard.keys!.public = UploadedPublicKey; // When const c = await bernard.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then const savedResult = c.save(); const cipherA = alice.seal.CipherText(); cipherA.load(alice.context!, savedResult); expect(alice.fromCypher(cipherA).slice(0, 1)).toEqual([2 + 3]); }); }); describe('#computeFromAny', () => { it('computes a sum from raw values', async () => { // Given const state = { data: client.createCypher([1]).save(), }; const event = { data: client.createCypher([2]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { const data = event.data + state.data + 2; return [ { ...state, data, }, ]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([5]); }); it('computes a sum from raw values', async () => { // Given const state = null; const event = { data: client.createCypher([2]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { const data = event.data + (state?.data ?? 1) + 2; return [ { ...state, data, }, ]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([5]); }); it('computes the addition assignation', async () => { // Given const state = { data: client.createCypher([1]).save(), }; const event = { data: client.createCypher([2]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { state.data += 2; return [state]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([3]); }); it('computes the subtraction assignation', async () => { // Given const state = { data: client.createCypher([1]).save(), }; const event = { data: client.createCypher([2]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { state.data -= 2; return [state]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([-1]); }); it('computes the multiplication assignation', async () => { // Given const state = { data: client.createCypher([2]).save(), }; const event = { data: client.createCypher([3]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { state.data *= event.data; return [state]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([6]); }); it('computes a subtraction from raw values', async () => { // Given const state = { data: client.createCypher([2]).save(), }; const event = { data: client.createCypher([1]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { const data = state.data - event.data; return [ { ...state, data, }, ]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([1]); }); it('computes the negation of a raw value', async () => { // Given const state = { data: client.createCypher([2]).save(), }; const event = { data: client.createCypher([1]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { const data = -event.data; return [ { ...state, data, }, ]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([-1]); }); it('computes the multiplication of raw values', async () => { // Given const state = { data: client.createCypher([2]).save(), }; const event = { data: client.createCypher([3]).save(), }; // When const res = await client.computeFromAny( async (state, event) => { const data = state.data * event.data; return [ { ...state, data, }, ]; }, state, event, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, res[0].data); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([6]); }); }); describe('#vm', () => { it('computes the sum of 2 integers in a vm', async () => { // Given const a = client.createCypher([1]).save(); const b = client.createCypher([2]).save(); // When const c = await client.compute( async function handler(a, b) { return a + b; }, a, b, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, c); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([3]); }); it('computes the sum of 2 integers in a vm with a sync function', async () => { // Given const a = client.createCypher([1]).save(); const b = client.createCypher([2]).save(); // When const c = await client.compute( function handler(a, b) { return a + b; }, a, b, ); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, c); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([3]); }); it('computes the sum of 2 integers with a predefined script', async () => { // Given const a = client.createCypher([1]).save(); const b = client.createCypher([2]).save(); const fn = client.compile( function handler(a, b) { return a + b; }.toString(), ); // When const c = await client.compute(fn, a, b); // Then const cipher = client.seal.CipherText(); cipher.load(client.context!, c); expect(client.fromCypher(cipher).slice(0, 1)).toEqual([3]); }); }); describe('bgv', () => { it('sum 2 integers together', async () => { // Given const client = new FHEClient({ scheme: 'bgv', }); await client.connect(); const a = client.createCypher([1, 2]); const b = client.createCypher([3, 4]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then expect(client.fromCypher(c).slice(0, 2)).toEqual([4, 6]); }); }); describe('bfv', () => { it('sum 2 integers together', async () => { // Given const client = new FHEClient({ scheme: 'bfv', }); await client.connect(); const a = client.createCypher([1, 2]); const b = client.createCypher([3, 4]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then expect(client.fromCypher(c).slice(0, 2)).toEqual([4, 6]); }); }); describe('ckks', () => { it('sum 2 integers together', async () => { // Given const client = new FHEClient({ scheme: 'ckks', }); await client.connect(); const a = client.createCypher([1.1, 2.1]); const b = client.createCypher([3.3, 4.4]); // When const c = await client.computeFromAnyToCiphers( async function handler(a, b) { return a + b; }, a, b, ); // Then const target = [4.4, 6.5]; expect( client .fromCypher(c) .slice(0, 2) .map((v, i) => Math.abs(v - target[i]) / target[i]) .reduce((s, c) => s + c, 0) / 2, ).toBeLessThanOrEqual(0.005); }); }); });