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

Update-rcfm-flow #219

Merged
merged 14 commits into from
Jun 17, 2024
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"
}
}
}
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> => {
xstelea marked this conversation as resolved.
Show resolved Hide resolved
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,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,
}
}
Loading
Loading