website_next: move hash logic to clients

This commit is contained in:
nym21
2026-07-01 17:04:29 +02:00
parent 5d83ee4d70
commit 153fcdf4e0
33 changed files with 1206 additions and 189 deletions
+27 -2
View File
@@ -24,6 +24,7 @@
* [series\_endpoint](#brk_client.BrkClient.series_endpoint)
* [index\_to\_date](#brk_client.BrkClient.index_to_date)
* [date\_to\_index](#brk_client.BrkClient.date_to_index)
* [address\_payload\_hash\_prefix](#brk_client.BrkClient.address_payload_hash_prefix)
* [get\_health](#brk_client.BrkClient.get_health)
* [get\_version](#brk_client.BrkClient.get_version)
* [get\_sync\_status](#brk_client.BrkClient.get_sync_status)
@@ -47,6 +48,7 @@
* [get\_difficulty\_adjustment](#brk_client.BrkClient.get_difficulty_adjustment)
* [get\_prices](#brk_client.BrkClient.get_prices)
* [get\_historical\_price](#brk_client.BrkClient.get_historical_price)
* [get\_address\_payload\_hash\_prefix\_matches](#brk_client.BrkClient.get_address_payload_hash_prefix_matches)
* [get\_address\_hash\_prefix\_matches](#brk_client.BrkClient.get_address_hash_prefix_matches)
* [get\_address](#brk_client.BrkClient.get_address)
* [get\_address\_txs](#brk_client.BrkClient.get_address_txs)
@@ -255,6 +257,17 @@ def date_to_index(index: Index, d: Union[date, datetime]) -> int
Convert a date/datetime to an index value for date-based indexes.
<a id="brk_client.BrkClient.address_payload_hash_prefix"></a>
#### address\_payload\_hash\_prefix
```python
def address_payload_hash_prefix(
payload: Union[bytes, bytearray, memoryview], nibbles: int) -> str
```
Compute the RapidHash v3 hash-prefix for raw address payload bytes.
<a id="brk_client.BrkClient.get_health"></a>
#### get\_health
@@ -607,6 +620,19 @@ Get historical BTC/USD price. Optionally specify a UNIX timestamp to get the pri
Endpoint: `GET /api/v1/historical-price`
<a id="brk_client.BrkClient.get_address_payload_hash_prefix_matches"></a>
#### get\_address\_payload\_hash\_prefix\_matches
```python
def get_address_payload_hash_prefix_matches(
addr_type: OutputType,
payload: Union[bytes, bytearray, memoryview],
nibbles: int) -> AddrHashPrefixMatches
```
Fetch address hash-prefix matches from raw payload bytes matching `addr_type` length.
<a id="brk_client.BrkClient.get_address_hash_prefix_matches"></a>
#### get\_address\_hash\_prefix\_matches
@@ -618,7 +644,7 @@ def get_address_hash_prefix_matches(addr_type: OutputType,
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}`
@@ -1769,4 +1795,3 @@ Compact OpenAPI specification.
Compact OpenAPI specification optimized for LLM consumption. Removes redundant fields while preserving essential API information. Full spec available at `/openapi.json`.
Endpoint: `GET /api.json`
+134 -2
View File
@@ -1912,6 +1912,124 @@ def _p(prefix: str, acc: str) -> str:
return f"{prefix}_{acc}" if acc else prefix
_MASK_64 = (1 << 64) - 1
_RAPIDHASH_SECRETS = (
0x2d358dccaa6c78a5,
0x8bb84b93962eacc9,
0x4b33a62ed433d4a3,
0x4d5a2da51de1aa47,
0xa0761d6478bd642f,
0xe7037ed1a0b428db,
0x90ed1765281c388c,
)
_RAPIDHASH_SEED = 0
def _u64(value: int) -> int:
return value & _MASK_64
def _rapid_mix(left: int, right: int) -> int:
result = _u64(left) * _u64(right)
return _u64(result) ^ _u64(result >> 64)
def _rapid_mum(left: int, right: int) -> Tuple[int, int]:
result = _u64(left) * _u64(right)
return _u64(result), _u64(result >> 64)
def _rapid_hash_seed(seed: int) -> int:
return _u64(seed ^ _rapid_mix(seed ^ _RAPIDHASH_SECRETS[2], _RAPIDHASH_SECRETS[1]))
_RAPIDHASH_SEED = _rapid_hash_seed(0)
def _read_u32(data: bytes, offset: int) -> int:
return int.from_bytes(data[offset:offset + 4], "little")
def _read_u64(data: bytes, offset: int) -> int:
return int.from_bytes(data[offset:offset + 8], "little")
def _rapid_hash_v3(payload: Union[bytes, bytearray, memoryview]) -> int:
data = bytes(payload)
length = len(data)
if length == 0:
raise ValueError("Expected a non-empty address payload")
if length > 65:
raise ValueError("Expected at most 65 address payload bytes")
seed = _RAPIDHASH_SEED
a = 0
b = 0
if length <= 16:
if length >= 4:
seed ^= length
if length >= 8:
a ^= _read_u64(data, 0)
b ^= _read_u64(data, length - 8)
else:
a ^= _read_u32(data, 0)
b ^= _read_u32(data, length - 4)
elif length > 0:
a ^= (data[0] << 45) | data[length - 1]
b ^= data[length >> 1]
remainder = length
else:
if length > 16:
seed = _rapid_mix(_read_u64(data, 0) ^ _RAPIDHASH_SECRETS[2], _read_u64(data, 8) ^ seed)
if length > 32:
seed = _rapid_mix(_read_u64(data, 16) ^ _RAPIDHASH_SECRETS[2], _read_u64(data, 24) ^ seed)
if length > 48:
seed = _rapid_mix(_read_u64(data, 32) ^ _RAPIDHASH_SECRETS[1], _read_u64(data, 40) ^ seed)
if length > 64:
seed = _rapid_mix(_read_u64(data, 48) ^ _RAPIDHASH_SECRETS[1], _read_u64(data, 56) ^ seed)
remainder = length
a ^= _read_u64(data, length - 16) ^ remainder
b ^= _read_u64(data, length - 8)
a ^= _RAPIDHASH_SECRETS[1]
b ^= seed
a, b = _rapid_mum(a, b)
return _rapid_mix(a ^ 0xaaaaaaaaaaaaaaaa, b ^ _RAPIDHASH_SECRETS[1] ^ remainder)
def _validate_hash_prefix_nibbles(nibbles: int) -> None:
if isinstance(nibbles, bool) or not isinstance(nibbles, int) or nibbles < 1 or nibbles > 16:
raise ValueError("Expected hash-prefix length from 1 to 16 hex nibbles")
def _address_payload_lengths(addr_type: OutputType) -> Tuple[int, ...]:
if addr_type == "p2a":
return (2,)
if addr_type == "p2pk":
return (33, 65)
if addr_type in ("p2pkh", "p2sh", "v0_p2wpkh"):
return (20,)
if addr_type in ("v0_p2wsh", "v1_p2tr"):
return (32,)
raise ValueError(f"Unsupported address type for address payload hash-prefix: {addr_type}")
def _validate_address_payload_for_type(addr_type: OutputType, payload: Union[bytes, bytearray, memoryview]) -> None:
length = len(bytes(payload))
expected = _address_payload_lengths(addr_type)
if length not in expected:
joined = " or ".join(str(value) for value in expected)
raise ValueError(f"Expected {addr_type} address payload length {joined} bytes")
def address_payload_hash_prefix(payload: Union[bytes, bytearray, memoryview], nibbles: int) -> str:
"""Compute the RapidHash v3 hash-prefix used by `/api/address/hash-prefix/{addr_type}/{prefix}`."""
_validate_hash_prefix_nibbles(nibbles)
return f"{_rapid_hash_v3(payload):016x}"[:nibbles]
# Date conversion constants
_GENESIS = date(2009, 1, 3) # day1 0, week1 0
_DAY_ONE = date(2009, 1, 9) # day1 1 (6 day gap after genesis)
@@ -8201,6 +8319,21 @@ class BrkClient(BrkClientBase):
"""Convert a date/datetime to an index value for date-based indexes."""
return _date_to_index(index, d)
@staticmethod
def address_payload_hash_prefix(payload: Union[bytes, bytearray, memoryview], nibbles: int) -> str:
"""Compute the RapidHash v3 hash-prefix for raw address payload bytes."""
return address_payload_hash_prefix(payload, nibbles)
def get_address_payload_hash_prefix_matches(
self,
addr_type: OutputType,
payload: Union[bytes, bytearray, memoryview],
nibbles: int,
) -> AddrHashPrefixMatches:
"""Fetch address hash-prefix matches from raw payload bytes matching addr_type length."""
_validate_address_payload_for_type(addr_type, payload)
return self.get_address_hash_prefix_matches(addr_type, address_payload_hash_prefix(payload, nibbles))
def get_health(self) -> Health:
"""Health check.
@@ -8449,7 +8582,7 @@ class BrkClient(BrkClientBase):
def get_address_hash_prefix_matches(self, addr_type: OutputType, prefix: str) -> AddrHashPrefixMatches:
"""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}`"""
return self.get_json(f'/api/address/hash-prefix/{addr_type}/{prefix}')
@@ -9163,4 +9296,3 @@ class BrkClient(BrkClientBase):
Endpoint: `GET /api.json`"""
return self.get_json('/api.json')
@@ -0,0 +1,48 @@
import pytest
from brk_client import BrkClient, address_payload_hash_prefix
VECTORS = (
(bytes([0x4E, 0x73]), "58101afa51a1ecfd"),
(bytes(range(20)), "c3327ecb8ae1ff23"),
(bytes(range(32)), "c0186990f026b180"),
(bytes(range(65)), "0d4b77027ae7d700"),
)
def test_address_payload_hash_prefix_vectors():
for payload, expected in VECTORS:
assert address_payload_hash_prefix(payload, 16) == expected
assert BrkClient.address_payload_hash_prefix(payload, 8) == expected[:8]
def test_address_payload_hash_prefix_validation():
with pytest.raises(ValueError, match="non-empty"):
address_payload_hash_prefix(b"", 16)
with pytest.raises(ValueError, match="at most 65"):
address_payload_hash_prefix(bytes(range(66)), 16)
with pytest.raises(ValueError, match="1 to 16"):
address_payload_hash_prefix(b"\x01\x02", 0)
def test_address_payload_hash_prefix_match_validation():
client = BrkClient("http://127.0.0.1:0")
client.get_address_hash_prefix_matches = lambda addr_type, prefix: {
"addr_type": addr_type,
"prefix": prefix,
"truncated": False,
"addresses": [],
}
assert client.get_address_payload_hash_prefix_matches("p2pkh", bytes(range(20)), 8) == {
"addr_type": "p2pkh",
"prefix": "c3327ecb",
"truncated": False,
"addresses": [],
}
with pytest.raises(ValueError, match="p2pkh address payload length 20 bytes"):
client.get_address_payload_hash_prefix_matches("p2pkh", b"\x01\x02", 8)
with pytest.raises(ValueError, match="Unsupported address type"):
client.get_address_payload_hash_prefix_matches("op_return", b"\x01\x02", 8)