Skip to main content

dealcode (Python)

Collision-free, random-looking codes from a counter. Python implementation of the dealcode spec.

Install

pip install dealcode

Requires Python ≥ 3.9. The only dependency is cryptography (PyCA), used for AES.

Quickstart

from dealcode import Dealcode

codec = Dealcode(key="0a1b...64-hex-chars-from-your-secret-manager")

codec.encode(0)        # '767a5b'   (6 hex chars)
codec.encode(1)        # '421163'   never collides with any other counter
codec.decode("421163") # 1

(The outputs shown are the real ones for this exact key and default config — with your own key every counter maps to a different, but equally stable, code.)

The key can be raw bytes (16/24/32 bytes are used as-is as an AES key) or any string/bytes, which are deterministically expanded to an AES-256 key. Generate one with openssl rand -hex 32 and keep it in your secret manager — the mapping is stable only while the key (and every other option) stays fixed.

Options

Dealcode(
    key,                    # bytes | str
    alphabet="hex",         # "dec" | "hex" | "base32" | "crockford" | "base36"
                            # | "base58" | "base62" | "base64url" | custom string
    min_length=6,           # codes start at this length...
    max_length=None,        # ...and grow one char at a time up to this (default: max for 2^63)
    domain="",              # namespace: same key, unrelated codes per domain
)
coupon = Dealcode(key, "crockford", domain="coupons")   # human-friendly, e.g. '7Q4WKZ'
order  = Dealcode(key, "dec", min_length=8, domain="orders")  # digits only
fixed  = Dealcode(key, "hex", min_length=16, max_length=16)   # constant-length

decode raises InvalidCodeError for malformed input — wrong length, characters outside the alphabet, or a value outside the issuable range. A well-formed code always decodes to some counter, whether or not that counter was ever issued (inherent to a permutation — see SPEC §7). Treat decode as parsing, not proof of existence: look the counter up before acting on it, and note that a one-character typo in a valid code can resolve to a different valid counter — add rate limiting (and, for human-typed flows, an existence check or your own check digit). encode raises RangeError outside [0, codec.capacity), and construction mistakes (bad key, alphabet, lengths, domain) raise ConfigError. All errors subclass DealcodeError (a ValueError).

Preset alphabets normalize obvious typing variants before decoding — hex is case-insensitive, and crockford also folds O → 0 and I/L1 — but normalization never strips anything: separators and whitespace are not removed. If you display codes grouped (XXXX-XXXX), strip the hyphens or spaces in your app before calling decode.

Using it with your database

Dealcode does not talk to your database — it only turns a counter into a code. Any source of never-repeating integers works. With PostgreSQL:

CREATE SEQUENCE order_code_seq AS bigint MINVALUE 0 START WITH 0;

CREATE TABLE orders (
  id   bigint PRIMARY KEY,          -- the counter
  code text NOT NULL UNIQUE,        -- safety net; alerts on config mistakes
  ...
);
import os

from sqlalchemy import text

from dealcode import Dealcode, InvalidCodeError

codec = Dealcode(key=os.environ["DEALCODE_KEY"], domain="orders")

def create_order(conn) -> str:
    n = conn.execute(text("SELECT nextval('order_code_seq')")).scalar_one()
    code = codec.encode(n)
    conn.execute(
        text("INSERT INTO orders (id, code) VALUES (:id, :code)"),
        {"id": n, "code": code},
    )
    return code

def find_order(conn, code: str):
    try:
        n = codec.decode(code)          # malformed codes never reach the DB
    except InvalidCodeError:
        return None
    return conn.execute(text("SELECT * FROM orders WHERE id = :id"), {"id": n}).first()

Sequences never hand out the same number twice (even across concurrent transactions and rollbacks), so codes never collide. Gaps in the sequence are invisible — codes look random anyway.

If the UNIQUE constraint on code ever fires, do not retry: it means the key/config changed for an existing namespace. Investigate.

Fixed-length cycling mode

For code shapes that must never grow — airline-PNR-style fixed-length codes — CyclingDealcode (SPEC §11) fills the entire fixed-length space, and when it is exhausted refills the same space through a different permutation instead of adding a character:

from dealcode import CyclingDealcode

pnr = CyclingDealcode(key, "crockford", length=6, domain="bookings")

code = pnr.encode(n)              # always exactly 6 chars; cycle = n // pnr.capacity
cycle = pnr.cycle_of(n)           # which cycle a counter belongs to — store it with the code
n = pnr.decode(code, cycle=3)     # the cycle is required context

Configuration mirrors Dealcode (key, alphabet, domain) with a single fixed length: 2 <= length <= 128 and 100 <= radix**length <= 2**63.

Codes repeat across cycles (the space is being reused — that's the point), so keep at most one cycle's codes live per uniqueness scope: retire or expire cycle e before issuing from e+1, index with UNIQUE(cycle, code) rather than UNIQUE(code), and store each live code's cycle — decode needs it. Decoding with a wrong (but in-range) cycle is not an error — it silently returns a different counter; the existence check on the decoded counter is what catches it.

Thread safety & performance

A Dealcode instance is immutable and thread-safe; create one per namespace at startup and reuse it. Encoding is ten AES-CBC-MAC rounds — tens of microseconds, no allocation-heavy paths, O(1) in the counter value.

Running the tests

From the repository root:

pip install -e ./python pytest    # or: export PYTHONPATH=python/src
python -m pytest python/tests

The suite covers the official NIST FF1 sample vectors, every shared cross-language vector in testvectors/, and behavioural/edge cases.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dealcode-1.0.0.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dealcode-1.0.0-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

Details for the file dealcode-1.0.0.tar.gz.

File metadata

  • Download URL: dealcode-1.0.0.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dealcode-1.0.0.tar.gz
Algorithm Hash digest
SHA256 8035e2bf3369f630655491a77f03dc6e5b9b249f6f710a6882f4550d8404873c
MD5 3c1469713b9469fbd76f079017f600a3
BLAKE2b-256 917428e49ede116832c3ad17ba5c75cfefdbff0dc96e1581a8c8566d75cb6115

See more details on using hashes here.

Provenance

The following attestation bundles were made for dealcode-1.0.0.tar.gz:

Publisher: publish-pypi.yml on algorix-hq/dealcode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dealcode-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: dealcode-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 14.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dealcode-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 00c26bc0d15119170dc10e1746c79c1c8cc23177454b6ca19a62e729dbab2ac0
MD5 17737f3f7405a054f57438f6aa9e9ba7
BLAKE2b-256 94bd8211833fe81a90d065fc8e33057599abca80cdbe49558dcafb6cd4e1f080

See more details on using hashes here.

Provenance

The following attestation bundles were made for dealcode-1.0.0-py3-none-any.whl:

Publisher: publish-pypi.yml on algorix-hq/dealcode

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.0.1

2 files

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page