콘텐츠로 이동

설정

dealcode 인스턴스("코덱")는 키와 네 가지 옵션으로 정의됩니다. 같은 설정은 모든 언어에서 같은 매핑을 만듭니다.

파라미터 타입 기본값 의미
key bytes 또는 string — (필수) AES 키 재료; 참고
alphabet string "hex" 프리셋 이름 또는 커스텀 알파벳
min_length integer 6 시작 코드 길이
max_length integer 전체 코드 공간이 2^63 − 1에 들어가는 가장 큰 길이 최대 코드 길이
domain string "" 네임스페이스 라벨, FF1 tweak에 바인딩

잘못된 설정은 생성 시점에 거절됩니다(ConfigError 또는 각 언어의 관용적 등가물) — 조용히 고쳐지는 일은 없습니다. 정확한 제약은 스펙에 있습니다.

알파벳

인덱스 i의 문자가 숫자값 i를 나타내고 코드는 빅엔디언으로 렌더링됩니다. 8가지 프리셋에는 합리적인 디코드 정규화가 딸려 있습니다:

이름 기수 문자 (순서대로) 디코드 정규화
dec 10 0123456789 없음
hex 16 0123456789abcdef 입력을 ASCII 소문자로
base32 32 ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 (RFC 4648) 입력을 ASCII 대문자로
crockford 32 0123456789ABCDEFGHJKMNPQRSTVWXYZ (Crockford Base32) ASCII 대문자화 후 O→0, I→1, L→1 매핑
base36 36 0123456789abcdefghijklmnopqrstuvwxyz 입력을 ASCII 소문자로
base58 58 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz (Bitcoin) 없음
base62 62 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 없음
base64url 64 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ (RFC 4648 §5) 없음

정규화는 decode 입력에만 적용되고 encode는 항상 표준 문자를 출력합니다. 사람이 입력하는 코드에는 crockford가 친절한 선택입니다: 혼동 문자가 없고 0 대신 O를 치는 오타도 자동으로 되돌립니다.

구분자는 무시되지 않습니다

Crockford의 원래 Base32 에세이와 달리 dealcode는 하이픈/공백을 건너뛰지 않습니다: decode("H4P-FG6")은 거부됩니다. 코드를 XXXX-XXXX처럼 묶어 표시한다면 디코드 전에 구분자(및 앞뒤 공백)를 제거하세요.

커스텀 알파벳은 서로 다른 출력 가능 ASCII 문자(0x210x7E, 공백과 제어 문자 제외) 2–94자로 된 아무 문자열이나 됩니다 — "!@#$%^&*"도 동작합니다. 커스텀 알파벳에는 정규화가 없습니다: 디코드 입력이 정확히 일치해야 합니다.

도메인

domain은 네임스페이스 라벨입니다("orders", "coupons", "invites"). 같은 키에 다른 도메인이면 서로 무관한 순열이 나옵니다 — 키 하나로 독립적인 코드 스트림을 무제한으로 만들 수 있고 네임스페이스마다 키를 따로 두는 것보다 운영이 훨씬 쌉니다. 도메인은 "dealcode/v1/" + domain 형태로 FF1 tweak에 바인딩되므로 포맷 v1은 미래의 v2와도, 같은 키의 다른 FF1 용도와도 분리됩니다.

제약: 유효한 유니코드(U+0000 금지, 짝 없는 서로게이트 금지), UTF-8 바이트 길이 ≤ 255.

키는 여러 모양으로 존재하고 모두 하나의 결정적 규칙으로 받아들여집니다 — 모든 언어가 공유하는 규칙입니다:

  • 길이가 정확히 16/24/32인 바이트 → 그대로 AES 키로 사용.
  • 그 외 비어 있지 않은 바이트와 모든 문자열 → AES-256 키로 확장: SHA-256("dealcode/v1/kdf" ‖ 재료).
  • 빈 키 재료 → ConfigError.

hex처럼 보이는 문자열은 hex 디코딩되지 않습니다

문자열은 항상 UTF-8 바이트로 취급되어 파생됩니다 — hex처럼 보여도 마찬가지입니다. openssl rand -hex 32의 출력을 문자열 그대로 넘기면 모든 언어가 같은 AES-256 키를 파생합니다. 하지만 한 서비스에서는 직접 hex 디코딩하고 다른 서비스에서는 문자열로 넘기면 서로 다른 두 순열이 됩니다. 한 형태를 정해 모든 곳에서 쓰세요. (추측 금지 규칙은 의도적입니다: 자동 감지는 "deadbeef..."를 모호하게 만듭니다.)

파생은 도메인 분리이지 패스워드 스트레칭이 아닙니다: 패스프레이즈 키의 강도는 정확히 그 패스프레이즈만큼입니다. 128비트 이상의 랜덤 재료(openssl rand -hex 32)를 쓰세요. 문자열 키 재료는 유효한 유니코드여야 합니다 — U+0000과 짝 없는 서로게이트는 조용히 재인코딩되지 않고 거절됩니다. 모든 언어가 같은 키를 파생하거나, 아무 언어도 파생하지 않거나 둘 중 하나입니다.

길이 스테이징

코드는 min_length에서 시작해 현재 길이가 소진됐을 때만 한 글자씩 자랍니다. 기수 r에서 첫 스테이지는 카운터 [0, r^min_length)를, 스테이지 d[r^(d−1), r^d)를 담당합니다:

  • hexmin_length=6: 6자리 코드 16,777,216개를 다 쓴 뒤에야 — 그때만 — 7자리로 넘어갑니다.
  • 길이가 다른 코드끼리는 자명하게 충돌 불가, 같은 길이 안에서는 FF1이 순열 — 따라서 전체 매핑이 전단사입니다.

min_length == max_length면 고정 길이 코드입니다. 기본 max_length는 부호 있는 64비트 카운터로 전체 코드 공간에 도달할 수 있는 가장 큰 길이입니다(hex → 15, dec → 18, base32/crockford/base36 → 12, base58/base62/base64url → 10). 길거나 고정된 모양(16자리 hex, 12자리 base62)을 위해 r^max_length ≤ 2^128까지 올릴 수 있습니다. 카운터는 여전히 2^63으로 제한되고 남는 코드 공간은 decode가 거절합니다.

작은 첫 스테이지(r^min_length ≥ 100까지)도 지원되고 상호 운용되지만 아주 작은 코드 공간은 손쉽게 열거됩니다 — 보안 모델을 보세요.

고정 길이 순환 모드

코드가 절대 길어지면 안 될 때 — 항공권 예약코드(PNR)처럼 항상 정확히 L자 — 순환 모드(SPEC §11)는 고정 길이를 유지한 채, 공간이 소진되면 한 글자를 늘리는 대신 다른 순열(사이클 1, 사이클 2, …)로 같은 공간을 다시 채웁니다. 카운터 n은 사이클 n ÷ rᴸ에 속하고 각 사이클은 가능한 모든 문자열을 키·사이클에 따라 달라지는 새로운 순서로 정확히 한 번씩 발급합니다.

사이클을 넘으면 코드가 반복됩니다 — 설계상 그렇습니다

재사용이 이 모드의 존재 이유이므로, 사이클을 가로지르는 전역 UNIQUE(code) 계약은 더 이상 성립하지 않습니다. 유일성 범위마다 한 사이클의 코드만 살아 있게 유지하고(넘어가기 전에 만료·회수), UNIQUE(cycle, code)로 인덱싱하고, 살아 있는 코드의 사이클을 저장하세요 — 같은 문자열이 사이클마다 다른 카운터로 풀리기 때문에 decode(code, cycle)에 사이클이 필요합니다.

제약: 2 ≤ L ≤ 128, radix^L ≥ 100, radix^L ≤ 2^63 (사이클이 카운터 공간 안에서 완주 가능해야 합니다 — 더 큰 고정 모양은 일반 모드의 min_length == max_length를 쓰세요).

설정은 출시되는 순간 동결

한 번 쓰면 끝인 설정

한 네임스페이스(하나의 카운터 시퀀스)에서 설정 전체 — 키, 알파벳, min_length, max_length, domain — 는 첫 코드가 나가는 순간 동결됩니다. 다른 설정은 다른 순열이고 한 카운터 공간 위의 두 순열은 이미 발급된 코드와 충돌할 수 있습니다.

새로운 체계가 필요하면? 새 도메인(또는 새 키 + 새 네임스페이스). 키 로테이션도 새 네임스페이스를 뜻합니다 — 기존 코드는 기존 설정에서만 복호화됩니다.

따름정리: 두 시퀀스를 같은 코덱+도메인에 넣지 말 것, 시퀀스를 뒤로 리셋하지 말 것, 그리고 데이터베이스 연동에서 설명한 UNIQUE 경보 인덱스를 유지할 것.