Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,10 @@ random60: 0x03699c13c10128b5
**Global lookup by public ID**
GSI: `GSI1PK = PUBLICID#{orderlyid}`, `GSI1SK = CONST`

**Postgres / MySQL**
- Store both `id_text VARCHAR(64) UNIQUE` and `id_bin BINARY(20)`
**SQL storage**
- Store the text form as `id_text VARCHAR(69) UNIQUE`
- Use `VARCHAR(64)` only if checksum suffixes are never stored
- Store the 20-byte binary body as `id_bin BINARY(20)` on MySQL/Aurora or `id_bin BYTEA` on PostgreSQL
- Index `id_bin` for range scans
- Use `id_text` for APIs/logs

Expand Down
6 changes: 5 additions & 1 deletion orderlyid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ func TestChecksumRoundTrip(t *testing.T) {
t.Fatalf("parse with checksum failed: %v", err)
}
// Tamper last char
bad := id[:len(id)-1] + "0"
repl := byte('0')
if id[len(id)-1] == repl {
repl = '1'
}
bad := id[:len(id)-1] + string(repl)
if _, err := Parse(bad); err == nil {
t.Fatalf("expected checksum mismatch")
} else if !errors.Is(err, ErrInvalidChecksum) {
Expand Down
27 changes: 13 additions & 14 deletions spec/0001-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,22 @@ OrderlyID provides typed, time-sortable, globally unique identifiers optimized f

## 6. Checksum

1. `now_ms = system_clock_ms()`. If privacy enabled, quantize.
2. If another ID in the same ms: increment 12-bit seq; wrap allowed.
3. Draw 60 bits from CSPRNG.
4. Pack fields into 160-bit body, encode to 32 Base32 chars.
5. Emit `"<prefix>_<payload>"`; append checksum if configured.

*Ordering semantics*: within same prefix, lexicographic sort ≈ `(time, flags, tenant, seq, shard, random)`.
- Domain: HRP-expanded `"<prefix>_"` plus payload symbols.
- Algorithm: Bech32 polymod, truncated to 20 bits and encoded as 4 Crockford Base32 characters.
- Verification: if present, parsers MUST validate the checksum and reject mismatches.
- False-accept probability: approximately 1 in 1,048,576.
- The checksum is an integrity check for transcription errors, not cryptographic authentication.

## 7. Generation

1. `now_ms = system_clock_ms()`. If privacy enabled, quantize.
2. If another ID in the same ms: increment 12-bit seq; wrap allowed.
3. Draw 60 bits from CSPRNG.
4. Pack fields into 160-bit body, encode to 32 Base32 chars.
5. Emit `"<prefix>_<payload>"`; append checksum if configured.
1. `now_ms = system_clock_ms()`. If privacy bucketing is enabled, quantize to the configured bucket size.
2. If another ID is generated in the same millisecond, increment the 12-bit `seq`; wrap is allowed.
3. Draw 60 bits from a CSPRNG.
4. Pack fields into the 160-bit body in big-endian order.
5. Encode the body into exactly 32 Crockford Base32 characters.
6. Emit `"<prefix>_<payload>"`; append `"-<checksum>"` if checksum is configured.

*Ordering semantics*: within same prefix, lexicographic sort ≈ `(time, flags, tenant, seq, shard, random)`.
*Ordering semantics*: within the same prefix, lexicographic sort ≈ `(time, flags, tenant, seq, shard, random)`.

---

Expand All @@ -96,7 +95,7 @@ OrderlyID provides typed, time-sortable, globally unique identifiers optimized f

## 10. Interop & Tests

- **SQL**: store `id_text VARCHAR(64)` and `id_bin BINARY(20)`; index `id_bin` for range scans.
- **SQL**: store the text form as `id_text VARCHAR(69) UNIQUE`; use `VARCHAR(64)` only if checksum suffixes are never stored. Store the 20-byte binary body as `id_bin BINARY(20)` on MySQL/Aurora or `id_bin BYTEA` on PostgreSQL; index `id_bin` for range scans.
- **DynamoDB**: use as sort key; shard global feeds via virtual partitions.
- **APIs/logs**: emit typed ID with checksum externally; redact tail if sensitive.
- **Conformance**: implementations claiming v1 MUST:
Expand Down
2 changes: 1 addition & 1 deletion spec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ checksum: 9xgg

## Interop & storage guidance

- **SQL**: store both `id_text VARCHAR(64) UNIQUE` and `id_bin BINARY(20)`; index `id_bin` for range scans.
- **SQL**: store the text form as `id_text VARCHAR(69) UNIQUE`; use `VARCHAR(64)` only if checksum suffixes are never stored. Store the 20-byte binary body as `id_bin BINARY(20)` on MySQL/Aurora or `id_bin BYTEA` on PostgreSQL; index `id_bin` for range scans.
- **DynamoDB**: use ID as sort key; to avoid hot partitions, hash into virtual shards.
- **APIs & logs**: prefer the typed public ID; redact tail chars if sensitive.

Expand Down
Loading