mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-01 22:39:03 -07:00
website_next: move hash logic to clients
This commit is contained in:
@@ -233,6 +233,32 @@ Defined in: [Developer/brk/modules/brk-client/index.js:1894](https://github.com/
|
||||
|
||||
***
|
||||
|
||||
### addressPayloadHashPrefix()
|
||||
|
||||
> `static` **addressPayloadHashPrefix**(`payload`, `nibbles`): `string`
|
||||
|
||||
Compute the RapidHash v3 hash-prefix for raw address payload bytes.
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### payload
|
||||
|
||||
`Uint8Array` | `ArrayBuffer` | `ArrayBufferView` | `number`[]
|
||||
|
||||
Raw address payload bytes. Must be 1 to 65 bytes.
|
||||
|
||||
##### nibbles
|
||||
|
||||
`number`
|
||||
|
||||
Prefix length from 1 to 16 hex nibbles.
|
||||
|
||||
#### Returns
|
||||
|
||||
`string`
|
||||
|
||||
***
|
||||
|
||||
### dateToIndex()
|
||||
|
||||
> **dateToIndex**(`index`, `d`): `number`
|
||||
@@ -427,7 +453,7 @@ Defined in: [Developer/brk/modules/brk-client/index.js:11512](https://github.com
|
||||
|
||||
Address hash-prefix matches
|
||||
|
||||
Find addresses by address type and address-payload hash prefix. Intended for privacy-preserving client-side wallet discovery without sending raw addresses or xpubs. Fetch metadata for the returned addresses through `/api/address/{address}`.
|
||||
Find addresses by address type and by the first 1-16 hex nibbles of RapidHash v3 over the raw address payload bytes. Intended for privacy-preserving client-side wallet discovery without sending raw addresses or xpubs. Fetch metadata for the returned addresses through `/api/address/{address}`.
|
||||
|
||||
Endpoint: `GET /api/address/hash-prefix/{addr_type}/{prefix}`
|
||||
|
||||
@@ -461,6 +487,50 @@ Endpoint: `GET /api/address/hash-prefix/{addr_type}/{prefix}`
|
||||
|
||||
***
|
||||
|
||||
### getAddressPayloadHashPrefixMatches()
|
||||
|
||||
> **getAddressPayloadHashPrefixMatches**(`addrType`, `payload`, `nibbles`, `options?`): `Promise`\<[`AddrHashPrefixMatches`](../interfaces/AddrHashPrefixMatches.md)\>
|
||||
|
||||
Fetch address hash-prefix matches from raw payload bytes matching `addrType` length.
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### addrType
|
||||
|
||||
[`OutputType`](../type-aliases/OutputType.md)
|
||||
|
||||
##### payload
|
||||
|
||||
`Uint8Array` | `ArrayBuffer` | `ArrayBufferView` | `number`[]
|
||||
|
||||
Raw payload bytes matching `addrType` length.
|
||||
|
||||
##### nibbles
|
||||
|
||||
`number`
|
||||
|
||||
Prefix length from 1 to 16 hex nibbles.
|
||||
|
||||
##### options?
|
||||
|
||||
###### cache?
|
||||
|
||||
`boolean`
|
||||
|
||||
###### onValue?
|
||||
|
||||
(`value`) => `void`
|
||||
|
||||
###### signal?
|
||||
|
||||
`AbortSignal`
|
||||
|
||||
#### Returns
|
||||
|
||||
`Promise`\<[`AddrHashPrefixMatches`](../interfaces/AddrHashPrefixMatches.md)\>
|
||||
|
||||
***
|
||||
|
||||
### getAddressMempoolTxs()
|
||||
|
||||
> **getAddressMempoolTxs**(`address`, `options?`): `Promise`\<[`Transaction`](../interfaces/Transaction.md)[]\>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[**brk-client**](../README.md)
|
||||
|
||||
***
|
||||
|
||||
[brk-client](../globals.md) / addressPayloadHashPrefix
|
||||
|
||||
# Function: addressPayloadHashPrefix()
|
||||
|
||||
> **addressPayloadHashPrefix**(`payload`, `nibbles`): `string`
|
||||
|
||||
Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`.
|
||||
|
||||
## Parameters
|
||||
|
||||
### payload
|
||||
|
||||
`Uint8Array` | `ArrayBuffer` | `ArrayBufferView` | `number`[]
|
||||
|
||||
Raw address payload bytes. Must be 1 to 65 bytes.
|
||||
|
||||
### nibbles
|
||||
|
||||
`number`
|
||||
|
||||
Prefix length from 1 to 16 hex nibbles.
|
||||
|
||||
## Returns
|
||||
|
||||
`string`
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
- [BrkClient](classes/BrkClient.md)
|
||||
- [BrkError](classes/BrkError.md)
|
||||
|
||||
## Functions
|
||||
|
||||
- [addressPayloadHashPrefix](functions/addressPayloadHashPrefix.md)
|
||||
|
||||
## Interfaces
|
||||
|
||||
- [\_0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern](interfaces/0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern.md)
|
||||
|
||||
+179
-2
@@ -2132,6 +2132,159 @@ const _m = (acc, s) => s ? (acc ? `${acc}_${s}` : s) : acc;
|
||||
const _p = (prefix, acc) => acc ? `${prefix}_${acc}` : prefix;
|
||||
|
||||
|
||||
|
||||
const _MASK_64 = 0xffffffffffffffffn;
|
||||
const _RAPIDHASH_SECRETS = /** @type {const} */ ([
|
||||
0x2d358dccaa6c78a5n,
|
||||
0x8bb84b93962eacc9n,
|
||||
0x4b33a62ed433d4a3n,
|
||||
0x4d5a2da51de1aa47n,
|
||||
0xa0761d6478bd642fn,
|
||||
0xe7037ed1a0b428dbn,
|
||||
0x90ed1765281c388cn,
|
||||
]);
|
||||
const _RAPIDHASH_SEED = _rapidHashSeed(0n);
|
||||
|
||||
/** @param {bigint} value */
|
||||
function _u64(value) {
|
||||
return value & _MASK_64;
|
||||
}
|
||||
|
||||
/** @param {bigint} left @param {bigint} right */
|
||||
function _rapidMix(left, right) {
|
||||
const result = _u64(left) * _u64(right);
|
||||
return _u64(result) ^ _u64(result >> 64n);
|
||||
}
|
||||
|
||||
/** @param {bigint} left @param {bigint} right @returns {[bigint, bigint]} */
|
||||
function _rapidMum(left, right) {
|
||||
const result = _u64(left) * _u64(right);
|
||||
return [_u64(result), _u64(result >> 64n)];
|
||||
}
|
||||
|
||||
/** @param {bigint} seed */
|
||||
function _rapidHashSeed(seed) {
|
||||
return _u64(seed ^ _rapidMix(seed ^ _RAPIDHASH_SECRETS[2], _RAPIDHASH_SECRETS[1]));
|
||||
}
|
||||
|
||||
/** @param {Uint8Array} bytes @param {number} offset */
|
||||
function _readU32(bytes, offset) {
|
||||
return (
|
||||
BigInt(bytes[offset]) |
|
||||
(BigInt(bytes[offset + 1]) << 8n) |
|
||||
(BigInt(bytes[offset + 2]) << 16n) |
|
||||
(BigInt(bytes[offset + 3]) << 24n)
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {Uint8Array} bytes @param {number} offset */
|
||||
function _readU64(bytes, offset) {
|
||||
return _readU32(bytes, offset) | (_readU32(bytes, offset + 4) << 32n);
|
||||
}
|
||||
|
||||
/** @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload */
|
||||
function _asUint8Array(payload) {
|
||||
if (payload instanceof Uint8Array) return payload;
|
||||
if (payload instanceof ArrayBuffer) return new Uint8Array(payload);
|
||||
if (ArrayBuffer.isView(payload)) return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength);
|
||||
if (Array.isArray(payload)) return new Uint8Array(payload);
|
||||
throw new Error("Expected address payload bytes");
|
||||
}
|
||||
|
||||
/** @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload */
|
||||
function _rapidHashV3(payload) {
|
||||
const bytes = _asUint8Array(payload);
|
||||
const length = bytes.length;
|
||||
if (length === 0) throw new Error("Expected a non-empty address payload");
|
||||
if (length > 65) throw new Error("Expected at most 65 address payload bytes");
|
||||
|
||||
let seed = _RAPIDHASH_SEED;
|
||||
let a = 0n;
|
||||
let b = 0n;
|
||||
let remainder;
|
||||
|
||||
if (length <= 16) {
|
||||
if (length >= 4) {
|
||||
seed ^= BigInt(length);
|
||||
if (length >= 8) {
|
||||
a ^= _readU64(bytes, 0);
|
||||
b ^= _readU64(bytes, length - 8);
|
||||
} else {
|
||||
a ^= _readU32(bytes, 0);
|
||||
b ^= _readU32(bytes, length - 4);
|
||||
}
|
||||
} else if (length > 0) {
|
||||
a ^= (BigInt(bytes[0]) << 45n) | BigInt(bytes[length - 1]);
|
||||
b ^= BigInt(bytes[length >> 1]);
|
||||
}
|
||||
remainder = BigInt(length);
|
||||
} else {
|
||||
seed = _rapidMix(_readU64(bytes, 0) ^ _RAPIDHASH_SECRETS[2], _readU64(bytes, 8) ^ seed);
|
||||
if (length > 32) {
|
||||
seed = _rapidMix(_readU64(bytes, 16) ^ _RAPIDHASH_SECRETS[2], _readU64(bytes, 24) ^ seed);
|
||||
if (length > 48) {
|
||||
seed = _rapidMix(_readU64(bytes, 32) ^ _RAPIDHASH_SECRETS[1], _readU64(bytes, 40) ^ seed);
|
||||
if (length > 64) {
|
||||
seed = _rapidMix(_readU64(bytes, 48) ^ _RAPIDHASH_SECRETS[1], _readU64(bytes, 56) ^ seed);
|
||||
}
|
||||
}
|
||||
}
|
||||
remainder = BigInt(length);
|
||||
a ^= _readU64(bytes, length - 16) ^ remainder;
|
||||
b ^= _readU64(bytes, length - 8);
|
||||
}
|
||||
|
||||
a ^= _RAPIDHASH_SECRETS[1];
|
||||
b ^= seed;
|
||||
[a, b] = _rapidMum(a, b);
|
||||
return _rapidMix(a ^ 0xaaaaaaaaaaaaaaaan, b ^ _RAPIDHASH_SECRETS[1] ^ remainder);
|
||||
}
|
||||
|
||||
/** @param {number} nibbles */
|
||||
function _validateHashPrefixNibbles(nibbles) {
|
||||
if (!Number.isInteger(nibbles) || nibbles < 1 || nibbles > 16) {
|
||||
throw new Error("Expected hash-prefix length from 1 to 16 hex nibbles");
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {OutputType} addrType @returns {number[]} */
|
||||
function _addressPayloadLengths(addrType) {
|
||||
switch (addrType) {
|
||||
case "p2a": return [2];
|
||||
case "p2pk": return [33, 65];
|
||||
case "p2pkh":
|
||||
case "p2sh":
|
||||
case "v0_p2wpkh": return [20];
|
||||
case "v0_p2wsh":
|
||||
case "v1_p2tr": return [32];
|
||||
default:
|
||||
throw new Error(`Unsupported address type for address payload hash-prefix: ${addrType}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {OutputType} addrType
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload
|
||||
*/
|
||||
function _validateAddressPayloadForType(addrType, payload) {
|
||||
const length = _asUint8Array(payload).length;
|
||||
const expected = _addressPayloadLengths(addrType);
|
||||
if (!expected.includes(length)) {
|
||||
throw new Error(`Expected ${addrType} address payload length ${expected.join(" or ")} bytes`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`.
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload - Raw address payload bytes
|
||||
* @param {number} nibbles - Prefix length from 1 to 16 hex nibbles
|
||||
* @returns {string}
|
||||
*/
|
||||
function addressPayloadHashPrefix(payload, nibbles) {
|
||||
_validateHashPrefixNibbles(nibbles);
|
||||
return _rapidHashV3(payload).toString(16).padStart(16, "0").slice(0, nibbles);
|
||||
}
|
||||
|
||||
// Index group constants and factory
|
||||
|
||||
const _i1 = /** @type {const} */ (["minute10", "minute30", "hour1", "hour4", "hour12", "day1", "day3", "week1", "month1", "month3", "month6", "year1", "year10", "halving", "epoch", "height"]);
|
||||
@@ -9091,6 +9244,30 @@ class BrkClient extends BrkClientBase {
|
||||
this.series = this._buildTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the RapidHash v3 hash-prefix for raw address payload bytes.
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload
|
||||
* @param {number} nibbles
|
||||
* @returns {string}
|
||||
*/
|
||||
static addressPayloadHashPrefix(payload, nibbles) {
|
||||
return addressPayloadHashPrefix(payload, nibbles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch address hash-prefix matches from raw address payload bytes.
|
||||
* @param {OutputType} addrType
|
||||
* @param {Uint8Array | ArrayBuffer | ArrayBufferView | number[]} payload - Raw payload bytes matching addrType length
|
||||
* @param {number} nibbles
|
||||
* @param {{ signal?: AbortSignal, onValue?: (value: AddrHashPrefixMatches) => void, cache?: boolean }} [options]
|
||||
* @returns {Promise<AddrHashPrefixMatches>}
|
||||
*/
|
||||
getAddressPayloadHashPrefixMatches(addrType, payload, nibbles, options = {}) {
|
||||
_validateAddressPayloadForType(addrType, payload);
|
||||
const prefix = addressPayloadHashPrefix(payload, nibbles);
|
||||
return this.getAddressHashPrefixMatches(addrType, prefix, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {SeriesTree}
|
||||
@@ -11500,7 +11677,7 @@ class BrkClient extends BrkClientBase {
|
||||
/**
|
||||
* Address hash-prefix matches
|
||||
*
|
||||
* Find addresses by address type and address-payload hash prefix. Intended for privacy-preserving client-side wallet discovery without sending raw addresses or xpubs. Fetch metadata for the returned addresses through `/api/address/{address}`.
|
||||
* Find addresses by address type and by the first 1-16 hex nibbles of RapidHash v3 over the raw address payload bytes. Intended for privacy-preserving client-side wallet discovery without sending raw addresses or xpubs. Fetch metadata for the returned addresses through `/api/address/{address}`.
|
||||
*
|
||||
* Endpoint: `GET /api/address/hash-prefix/{addr_type}/{prefix}`
|
||||
*
|
||||
@@ -12766,4 +12943,4 @@ class BrkClient extends BrkClientBase {
|
||||
|
||||
}
|
||||
|
||||
export { BrkClient, BrkError };
|
||||
export { BrkClient, BrkError, addressPayloadHashPrefix };
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"scripts": {
|
||||
"test": "node tests/basic.js && node tests/tree.js",
|
||||
"test:basic": "node tests/basic.js",
|
||||
"test:hash-prefix": "node tests/hash_prefix.js",
|
||||
"test:tree": "node tests/tree.js"
|
||||
},
|
||||
"description": "Bitcoin on-chain analytics client — thousands of metrics, block explorer, and address index",
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { BrkClient, addressPayloadHashPrefix } from "../index.js";
|
||||
|
||||
const vectors = [
|
||||
[Uint8Array.of(0x4e, 0x73), "58101afa51a1ecfd"],
|
||||
[Uint8Array.from({ length: 20 }, (_, i) => i), "c3327ecb8ae1ff23"],
|
||||
[Uint8Array.from({ length: 32 }, (_, i) => i), "c0186990f026b180"],
|
||||
[Uint8Array.from({ length: 65 }, (_, i) => i), "0d4b77027ae7d700"],
|
||||
];
|
||||
|
||||
for (const [payload, hash] of vectors) {
|
||||
assert.equal(addressPayloadHashPrefix(payload, 16), hash);
|
||||
assert.equal(BrkClient.addressPayloadHashPrefix(payload, 8), hash.slice(0, 8));
|
||||
}
|
||||
|
||||
assert.throws(() => addressPayloadHashPrefix(Uint8Array.of(), 16), /non-empty/);
|
||||
assert.throws(() => addressPayloadHashPrefix(new Uint8Array(66), 16), /at most 65/);
|
||||
assert.throws(() => addressPayloadHashPrefix(Uint8Array.of(1, 2), 0), /1 to 16/);
|
||||
|
||||
const client = new BrkClient("http://127.0.0.1:0");
|
||||
client.getAddressHashPrefixMatches = (addrType, prefix) => ({ addrType, prefix, truncated: false, addresses: [] });
|
||||
|
||||
assert.deepEqual(
|
||||
client.getAddressPayloadHashPrefixMatches("p2pkh", Uint8Array.from({ length: 20 }, (_, i) => i), 8),
|
||||
{ addrType: "p2pkh", prefix: "c3327ecb", truncated: false, addresses: [] },
|
||||
);
|
||||
assert.throws(
|
||||
() => client.getAddressPayloadHashPrefixMatches("p2pkh", Uint8Array.of(1, 2), 8),
|
||||
/p2pkh address payload length 20 bytes/,
|
||||
);
|
||||
assert.throws(
|
||||
() => client.getAddressPayloadHashPrefixMatches("op_return", Uint8Array.of(1, 2), 8),
|
||||
/Unsupported address type/,
|
||||
);
|
||||
Reference in New Issue
Block a user