From 15b814589e0b96956d66ec3c11c633ebb9a18264 Mon Sep 17 00:00:00 2001 From: Alex Stelea Date: Wed, 19 Jun 2024 17:38:47 +0100 Subject: [PATCH 1/3] feat: add derive shared secret with hkdf --- .../wallet-request/crypto/curve25519.spec.ts | 41 +++++++++++++++++++ .../wallet-request/crypto/curve25519.ts | 22 ++++++++-- .../identity/identity.module.ts | 9 ++-- .../radix-connect-relay.module.ts | 12 ++++-- .../modules/wallet-request/wallet-request.ts | 1 + 5 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts diff --git a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts new file mode 100644 index 00000000..e5224ad4 --- /dev/null +++ b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest' +import { Curve25519 } from './curve25519' + +const keyPair1 = Curve25519( + '4181137e23935d9b0e2bc39a798817df6bdddaab415d604801ef76b412f48124', +) +const keyPair2 = Curve25519( + 'c1c117c059232c18263704a500f2e800d5c8e8f63ef7719e9b033602488644dd', +) + +const dAppDefinitionAddress = + 'account_tdx_2_12yf9gd53yfep7a669fv2t3wm7nz9zeezwd04n02a433ker8vza6rhe' + +describe('Curve25519', () => { + it('should generate a key pair', () => { + expect(keyPair1.getPrivateKey()).toBeDefined() + expect(keyPair1.x25519.getPublicKey()).toBeDefined() + expect(keyPair1.ed25519.getPublicKey()).toBeDefined() + expect(keyPair2.getPrivateKey()).toBeDefined() + expect(keyPair2.x25519.getPublicKey()).toBeDefined() + expect(keyPair2.ed25519.getPublicKey()).toBeDefined() + }) + it.only('should calculate a shared secret', () => { + const sharedSecretResult = keyPair1.x25519.calculateSharedSecret( + keyPair2.x25519.getPublicKey(), + dAppDefinitionAddress, + ) + + if (sharedSecretResult.isErr()) throw sharedSecretResult.error + + console.log({ + sharedSecretResult: sharedSecretResult.value, + keyPair1PrivateKeyKey: keyPair2.x25519.getPublicKey(), + keyPair2PublicKey: keyPair2.x25519.getPublicKey(), + }) + + expect(sharedSecretResult.value).toBe( + 'e9278143a272e9cce596335d0f29e0194305a2a00b57501bc4c21a432d5c2b49264231657e186b6e70a13da4bbf23be6', + ) + }) +}) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts index cdf66e15..8a4071d8 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts @@ -1,6 +1,8 @@ import { x25519, ed25519 } from '@noble/curves/ed25519' import { Buffer } from 'buffer' import { Result, err, ok } from 'neverthrow' +import { hkdf } from '@noble/hashes/hkdf' +import { sha256 } from '@noble/hashes/sha256' const toHex = (input: Uint8Array) => Buffer.from(input).toString('hex') @@ -8,7 +10,10 @@ export type KeyPairProvider = (privateKeyHex?: string) => { getPrivateKey: () => string x25519: { getPublicKey: () => string - calculateSharedSecret: (publicKeyHex: string) => Result + calculateSharedSecret: ( + publicKeyHex: string, + dAppDefinitionAddress: string, + ) => Result } ed25519: { getPublicKey: () => string @@ -24,9 +29,20 @@ export const Curve25519: KeyPairProvider = ( const getPrivateKey = () => privateKeyHex const x25519Api = { getPublicKey: () => toHex(x25519.getPublicKey(privateKeyHex)), - calculateSharedSecret: (publicKeyHex: string): Result => { + calculateSharedSecret: ( + publicKeyHex: string, + dAppDefinitionAddress: string, + ): Result => { try { - return ok(toHex(x25519.getSharedSecret(privateKeyHex, publicKeyHex))) + const sharedSecret = x25519.getSharedSecret(privateKeyHex, publicKeyHex) + const derived = hkdf( + sha256, + sharedSecret, + Buffer.from(dAppDefinitionAddress, 'utf-8'), + 'RCfM', + 48, + ) + return ok(toHex(derived)) } catch (error) { return err(error as Error) } diff --git a/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts b/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts index 7d5016cc..9018c6df 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/identity/identity.module.ts @@ -16,6 +16,7 @@ export type IdentityStore = { export type IdentityModule = ReturnType export const IdentityModule = (input: { logger?: Logger + dAppDefinitionAddress: string providers: { storageModule: StorageModule KeyPairModule: KeyPairProvider @@ -57,9 +58,11 @@ export const IdentityModule = (input: { .mapErr(() => ({ reason: 'couldNotDeriveSharedSecret' })) .andThen((identity) => identity - ? identity.x25519.calculateSharedSecret(publicKey).mapErr(() => ({ - reason: 'FailedToDeriveSharedSecret', - })) + ? identity.x25519 + .calculateSharedSecret(publicKey, input.dAppDefinitionAddress) + .mapErr(() => ({ + reason: 'FailedToDeriveSharedSecret', + })) : err({ reason: 'DappIdentityNotFound' }), ) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts index ed317f18..45d9981c 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/transport/radix-connect-relay/radix-connect-relay.module.ts @@ -1,5 +1,5 @@ import { ResultAsync, err, errAsync, ok } from 'neverthrow' -import { Subject, Subscription, share } from 'rxjs' +import { Subscription } from 'rxjs' import { EncryptionModule, transformBufferToSealbox } from '../../encryption' import { Session, SessionModule } from '../../session/session.module' import type { @@ -13,12 +13,11 @@ import { DeepLinkModule } from './deep-link.module' import { IdentityModule } from '../../identity/identity.module' import { RequestItemModule } from '../../request-items/request-item.module' import { StorageModule } from '../../../storage' -import { Curve25519, KeyPairProvider } from '../../crypto' +import { Curve25519 } from '../../crypto' import { RadixConnectRelayApiService, WalletResponse, } from './radix-connect-relay-api.service' -import { RequestItem } from 'radix-connect-common' import type { TransportProvider } from '../../../../_types' import { base64urlEncode } from './helpers/base64url' @@ -27,6 +26,7 @@ export const RadixConnectRelayModule = (input: { baseUrl: string logger?: Logger walletUrl: string + dAppDefinitionAddress: string providers: { requestItemModule: RequestItemModule storageModule: StorageModule @@ -53,6 +53,7 @@ export const RadixConnectRelayModule = (input: { providers?.identityModule ?? IdentityModule({ logger, + dAppDefinitionAddress: input.dAppDefinitionAddress, providers: { storageModule: storageModule.getPartition('identities'), KeyPairModule: Curve25519, @@ -221,7 +222,10 @@ export const RadixConnectRelayModule = (input: { return errAsync({ reason: walletResponse.error }) } return dAppIdentity.x25519 - .calculateSharedSecret(walletResponse.publicKey) + .calculateSharedSecret( + walletResponse.publicKey, + input.dAppDefinitionAddress, + ) .mapErr(() => ({ reason: 'FailedToDeriveSharedSecret' })) .asyncAndThen((sharedSecret) => decryptWalletResponseData(sharedSecret, walletResponse.data), diff --git a/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts b/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts index 7da6ee7b..d336206a 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts @@ -103,6 +103,7 @@ export const WalletRequestModule = (input: { logger, walletUrl: 'radixWallet://', baseUrl: 'https://radix-connect-relay.radixdlt.com', + dAppDefinitionAddress: input.dAppDefinitionAddress, providers: { requestItemModule, storageModule, From 2747638d565ac4ff8f6e1285ba2a8c647e3331a0 Mon Sep 17 00:00:00 2001 From: Alex Stelea Date: Thu, 20 Jun 2024 10:00:44 +0100 Subject: [PATCH 2/3] code: change kdf output length --- .../src/modules/wallet-request/crypto/curve25519.spec.ts | 2 +- .../src/modules/wallet-request/crypto/curve25519.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts index e5224ad4..5cc8e30c 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts @@ -35,7 +35,7 @@ describe('Curve25519', () => { }) expect(sharedSecretResult.value).toBe( - 'e9278143a272e9cce596335d0f29e0194305a2a00b57501bc4c21a432d5c2b49264231657e186b6e70a13da4bbf23be6', + 'e9278143a272e9cce596335d0f29e0194305a2a00b57501bc4c21a432d5c2b49', ) }) }) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts index 8a4071d8..407704a2 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.ts @@ -40,7 +40,7 @@ export const Curve25519: KeyPairProvider = ( sharedSecret, Buffer.from(dAppDefinitionAddress, 'utf-8'), 'RCfM', - 48, + 32, ) return ok(toHex(derived)) } catch (error) { From 8f36a29e2db0838fecf6b3a10f5b1a9872154ec6 Mon Sep 17 00:00:00 2001 From: Alex Stelea Date: Thu, 20 Jun 2024 14:11:08 +0100 Subject: [PATCH 3/3] test: update ecdh tests --- .../wallet-request/crypto/curve25519.spec.ts | 2 +- .../identity/tests/ecdh-key-exchange.test.ts | 2 +- .../test-vectors/shared-secret-derivation.ts | 20 +++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts index 5cc8e30c..aa90da00 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/crypto/curve25519.spec.ts @@ -30,7 +30,7 @@ describe('Curve25519', () => { console.log({ sharedSecretResult: sharedSecretResult.value, - keyPair1PrivateKeyKey: keyPair2.x25519.getPublicKey(), + keyPair1PrivateKeyKey: keyPair2.getPrivateKey(), keyPair2PublicKey: keyPair2.x25519.getPublicKey(), }) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/ecdh-key-exchange.test.ts b/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/ecdh-key-exchange.test.ts index b463b596..c3d87495 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/ecdh-key-exchange.test.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/ecdh-key-exchange.test.ts @@ -7,7 +7,7 @@ describe('ECDH key exchange', () => { for (const { privateKey1, publicKey2, sharedSecret } of testVectors) { expect( Curve25519(privateKey1) - .x25519.calculateSharedSecret(publicKey2) + .x25519.calculateSharedSecret(publicKey2, 'dapp_definition_address') ._unsafeUnwrap(), ).toBe(sharedSecret) } diff --git a/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/test-vectors/shared-secret-derivation.ts b/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/test-vectors/shared-secret-derivation.ts index 9e363213..f471d55f 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/test-vectors/shared-secret-derivation.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/identity/tests/test-vectors/shared-secret-derivation.ts @@ -9,7 +9,7 @@ export const testVectors = [ publicKey2: 'd75c0da37485122bdfd27998015791d34b27f5b1d0ff013b826002e3c825613a', sharedSecret: - '1ce63dd27a4c1a65a4d4b8e6e9c7e3b486d4c44f247dbb08a47501d80799ea4a', + '98c7bbd6b839074df4909203315167a15bdf1278601d931cc2318da23ece21f3', }, { privateKey1: @@ -21,7 +21,7 @@ export const testVectors = [ publicKey2: '3b432de67106d808e2618492c3fa8a2c62e4541600b03b19ef6ad8c0a923a45f', sharedSecret: - '31a1bdd2e5e5e8d2fb4c5aed7d42076216543b6debe6427868a0d67de1e25112', + '4ec4bc2b16fef13d0efb3f52ef231da826d378968d6df628a076d5d0b7acca2e', }, { privateKey1: @@ -33,7 +33,7 @@ export const testVectors = [ publicKey2: '793638fae78fd3ae006295fe935243f4cde600897595b3fe8efe0df22061f830', sharedSecret: - '0dc255663605768d1fe8e119eac29f37eed2eb06c546d505fe03fd949dad203f', + 'a92f73fd63e8e902645ce461495e49b6c9079877bb19d9d315df9ae2e2674130', }, { privateKey1: @@ -45,7 +45,7 @@ export const testVectors = [ publicKey2: '1beec61acc925777849084d7ee391684d38f098fa915453f28e57030abadd557', sharedSecret: - 'a41c50f907e0372fbf192f3c7586bb3d532205cd199df0845f828d9c55c99c1c', + 'cf702edb32bebde72c138cd08eda6f68f96d4242a4dc5709b4e09c000f875cc8', }, { privateKey1: @@ -57,7 +57,7 @@ export const testVectors = [ publicKey2: 'e9eb0baa91c72e30d01c01158b559da2bedd7b42a2672b1c589585750f3de658', sharedSecret: - '7624bd005c3c231b4e6194cd6ad5cfdef7a41ee02a94ab42a0d95399a10c0d0e', + 'de29b74e485da2677b1b631e1657b3b436695b0b62d731b04bc78538bf7c8030', }, { privateKey1: @@ -69,7 +69,7 @@ export const testVectors = [ publicKey2: '4a60f1d8af2b9ea5a78010eb095d8d8c65ae6dee7dba23b8868ca1801855425e', sharedSecret: - '6badb61762c79c58aeba50408dbb696b81c34e72c062ecbbfa484f574f645c4e', + '575689d7d21ce289bf8aac188e2f45120b2076ce8f373412eb352a3dd637f944', }, { privateKey1: @@ -81,7 +81,7 @@ export const testVectors = [ publicKey2: 'cc66c66e8a830ad2003fddd5e9a723b4b558c77f5b9f66641c87cdd369f5cd4c', sharedSecret: - 'd4c7fb9ce163b82ee64894fe1ad420464f8d4b842608f70475141b9152f2683d', + 'c9ce908382a73f1b92976fb4e9e152aff8c6f1338db4b44616392a13a4788cc4', }, { privateKey1: @@ -93,7 +93,7 @@ export const testVectors = [ publicKey2: '8f618f9eee8eb6492ba5fcc2d5c8fd31432aac8e0281f1c9afc44a8b33261724', sharedSecret: - '61e93b25df04d60dfca080d806e7c237b847cb922ff09652d2bea91060d78a77', + 'e080f0a6e3ba84c1644aa8c9252f6cc67936403a346747206158999b192205b9', }, { privateKey1: @@ -105,7 +105,7 @@ export const testVectors = [ publicKey2: 'f38c1c87c273b2ec5a957b2f0112de3c711e06eee1d16186ecfd9e4f2730286f', sharedSecret: - '99691583adda005d1ef709bc0b3725e5cb2359bbdd898e984eeac08ccb228941', + '839a0ac8eace95917b28da42dfad37dc999cf48f26f8f7345788de6a6c5b0e9a', }, { privateKey1: @@ -117,6 +117,6 @@ export const testVectors = [ publicKey2: '6e5a9113da4446a46b9b8e2d5616a32602655a5bc9d5e997b5a0e8fc5cf6dd64', sharedSecret: - '67d975dedf4ef073c9083222499fd987511f4b8d4055cf7d38e2f5d08627232f', + '4cef0bccdf3fdab0668733b45ed69eebe6bfc7a38406220936fcf2359360e802', }, ]