1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""
Generator for the default Unicode wordlist used by
`origin_crypto_sdk::recovery::unicode_cipher` (Phase 0).
Source of truth for the 2048 codepoints. Run manually when the
wordlist composition changes:
cd origin-crypto-sdk
python3 scripts/generate_wordlist.py > src/recovery/default_wordlist.txt
The output is a flat string of exactly 2048 codepoints (one per char),
no separators, no trailing newline. Default `include_str!`'d into
`src/recovery/unicode_cipher.rs` and parsed at process startup via
`LazyLock`. The 2048-char total is verified at test time.
Block selection policy (matches the encoding-decision rationale we locked):
* Pure-symbols, NO emoji. Zero ZWJ / no skin-tone-modifier / no
variation-selector risk.
* No Latin / Cyrillic / Greek / Digits — visibility-disjoint by
block design, no homoglyph attack surface.
* Floor on format coverage: every codepoint is in the BMP or in a
universally-supported range on Linux/macOS/Windows/iOS/Android
terminals with the default Unicode font.
* Visually disjoint by Unicode committee design — geometric shapes
differ in stroke, math symbols differ in glyph structure, arrows
encode direction.
"""
# Allowed Unicode ranges. Each tuple is (start_inclusive, end_inclusive).
# Order matters: codepoints are sliced in this order; the script
# truncates at exactly 2048 entries.
: =
# Total pre-filter budget over ~2360 codepoints; the script truncates to
# exactly the first WORDLIST_LEN (=2048).
# Hard cap — must mirror `pub const WORDLIST_LEN` in
# `src/recovery/unicode_cipher.rs`.
: = 2048
"""Return True iff `cp` should be admitted to the wordlist.
Rejects control, format, combining-mark, BIDI-mark, default-ignorable,
Variation-Selector, AND NFKC-multi-codepoint codepoints. See the
policy in the module doc. The runtime check in Rust mirrors this;
funnelling both checks through this function keeps the generator
and the runtime consistent.
"""
=
=
# control / format / surrogate / unassigned
return False
# any combining mark
return False
# ZWSP / ZWNJ / ZWJ / ZWNBSP
return False
# Variation Selectors
return False
# LRM / RLM (BIDI)
return False
# BIDI embedding controls
return False
# Word joiner / invisibles
return False
# NFKC multi-codepoint decompositions are silent-malleability vectors
# at decode time (a user could type the same visual symbol in two
# different byte sequences). Reject them at curation so the runtime
# check stays compatible with the generated wordlist.
=
return False
return True
: =
continue
break
break
return 1
# Sanity: no duplicates (BLOCKS don't overlap, but be defensive).
return 1
# Emit a flat UTF-8 string — no separators, no trailing newline
# so `include_str!` reads exactly 2048 chars.
return 0