| Crates.io | bitcoin-asmap |
| lib.rs | bitcoin-asmap |
| version | 0.1.19 |
| created_at | 2023-01-19 08:44:09.603387+00 |
| updated_at | 2025-12-01 04:08:47.71895+00 |
| description | Bit-exact Rust reimplementation of Bitcoin Core's ASMAP decoder, interpreter, and structural validator for mapping IP addresses to autonomous system numbers (ASNs). |
| homepage | |
| repository | https://github.com/klebs6/bitcoin-rs |
| max_upload_size | |
| id | 762409 |
| size | 212,652 |
High‑fidelity Rust implementation of Bitcoin Core's ASMAP interpreter, decoder, and structural validator.
This crate provides a bit‑exact reimplementation of the Autonomous System (AS) mapping logic used by Bitcoin Core to probabilistically infer the AS number (ASN) associated with a peer's IP address. It is designed for:
An ASMAP file encodes a decision tree over IP address bits that maps each address to an Autonomous System Number (ASN). In Bitcoin Core, this mapping is used to diversify peer selection by AS to reduce the risk that many peers are controlled by the same routing domain.
Key ideas:
[bool; 128] (for IPv6) or appropriately padded for IPv4‑in‑IPv6.asmap: &[bool] which is interpreted to yield a final ASN.This crate mirrors the C++ reference implementation used by Bitcoin Core, including its variable‑length integer coding, instruction set, and comprehensive structural sanity checks.
#[repr(u32)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Instruction {
RETURN,
JUMP,
MATCH,
DEFAULT,
}
``
The instruction stream is encoded as bits and decoded via `decode_type`, which internally uses `decode_bits` and a compact opcode encoding.
- **RETURN**: Decode and return an ASN; terminates the program if structurally valid.
- **JUMP**: Conditionally jump forward in the instruction stream based on the next IP bit.
- **MATCH**: Match a short bit pattern against the next IP bits; either continue or fall back to the current default ASN.
- **DEFAULT**: Update the current default ASN used when a `MATCH` fails.
These instructions, combined with a stack of jump targets, implement a structured decision tree with compact representation.
---
## Public API
### Reading and validating an ASMAP file
```rust
use bitcoin_asmap::decode_asmap;
let asmap_bits = decode_asmap("/path/to/asmap.dat");
if asmap_bits.is_empty() {
// Failed to read or validate ASMAP
}
decode_asmap<P: AsRef<Path>>(path: P) -> Vec<bool>:
Vec<bool> bit stream (bit 0 is the LSB of each byte).sanity_check_as_map.This is the entry point you usually want when consuming an ASMAP file shipped with Bitcoin Core or derived tools.
use bitcoin_asmap::{decode_asmap, interpret};
let asmap = decode_asmap("/path/to/asmap.dat");
if asmap.is_empty() {
panic!("Invalid ASMAP");
}
// Example: represent an IPv4 address as 128 bits (IPv4‑mapped)
fn ipv4_to_bits(addr: [u8; 4]) -> Vec<bool> {
// Very naive 32‑bit big‑endian; real usage should follow Bitcoin Core's
// IPv4‑in‑IPv6 representation and bit ordering.
let mut bits = Vec::with_capacity(32);
for byte in addr {
for offset in (0..8).rev() { // big‑endian per byte
bits.push(((byte >> offset) & 1) != 0);
}
}
bits
}
let ip_bits = ipv4_to_bits([203, 0, 113, 1]);
let asn = interpret(&asmap, &ip_bits);
if asn == 0 {
// 0 is not a valid ASN; indicates structural failure during interpret()
}
interpret(asmap: &[bool], ip: &[bool]) -> u32:
ip's bits.0 on structural failure (e.g., invalid jumps, truncated encodings), even if sanity_check_as_map should normally prevent such cases in production.Important: This crate expects the same IP bit ordering and normalization that Bitcoin Core uses. If you want behavior identical to Core, ensure you pass in
ipbits constructed according to its reference logic (e.g., IPv4 mapped into IPv6).
use bitcoin_asmap::{decode_asmap, sanity_check_as_map};
let asmap = decode_asmap("/path/to/asmap.dat");
if asmap.is_empty() {
// Already failed, but you can also explicitly check:
}
let ok = sanity_check_as_map(&asmap, 128);
assert!(ok);
sanity_check_as_map(asmap: &[bool], bits: i32) -> bool:
DEFAULTs.RETURN immediately after DEFAULT.RETURN.MATCH instructions.true iff the structure is valid.This is useful if you:
decode_asmap.These are low‑level helpers that directly mirror the C++ implementation and are usually not needed by typical users but are valuable for experimentation or custom tooling.
decodeasnpub fn decodeasn(asmap: &[bool], pos: &mut usize) -> u32
Decodes an ASN value from asmap starting at *pos using a variable‑length integer scheme defined by ASN_BIT_SIZES.
pos is updated in‑place.INVALID sentinel value (internal constant) on failure.decode_jumppub fn decode_jump(asmap: &[bool], pos: &mut usize) -> u32
Decodes a jump offset using JUMP_BIT_SIZES. The returned value is a forward offset from the current pos.
decode_matchpub fn decode_match(asmap: &[bool], pos: &mut usize) -> u32
Decodes a match pattern plus a sentinel bit using MATCH_BIT_SIZES. The number of data bits in the pattern is count_bits(m) - 1, where m is the decoded value.
decode_bitspub fn decode_bits(
asmap: &[bool],
pos: &mut usize,
minval: u8,
bit_sizes: &[u8],
) -> u32
This is the core variable‑length integer decoder, parameterized by:
minval: base value added to the final decoded integer.bit_sizes: an increasing sequence of mantissa widths.The decoding logic is exponential‑Golomb‑like:
bitsize except the last, it reads an exponent bit.1, it increments val by 1 << bitsize and continues to the next bitsize.0, it consumes bitsize mantissa bits into val and returns.bitsize, no exponent bit is read; instead, the path falls through if all previous exponent bits were 1.On early EOF, returns INVALID and leaves an error trace.
decode_typepub fn decode_type(asmap: &[bool], pos: &mut usize) -> Instruction
Decodes an instruction opcode using TYPE_BIT_SIZES and returns the corresponding Instruction variant.
count_bits#[inline]
pub fn count_bits(x: u32) -> u32 {
x.count_ones()
}
Helper that wraps u32::count_ones for internal clarity.
The implementation uses structured logging macros such as trace!, debug!, info!, and error! (typically from the tracing crate). These log at every decision point:
To benefit from these diagnostics, configure a compatible subscriber in your application, for example:
use tracing_subscriber::FmtSubscriber;
fn main() {
let subscriber = FmtSubscriber::builder()
.with_max_level(tracing::Level::TRACE)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");
// use bitcoin_asmap here
}
decode_asmap returns an empty Vec<bool> on any I/O error or failed sanity check. Empty means "do not use".interpret returns 0 on structural failure; 0 is not a valid ASN by design.INVALID sentinel (not exposed) to signal truncated encodings.sanity_check_as_map should be run on any externally provided ASMAP before using interpret in production.If you require stronger typing (e.g., non‑zero ASN newtypes) or richer error types, you can wrap this crate in a thin adapter layer.
Vec<bool>, which is compact but may have non‑trivial overhead for heavy random access. This mirrors the reference C++ behavior but not necessarily the most optimal Rust representation.O(|asmap| + |ip|) time.O(|asmap|) with additional structural invariants) and should be performed once per ASMAP file, not per lookup.For workloads performing a large number of interpret calls against a static ASMAP, the typical pattern is:
decode_asmap (or manually reading the file and using sanity_check_as_map).Vec<bool> for all subsequent IP lookups.bitcoin-asmap0.1.19