Developer Integration Guide¶
This guide is for developers embedding entviz in an application: rendering a value to an SVG, reading its structured entropy characterization, and showing the result in a UI. It is deliberately tight — for the normative details of the algorithm, the render model, and the characterization, see the specification.
What entviz is¶
Entviz turns a high-entropy value — a cryptographic key, a signature, a digest, a UUID, a blockchain address, a DID, a URN — into a compact SVG diagram whose colors, shapes, surround patterns, and text a human can compare at a glance. The core use case is visual comparison: render two values the same way, side by side, and any visible difference means the values differ. (Entviz is a recognition aid, never a substitute for a byte-for-byte equality check — a match only means no difference was found at the resolution you looked.)
The same algorithm is implemented, and certified against a shared conformance corpus, in five languages — Python (the reference), Rust, Go, Java, and TypeScript/JavaScript (with a React component) — plus a live browser playground. The full directory of implementations lists every package and its API docs.
Quickstart per language¶
Every implementation exposes a render entry point that takes an entropy string
(plus optional aspect ratio, font size, and note) and returns an SVG string.
All five also expose a public characterize function that returns the structured
characterization directly. In every
implementation, the eight characterization fields are also emitted onto the root
<svg> as data-* attributes, so a
consumer can read them off any rendered entviz without a separate call.
from entviz.pipeline import render
from entviz.characterize import characterize
svg = render("550e8400-e29b-41d4-a716-446655440000") # -> SVG string
# render(entropy, target_ar=1.0, font_size_pt=12, note=None)
ch = characterize("550e8400-e29b-41d4-a716-446655440000")
# -> dict: {"encoding": "hex", "scheme": "uuid", "role": "identifier",
# "qualifiers": {}, "size_basis": "decoded", "size_bits": 128,
# "parts": [...], "entropy_type": "uuid"}
use entviz::render;
use entviz::characterize::characterize;
// render(entropy, target_ar, font_size_pt, note) -> Result<String, RenderError>
let svg = render("550e8400-e29b-41d4-a716-446655440000", 1.0, 12.0, None)?;
// characterize(entropy) -> Result<Characterization, ParseError>
let ch = characterize("550e8400-e29b-41d4-a716-446655440000")?;
assert_eq!(ch.scheme.as_deref(), Some("uuid"));
assert_eq!(ch.role, Some("identifier"));
assert_eq!(ch.size_bits, 128);
import entviz "github.com/dhh1128/entviz-go"
// Render(entropy, targetAR, fontSizePt, note) -> (svg, error); note is *string.
svg, err := entviz.Render("550e8400-e29b-41d4-a716-446655440000", 1.0, 12.0, nil)
// Characterize(entropy) -> (*Characterization, error).
ch, err := entviz.Characterize("550e8400-e29b-41d4-a716-446655440000")
// ch.Scheme -> *"uuid", ch.Role -> *"identifier", ch.SizeBits -> 128
// ch.QualifiersJSON() and ch.PartsJSON() give the compact-JSON forms.
<dependency>
<groupId>io.github.dhh1128</groupId>
<artifactId>entviz</artifactId>
<version>0.15.0</version>
</dependency>
import io.github.dhh1128.entviz.Entviz;
import io.github.dhh1128.entviz.Characterization;
import io.github.dhh1128.entviz.RenderOptions;
// Defaults: aspect ratio 1.0, 12pt, no note.
String svg = Entviz.render("550e8400-e29b-41d4-a716-446655440000");
String wide = Entviz.render("550e8400-e29b-41d4-a716-446655440000",
new RenderOptions(2.0, 12.0, "id"));
// characterize(entropy) -> Characterization (record, public accessors).
Characterization ch = Entviz.characterize("550e8400-e29b-41d4-a716-446655440000");
// ch.scheme() -> "uuid", ch.role() -> "identifier", ch.sizeBits() -> 128
// (scheme() and role() are null when absent); ch.qualifiers(), ch.parts().
import { render, characterize } from "@entviz/core";
const svg = render("550e8400-e29b-41d4-a716-446655440000");
const svg2 = render("0123…", { targetAr: 2.0, fontSizePt: 16, note: "git" });
const ch = characterize("550e8400-e29b-41d4-a716-446655440000");
// -> { encoding: "hex", scheme: "uuid", role: "identifier", qualifiers: {},
// sizeBasis: "decoded", sizeBits: 128, parts: [...], entropyType: "uuid" }
React — @entviz/react wraps the renderer in components (the fastest path
for a UI; see Using the React components):
The characterization model¶
The entropy characterization re-expresses the parser's recognition of an input along independent axes, so consumers read structured fields instead of string-parsing a display label. It is reporting-only: it changes no pixel, no fingerprint input, and no label — it is metadata about the input. Every input yields the same eight fields:
| Field | Description |
|---|---|
encoding |
The declared alphabet of the core (hex, base58, bech32, base32, base64, base64url, base36, crockford32, decimal) — the alphabet that drives tokenization. |
scheme |
The recognizer/namespace that fired (cesr, btc, eth, cid, did, urn, ssh, uuid, …), or null when only bare-encoding detection matched. |
role |
The semantic role of the bits, from the closed enum {key, signature, digest, address, identifier}, or null when undetermined. |
qualifiers |
An object of independently-varying facets the recognizer recovered, e.g. {"network": "mainnet"}, {"algorithm": "ed25519"}, {"version": 1, "codec": "dag-pb", "hash": "sha2-256"}; {} when none. |
size_basis |
"decoded" or "utf8" — how size_bits is measured. Scheme-driven (did/urn/UTF-8-fallback are utf8; everything else decoded), never inferred from the alphabet or content shape. |
size_bits |
Value size in bits, always a multiple of 8, computed from the core only. Reporting-only — see the caveat below. |
parts |
The ordered [{text, bind}] list the input was cut into, in reading order. bind ∈ {none, fold, core}. |
entropy_type |
Derived convenience field, equal to scheme when non-null, otherwise encoding. |
The closed role enum is exactly key | signature | digest | address |
identifier (plus null). role is asserted only where the generic
recognizer entviz already runs self-declares it — so a did:key is
identifier (not key), a did:pkh carrying an Ethereum address is
identifier (not address), and a urn:isbn is identifier. Entviz does not
guess.
entropy_type is defined as scheme ?? encoding. This is the canonical
promotion of the old per-implementation entropyType — one definition, identical
across all five ports, instead of a value each impl re-derived from a label
string.
size_bits is reporting-only
size_bits is not the truncation basis. The large-input (head/middle/
tail) trigger for values over 512 bits uses the tokenizer's byte-length
measure, which is unchanged and distinct from size_bits. Use size_bits
for display and coarse "is this big?" decisions only; do not treat it as the
truncation boundary. See Resolution A.
The visible label¶
The text entviz draws is a projection of the characterization fields, not a
separately hand-fused string. The top strip follows one grammar —
[+hash ]PRIMARY[, MOD][, SIZE][, PREFIX], joined with a comma-space and with no
trailing ::
- PRIMARY is the scheme name (
ETH,BTC,UUID,CESR,CIDv1,SSH), or — for self-describing prefix schemes — the framing prefix itself (did:key,urn:isbn). When no scheme fired it is the encoding name (hex,b64) ortextfor the UTF-8 fallback. - MOD carries the meaningful qualifiers (e.g. CESR primitive
Ed25519 nt, SSHed25519/ecdsa-p256, CID codecdag-pb) — shown only when they depart from the silent default. - SIZE is shown in bits for decoded values (
hex, 256-bit), in bytes for thetextUTF-8 fallback (text, 56-byte), and omitted for structured identifiers whose size is fixed or a category error (DID, URN, UUID, ETH). - PREFIX (v15) echoes the literal front prefix stripped from the visualized
core (
0x,bc1,cosmos1, StellarG, the SSH header, …) so a reader can reconcile the pasted value against the cells, whose first character is otherwise silently different. Shown in addition to the type name (ETH, 0x;ADA, addr1;bech32, cosmos1); it is the only slot that truncates — a long prefix (in practice only SSH's structural header) is cut to<head>.... Folded identity prefixes (did/urn/gitoid/swhid) are not repeated; they are already the PRIMARY slot.
The +hash marker (bold dark-red) replaces v14's fingerprint of for inputs
over 512 bits, where the middle cells are a fingerprint readout rather than input
bytes — the value augmented with a hash, not a hash.
Examples: ETH, 0x, UUID, CESR, Blake3-256, CESR, Ed25519 nt,
SSH, ed25519, 264-bit, AAAA..., CIDv1, dag-pb, b, hex, 256-bit,
text, 56-byte, did:key, urn:isbn, bech32, cosmos1, +hash b64, 712-bit.
The bottom strip shows any bound checksum as ...<suffix> (optionally
followed by (<note>)). This checksum is verified: a parser surfaces it
only after checking it, so an input whose structure matches a scheme but whose
checksum is invalid is rejected as an error, never rendered with a bad
checksum on display. See the specification for the normative grammar
and checksum rules.
Consuming the fields¶
There are two equivalent ways to obtain the characterization.
(a) Call characterize() directly¶
In all five languages, call the public characterize function shown in the
quickstart. It returns the eight fields as a
structured object (a dict / struct / record / interface) — no string parsing.
This is the right choice when you have the raw value in hand and want the
characterization without rendering.
(b) Read the data-* attributes off a rendered SVG¶
Every implementation emits the eight fields onto the root <svg> element, so
any consumer — in any language, including one that only receives the rendered SVG
— can recover them by reading attributes. This is also how the conformance
checker compares each implementation against its own characterization. The
attributes are:
| Attribute | Field | Serialization |
|---|---|---|
data-encoding |
encoding |
string |
data-scheme |
scheme |
string (empty string for null) |
data-role |
role |
string (empty string for null) |
data-qualifiers |
qualifiers |
compact JSON object |
data-size-basis |
size_basis |
string |
data-size-bits |
size_bits |
integer as a decimal string |
data-parts |
parts |
compact JSON array |
data-entropy-type |
entropy_type |
string |
These are advisory metadata that add no ink (the closed SVG profile explicitly
permits extra data-* attributes), so emitting them changes nothing a viewer
sees.
Using the React components¶
For web and mobile UIs — the most common way to embed entviz —
@entviz/react wraps the core
renderer in ready-made components. Try them all in the live playground,
then reach for:
<Entviz>— render one value as a comparable SVG. Props:value,targetAr,fontSizePt,note,title,style,onError(see the quickstart above).<EntvizCompare>— render two values and surface any visible difference. This is the core comparison use case, built for you — the "dedicated comparison UI" the manual procedure below otherwise stands in for.<EntvizPill>— a compact inline pill driven by the structured characterization, not by parsing the label string (the exact pain the characterization removes). It shows the value's type (as a per-role icon, or text) and affords locate / expand / copy. In the default wild posture it makes no equality claim (recognition ≠ verification) and carries no value-derived visual. A host that owns a trusted, single-origin corpus (e.g. its own KERI KEL) can opt a same-origin value set — via a per-value trust posture — into value-derived recognition aids (a mnemonic, a colorbar cap, a color tint) that make recurrence scannable; verification still routes through the compare flow. See the threat model.<EntvizVoiceCompare>— the read-aloud comparison flow (name the color-bar band letters in order) for verifying two values over a voice call.
Whatever you build, model it on the pill's principle: drive presentation from
the structured fields, and never let a glance at a badge stand in for a real
comparison. For full props and source, see the
@entviz/react README
and the entviz-js repo.
Conformance¶
Correctness is defined by the spec's three conformance tiers:
- Tier A — render model. The abstract render model recovered from the SVG's required attributes matches the golden model field-for-field (semantic correctness; localizes failures).
- Tier B — canonical raster. The SVG, rasterized by the single pinned reference rasterizer, matches the golden raster pixel-for-pixel outside text regions (visual correctness).
- Tier C — browser smoke. A subset is rendered in a headless browser and screenshot-compared with loose tolerance (deployment sanity; non-blocking).
All five implementations are certified against the shared conformance corpus
that lives in compliance/
in the reference repo. The corpus also certifies that every implementation emits
an identical characterization for each input: the eight fields appear in each
input's golden model.json, and the checker recovers them from each
implementation's own data-* attributes and compares them. So characterize()
in any language, and the data-* attributes on any rendered SVG, agree by
construction.
Comparing two entvizes¶
Entviz is built for comparison, not memorization. A human comparing two entvizes should:
- Render both the same way — same point size, font, and background, shown side by side at the same scale. Differences in scale, zoom, or surrounding color can hide or fake a difference.
- Reject on any visible difference. The check is asymmetric: any visible difference in the text, color bar, surround pattern, blank positions, ellipse, or quartile marks means the values are different. A match is never proof of identity.
- Compare every channel, not just one. Scan the text, the color bar (each
band carries a letter
w/g/r/b/k, read aloud left to right), the surround rings, the blank-cell positions, and the overlays. A reader who habitually checks only one landmark is the easiest to fool.
The read-aloud convention — naming the color-bar band letters in order —
gives two people a channel for comparing entvizes over a voice call (and drives
<EntvizVoiceCompare>). For the full treatment of the perceptual channels and the
comparison model, see The channels at a glance
in the spec.