diff --git a/package.json b/package.json index 41bba199..7c774fc3 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@stablelib/sha256": "^1.0.1", "@stablelib/x25519": "^1.0.1", "@stablelib/xchacha20poly1305": "^1.0.1", + "bech32": "^2.0.0", "canonicalize": "^1.0.5", "did-resolver": "^3.1.1", "elliptic": "^6.5.4", diff --git a/src/VerifierAlgorithm.ts b/src/VerifierAlgorithm.ts index 9dcfaecd..be12614a 100644 --- a/src/VerifierAlgorithm.ts +++ b/src/VerifierAlgorithm.ts @@ -4,6 +4,7 @@ import { verify } from '@stablelib/ed25519' import type { VerificationMethod } from 'did-resolver' import { bases } from 'multiformats/basics' import { hexToBytes, base58ToBytes, base64ToBytes, bytesToHex, EcdsaSignature, stringToBytes } from './util' +import { verifyBlockchainAccountId } from './blockchains' const secp256k1 = new EC('secp256k1') @@ -60,7 +61,7 @@ export function verifyES256K( const fullPublicKeys = authenticators.filter(({ ethereumAddress, blockchainAccountId }) => { return typeof ethereumAddress === 'undefined' && typeof blockchainAccountId === 'undefined' }) - const ethAddressKeys = authenticators.filter(({ ethereumAddress, blockchainAccountId }) => { + const blockchainAddressKeys = authenticators.filter(({ ethereumAddress, blockchainAccountId }) => { return typeof ethereumAddress !== 'undefined' || typeof blockchainAccountId !== undefined }) @@ -73,8 +74,8 @@ export function verifyES256K( } }) - if (!signer && ethAddressKeys.length > 0) { - signer = verifyRecoverableES256K(data, signature, ethAddressKeys) + if (!signer && blockchainAddressKeys.length > 0) { + signer = verifyRecoverableES256K(data, signature, blockchainAddressKeys) } if (!signer) throw new Error('invalid_signature: Signature invalid for JWT') @@ -111,7 +112,8 @@ export function verifyRecoverableES256K( keyHex === recoveredPublicKeyHex || keyHex === recoveredCompressedPublicKeyHex || pk.ethereumAddress?.toLowerCase() === recoveredAddress || - pk.blockchainAccountId?.split('@eip155')?.[0].toLowerCase() === recoveredAddress + pk.blockchainAccountId?.split('@eip155')?.[0].toLowerCase() === recoveredAddress || // CAIP-2 + verifyBlockchainAccountId(recoveredPublicKeyHex, pk.blockchainAccountId) // CAIP-10 ) }) diff --git a/src/__tests__/Ripemd160.test.ts b/src/__tests__/Ripemd160.test.ts new file mode 100644 index 00000000..58a39b11 --- /dev/null +++ b/src/__tests__/Ripemd160.test.ts @@ -0,0 +1,70 @@ +// 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) + }) +}); diff --git a/src/__tests__/VerifierAlgorithm.test.ts b/src/__tests__/VerifierAlgorithm.test.ts index 391cd67f..8d6642bc 100644 --- a/src/__tests__/VerifierAlgorithm.test.ts +++ b/src/__tests__/VerifierAlgorithm.test.ts @@ -1,12 +1,14 @@ import VerifierAlgorithm from '../VerifierAlgorithm' import { createJWT } from '../JWT' -import { toEthereumAddress } from '../Digest' import nacl from 'tweetnacl' import { ec as EC } from 'elliptic' import { base64ToBytes, bytesToBase58, bytesToBase64, hexToBytes, bytesToBase64url, bytesToMultibase } from '../util' import * as u8a from 'uint8arrays' import { EdDSASigner } from '../signers/EdDSASigner' import { ES256KSigner } from '../signers/ES256KSigner' +import { toEthereumAddress } from '../Digest' +import { publicKeyToAddress as toBip122Address } from '../blockchains/bip122' +import { publicKeyToAddress as toCosmosAddressWithoutPrefix } from '../blockchains/cosmos' const secp256k1 = new EC('secp256k1') @@ -43,7 +45,10 @@ const publicKeyJwk = { y: bytesToBase64url(hexToBytes(kp.getPublic().getY().toString('hex'))), } const publicKeyMultibase = bytesToMultibase(hexToBytes(publicKey), 'base58btc') -const address = toEthereumAddress(publicKey) +const eip155 = toEthereumAddress(publicKey) +const bip122 = toBip122Address(publicKey) +const cosmosPrefix = 'example' +const cosmos = toCosmosAddressWithoutPrefix(publicKey, cosmosPrefix) const signer = ES256KSigner(privateKey) const recoverySigner = ES256KSigner(privateKey, true) @@ -72,14 +77,35 @@ const ethAddress = { id: `${did}#keys-3`, type: 'Secp256k1VerificationKey2018', controller: did, - ethereumAddress: address, + ethereumAddress: eip155, } const blockchainAddress = { id: `${did}#keys-blockchain`, type: 'EcdsaSecp256k1RecoveryMethod2020', controller: did, - blockchainAccountId: `${address}@eip155:1`, + blockchainAccountId: `${eip155}@eip155:1`, +} + +const blockchainAddressCaip10 = { + id: `${did}#keys-blockchain`, + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: did, + blockchainAccountId: `eip155:1:${eip155}`, +} + +const blockchainAddressBip122 = { + id: `${did}#keys-blockchain`, + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: did, + blockchainAccountId: `bip122:000000000019d6689c085ae165831e93:${bip122}`, +} + +const blockchainAddressCosmos = { + id: `${did}#keys-blockchain`, + type: 'EcdsaSecp256k1RecoveryMethod2020', + controller: did, + blockchainAccountId: `cosmos:${cosmosPrefix}:${cosmos}`, } const compressedKey = { @@ -93,7 +119,7 @@ const recoveryMethod2020Key = { id: `${did}#keys-recovery`, type: 'EcdsaSecp256k1RecoveryMethod2020', controller: did, - ethereumAddress: address, + ethereumAddress: eip155, } const edKey = { @@ -224,6 +250,27 @@ describe('ES256K', () => { return expect(verifier(parts[1], parts[2], [blockchainAddress])).toEqual(blockchainAddress) }) + it('validates signature produced by blockchainAccountId - CAIP 10 (EIP 155)', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + return expect(verifier(parts[1], parts[2], [blockchainAddressCaip10])).toEqual(blockchainAddressCaip10) + }) + + it('validates signature produced by blockchainAccountId - CAIP 10 (BIP 122)', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + return expect(verifier(parts[1], parts[2], [blockchainAddressBip122])).toEqual(blockchainAddressBip122) + }) + + it('validates signature produced by blockchainAccountId - CAIP 10 (Cosmos)', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + return expect(verifier(parts[1], parts[2], [blockchainAddressCosmos])).toEqual(blockchainAddressCosmos) + }) + it('validates signature produced by EcdsaSecp256k1RecoveryMethod2020 - github #152', async () => { expect.assertions(1) const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer }) @@ -263,6 +310,27 @@ describe('ES256K-R', () => { return expect(verifier(parts[1], parts[2], [ecKey1, blockchainAddress])).toEqual(blockchainAddress) }) + it('validates signature with blockchainAccountId - CAIP 10 (EIP 155)', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer: recoverySigner, alg: 'ES256K-R' }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + return expect(verifier(parts[1], parts[2], [ecKey1, blockchainAddressCaip10])).toEqual(blockchainAddressCaip10) + }) + + it('validates signature with blockchainAccountId - CAIP 10 (BIP 122)', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer: recoverySigner, alg: 'ES256K-R' }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + return expect(verifier(parts[1], parts[2], [ecKey1, blockchainAddressBip122])).toEqual(blockchainAddressBip122) + }) + + it('validates signature with blockchainAccountId - CAIP 10 (COSMOS)', async () => { + expect.assertions(1) + const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer: recoverySigner, alg: 'ES256K-R' }) + const parts = jwt.match(/^([a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) + return expect(verifier(parts[1], parts[2], [ecKey1, blockchainAddressCosmos])).toEqual(blockchainAddressCosmos) + }) + it('validates signature with EcdsaSecp256k1RecoveryMethod2020 - github #152', async () => { expect.assertions(1) const jwt = await createJWT({ bla: 'bla' }, { issuer: did, signer: recoverySigner, alg: 'ES256K-R' }) diff --git a/src/blockchains/bip122.ts b/src/blockchains/bip122.ts new file mode 100644 index 00000000..07401c72 --- /dev/null +++ b/src/blockchains/bip122.ts @@ -0,0 +1,15 @@ +import * as u8a from 'uint8arrays' +import { bytesToBase58 } from '../util' +import { sha256 } from '../Digest' +import { Ripemd160 } from './utils/ripemd160' + +export const publicKeyToAddress = (publicKey: string): string => { + const publicKeyBuffer = u8a.fromString(publicKey, 'hex') + const publicKeyHash = new Ripemd160().update(sha256(publicKeyBuffer)).digest() + const step1 = '00' + u8a.toString(publicKeyHash, 'hex') + const step2 = sha256(u8a.fromString(step1, 'hex')) + const step3 = sha256(step2) + const checksum = u8a.toString(step3, 'hex').substring(0, 8) + const step4 = step1 + checksum + return bytesToBase58(u8a.fromString(step4, 'hex')) +} diff --git a/src/blockchains/cosmos.ts b/src/blockchains/cosmos.ts new file mode 100644 index 00000000..7e96f164 --- /dev/null +++ b/src/blockchains/cosmos.ts @@ -0,0 +1,14 @@ +import { ec as EC } from 'elliptic' +import { bech32 } from 'bech32' +import * as u8a from 'uint8arrays' +import { sha256 } from '../Digest' +import { Ripemd160 } from './utils/ripemd160' + +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 words = bech32.toWords(hash) + return bech32.encode(prefix, words).replace(prefix, '') +} diff --git a/src/blockchains/index.ts b/src/blockchains/index.ts new file mode 100644 index 00000000..7e741fe4 --- /dev/null +++ b/src/blockchains/index.ts @@ -0,0 +1,24 @@ +import { publicKeyToAddress as bip122 } from './bip122' +import { publicKeyToAddress as cosmos } from './cosmos' +import { toEthereumAddress } from '../Digest' + +export const verifyBlockchainAccountId = (publicKey: string, blockchainAccountId: string | undefined): boolean => { + if (blockchainAccountId) { + const chain = blockchainAccountId.split(':') + switch (chain[0]) { + case 'bip122': + chain[chain.length - 1] = bip122(publicKey) + break + case 'cosmos': + chain[chain.length - 1] = cosmos(publicKey, chain[1]) + break + case 'eip155': + chain[chain.length - 1] = toEthereumAddress(publicKey) + break + default: + return false + } + return chain.join(':') === blockchainAccountId + } + return false +} diff --git a/src/blockchains/utils/ripemd160.ts b/src/blockchains/utils/ripemd160.ts new file mode 100644 index 00000000..cb3f9091 --- /dev/null +++ b/src/blockchains/utils/ripemd160.ts @@ -0,0 +1,199 @@ +// https://github.com/crypto-browserify/ripemd160/blob/master/index.js + +const zl = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, 10, 14, + 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7, 12, 2, + 10, 14, 1, 3, 8, 11, 6, 15, 13, +] + +const zr = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, 5, 1, + 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4, 1, 5, + 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, +] + +const sl = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 11, + 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15, 5, + 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, +] + +const sr = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 9, 7, + 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5, 12, 9, + 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, +] + +const hl = [0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] +const hr = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000] + +function rotl(x: number, n: number) { + return (x << n) | (x >>> (32 - n)) +} + +function fn1(a: number, b: number, c: number, d: number, e: number, m: number, k: number, s: number) { + return (rotl((a + (b ^ c ^ d) + m + k) | 0, s) + e) | 0 +} + +function fn2(a: number, b: number, c: number, d: number, e: number, m: number, k: number, s: number) { + return (rotl((a + ((b & c) | (~b & d)) + m + k) | 0, s) + e) | 0 +} + +function fn3(a: number, b: number, c: number, d: number, e: number, m: number, k: number, s: number) { + return (rotl((a + ((b | ~c) ^ d) + m + k) | 0, s) + e) | 0 +} + +function fn4(a: number, b: number, c: number, d: number, e: number, m: number, k: number, s: number) { + return (rotl((a + ((b & d) | (c & ~d)) + m + k) | 0, s) + e) | 0 +} + +function fn5(a: number, b: number, c: number, d: number, e: number, m: number, k: number, s: number) { + return (rotl((a + (b ^ (c | ~d)) + m + k) | 0, s) + e) | 0 +} + +export class Ripemd160 { + // state + private _a = 0x67452301 + private _b = 0xefcdab89 + private _c = 0x98badcfe + private _d = 0x10325476 + private _e = 0xc3d2e1f0 + private _blockOffset = 0 + private _block: Uint8Array + private _blockSize: number + private _length = [0, 0, 0, 0] + private _finalized: boolean + + constructor(blockSize = 64) { + this._block = new Uint8Array(blockSize) + this._blockSize = blockSize + this._blockOffset = 0 + this._length = [0, 0, 0, 0] + this._finalized = false + } + + update = (data: Uint8Array): Ripemd160 => { + if (this._finalized) throw new Error('Digest already called') + + // consume data + const block = this._block + let offset = 0 + while (this._blockOffset + data.length - offset >= this._blockSize) { + for (let i = this._blockOffset; i < this._blockSize; ) block[i++] = data[offset++] + this._update() + this._blockOffset = 0 + } + while (offset < data.length) block[this._blockOffset++] = data[offset++] + + // update length + for (let j = 0, carry = data.length * 8; carry > 0; ++j) { + this._length[j] += carry + carry = (this._length[j] / 0x0100000000) | 0 + if (carry > 0) this._length[j] -= 0x0100000000 * carry + } + + return this + } + + digest = (): Uint8Array => { + if (this._finalized) throw new Error('Digest already called') + this._finalized = true + + const digest = this._digest() + + // reset state + this._block.fill(0) + this._blockOffset = 0 + for (let i = 0; i < 4; ++i) this._length[i] = 0 + + return digest + } + + private _update = () => { + const words = new Array(16) + const temp = new DataView(this._block.buffer) + for (let j = 0; j < 16; ++j) words[j] = words[j] = temp.getInt32(j * 4, true) + + let al = this._a | 0 + let bl = this._b | 0 + let cl = this._c | 0 + let dl = this._d | 0 + let el = this._e | 0 + + let ar = this._a | 0 + let br = this._b | 0 + let cr = this._c | 0 + let dr = this._d | 0 + let er = this._e | 0 + + // computation + for (let i = 0; i < 80; i += 1) { + let tl + let tr + if (i < 16) { + tl = fn1(al, bl, cl, dl, el, words[zl[i]], hl[0], sl[i]) + tr = fn5(ar, br, cr, dr, er, words[zr[i]], hr[0], sr[i]) + } else if (i < 32) { + tl = fn2(al, bl, cl, dl, el, words[zl[i]], hl[1], sl[i]) + tr = fn4(ar, br, cr, dr, er, words[zr[i]], hr[1], sr[i]) + } else if (i < 48) { + tl = fn3(al, bl, cl, dl, el, words[zl[i]], hl[2], sl[i]) + tr = fn3(ar, br, cr, dr, er, words[zr[i]], hr[2], sr[i]) + } else if (i < 64) { + tl = fn4(al, bl, cl, dl, el, words[zl[i]], hl[3], sl[i]) + tr = fn2(ar, br, cr, dr, er, words[zr[i]], hr[3], sr[i]) + } else { + // if (i<80) { + tl = fn5(al, bl, cl, dl, el, words[zl[i]], hl[4], sl[i]) + tr = fn1(ar, br, cr, dr, er, words[zr[i]], hr[4], sr[i]) + } + + al = el + el = dl + dl = rotl(cl, 10) + cl = bl + bl = tl + + ar = er + er = dr + dr = rotl(cr, 10) + cr = br + br = tr + } + + // update state + const t = (this._b + cl + dr) | 0 + this._b = (this._c + dl + er) | 0 + this._c = (this._d + el + ar) | 0 + this._d = (this._e + al + br) | 0 + this._e = (this._a + bl + cr) | 0 + this._a = t + } + + private _digest = () => { + // create padding and handle blocks + this._block[this._blockOffset++] = 0x80 + if (this._blockOffset > 56) { + this._block.fill(0, this._blockOffset, 64) + this._update() + this._blockOffset = 0 + } + + this._block.fill(0, this._blockOffset, 56) + const temp = new DataView(this._block.buffer) + temp.setUint32(56, this._length[0], true) + temp.setUint32(60, this._length[1], true) + this._block = new Uint8Array(temp.buffer) + this._update() + + // produce result + const buffer = new DataView(new Uint8Array(20).buffer) + buffer.setInt32(0, this._a, true) + buffer.setInt32(4, this._b, true) + buffer.setInt32(8, this._c, true) + buffer.setInt32(12, this._d, true) + buffer.setInt32(16, this._e, true) + + return new Uint8Array(buffer.buffer) + } +} diff --git a/yarn.lock b/yarn.lock index b5c95825..d7fa161f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2601,6 +2601,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + before-after-hook@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"