Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 0 additions & 50 deletions packages/common/src/services/auth/hedgehogLib/authWorker.ts

This file was deleted.

37 changes: 37 additions & 0 deletions packages/common/src/services/auth/hedgehogLib/hedgehogLib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { recoverMessageAddress } from 'viem'
import { signMessage } from 'viem/accounts'
import { describe, expect, it } from 'vitest'

import { createKeyAsync, createKeySync } from './scrypt'
import { hexToBytesLikeBuffer, waitUntil } from './utils'

import {
Expand Down Expand Up @@ -388,6 +389,42 @@ describe('createKey (scrypt)', () => {
}, 60_000)
})

describe('createKey (scrypt) vs the old web worker', () => {
// Outputs of the scrypt-js@2.0.4 + buffer scripts the old web worker loaded
// from jsdelivr, run with the same inputs and JSON round-trip.
const vectors: Array<[string, string, string]> = [
[
'hunter2',
'0123456789abcdef0123456789abcdef',
'e9f55191be7eeaab074726a9860cb95e57eeac613e5b25415655903fc70a25c4'
],
[
'dylan@audius.example:::correct horse battery staple',
'a1b2c3d4e5f60718293a4b5c6d7e8f90',
'aac7f65da104b69fa2e8c95ec4f6a30f43ce4377f55576611f28928a86d93053'
],
[
'pässwörd ñ 日本語 🎵🎧',
'ffeeddccbbaa99887766554433221100',
'd4acf4d5eb6a4828cab6f9e02db11fef876fbfd243e968308fbbe983ab4eec79'
],
[
'',
'deadbeefdeadbeefdeadbeefdeadbeef',
'ad5bfdb843b3788277d9d287ebf5b45c127577c12e585579a0946ad55f1109db'
]
]

it('createKeySync and createKeyAsync match', async () => {
for (const [str, iv, keyHex] of vectors) {
const sync = createKeySync(str, iv)
expect(sync.keyHex).toBe(keyHex)
expect(Buffer.from(sync.keyBuffer).toString('hex')).toBe(keyHex)
expect((await createKeyAsync(str, iv)).keyHex).toBe(keyHex)
}
}, 60_000)
})

describe('identity auth signature (personal_sign)', () => {
it('matches eth-sig-util personalSign byte for byte', async () => {
for (let i = 0; i < 100; i++) {
Expand Down
3 changes: 1 addition & 2 deletions packages/common/src/services/auth/hedgehogLib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ export {
bufferFromHexString,
getPlatformCreateKey,
getPlatformLocalStorage,
waitUntil,
WebWorker
waitUntil
} from './utils'
export type {
ChangeCredentialsArgs,
Expand Down
30 changes: 30 additions & 0 deletions packages/common/src/services/auth/hedgehogLib/scrypt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { scrypt, scryptAsync } from '@noble/hashes/scrypt'
import { bytesToHex } from '@noble/hashes/utils'

import type { PrivateKey } from './types'

// Changing these params or how the inputs are encoded changes every user's key.
// The ivHex string itself (its utf8 bytes, not the decoded hex) is the salt.
const SCRYPT_PARAMS = { N: 32768, r: 8, p: 1, dkLen: 32 }

const encoder = new TextEncoder()

const toPrivateKey = (keyBuffer: Uint8Array): PrivateKey => ({
keyHex: bytesToHex(keyBuffer),
keyBuffer
})

/** Blocks for about a second. Only call this off the main thread. */
export const createKeySync = (encryptStr: string, ivHex: string) =>
toPrivateKey(
scrypt(encoder.encode(encryptStr), encoder.encode(ivHex), SCRYPT_PARAMS)
)

export const createKeyAsync = async (encryptStr: string, ivHex: string) =>
toPrivateKey(
await scryptAsync(
encoder.encode(encryptStr),
encoder.encode(ivHex),
SCRYPT_PARAMS
)
)
60 changes: 8 additions & 52 deletions packages/common/src/services/auth/hedgehogLib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import { authWorker } from './authWorker'
import type { CreateKey, LocalStorage, PrivateKey } from './types'

const SCRYPT_N = 32768
const SCRYPT_R = 8
const SCRYPT_P = 1
const SCRYPT_DK_LEN = 32
import type { CreateKey, LocalStorage } from './types'

// https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript
export function bufferFromHexString(hexString: string) {
Expand Down Expand Up @@ -53,14 +47,6 @@ export function randomBytes(length: number) {
return bytes
}

export function WebWorker(worker: () => void) {
if (typeof window !== 'undefined' && window.Worker) {
const code = worker.toString()
const blob = new Blob(['(' + code + ')()'])
return new window.Worker(URL.createObjectURL(blob))
} else throw new Error('Cannot call web worker on the server')
}

function isReactNativeEnv() {
return typeof navigator !== 'undefined' && navigator.product === 'ReactNative'
}
Expand All @@ -74,10 +60,6 @@ function isWebEnv() {
)
}

function isNodeEnv() {
return typeof window === 'undefined' || window === null
}

export function getPlatformLocalStorage(): LocalStorage {
if (isWebEnv()) {
return window.localStorage
Expand All @@ -87,41 +69,15 @@ export function getPlatformLocalStorage(): LocalStorage {
)
}

/**
* Default scrypt createKey. Runs on the calling thread, so web passes its own
* worker-based createKey and mobile passes a native one.
*/
export function getPlatformCreateKey(): CreateKey {
/**
* Given a user encryptStr and initialization vector, generate a private key
* @param encryptStr String to encrypt (can be user password or some kind of lookup key)
* @param ivHex hex string iv value. Its utf8 bytes (not the decoded hex) are the salt.
*/
const createKey = async (encryptStr: string, ivHex: string) => {
if (isWebEnv()) {
return new Promise<PrivateKey>((resolve) => {
const worker = WebWorker(authWorker)
worker.postMessage(JSON.stringify({ encryptStr, ivHex }))

worker.onmessage = (event) => {
resolve(event.data)
}
})
}

if (isNodeEnv()) {
const { scryptAsync } = await import('@noble/hashes/scrypt')
const encoder = new TextEncoder()
const keyBuffer = await scryptAsync(
encoder.encode(encryptStr),
encoder.encode(ivHex),
{ N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, dkLen: SCRYPT_DK_LEN }
)
return { keyHex: bytesToHexString(keyBuffer), keyBuffer }
}

throw new Error(
'Please pass in valid createKey function into the Hedgehog constructor'
)
return async (encryptStr, ivHex) => {
const { createKeyAsync } = await import('./scrypt')
return await createKeyAsync(encryptStr, ivHex)
}

return createKey
}

/**
Expand Down
5 changes: 4 additions & 1 deletion packages/web/src/services/audius-sdk/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { audiusChain } from 'app/audiusChain'
import { env } from '../env'
import { localStorage } from '../local-storage'

import { createKey } from './createKey'

export const getAudiusWalletClient = async (): Promise<AudiusWalletClient> => {
// Check if the user has already connected Hedgehog first...
await authService.hedgehogInstance.waitUntilReady()
Expand Down Expand Up @@ -102,7 +104,8 @@ export const getAudiusWalletClient = async (): Promise<AudiusWalletClient> => {

export const authService = createAuthService({
localStorage,
identityServiceEndpoint: env.IDENTITY_SERVICE
identityServiceEndpoint: env.IDENTITY_SERVICE,
createKey
})

export const solanaWalletService = createHedgehogSolanaWalletService(
Expand Down
24 changes: 24 additions & 0 deletions packages/web/src/services/audius-sdk/createKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { PrivateKey } from '@audius/common/src/services/auth/hedgehogLib/types'

/**
* Runs scrypt in a worker so login doesn't freeze the page. The worker is
* bundled inline and started from a blob URL, which also works under the
* desktop app's custom scheme.
*/
export const createKey = async (encryptStr: string, ivHex: string) => {
const { default: CreateKeyWorker } = await import(
'./createKey.worker?worker&inline'
)
const worker = new CreateKeyWorker()
try {
return await new Promise<PrivateKey>((resolve, reject) => {
worker.onmessage = (event: MessageEvent<PrivateKey>) =>
resolve(event.data)
worker.onerror = (event) =>
reject(event.error ?? new Error(event.message))
worker.postMessage({ encryptStr, ivHex })
})
} finally {
worker.terminate()
}
}
8 changes: 8 additions & 0 deletions packages/web/src/services/audius-sdk/createKey.worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createKeySync } from '@audius/common/src/services/auth/hedgehogLib/scrypt'

self.onmessage = (
event: MessageEvent<{ encryptStr: string; ivHex: string }>
) => {
const { encryptStr, ivHex } = event.data
self.postMessage(createKeySync(encryptStr, ivHex))
}
Loading