paulmillr / noble-hashes

Audited & minimal JS implementation of hash functions, MACs and KDFs.
https://paulmillr.com/noble
MIT License
573 stars 46 forks source link

Simple isomorphic wrapper ? #56

Closed sublimator closed 1 year ago

sublimator commented 1 year ago

For things like ripple library where they are using createHash one needs to create little wrappers to replace existing functions, ala:

const HASH_BYTES = 32

const sha512Half = (() => {
  const isBrowser = typeof window !== 'undefined'

  if (isBrowser) {
    const { sha512 } = require('@noble/hashes/sha512')
    const { bytesToHex, hexToBytes } = require('@noble/hashes/utils')

    return function sha512Half(hex: string): string {
      const digestHalf = sha512(hexToBytes(hex)).slice(0, HASH_BYTES)
      return bytesToHex(digestHalf).toUpperCase()
    }
  } else {
    const crypto = require('crypto')

    return function sha512Half(hex: string): string {
      const hash = crypto.createHash('sha512')
      hash.update(Buffer.from(hex, 'hex'))
      const digestHalf = hash.digest().slice(0, HASH_BYTES)
      return digestHalf.toString('hex').toUpperCase()
    }
  }
})()

to replace:

const HASH_SIZE = 64

function sha512Half(hex: string): string {
  return createHash('sha512')
    .update(Buffer.from(hex, 'hex'))
    .digest('hex')
    .toUpperCase()
    .slice(0, HASH_SIZE)
}

This is annoying, and less than ideal because you have to mess with your webpack resolve.alias rather than rely upon a browser field in package.json.

I think a simple wrapper like this would go along way:

type CreateHashFn = (algo: 'sha512' | 'sha256' | ... ) => {
  update(buffer: Uint8Array): void
  digest(): Uint8Array
}

Are you interested in doing something like this under @noble/ ?

paulmillr commented 1 year ago

Hey Nicholas.

I think a simple wrapper like this would go along way:

Not interested, because this (old node.js) approach is not tree-shaking friendly.

We specifically use separate files to ensure sha256 code is not included if you're only using sha512, which helps to reduce bundle size.

sublimator commented 1 year ago

Fair.

How do you suggest handling this?

SImilar structure

@ns/misohashy/hash256 etc ?

?

paulmillr commented 1 year ago
import { sha256 } from "@noble/hashes/sha256";
import { sha512 } from "@noble/hashes/sha512";
import { sha3_256 } from "@noble/hashes/sha3";

function createHash(name) {
  if (name === 'sha256') return sha256;
  else if (name === 'sha512') return sha512;
  else if (name === 'sha3_256') return sha3_256;
}
sublimator commented 1 year ago

@paulmillr

I want something that doesn't require configuration for each bundler, and would support tree shaking, so it should just expose a browser impl via package.json. I'm not sure the state of "exports" these days in that regard, but potentially that.

I was thinking more along the lines of:

import { sha256 } from "iso-hashes/sha256"

It would be a thin wrapper around @noble/hashes/sha256 for browser and crypto.createHash for node

Interface along these lines:

type ByteEncodedString = string
type Input = Uint8Array | number[] | ByteEncodedString

interface Hash {
  update(bytes: Input): this
  digest(): Uint8Array
}

type HashFn = {
  (input: Input): Uint8Array
  create(): Hash
}
paulmillr commented 1 year ago

Yeah, separate files for each hash could work in this case.

sublimator commented 1 year ago

I'm not sure the state of "exports" these days

Turns out nodeResolution: Node16 is required for TS to honor it