Skip to content
Closed
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
1 change: 1 addition & 0 deletions Dockerfile.node
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ FROM node:${NODE_VERSION}-bullseye AS build
WORKDIR /usr/src/network
COPY . .
RUN --mount=type=cache,target=/root/.npm \
npm run bootstrap-pkg --package=@streamr/utils && \
npm run bootstrap-pkg --package=@streamr/proto-rpc && \
npm run bootstrap-pkg --package=@streamr/autocertifier-client && \
npm run bootstrap-pkg --package=@streamr/dht && \
Expand Down
2,407 changes: 1,762 additions & 645 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion packages/utils/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
export { default } from '../../jest.config'
import type { Config } from "@jest/types"
import defaultConfig from "../../jest.config"

const config: Config.InitialOptions = {
...defaultConfig,
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/node/$1",
},
}

export default config
8 changes: 7 additions & 1 deletion packages/utils/karma.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { createKarmaConfig, createWebpackConfig } from '@streamr/browser-test-runner'
import { resolve } from 'path'

const TEST_PATHS = ['test/**/*.ts']

export default createKarmaConfig(TEST_PATHS, createWebpackConfig({
libraryName: 'utils',
fallback: {
module: false
}
},
alias: {
'@': resolve(__dirname, 'src/browser'),
os: resolve(__dirname, 'src/browser/os.ts'),
path: 'path-browserify',
},
}))
38 changes: 33 additions & 5 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,33 @@
"url": "git+https://github.com/streamr-dev/network.git",
"directory": "packages/utils"
},
"main": "./dist/src/exports.js",
"types": "./dist/src/exports.d.ts",
"exports": {
".": {
"browser": {
"types": "./dist/browser/src/exports.d.ts",
"import": "./dist/browser/src/exports.js",
"require": "./dist/browser/src/exports.cjs"
},
"default": {
"types": "./dist/node/src/exports.d.ts",
"import": "./dist/node/src/exports.js",
"require": "./dist/node/src/exports.cjs"
}
}
},
"files": [
"dist",
"dist/node/src/exports.*",
"dist/browser/src/exports.*",
"!*.tsbuildinfo",
"README.md",
"LICENSE"
],
"scripts": {
"prebuild": "npm run reset-self",
"build": "tsc -b",
"check": "tsc -p tsconfig.jest.json",
"postbuild": "NODE_OPTIONS=\"--import tsx\" rollup -c rollup.config.mts",
"check": "tsc -b tsconfig.jest.json",
"reset-self": "rimraf --glob '**/*.tsbuildinfo'",
"clean": "jest --clearCache --config '{}' || true; rm -rf dist *.tsbuildinfo node_modules/.cache || true",
"eslint": "eslint --cache --cache-location=node_modules/.cache/.eslintcache/ '*/**/*.{js,ts}'",
"test": "jest",
Expand All @@ -28,16 +44,28 @@
"dependencies": {
"@noble/curves": "^1.9.7",
"@noble/post-quantum": "^0.4.1",
"buffer": "^6.0.3",
"buffer-shim": "^1.0.1",
"eventemitter3": "^5.0.0",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"path-browserify": "^1.0.1",
"pino": "^10.1.0",
"pino-pretty": "^13.1.2",
"readable-stream": "^4.7.0",
"secp256k1": "^5.0.1",
"sha3": "^2.1.4"
},
"devDependencies": {
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-node-resolve": "^16.0.3",
"@streamr/browser-test-runner": "^0.0.1",
"@types/lodash": "^4.17.21",
"@types/secp256k1": "^4.0.7"
"@types/md5": "^2.3.6",
"@types/secp256k1": "^4.0.7",
"rimraf": "^6.1.2",
"rollup": "^4.53.3",
"rollup-plugin-dts": "^6.3.0",
"tsx": "^4.21.0"
}
}
140 changes: 140 additions & 0 deletions packages/utils/rollup.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { defineConfig, type RollupOptions } from 'rollup'
import { dts } from 'rollup-plugin-dts'
import alias, { type Alias } from '@rollup/plugin-alias'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import { fileURLToPath } from 'node:url'

const nodejsAliases: Alias[] = [
{
find: /^@\//,
replacement: fileURLToPath(
new URL('./dist/node/src/node/', import.meta.url)
),
},
]

const browserAliases: Alias[] = [
{
find: /^@\//,
replacement: fileURLToPath(
new URL('./dist/browser/src/browser/', import.meta.url)
),
},
{
find: 'os',
replacement: fileURLToPath(
new URL('./dist/browser/src/browser/os.js', import.meta.url)
),
},
{
find: 'path',
replacement: 'path-browserify',
},
{
find: 'stream',
replacement: 'readable-stream',
},
{
find: /^pino$/,
replacement: 'pino/browser',
},
]

export default defineConfig([
nodejs(),
nodejsTypes(),
browser(),
browserTypes(),
])

function nodejs(): RollupOptions {
return {
input: './dist/node/src/exports.js',
output: [
{
format: 'es',
file: './dist/node/src/exports.js',
sourcemap: true,
},
{
format: 'cjs',
file: './dist/node/src/exports.cjs',
sourcemap: true,
},
],
plugins: [
alias({
entries: nodejsAliases,
}),
nodeResolve({
preferBuiltins: true,
}),
],
external: [
/node_modules/
]
}
}

function browser(): RollupOptions {
return {
input: './dist/browser/src/exports-browser.js',
output: [
{
format: 'es',
file: './dist/browser/src/exports.js',
sourcemap: true,
},
{
format: 'cjs',
file: './dist/browser/src/exports.cjs',
sourcemap: true,
},
],
plugins: [
alias({
entries: browserAliases,
}),
nodeResolve({ browser: true }),
],
external: [
/node_modules/
]
}
}

function nodejsTypes(): RollupOptions {
return {
input: './dist/node/src/exports.d.ts',
output: [
{
file: './dist/node/src/exports.d.ts',
},
],
plugins: [
alias({
entries: nodejsAliases,
}),
nodeResolve(),
dts(),
],
}
}

function browserTypes(): RollupOptions {
return {
input: './dist/browser/src/exports-browser.d.ts',
output: [
{
file: './dist/browser/src/exports.d.ts',
},
],
plugins: [
alias({
entries: browserAliases,
}),
nodeResolve({ browser: true }),
dts(),
],
}
}
19 changes: 9 additions & 10 deletions packages/utils/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import pino from 'pino'
import path from 'path'
import without from 'lodash/without'
import padEnd from 'lodash/padEnd'
import { env } from '@/env'

export type LogLevel = 'silent' | 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace'

Expand All @@ -18,23 +19,21 @@ const parseBoolean = (value: string | undefined) => {
}
}

declare let window: any

/**
* Disabled when in browser or when environment variable DISABLE_PRETTY_LOG is set to true.
* Disabled when environment variable DISABLE_PRETTY_LOG is set to true.
*/
function isPrettyPrintDisabled(): boolean {
return typeof window === 'object' || (parseBoolean(process.env.DISABLE_PRETTY_LOG) ?? false)
return parseBoolean(env.DISABLE_PRETTY_LOG) ?? false
}

function isJestRunning(): boolean {
return process.env.JEST_WORKER_ID !== undefined
return env.JEST_WORKER_ID !== undefined
}

const rootLogger = pino({
name: 'rootLogger',
enabled: !process.env.NOLOG,
level: process.env.LOG_LEVEL ?? 'info',
enabled: !env.NOLOG,
level: env.LOG_LEVEL ?? 'info',
formatters: {
level: (label) => {
return { level: label } // log level as string instead of number
Expand All @@ -43,7 +42,7 @@ const rootLogger = pino({
transport: isPrettyPrintDisabled() ? undefined : {
target: 'pino-pretty',
options: {
colorize: parseBoolean(process.env.LOG_COLORS) ?? true,
colorize: parseBoolean(env.LOG_COLORS) ?? true,
singleLine: true,
translateTime: 'yyyy-mm-dd"T"HH:MM:ss.l',
ignore: 'pid,hostname',
Expand Down Expand Up @@ -95,7 +94,7 @@ export class Logger {
name: Logger.createName(loggerModule),
...contextBindings
}, {
level: process.env.LOG_LEVEL ?? defaultLogLevel
level: env.LOG_LEVEL ?? defaultLogLevel
})
this.fatal = wrappedMethodCall(this.logger.fatal.bind(this.logger))
this.error = wrappedMethodCall(this.logger.error.bind(this.logger))
Expand All @@ -114,7 +113,7 @@ export class Logger {
const parts = parsedPath.dir.split(path.sep)
fileId = parts[parts.length - 1]
}
const longName = without([process.env.STREAMR_APPLICATION_ID, fileId], undefined).join(':')
const longName = without([env.STREAMR_APPLICATION_ID, fileId], undefined).join(':')
return isPrettyPrintDisabled() ?
longName : padEnd(longName.substring(0, this.NAME_LENGTH), this.NAME_LENGTH, ' ')
}
Expand Down
24 changes: 11 additions & 13 deletions packages/utils/src/SigningUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { randomBytes } from '@noble/post-quantum/utils'
import { p256 } from '@noble/curves/p256'
import { areEqualBinaries, binaryToHex } from './binaryUtils'
import type { UserIDRaw } from './UserID'
import { getSubtle } from './crossPlatformCrypto'
import type { webcrypto } from 'crypto'
import { type CryptoKey, getSubtle, type Jwk } from '@/crypto'

export const KEY_TYPES = [
'ECDSA_SECP256K1_EVM',
Expand All @@ -18,9 +17,6 @@ export const KEY_TYPES = [
export type KeyType = typeof KEY_TYPES[number]

const ECDSA_SECP256K1_EVM_SIGN_MAGIC = '\u0019Ethereum Signed Message:\n'
const keccak = new Keccak(256)

const subtleCrypto = getSubtle()

export interface KeyPair {
publicKey: Uint8Array
Expand Down Expand Up @@ -58,7 +54,7 @@ export class EcdsaSecp256k1Evm extends SigningUtil {
}

keccakHash(message: Uint8Array, useEthereumMagic: boolean = true): Buffer {
keccak.reset()
const keccak = new Keccak(256)
keccak.update(useEthereumMagic ? Buffer.concat([
Buffer.from(ECDSA_SECP256K1_EVM_SIGN_MAGIC + message.length),
message
Expand Down Expand Up @@ -91,7 +87,7 @@ export class EcdsaSecp256k1Evm extends SigningUtil {
throw new Error(`Expected 65 bytes (an ECDSA uncompressed public key with header byte). Got length: ${publicKey.length}`)
}
const pubKeyWithoutFirstByte = publicKey.subarray(1, publicKey.length)
keccak.reset()
const keccak = new Keccak(256)
keccak.update(Buffer.from(pubKeyWithoutFirstByte))
const hashOfPubKey = keccak.digest('binary')
return hashOfPubKey.subarray(12, hashOfPubKey.length)
Expand Down Expand Up @@ -167,7 +163,7 @@ export class EcdsaSecp256r1 extends SigningUtil {
throw new Error(`Unexpected public key length: ${publicKey.length}`)
}

privateKeyToJWK(privateKey: Uint8Array): webcrypto.JsonWebKey {
privateKeyToJWK(privateKey: Uint8Array): Jwk {
const publicKey = this.getPublicKeyFromPrivateKey(privateKey, false)
// uncompressed publicKey = [header (1 byte), x (32 bytes), y (32 bytes)
const x = publicKey.subarray(1, 33)
Expand Down Expand Up @@ -199,7 +195,9 @@ export class EcdsaSecp256r1 extends SigningUtil {
* Pass the privateKey in JsonWebKey format for a slight performance gain.
* You can convert raw keys to JWK using the privateKeyToJWK function.
*/
async createSignature(payload: Uint8Array, privateKey: Uint8Array | webcrypto.JsonWebKey): Promise<Uint8Array> {
async createSignature(payload: Uint8Array, privateKey: Uint8Array | Jwk): Promise<Uint8Array> {
const subtleCrypto = getSubtle()

const jwk = privateKey instanceof Uint8Array ? this.privateKeyToJWK(privateKey) : privateKey

/**
Expand Down Expand Up @@ -229,8 +227,8 @@ export class EcdsaSecp256r1 extends SigningUtil {
return new Uint8Array(signature)
}

private async publicKeyToCryptoKey(publicKey: Uint8Array): Promise<webcrypto.CryptoKey> {
return subtleCrypto.importKey(
private async publicKeyToCryptoKey(publicKey: Uint8Array): Promise<CryptoKey> {
return getSubtle().importKey(
'raw',
publicKey,
{
Expand All @@ -243,7 +241,7 @@ export class EcdsaSecp256r1 extends SigningUtil {
}

async verifySignature(publicKey: UserIDRaw, payload: Uint8Array, signature: Uint8Array): Promise<boolean> {
let key: webcrypto.CryptoKey
let key: CryptoKey | undefined

try {
key = await this.publicKeyToCryptoKey(publicKey)
Expand All @@ -257,7 +255,7 @@ export class EcdsaSecp256r1 extends SigningUtil {
}
}

const isValid = await subtleCrypto.verify(
const isValid = await getSubtle().verify(
{
name: 'ECDSA',
hash: { name: 'SHA-256' }
Expand Down
Loading