Repository: decentralized-identity/did-jwt
Pull request #271 (2023-03-06 15:00:16)

fix: provide hash algorithms as exported functions
Call implementations of hash functions from native crypto library

mirceanis: This library is used in contexts other than nodejs.
Will this change also work in browser and react-native environments?
klaus-sap: Good point, thank you. I will update this PR

Patch:
diff --git a/src/Digest.ts b/src/Digest.ts
index 82cb109..0ef798a 100644
--- a/src/Digest.ts
+++ b/src/Digest.ts
@@ -1,4 +1,5 @@
 import { hash } from '@stablelib/sha256'
+import { Ripemd160 } from './blockchains/utils/ripemd160'
 import * as u8a from 'uint8arrays'
 import sha3 from 'js-sha3'
 
@@ -16,6 +17,10 @@ export function toEthereumAddress(hexPublicKey: string): string {
   return `0x${u8a.toString(keccak(hashInput).slice(-20), 'base16')}`
 }
 
+export function ripemd160(data: Uint8Array): Uint8Array {
+  return new Ripemd160().update(data).digest()
+}
+
 function writeUint32BE(value: number, array = new Uint8Array(4)): Uint8Array {
   const encoded = u8a.fromString(value.toString(), 'base10')
   array.set(encoded, 4 - encoded.length)
@@ -44,5 +49,5 @@ export function concatKDF(
 
   // since our key lenght is 256 we only have to do one round
   const roundNumber = 1
-  return hash(u8a.concat([writeUint32BE(roundNumber), secret, value]))
+  return sha256(u8a.concat([writeUint32BE(roundNumber), secret, value]))
 }
diff --git a/src/blockchains/bip122.ts b/src/blockchains/bip122.ts
index 6976e9b..7122ffa 100644
--- a/src/blockchains/bip122.ts
+++ b/src/blockchains/bip122.ts
@@ -1,13 +1,12 @@
 import * as u8a from 'uint8arrays'
 import { bytesToBase58, base58ToBytes } from '../util'
-import { sha256 } from '../Digest'
-import { Ripemd160 } from './utils/ripemd160'
+import { sha256, ripemd160 } from '../Digest'
 
 export const publicKeyToAddress = (publicKey: string, otherAddress: string): string => {
   // Use the same version/prefix byte as the given address.
   const version = u8a.toString(base58ToBytes(otherAddress).slice(0, 1), 'hex')
   const publicKeyBuffer = u8a.fromString(publicKey, 'hex')
-  const publicKeyHash = new Ripemd160().update(sha256(publicKeyBuffer)).digest()
+  const publicKeyHash = ripemd160(sha256(publicKeyBuffer))
   const step1 = version + u8a.toString(publicKeyHash, 'hex')
   const step2 = sha256(u8a.fromString(step1, 'hex'))
   const step3 = sha256(step2)
diff --git a/src/blockchains/cosmos.ts b/src/blockchains/cosmos.ts
index b1c10ec..865a407 100644
--- a/src/blockchains/cosmos.ts
+++ b/src/blockchains/cosmos.ts
@@ -1,8 +1,7 @@
 import elliptic from 'elliptic'
 import { bech32 } from 'bech32'
 import * as u8a from 'uint8arrays'
-import { sha256 } from '../Digest'
-import { Ripemd160 } from './utils/ripemd160'
+import { sha256, ripemd160 } from '../Digest'
 
 const EC = elliptic.ec
 
@@ -10,7 +9,7 @@ export const publicKeyToAddress = (publicKey: string, prefix: string): string =>
   const ec = new EC('secp256k1')
   const compressedPublicKey = ec.keyFromPublic(publicKey, 'hex').getPublic().encode('hex', true)
   const publicKeyBuffer = u8a.fromString(compressedPublicKey, 'hex')
-  const hash = new Ripemd160().update(sha256(publicKeyBuffer)).digest()
+  const hash = ripemd160(sha256(publicKeyBuffer))
   const words = bech32.toWords(hash)
   return bech32.encode(prefix, words).replace(prefix, '')
 }


Test patch:
diff --git a/src/__tests__/Digest.test.ts b/src/__tests__/Digest.test.ts
new file mode 100644
index 0000000..6e56581
--- /dev/null
+++ b/src/__tests__/Digest.test.ts
@@ -0,0 +1,125 @@
+import { keccak, ripemd160, sha256, toEthereumAddress } from '../Digest'
+import * as u8a from 'uint8arrays'
+
+// https://www.di-mgt.com.au/sha_testvectors.html
+describe('sha256', () => {
+  it('message: "" (empty string)', async () => {
+    expect.assertions(2)
+    const hash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
+    expect(u8a.toString(sha256(''), 'hex')).toBe(hash)
+    expect(u8a.toString(sha256(new Uint8Array(0)), 'hex')).toBe(hash)
+  })
+  it('message: "abc" (length 3)', async () => {
+    expect.assertions(2)
+    const hash = 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
+    expect(u8a.toString(sha256('abc'), 'hex')).toBe(hash)
+    expect(u8a.toString(sha256(new Uint8Array([0x61, 0x62, 0x63])), 'hex')).toBe(hash)
+  })
+  it('message: "abc..." (length 56)', async () => {
+    expect.assertions(2)
+    const message = 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'
+    const hash = '248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1'
+    expect(u8a.toString(sha256(message), 'hex')).toBe(hash)
+    expect(u8a.toString(sha256(u8a.fromString(message)), 'hex')).toBe(hash)
+  })
+  it('message: "abc..." (length 112)', async () => {
+    expect.assertions(2)
+    const message =
+      'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'
+    const hash = 'cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1'
+    expect(u8a.toString(sha256(message), 'hex')).toBe(hash)
+    expect(u8a.toString(sha256(u8a.fromString(message)), 'hex')).toBe(hash)
+  })
+})
+
+// https://keccak-256.4tools.net/
+describe('keccak', () => {
+  it('message: "" (empty string)', async () => {
+    expect.assertions(1)
+    const hash = 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
+    expect(u8a.toString(keccak(new Uint8Array(0)), 'hex')).toBe(hash)
+  })
+  it('message: "abc" (length 3)', async () => {
+    expect.assertions(1)
+    const hash = '4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45'
+    expect(u8a.toString(keccak(new Uint8Array([0x61, 0x62, 0x63])), 'hex')).toBe(hash)
+  })
+})
+
+// https://mycrypto.tools/sample_ethaddresses.html
+describe('Ethereum addresses', () => {
+  it('public key: "04782291..."', async () => {
+    expect.assertions(1)
+    const publicKey =
+      '047822917c9faccf83219eafa79866e37c56d5873a5bc11b5eb8b6747e328b6800d9b51749f9e15f7c0effc8a9f899dcf17d71e1ebe1ad3d6047b215636ff9b4e1'
+    const ethereumAddress = '0xDBC05B1ECB4FDAEF943819C0B04E9EF6DF4BABD6'
+    expect(toEthereumAddress(publicKey)).toBe(ethereumAddress.toLowerCase())
+  })
+  it('public key: "04bbe06c..."', async () => {
+    expect.assertions(1)
+    const publicKey =
+      '04bbe06c9dd095cdf0aded667ea17621e8c1fdcd36ffe112a9c94e47aa6be1406a666e1001cf0067d0f9a541043dfc5438ead7be3ecbcdc328b67d8f966bceea63'
+    const ethereumAddress = '0x721B68FA152A930F3DF71F54AC1CE7ED3AC5F867'
+    expect(toEthereumAddress(publicKey)).toBe(ethereumAddress.toLowerCase())
+  })
+})
+
+// https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+describe('Ripemd160', () => {
+  it('message: "" (empty string)', () => {
+    expect.assertions(1)
+    const rawString = ''
+    const expectedHash = '9c1185a5c5e9fc54612808977ee8f548b2258d31'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+  it('message: "a"', () => {
+    expect.assertions(1)
+    const rawString = 'a'
+    const expectedHash = '0bdc9d2d256b3ee9daae347be6f4dc835a467ffe'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+  it('message: "abc"', () => {
+    expect.assertions(1)
+    const rawString = 'abc'
+    const expectedHash = '8eb208f7e05d987a9b044a8e98c6b087f15a0bfc'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+  it('message: "message digest"', () => {
+    expect.assertions(1)
+    const rawString = 'message digest'
+    const expectedHash = '5d0689ef49d2fae572b881b123a85ffa21595f36'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+  it('message: "abcdefghijklmnopqrstuvwxyz"', () => {
+    expect.assertions(1)
+    const rawString = 'abcdefghijklmnopqrstuvwxyz'
+    const expectedHash = 'f71c27109c692c1b56bbdceb5b9d2865b3708dbc'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+  it('message: "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', () => {
+    expect.assertions(1)
+    const rawString = 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'
+    const expectedHash = '12a053384a9c0c88e405a06c27dcf49ada62eb2b'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+  it('message: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"', () => {
+    expect.assertions(1)
+    const rawString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+    const expectedHash = 'b0e20b6e3116640286ed3a87a5713079b21f5189'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+  it('message: 8 times "1234567890"', () => {
+    expect.assertions(1)
+    const rawString = '1234567890'.repeat(8)
+    const expectedHash = '9b752e45573d4b39f4dbd3323cab82bf63326bfb'
+    const actualHash = u8a.toString(ripemd160(u8a.fromString(rawString, 'ascii')), 'hex')
+    return expect(actualHash).toBe(expectedHash)
+  })
+})
diff --git a/src/__tests__/Ripemd160.test.ts b/src/__tests__/Ripemd160.test.ts
deleted file mode 100644
index f4cd701..0000000
--- a/src/__tests__/Ripemd160.test.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-// https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
-
-import * as u8a from 'uint8arrays'
-import { Ripemd160 } from '../blockchains/utils/ripemd160'
-
-describe('Ripemd160', () => {
-  it('message: "" (empty string)', () => {
-    expect.assertions(1)
-    const rawString = ''
-    const expectedHash = '9c1185a5c5e9fc54612808977ee8f548b2258d31'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: "a"', () => {
-    expect.assertions(1)
-    const rawString = 'a'
-    const expectedHash = '0bdc9d2d256b3ee9daae347be6f4dc835a467ffe'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: "abc"', () => {
-    expect.assertions(1)
-    const rawString = 'abc'
-    const expectedHash = '8eb208f7e05d987a9b044a8e98c6b087f15a0bfc'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: "message digest"', () => {
-    expect.assertions(1)
-    const rawString = 'message digest'
-    const expectedHash = '5d0689ef49d2fae572b881b123a85ffa21595f36'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: "abcdefghijklmnopqrstuvwxyz"', () => {
-    expect.assertions(1)
-    const rawString = 'abcdefghijklmnopqrstuvwxyz'
-    const expectedHash = 'f71c27109c692c1b56bbdceb5b9d2865b3708dbc'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"', () => {
-    expect.assertions(1)
-    const rawString = 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'
-    const expectedHash = '12a053384a9c0c88e405a06c27dcf49ada62eb2b'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"', () => {
-    expect.assertions(1)
-    const rawString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
-    const expectedHash = 'b0e20b6e3116640286ed3a87a5713079b21f5189'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: 8 times "1234567890"', () => {
-    expect.assertions(1)
-    const rawString = '1234567890'.repeat(8)
-    const expectedHash = '9b752e45573d4b39f4dbd3323cab82bf63326bfb'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-  it('message: 1 million times "a"', () => {
-    expect.assertions(1)
-    const rawString = 'a'.repeat(1000000)
-    const expectedHash = '52783243c1697bdbe16d37f97f68f08325dc1528'
-    const actualHash = u8a.toString(new Ripemd160().update(u8a.fromString(rawString, 'ascii')).digest(), 'hex')
-    return expect(actualHash).toBe(expectedHash)
-  })
-})