regit-identifiers
Securities identifier validation. Zero-dependency, pure Rust, no_std.
What it does
regit-identifiers parses, validates, and converts the identifiers that name
a security or a market: ISIN, CUSIP / CINS, SEDOL, LEI,
BIC, MIC, FIGI, CFI, and the national numbers WKN and
VALOR.
For every identifier that carries one, the check digit is recomputed from its
governing standard and verified — never trusted. Structural rules (length,
character set, segment layout, country codes) are enforced exactly as the
standard specifies. A unified SecurityId auto-detects which
kind of identifier a raw string is, and a conversion layer moves between the
ones that are formally related.
Every rule is traced to a citable standard in SPEC.md. A regulator, an auditor, or a new engineer can open any source file and check it against ISO 6166, ISO 17442, ISO 9362, ISO 10383, ISO 10962, or the CUSIP/SEDOL/FIGI specifications.
Why this crate exists
An identifier is the primary key of a security. A trade is routed, a position is settled, a holding is reported to a regulator, and a corporate action is applied — all against an identifier. If a malformed or mistyped identifier is accepted as valid, every one of those operations is wrong, and the error is silent until it is expensive.
Most identifiers defend against exactly this with a check digit — a
redundancy that catches the overwhelming majority of single-character typos
and digit transpositions. But the check is only as good as its
implementation, and the algorithms are deceptively easy to get subtly wrong:
ISIN expands letters to two digits before applying Luhn weights; FIGI doubles
from the second-rightmost character, not the rightmost; SEDOL uses a fixed
weight vector and, unlike the others, never splits two-digit products; LEI is a
whole-string modulus that overflows a 64-bit integer. A validator that gets any
of these wrong reports valid on bad data — the worst possible failure.
regit-identifiers implements each algorithm from its governing standard,
verifies it against hand-computed worked examples for real instruments, and
ships the verification alongside the code. It is no_std and allocation-free,
so the same audited logic runs in a backend service, a WASM bundle, or on an
embedded device with no change.
This sits within Regit OS: regit-identifiers is the
reference-data layer — the component that decides whether an identifier is real
before anything downstream acts on it.
Quick start
[]
= "1.0"
use ;
// Parse and validate — the check digit is recomputed and verified.
let isin = parse.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
// A wrong check digit is rejected, not silently accepted.
assert!;
// Auto-detect which kind of identifier a raw string is.
match detect
See examples/quickstart.rs for a complete tour
covering every identifier, the conversion layer, and the MIC registry.
Identifiers covered
| Identifier | Standard | Length | Check digit | Module |
|---|---|---|---|---|
| ISIN | ISO 6166 | 12 | Luhn mod-10 over expanded string | isin |
| CUSIP / CINS | ANSI X9.6 | 9 | Modulus 10 double-add-double | cusip |
| SEDOL | LSE | 7 | Weighted sum mod-10 | sedol |
| LEI | ISO 17442 | 20 | ISO 7064 MOD 97-10 | lei |
| FIGI | ANSI X9.145 | 12 | Modulus 10 double-add-double | figi |
| BIC / SWIFT | ISO 9362 | 8 or 11 | — structural | bic |
| MIC | ISO 10383 | 4 | — structural + registry | mic |
| CFI | ISO 10962 | 6 | — structural | cfi |
| WKN | WM Datenservice | 6 | — structural | wkn |
| VALOR | SIX | 1–9 | — structural | valor |
The check-digit core
The thesis of this crate is that a securities identifier is a check-digit
scheme over an alphanumeric alphabet. The checkdigit
module exposes that core directly:
use checkdigit;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
Each function is the algorithm from its standard, documented with the derivation and a worked example.
Conversions
The convert module moves between identifiers that are
formally related — when, and only when, the relationship is defined:
- a US/Canada ISIN ↔ CUSIP (the NSIN body is the CUSIP);
- a UK/Ireland ISIN ↔ SEDOL (the SEDOL is left-padded into the NSIN);
- a Swiss ISIN ↔ VALOR;
- building an ISIN from any country code plus a national number.
Conversions that have no defined meaning — a German ISIN to a CUSIP, say — return a typed error rather than a wrong answer.
Unified detection
SecurityId is a single type that holds any identifier, and
SecurityId::detect recognises which one a raw string is — by length,
structure, and, where one exists, a passing check digit. The trial order is
LEI → ISIN → FIGI → CUSIP → SEDOL → BIC → MIC, checksum-strength first;
the ambiguous structural-only kinds (CFI, WKN, VALOR) are not auto-detected
(parse them explicitly). It is the entry point when the kind of an identifier
is not known ahead of time.
Architecture
src/
lib.rs # Module declarations + re-exports
errors.rs # Typed errors — ValidationError, ConversionError
charset.rs # ASCII alphabet primitives (internal)
checkdigit.rs # Check-digit algorithms — ISIN, CUSIP, SEDOL, LEI, FIGI
isin.rs # ISIN — ISO 6166
cusip.rs # CUSIP and CINS
sedol.rs # SEDOL
lei.rs # LEI — ISO 17442
figi.rs # FIGI — ANSI X9.145
bic.rs # BIC — ISO 9362
mic.rs # MIC — ISO 10383
cfi.rs # CFI — ISO 10962
wkn.rs # WKN — German national number
valor.rs # VALOR — Swiss national number
country.rs # ISO 3166-1 alpha-2 + ISIN prefix table
mic_registry.rs # Embedded ISO 10383 registry snapshot (feature `mic-registry`)
convert.rs # Cross-identifier conversions
detect.rs # Unified SecurityId + auto-detection
One file, one identifier. Each type is Copy, allocation-free, and validated
on construction.
Testing
Tests are anchored on real, well-known instruments with hand-computed
check digits — Apple's ISIN and CUSIP, BAE Systems' SEDOL, IBM's FIGI, a
Bloomberg LEI — together with the documented invalid cases for each standard,
proptest round-trip invariants, and the conversion identities.
Code quality
#![no_std], allocation-free — runs in services, WASM, and on embedded targets with nostd#![forbid(unsafe_code)]crate-wideclippy::pedanticwith zero warnings- No
unwrap(),expect(), orpanic!()in library code — every failure path is a typedResult - Every public item documented with its governing standard and a runnable example
- Deterministic: the same input always produces the same verdict
Dependencies
Runtime: zero. Not std, not alloc, no FFI. Every check-digit algorithm
is hand-rolled from its governing standard. Licence and supply-chain policy is
enforced via cargo-deny (deny.toml).
The default mic-registry feature embeds a dated snapshot of the ISO 10383
registry as static, no_std-clean data. Disable it for a structural-only MIC
build: cargo build --no-default-features.
Standards
All algorithms implemented from their governing standard — no ports from other implementations.
| Standard | Identifier |
|---|---|
| ISO 6166 | ISIN |
| ANSI X9.6 — CUSIP Global Services | CUSIP, CINS |
| London Stock Exchange SEDOL Masterfile | SEDOL |
| ISO 17442 / ISO 7064 MOD 97-10 | LEI |
| ISO 9362 | BIC |
| ISO 10383 | MIC |
| ANSI X9.145 / OMG (OpenFIGI) | FIGI |
| ISO 10962 | CFI |
| ISO 3166-1 alpha-2 | country codes |
Documentation
- SPEC.md — every structural rule and check-digit algorithm, traced to its standard, with worked examples
- CHANGELOG.md — release history
- SECURITY.md — vulnerability disclosure policy
License
Apache License 2.0. See LICENSE and NOTICE.
Copyright 2026 Regit.io — Nicolas Koenig
Part of Regit OS — the operating system for investment products. From Luxembourg.