ox
Version:
Ethereum Standard Library
189 lines (175 loc) • 7.22 kB
text/typescript
import * as Bytes from './Bytes.js'
import * as Errors from './Errors.js'
import * as Hash from './Hash.js'
import * as Hex from './Hex.js'
/**
* Checks if an input is matched in the bloom filter.
*
* @example
* ```ts twoslash
* import { Bloom } from 'ox'
*
* Bloom.contains(
* '0x00000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000008000000001000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
* '0xef2d6d194084c2de36e0dabfce45d046b37d1106'
* )
* // @log: true
* ```
*
* @param bloom - Bloom filter value.
* @param input - Input to check.
* @returns Whether the input is matched in the bloom filter.
*/
export function contains(
bloom: Hex.Hex,
input: Hex.Hex | Bytes.Bytes,
): boolean {
return containsPrepared(prepare(bloom), input)
}
export declare namespace contains {
type ErrorType =
| Bytes.fromHex.ErrorType
| Hash.keccak256.ErrorType
| InvalidBloomError
| Errors.GlobalErrorType
}
/** Prepared bloom filter for use with {@link ox#Bloom.(containsPrepared:function)}/{@link ox#Bloom.(containsHash:function)}. */
export type Prepared = {
filter: Uint8Array
}
/**
* Prepares a bloom filter for repeated membership checks against the same filter.
*
* Pairs with {@link ox#Bloom.(containsPrepared:function)} (or {@link ox#Bloom.(containsHash:function)}) to avoid
* the per-call hex-to-bytes conversion that {@link ox#Bloom.(contains:function)} pays.
*
* @example
* ```ts twoslash
* import { Bloom } from 'ox'
*
* const prepared = Bloom.prepare(
* '0x00000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000008000000001000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
* )
* Bloom.containsPrepared(
* prepared,
* '0xef2d6d194084c2de36e0dabfce45d046b37d1106'
* )
* // @log: true
* ```
*
* @param bloom - Bloom filter value.
* @returns A prepared bloom filter.
*/
export function prepare(bloom: Hex.Hex): Prepared {
if (!validate(bloom)) throw new InvalidBloomError({ bloom })
return { filter: Bytes.fromHex(bloom) }
}
export declare namespace prepare {
type ErrorType =
| Bytes.fromHex.ErrorType
| InvalidBloomError
| Errors.GlobalErrorType
}
/**
* Checks if an input is matched in a {@link ox#Bloom.Prepared} bloom filter.
*
* @example
* ```ts twoslash
* import { Bloom } from 'ox'
*
* const prepared = Bloom.prepare(
* '0x00000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000008000000001000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
* )
* Bloom.containsPrepared(
* prepared,
* '0xef2d6d194084c2de36e0dabfce45d046b37d1106'
* )
* // @log: true
* ```
*
* @param prepared - Prepared bloom filter.
* @param input - Input to check.
* @returns Whether the input is matched in the bloom filter.
*/
export function containsPrepared(
prepared: Prepared,
input: Hex.Hex | Bytes.Bytes,
): boolean {
return containsHash(prepared, Hash.keccak256(input, { as: 'Bytes' }))
}
export declare namespace containsPrepared {
type ErrorType = Hash.keccak256.ErrorType | Errors.GlobalErrorType
}
/**
* Checks if a precomputed `keccak256` hash is matched in a {@link ox#Bloom.Prepared}
* bloom filter. Use when the caller already has the hash and wants to skip the
* keccak inside `containsPrepared`.
*
* @example
* ```ts twoslash
* import { Bloom, Hash } from 'ox'
*
* const prepared = Bloom.prepare(
* '0x00000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000008000000001000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
* )
* const hash = Hash.keccak256(
* '0xef2d6d194084c2de36e0dabfce45d046b37d1106',
* { as: 'Bytes' }
* )
* Bloom.containsHash(prepared, hash)
* // @log: true
* ```
*
* @param prepared - Prepared bloom filter.
* @param hash - Precomputed `keccak256` hash of the input, as `Bytes`.
* @returns Whether the input is matched in the bloom filter.
*/
export function containsHash(prepared: Prepared, hash: Bytes.Bytes): boolean {
const { filter } = prepared
for (const i of [0, 2, 4]) {
const bit = (hash[i + 1]! + (hash[i]! << 8)) & 0x7ff
if ((filter[256 - 1 - Math.floor(bit / 8)]! & (1 << (bit % 8))) === 0)
return false
}
return true
}
export declare namespace containsHash {
type ErrorType = Errors.GlobalErrorType
}
/**
* Checks if a string is a valid bloom filter value.
*
* @example
* ```ts twoslash
* import { Bloom } from 'ox'
*
* Bloom.validate('0x')
* // @log: false
*
* Bloom.validate(
* '0x00000000000000000000008000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000044000200000000000000000002000000000000000000000040000000000000000000000000000020000000000000000000800000000000800000000000800000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808002000000000400000000000000000000000060000000000000000000000000000000000000000000000100000000000002000000'
* )
* // @log: true
* ```
*
* @param value - Value to check.
* @returns Whether the value is a valid bloom filter.
*/
export function validate(value: string): value is Hex.Hex {
return Hex.validate(value) && Hex.size(value) === 256
}
export declare namespace validate {
type ErrorType =
| Hex.validate.ErrorType
| Hex.size.ErrorType
| Errors.GlobalErrorType
}
/** Thrown when a value is not a valid bloom filter. */
export class InvalidBloomError extends Errors.BaseError {
override readonly name = 'Bloom.InvalidBloomError'
constructor({ bloom }: { bloom: string }) {
super(
`Value \`${bloom}\` is not a valid bloom filter (must be a 256-byte hex string).`,
)
}
}