diff --git a/packages/bitswap/src/want-list.ts b/packages/bitswap/src/want-list.ts index 0d5f6d2a8..5fcc0e183 100644 --- a/packages/bitswap/src/want-list.ts +++ b/packages/bitswap/src/want-list.ts @@ -372,7 +372,7 @@ export class WantList extends TypedEventEmitter implements Start const cidVersion = values[0] const multicodec = values[1] const hashAlg = values[2] - // const hashLen = values[3] // We haven't need to use this so far + const hashLen = values[3] const hasher = hashAlg === sha256.code ? sha256 : await this.hashLoader?.getHasher(hashAlg) @@ -381,7 +381,9 @@ export class WantList extends TypedEventEmitter implements Start continue } - let hash: any = hasher.digest(block.data) + let hash: any = hasher.digest(block.data, { + truncate: hashLen + }) if (hash.then != null) { hash = await hash diff --git a/packages/bitswap/test/bitswap.spec.ts b/packages/bitswap/test/bitswap.spec.ts index 29f6fcb4c..25bb1f7a5 100644 --- a/packages/bitswap/test/bitswap.spec.ts +++ b/packages/bitswap/test/bitswap.spec.ts @@ -5,12 +5,14 @@ import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { expect } from 'aegir/chai' import { MemoryBlockstore } from 'blockstore-core' import { CID } from 'multiformats/cid' -import { sha256 } from 'multiformats/hashes/sha2' +import * as raw from 'multiformats/codecs/raw' +import { sha256, sha512 } from 'multiformats/hashes/sha2' import pDefer from 'p-defer' import Sinon from 'sinon' import { stubInterface } from 'sinon-ts' import { Bitswap } from '../src/bitswap.js' import { cidToPrefix } from '../src/utils/cid-prefix.js' +import type { MultihashHasherLoader } from '../src/index.ts' import type { BitswapMessageEventDetail } from '../src/network.js' import type { Routing } from '@helia/interface/routing' import type { Libp2p, PeerId } from '@libp2p/interface' @@ -29,11 +31,13 @@ describe('bitswap', () => { let bitswap: Bitswap let cid: CID let block: Uint8Array + let hashLoader: StubbedInstance beforeEach(async () => { block = Uint8Array.from([0, 1, 2, 3, 4]) const mh = await sha256.digest(block) cid = CID.createV0(mh).toV1() + hashLoader = stubInterface() components = { peerId: peerIdFromPrivateKey(await generateKeyPair('Ed25519')), @@ -47,6 +51,8 @@ describe('bitswap', () => { bitswap = new Bitswap({ ...components, logger: defaultLogger() + }, { + hashLoader }) components.libp2p.getConnections.returns([]) @@ -107,6 +113,59 @@ describe('bitswap', () => { expect(b).to.equalBytes(block) }) + it('should want a block with a truncated hash', async () => { + hashLoader.getHasher.withArgs(sha512.code).resolves(sha512) + + const mh = await sha512.digest(block, { + truncate: 32 + }) + cid = CID.createV1(raw.code, mh) + + const remotePeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519')) + const findProvsSpy = bitswap.network.findAndConnect = Sinon.stub() + findProvsSpy.resolves() + + // add peer + bitswap.wantList.peers.set(remotePeer, new Set()) + + // wait for message send to peer + const sentMessages = pDefer() + + bitswap.network.sendMessage = async (peerId) => { + if (remotePeer.equals(peerId)) { + sentMessages.resolve() + } + } + + const p = bitswap.want(cid) + + // wait for message send to peer + await sentMessages.promise + + // provider sends message + bitswap.network.safeDispatchEvent('bitswap:message', { + detail: { + peer: remotePeer, + message: { + blocks: [{ + prefix: cidToPrefix(cid), + data: block + }], + blockPresences: [], + pendingBytes: 0 + } + } + }) + + const b = await p + + // should have added cid to wantlist and searched for providers + expect(findProvsSpy.called).to.be.true() + + // should have cancelled the notification request + expect(b).to.equalBytes(block) + }) + it('should abort wanting a block that is not available on the network', async () => { const p = bitswap.want(cid, { signal: AbortSignal.timeout(100) diff --git a/packages/utils/src/utils/networked-storage.ts b/packages/utils/src/utils/networked-storage.ts index 0b01f60f6..b9c9594b6 100644 --- a/packages/utils/src/utils/networked-storage.ts +++ b/packages/utils/src/utils/networked-storage.ts @@ -413,7 +413,10 @@ export const getCidBlockVerifierFunction = (cid: CID, hasher: MultihashHasher): return async (block: Uint8Array): Promise => { // verify block let hash: MultihashDigest - const res = hasher.digest(block) + const res = hasher.digest(block, { + // support truncated hashes where they are truncated + truncate: cid.multihash.digest.byteLength + }) if (isPromise(res)) { hash = await res diff --git a/packages/utils/test/utils/networked-storage.spec.ts b/packages/utils/test/utils/networked-storage.spec.ts index 2717ecb8a..dae6365f9 100644 --- a/packages/utils/test/utils/networked-storage.spec.ts +++ b/packages/utils/test/utils/networked-storage.spec.ts @@ -11,12 +11,13 @@ import toBuffer from 'it-to-buffer' import { CID } from 'multiformats/cid' import * as raw from 'multiformats/codecs/raw' import { identity } from 'multiformats/hashes/identity' +import { sha256, sha512 } from 'multiformats/hashes/sha2' import Sinon from 'sinon' import { stubInterface } from 'sinon-ts' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { getHasher } from '../../src/utils/get-hasher.js' -import { NetworkedStorage } from '../../src/utils/networked-storage.js' +import { getCidBlockVerifierFunction, NetworkedStorage } from '../../src/utils/networked-storage.js' import { createBlock } from '../fixtures/create-block.js' import type { NetworkedStorageComponents } from '../../src/utils/networked-storage.js' import type { BlockBroker } from '@helia/interface/blocks' @@ -234,4 +235,28 @@ describe('networked-storage', () => { expect(block).to.equalBytes(blocks[0].block) expect(slowBroker.retrieve.getCall(0)).to.have.nested.property('args[1].signal.aborted', true) }) + + describe('block verifier', () => { + it('should verify a block', async () => { + const block = Uint8Array.from([0, 1, 2, 3, 4]) + const digest = await sha256.digest(block) + const cid = CID.createV1(raw.code, digest) + const fn = getCidBlockVerifierFunction(cid, sha256) + + // no promise rejection is a success + await fn(block) + }) + + it('should verify a block with a truncated hash', async () => { + const block = Uint8Array.from([0, 1, 2, 3, 4]) + const digest = await sha512.digest(block, { + truncate: 32 + }) + const cid = CID.createV1(raw.code, digest) + const fn = getCidBlockVerifierFunction(cid, sha512) + + // no promise rejection is a success + await fn(block) + }) + }) })