Skip to content

Commit

Permalink
Merge pull request #219 from radixdlt/update-rcfm-flow
Browse files Browse the repository at this point in the history
Update-rcfm-flow
  • Loading branch information
xstelea authored Jun 17, 2024
2 parents c9e788a + e2e202a commit 5e0ca98
Show file tree
Hide file tree
Showing 16 changed files with 441 additions and 523 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/dapp-toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
},
"dependencies": {
"@noble/curves": "^1.4.0",
"base64url": "^3.0.1",
"blakejs": "^1.2.1",
"bowser": "^2.11.0",
"buffer": "^6.0.3",
"immer": "^10.0.4",
Expand Down Expand Up @@ -91,4 +93,4 @@
"publishConfig": {
"registry": "https://registry.npmjs.org"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest'
import { blake2b } from './blake2b'

describe('blake2b', () => {
it('should hash a string', async () => {
const result = blake2b(Buffer.from('test'))

if (result.isErr()) throw result.error

expect(result.value.toString('hex')).toBe(
'928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202',
)
})

it('should generate an array of blake2b hashes', async () => {
const vectors = new Array(100).fill(null).map(() => {
const buffer = Buffer.from(crypto.getRandomValues(new Uint8Array(64)))
const blake2bHash = blake2b(buffer)._unsafeUnwrap().toString('hex')
const message = buffer.toString('hex')
return { message, blake2bHash }
})
})
})
26 changes: 26 additions & 0 deletions packages/dapp-toolkit/src/modules/wallet-request/crypto/blake2b.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Result } from 'neverthrow'
import { err, ok } from 'neverthrow'
import blake from 'blakejs'
import { Buffer } from 'buffer'

const bufferToArrayBuffer = (buffer: Buffer): ArrayBuffer => {
const arrayBuffer = new ArrayBuffer(buffer.length)
const view = new Uint8Array(arrayBuffer)
for (let i = 0; i < buffer.length; ++i) {
view[i] = buffer[i]
}
return arrayBuffer
}

const bufferToUnit8Array = (buffer: Buffer): Uint8Array =>
new Uint8Array(bufferToArrayBuffer(buffer))

export const blake2b = (input: Buffer): Result<Buffer, Error> => {
try {
return ok(blake.blake2bHex(bufferToUnit8Array(input), undefined, 32)).map(
(hex) => Buffer.from(hex, 'hex'),
)
} catch (error) {
return err(error as Error)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Buffer } from 'buffer'
import type { Result } from 'neverthrow'
import { blake2b } from './blake2b'
import { Logger } from '../../../helpers'

export const createSignatureMessage = ({
interactionId,
dAppDefinitionAddress,
origin,
logger,
}: {
logger?: Logger
interactionId: string
dAppDefinitionAddress: string
origin: string
}): Result<string, { reason: string; jsError: Error }> => {
const prefix = 'C'
const prefixBuffer = Buffer.from('C', 'ascii')
const lengthOfDappDefAddress = dAppDefinitionAddress.length
const lengthOfDappDefAddressBuffer = Buffer.from(
lengthOfDappDefAddress.toString(16),
'hex',
)
const dappDefAddressBuffer = Buffer.from(dAppDefinitionAddress, 'utf-8')
const originBuffer = Buffer.from(origin, 'utf-8')
const interactionIdBuffer = Buffer.from(interactionId, 'utf-8')

const messageBuffer = Buffer.concat([
prefixBuffer,
interactionIdBuffer,
lengthOfDappDefAddressBuffer,
dappDefAddressBuffer,
originBuffer,
])

const blake2bHash = blake2b(messageBuffer)
.map((hash) => {
logger?.debug({
method: 'createSignatureMessage',
messagePartsRaw: [
prefix,
interactionId,
lengthOfDappDefAddress,
dAppDefinitionAddress,
origin,
],
messageParts: [
prefixBuffer.toString('hex'),
interactionIdBuffer.toString('hex'),
lengthOfDappDefAddressBuffer.toString('hex'),
dappDefAddressBuffer.toString('hex'),
originBuffer.toString('hex'),
],
message: messageBuffer.toString('hex'),
blake2bHash: hash.toString('hex'),
})
return Buffer.from(hash).toString('hex')
})
.mapErr((jsError) => ({ reason: 'couldNotHashMessage', jsError }))

return blake2bHash
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { x25519 } from '@noble/curves/ed25519'
import { x25519, ed25519 } from '@noble/curves/ed25519'
import { Buffer } from 'buffer'
import { Result, err, ok } from 'neverthrow'

const toHex = (input: Uint8Array) => Buffer.from(input).toString('hex')

export type KeyPairProvider = (privateKeyHex?: string) => {
getPublicKey: () => string
getPrivateKey: () => string
calculateSharedSecret: (publicKeyHex: string) => Result<string, Error>
x25519: {
getPublicKey: () => string
calculateSharedSecret: (publicKeyHex: string) => Result<string, Error>
}
ed25519: {
getPublicKey: () => string
sign: (messageHex: string) => Result<string, Error>
}
}

export type Curve25519 = ReturnType<typeof Curve25519>
Expand All @@ -16,18 +22,31 @@ export const Curve25519: KeyPairProvider = (
privateKeyHex = toHex(x25519.utils.randomPrivateKey()),
) => {
const getPrivateKey = () => privateKeyHex
const x25519Api = {
getPublicKey: () => toHex(x25519.getPublicKey(privateKeyHex)),
calculateSharedSecret: (publicKeyHex: string): Result<string, Error> => {
try {
return ok(toHex(x25519.getSharedSecret(privateKeyHex, publicKeyHex)))
} catch (error) {
return err(error as Error)
}
},
} as const

const getPublicKey = () => toHex(x25519.getPublicKey(privateKeyHex))
const ed25519Api = {
getPublicKey: () => toHex(ed25519.getPublicKey(privateKeyHex)),
sign: (messageHex: string): Result<string, Error> => {
try {
return ok(toHex(ed25519.sign(privateKeyHex, messageHex)))
} catch (error) {
return err(error as Error)
}
},
} as const

const calculateSharedSecret = (
publicKeyHex: string,
): Result<string, Error> => {
try {
return ok(toHex(x25519.getSharedSecret(privateKeyHex, publicKeyHex)))
} catch (error) {
return err(error as Error)
}
return {
getPrivateKey,
x25519: x25519Api,
ed25519: ed25519Api,
}

return { getPublicKey, getPrivateKey, calculateSharedSecret }
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './curve25519'
export * from './blake2b'
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { err, ok, okAsync } from 'neverthrow'
import { ResultAsync, err, ok, okAsync } from 'neverthrow'
import { StorageModule } from '../../storage/local-storage.module'
import type { KeyPairProvider } from '../crypto'
import { createSignatureMessage } from '../crypto/create-signature-message'
import { Logger } from '../../../helpers'

export const IdentityKind = {
dApp: 'dApp',
Expand All @@ -13,6 +15,7 @@ export type IdentityStore = {

export type IdentityModule = ReturnType<typeof IdentityModule>
export const IdentityModule = (input: {
logger?: Logger
providers: {
storageModule: StorageModule<IdentitySecret>
KeyPairModule: KeyPairProvider
Expand Down Expand Up @@ -42,23 +45,65 @@ export const IdentityModule = (input: {
)

const getOrCreateIdentity = (kind: IdentityKind) =>
getIdentity(kind).andThen((keyPair) =>
keyPair ? okAsync(keyPair) : createIdentity(kind),
)
getIdentity(kind)
.andThen((keyPair) => (keyPair ? okAsync(keyPair) : createIdentity(kind)))
.mapErr((error) => ({
reason: 'couldNotGetOrCreateIdentity',
jsError: error,
}))

const deriveSharedSecret = (kind: IdentityKind, publicKey: string) =>
getIdentity(kind)
.mapErr(() => ({ reason: 'couldNotDeriveSharedSecret' }))
.andThen((identity) =>
identity
? identity.calculateSharedSecret(publicKey).mapErr(() => ({
? identity.x25519.calculateSharedSecret(publicKey).mapErr(() => ({
reason: 'FailedToDeriveSharedSecret',
}))
: err({ reason: 'DappIdentityNotFound' }),
)

const createSignature = ({
kind,
interactionId,
dAppDefinitionAddress,
origin,
}: {
kind: IdentityKind
interactionId: string
dAppDefinitionAddress: string
origin: string
}): ResultAsync<
{ signature: string; publicKey: string },
{
reason: string
jsError: Error
}
> =>
getOrCreateIdentity(kind).andThen((identity) =>
createSignatureMessage({
interactionId,
dAppDefinitionAddress,
origin,
logger: input.logger,
}).andThen((message) =>
identity.ed25519
.sign(message)
.map((signature) => ({
signature,
publicKey: identity.x25519.getPublicKey(),
identity: identity.ed25519.getPublicKey(),
}))
.mapErr((error) => ({
reason: 'couldNotSignMessage',
jsError: error,
})),
),
)

return {
get: (kind: IdentityKind) => getOrCreateIdentity(kind),
deriveSharedSecret,
createSignature,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('ECDH key exchange', () => {
for (const { privateKey1, publicKey2, sharedSecret } of testVectors) {
expect(
Curve25519(privateKey1)
.calculateSharedSecret(publicKey2)
.x25519.calculateSharedSecret(publicKey2)
._unsafeUnwrap(),
).toBe(sharedSecret)
}
Expand Down
Loading

0 comments on commit 5e0ca98

Please sign in to comment.