├── .gitignore ├── DiceTables.tbl ├── README.md ├── SSS32.ps ├── docs └── wallets.md ├── images └── volvelles.jpg ├── mathematical-companion ├── images │ ├── addition-wheel.jpg │ ├── fusion-fold.jpg │ ├── fusion-wheel.jpg │ ├── recovery-wheel.jpg │ └── translation-wheel.jpg └── main.tex ├── polymod.md └── reference └── rust-codex32 ├── Cargo.toml ├── LICENSE └── src ├── checksum.rs ├── gf32.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | reference/rust-codex32/Cargo.lock 2 | reference/rust-codex32/target/ 3 | 4 | mathematical-companion/*.aux 5 | mathematical-companion/*.log 6 | mathematical-companion/*.out 7 | mathematical-companion/*.pdf 8 | mathematical-companion/*.toc 9 | -------------------------------------------------------------------------------- /DiceTables.tbl: -------------------------------------------------------------------------------- 1 | Dice Tables 2 | 3 | 1d4;1d8 4 | .TS 5 | box; 6 | cFCW || cFCW | cFCW | cFCW | cFCW | cFCW | cFCW | cFCW | cFCW. 7 | 1 2 3 4 5 6 7 8 8 | = 9 | 1 Q P Z R Y 9 X 8 10 | _ 11 | 2 G F 2 T V D W 0 12 | _ 13 | 3 S 3 J N 5 4 K H 14 | _ 15 | 4 C E 6 M U A 7 L 16 | .TE 17 | 18 | 2d6 19 | .TS 20 | box; 21 | cFCW || cFCW | cFCW | cFCW | cFCW | cFCW | cFCW. 22 | 1 2 3 4 5 6 23 | = 24 | 1 Q P Z R 25 | _ 26 | 2 Y 9 X 8 G F 27 | _ 28 | 3 2 T V D W 0 29 | _ 30 | 4 S 3 J N 5 4 31 | _ 32 | 5 K H C E 6 M 33 | _ 34 | 6 U A 7 L 35 | .TE 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codex32 2 | 3 | **codex32** is a scheme for checksumming and Shamir Secret Sharing based on paper computers (volvelles). 4 | It is currently under construction and far from production-ready, but is usable by motivated experimentors. 5 | This scheme is tedious and not for the faint-of-heart, but does not require any mathematical understanding; only perseverance, focus, and an ability to follow precise instructions. 6 | 7 | We welcome feedback from everybody, either through issues on this Github repo or email to `pearlwort@wpsoftware.net`. 8 | 9 | ![Image of two Volvelles used for secret recovery](./images/volvelles.jpg) 10 | 11 | ## What is this repo? 12 | 13 | Aside from documentation, this repository contains a single file, `SSS32.ps`, which contains the entire source code of the project. 14 | It is hand-written Postscript, which means that it can be opened by a document viewer but also in a text editor, which will reveal the code used to generate the wheels and worksheets. 15 | (If you are unable to open the file with a popular document viewer, please let us know!) 16 | 17 | To produce a PDF file, on a Linux machine the most straightforward way is to run the command 18 | ``` 19 | ps2pdf -dPDFSETTINGS=/prepress SSS32.ps 20 | 21 | ``` 22 | 23 | If you are a software developer or mathematician who would like to contribute, feel free to open a pull request, join us on IRC (Libera) `#volvelle-wizards`, or contact Pearlwort by email. 24 | 25 | ## What is this project? 26 | 27 | This project is a scheme to generate, encode, checksum, split and recover Bitcoin secret keys, using pencil, paper and lookup tables (alternately, volvelles). 28 | It works with 128- or 256-bit secrets, encoded in the [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) alphabet. 29 | **No wallets currently support such secrets. Do not use this scheme with real money.** 30 | 31 | The project began in early 2020, as an extension of a "[2018 blog point on computing Bech32 checksums with pen and paper](http://r6.ca/blog/20180106T164028Z.html)". 32 | It uses a stronger error-correcting code than bech32 and introduces volvelles as a faster and less error-prone alternate to lookup tables. 33 | Initially it was a hobby project by the first author, Leon Olsson Curr, who was later joined by Pearlwort Snead. 34 | Pearlwort is the primary advocate for mainstream usage and any real-life problems or complaints should be directed to him. 35 | 36 | codex32, in addition to generating and verifying checksums, also includes an implementation of Shamir's Secret Sharing Scheme (SSSS). 37 | This scheme gives users the ability to split their checksummed secrets into many pieces, such that the original secret can be recovered by threshold-many pieces. 38 | The threshold is set by the user and is typically 2 or 3. 39 | 40 | ## Where are the artistic wheels? 41 | 42 | This repository is actively being developed and does not cointain the latest experimental work. In particular, 43 | 44 | * The color illustrations are available in [Pearlwort's "complete" branch](https://github.com/apoelstra/SSS32/tree/complete) 45 | * The mathematical companion can be found in [a separate repo](https://github.com/apoelstra/volvelle-math-companion) 46 | * The official website is at https://secretcodex32.com 47 | 48 | # For Wallet Developers 49 | 50 | See our [Wallet Developer Guide](./docs/wallets.md) 51 | 52 | -------------------------------------------------------------------------------- /SSS32.ps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 2 | %%Orientation: Portrait 3 | %%Pages: 21 4 | %%EndComments 5 | %%BeginSetup 6 | [(Shamir's Secret) (Sharing Codex)] 7 | (revision alpha-4.6) 8 | [ 9 | (MIT License) 10 | () 11 | (Copyright (c) 2020 Blockstream) 12 | () 13 | (Permission is hereby granted, free of charge, to any person obtaining a copy) 14 | (of this software and associated documentation files (the "Software"), to deal) 15 | (in the Software without restriction, including without limitation the rights) 16 | (to use, copy, modify, merge, publish, distribute, sublicense, and/or sell) 17 | (copies of the Software, and to permit persons to whom the Software is) 18 | (furnished to do so, subject to the following conditions:) 19 | () 20 | (The above copyright notice and this permission notice shall be included in all) 21 | (copies or substantial portions of the Software.) 22 | () 23 | (THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR) 24 | (IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,) 25 | (FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE) 26 | (AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER) 27 | (LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,) 28 | (OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE) 29 | (SOFTWARE.) 30 | ] 31 | [ 32 | (WARNING: Seriously, this is a work in progress, and it is only a concept right now.) 33 | (If you try to use this for your valuable data, I promise you will lose your data.) 34 | (You will lose this document and come back here only to find that I have made incompatible changes,) 35 | (and your data is lost forever. Even if you don't lose this document, there is no warranty or) 36 | (guarantee of any kind that you will be able to recover successfully recover your data.) 37 | ] 38 | %************************************************************************ 39 | %************************************************************************ 40 | %* 41 | %* Section One: Preamble 42 | %* 43 | %************************************************************************ 44 | %************************************************************************ 45 | 46 | %****************** 47 | %* Front matter 48 | %* 49 | %* Define variables for the preceeding front matter text. 50 | %* 51 | /warning exch def 52 | /MIT exch def 53 | /ver exch def 54 | /title exch def 55 | 56 | /README [ 57 | (MATERIALS: Scissors, X-Acto knife, brass fasteners) 58 | () 59 | (CONSTRUCTION:) 60 | (The bottom discs are the discs with a small circle in the center of them.) 61 | (The top discs are the discs with a small cross in the centre. All the top discs are identical. Cut out each disc.) 62 | (Cut out the small centre circle in each the bottom discs. Cut out each of the 32 squares of each top disc.) 63 | (Cut a slit along one one of the small lines of the cross in each of the top discs.) 64 | (Pass the brass fastener through a top disc and bottom disc.) 65 | (Fold the legs of the fastener apart to secure the top and bottom discs together.) 66 | (The two discs should now be able to rotate about their common centre.) 67 | () 68 | (SECRET RECOVERY:) 69 | (Find 2 of your secured shares.) 70 | (Make sure all your shares have the same identifier and a valid checksum (see checksum worksheet).) 71 | (Dial the recover share disc to the share index of the first secured share (the 5th character).) 72 | (Look up the symbol associated with the share index of the other share.) 73 | (Set your translation disc to that symbol and translate the first secured share by looking up every charater in turn.) 74 | (Dial the recover share disc to the share index of the second secured share (the 5th character).) 75 | (Look up the symbol associated with the share index of the first share.) 76 | (Set your translation disc to that symbol and translate the second secured share by looking up every charater in turn.) 77 | (Add the two translated shares, character by character, using the Addition disc.) 78 | (The result is your secret share and will have share index S and a valid checksum.) 79 | ] def 80 | 81 | %****************** 82 | %* Helper Functions and Utilities 83 | %* 84 | 85 | % determinant : matrix -- det(matrix) 86 | /determinant { 87 | 1 0 2 index dtransform 88 | 0 1 5 4 roll dtransform 89 | 3 1 roll mul 90 | 3 1 roll mul 91 | exch sub 92 | } bind def 93 | 94 | % tan : angle -- tan(angle) 95 | /tan { 96 | dup sin exch cos div 97 | } bind def 98 | 99 | % arcsin: y h -- arcsin(y/h) 100 | /arcsin { 101 | dup mul 1 index dup mul sub sqrt 102 | atan 103 | } bind def 104 | 105 | % arccos: x h -- arccos(x/h) 106 | /arccos { 107 | dup mul 1 index dup mul sub sqrt 108 | exch atan 109 | } bind def 110 | 111 | % Given a rod of length 2r, what angle does it fit inside a w by h sized box so that 112 | % the ends of the rod are equaldistant from the two sides making a corner with the box. 113 | /angleinbox { 114 | 10 dict begin 115 | { /r /h /w } {exch def} forall 116 | h w sub 117 | 2 2 sqrt r mul mul 118 | arcsin 45 sub 119 | end 120 | } bind def 121 | 122 | % Constructs a coordinate transformation used for the illustration of folding volvelles. 123 | /foldprojection { 124 | 10 dict begin 125 | /foldangle exch def 126 | /squish 0.25 def 127 | /squash 1 squish dup mul sub sqrt def % sqrt (1 - squish^2) 128 | /rollangle squish neg 1 atan def 129 | [rollangle cos squish mul 130 | rollangle sin 131 | dup neg squish mul foldangle cos mul foldangle sin squash mul add 132 | rollangle cos foldangle cos mul 133 | 0 0 ] 134 | end 135 | 136 | } bind def 137 | /concatstrings % (a) (b) -> (ab) 138 | { exch dup length 139 | 2 index length add string 140 | dup dup 4 2 roll copy length 141 | 4 -1 roll putinterval 142 | } bind def 143 | 144 | %****************** 145 | %* Field Arthmetic 146 | %* 147 | %* Calculations within GF(32), extended with the "null element", represented by 148 | %* numberic 32, which is displayed as a blank, and on which every operation 149 | %* returns null again. Used to represent incomplete/unknown data. 150 | %* 151 | %* Our generator for GF(32) has minimum polynomial x^5 + x^3 + 1. 152 | %* 153 | /gf32add % x y -> x [+] y where [+] is addition in GF32. 154 | % returns 32 if x or y is out of range. 155 | % Note that x [+] y = x [-] y in GF32. 156 | { % x y 157 | 2 copy 32 ge % x y x (y >= 32) 158 | exch 32 ge or % x y (y >= 32 || x >= 32) 159 | {pop pop 32} % 32 160 | {xor} % x [+] y 161 | ifelse % if (y >= 32 || x >= 32) then 32 else (x [+] y) 162 | } bind def 163 | 164 | /gf32mulalpha % x -> x [*] alpha where [*] is multiplicaiton in GF32 and alpha is represted by 0b00010. 165 | { % x 166 | 2 mul % 2*x 167 | dup 32 ge % 2*x (2*x >= 0b100000) 168 | { 41 xor } % 2*x `xor` 0b101001 169 | if % if (2*x >= 0xb100000) then 2*x `xor` 0x0b101001 else 2*x 170 | } bind def 171 | 172 | /gf32mul % x y -> x [*] y where [*] is multiplication in GF32. 173 | % returns 32 if x or y is out of range. 174 | { % x y 175 | 10 dict begin 176 | { /y /x } {exch def} forall 177 | x 32 ge y 32 ge or % (y >= 32 || x >= 32) 178 | {32} % 32 179 | { 180 | /xShift x def 181 | /yAlpha y def 182 | 0 % 0 183 | 5 { % ((x & 0b001..1) [*] y) (x >> i) (y [*] alpha[^i]) 184 | xShift 1 and yAlpha mul xor % ((x & 0b001..1) [*] y [+] ((x >> i) & 1) * (y [*] alpha [^i])) 185 | /xShift xShift -1 bitshift def 186 | /yAlpha yAlpha gf32mulalpha def 187 | } repeat % ((x & 0b11111) [*] y) 188 | } ifelse % if (y >= 32 || x >= 32) then 32 else (x [*] y) 189 | end 190 | } bind def 191 | 192 | /gf32inv % x -> x [^-1] where [^-1] is the inverse operation in GF32. 193 | % returns 0 when given 0. 194 | % returns 32 if x is out of range. 195 | { % x 196 | dup dup gf32mul % x x[^2] 197 | dup gf32mul gf32mul % x[^5] 198 | dup dup gf32mul gf32mul % x[^15] 199 | dup gf32mul % x[^30] 200 | % x[^-1] 201 | } bind def 202 | 203 | /lagrange % x xj [x[0] .. x[k]] -> l[j](x) 204 | % returns the lagrange basis polynomial l[j] evaluated at x for interpolation of coordinates [x[0] .. x[k]]. 205 | % Requires xj `elem` [x[0] ... x[k]] 206 | { % x xj [x[0] .. x[k]] 207 | 10 dict begin 208 | { /xs /xj /x } {exch def} forall 209 | 1 xs % 1 [x[0] .. x[k]] 210 | { % let P = product [(x [-] x[m]) [/] (xj [-] x[m]) | m <- [0..i-1], x[m] /= xj] 211 | % P x[i] 212 | /xi exch def % P 213 | xi xj gf32add % P (xj [-] x[i]) 214 | dup 0 eq % P (xj [-] x[i]) (xj [-] x[i] == 0) 215 | { pop } % P 216 | { gf32inv gf32mul % (P [/] (xj [-] x[i]) 217 | xi x gf32add gf32mul % (P [*] (x [-] x[i]) [/] (xj [-] x[i])) 218 | } 219 | ifelse % (if xj == x[i] then P else (P [*] (x [-] x[i]) [/] (xj [-] x[i])) 220 | } forall % x xj (product [(x [-] x[m]) [/] (xj [-] x[m]) | m <- [0..k], x[m] /= xj]) 221 | end 222 | } bind def 223 | 224 | /makeShare % sS sA i -> si 225 | { 3 2 roll 1 index permS 0 get permS 0 2 getinterval lagrange gf32mul 226 | 3 1 roll permS 1 get permS 0 2 getinterval lagrange gf32mul 227 | xor 228 | } bind def 229 | 230 | /gf32mularray % x b -> x * b 231 | { [ 3 1 roll { 1 index gf32mul exch } forall pop ] 232 | } bind def 233 | 234 | /gf32addarray % a b -> a + b pointwise 235 | { [ 3 1 roll 0 1 2 index length 1 sub { 2 index 1 index get 2 index 2 index get gf32add exch pop 3 1 roll } for pop pop ] 236 | } bind def 237 | 238 | %****************** 239 | %* Code Parameters 240 | %* 241 | %* Data related to the representation of GF(32) elements 242 | %* 243 | 244 | /perm [29 24 13 25 9 8 23 18 22 31 27 19 1 0 3 16 11 28 12 14 6 4 2 15 10 17 21 20 26 30 7 5 ] def 245 | /permS [16 29 24 13 25 9 8 23 18 22 31 27 19 1 0 3 11 28 12 14 6 4 2 15 10 17 21 20 26 30 7 5 ] def 246 | /permV [22 11 10 29 31 28 17 24 27 12 21 13 19 14 20 25 1 6 26 9 0 4 30 8 3 2 7 23 16 15 5 18 ] def 247 | /permId [ 0 1 31 {} for ] def 248 | 249 | /code [/Q /P /Z /R /Y /nine /X /eight /G /F /two /T /V /D /W /zero /S /three /J /N /five /four /K /H /C /E /six /M /U /A /seven /L /space] def 250 | /code2 [/multiply /aleph /alpha /beta /Gamma /Delta /epsilon /eta /Theta /Lambda /mu /Xi /Pi /rho /Sigma /Phi /Psi /Omega /at /numbersign /percent /cent /yen /Euro /currency /circleplus /dagger /daggerdbl /section /paragraph /diamond /heart /space ] def 251 | 252 | /decode { 253 | [ exch { << 254 | 113 0 255 | 81 0 256 | 112 1 257 | 80 1 258 | 122 2 259 | 90 2 260 | 114 3 261 | 82 3 262 | 121 4 263 | 89 4 264 | 57 5 265 | 120 6 266 | 88 6 267 | 56 7 268 | 103 8 269 | 71 8 270 | 102 9 271 | 70 9 272 | 50 10 273 | 116 11 274 | 84 11 275 | 118 12 276 | 86 12 277 | 100 13 278 | 68 13 279 | 119 14 280 | 87 14 281 | 48 15 282 | 115 16 283 | 83 16 284 | 51 17 285 | 106 18 286 | 74 18 287 | 110 19 288 | 78 19 289 | 53 20 290 | 52 21 291 | 107 22 292 | 75 22 293 | 104 23 294 | 72 23 295 | 99 24 296 | 67 24 297 | 101 25 298 | 69 25 299 | 54 26 300 | 109 27 301 | 77 27 302 | 117 28 303 | 85 28 304 | 97 29 305 | 65 29 306 | 55 30 307 | 108 31 308 | 76 31 309 | 32 32 310 | >> exch get } forall ] 311 | } bind def 312 | 313 | %****************** 314 | %* BCH 315 | %* 316 | %* Data and functions related to the error-correcting code. 317 | %* 318 | /polymodulus [25 27 17 8 0 25 25 25 31 27 24 16 16] def % coefficents from c12 to c0 319 | /checksum [16 25 24 3 25 11 16 23 29 3 25 17 10] def 320 | /checksumstring { polymodulus length array checksum 0 1 polymodulus length 1 sub {3 copy exch 1 index get code exch 1 getinterval putinterval pop } for pop } bind def 321 | 322 | /polymod0 % array -> [ c5 c4 c3 c2 c1 c0 ] 323 | { [ polymodulus length {0} repeat ] 324 | exch 325 | { [ exch 2 index 1 polymodulus length 1 sub getinterval aload pop polymodulus length dup 1 sub roll ] exch 0 get polymodulus gf32mularray gf32addarray } forall 326 | } bind def 327 | 328 | /polymodshift2 % c7 c6 -> [ c5 c4 c3 c2 c1 c0 ] 329 | { [ 3 1 roll polymodulus length {0} repeat ] polymod0 330 | } bind def 331 | 332 | /polymodhrp % string -> [ c5 c4 c3 c2 c1 c0 ] 333 | { 334 | [ exch 1 exch dup { dup dup 65 ge exch 90 le exch and { 32 add } if 32 idiv exch } forall 0 exch { 31 and } forall ] polymod0 335 | } bind def 336 | 337 | %************************************************************************ 338 | %************************************************************************ 339 | %* 340 | %* Section Two: Graphics 341 | %* 342 | %************************************************************************ 343 | %************************************************************************ 344 | 345 | %****************** 346 | %* Helper Functions and Utilities 347 | %* 348 | 349 | /pgsize currentpagedevice /PageSize known 350 | { currentpagedevice /PageSize get 351 | } { 352 | [611.842163 791.842163] % letter size 353 | } ifelse 354 | def 355 | 356 | 20 dict dup /portraitPage exch def begin 357 | pgsize aload pop [ /pageH /pageW ] { exch def } forall 358 | /centerX pageW 2 div def 359 | /centerY pageH 2 div def 360 | /marginX1 36 def 361 | /marginX2 pageW 36 sub def 362 | /marginY1 pageH 48 sub def 363 | /marginY2 48 def 364 | /marginW marginX2 marginX1 sub def 365 | /marginH marginY2 marginY1 sub def 366 | 367 | % Draw a line indicating where the margins of the page are; can be used 368 | % for debugging graphical output 369 | /drawMargin { 370 | gsave 371 | 0 setgray thin line 372 | marginX1 marginY1 marginW marginH rectstroke 373 | grestore 374 | } bind def 375 | 376 | % Draw the page number and any (TODO) content in the page content array 377 | % Takes the pagenum as a numeric value 378 | /drawPageContent { 379 | 10 dict begin 380 | /pagenum exch def 381 | gsave 382 | /Times-Roman findfont 12 scalefont setfont 383 | centerX marginY2 moveto 384 | pagenum pagenum 10 lt { 1 } { 2 } ifelse string cvs show 385 | grestore 386 | end 387 | } bind def 388 | end 389 | 390 | % landscapePage is a modified copy of portraitPage 391 | portraitPage dup 20 dict copy dup /landscapePage exch def begin 392 | pgsize aload pop exch [ /pageH /pageW ] { exch def } forall 393 | /centerX pageW 2 div def 394 | /centerY pageH 2 div def 395 | /marginX1 36 def 396 | /marginX2 pageW 36 sub def 397 | /marginY1 pageH 48 sub def 398 | /marginY2 48 def 399 | /marginW marginX2 marginX1 sub def 400 | /marginH marginY2 marginY1 sub def 401 | /pageW portraitPage /pageH get def 402 | /pageH portraitPage /pageW get def 403 | 404 | /drawPageContent { 405 | 90 rotate 406 | 0 pageH neg translate 407 | portraitPage /drawPageContent get exec 408 | } bind def 409 | end 410 | 411 | % line : width -- 412 | /line { 413 | setlinewidth 414 | 1 setlinecap 415 | 1 setlinejoin 416 | [] 0 setdash 417 | } bind def 418 | 419 | /verythin 0.2 def 420 | /thin 0.4 def 421 | /thick 0.8 def 422 | /verythick 1.2 def 423 | 424 | /pen { 425 | 50 setlinewidth 426 | 1 setlinecap 427 | 1 setlinejoin 428 | [] 0 setdash 429 | } bind def 430 | 431 | % Runs stroke under a uniformly scaled matrix. 432 | % ps2pdf doesn't seem to handle strokes under a non-uniformly scaled matrix properly. 433 | /resetstroke { 434 | matrix currentmatrix 435 | dup determinant abs 436 | initmatrix 437 | matrix currentmatrix determinant abs div 438 | sqrt dup scale 439 | stroke 440 | setmatrix 441 | } bind def 442 | 443 | /brass { 0.7098 0.651 0.2588 } def 444 | /pink { 1 0.9 0.9 } def 445 | 446 | /substitute << 447 | /Omega /uni03A9 448 | /circleplus /uni2295 449 | >> def 450 | 451 | % codexshow : /glyph size -- 452 | /codexshow { 453 | 10 dict begin 454 | /sz exch def 455 | /charname exch def 456 | /basefont /Courier findfont def 457 | /basechars basefont /CharStrings get def 458 | /backupfont /Symbol findfont def 459 | substitute charname known basechars charname known not and 460 | { basechars substitute charname get known { /charname substitute charname get def } if } if 461 | basechars charname known 462 | { basefont sz scalefont setfont charname glyphshow } 463 | { backupfont sz scalefont setfont charname glyphshow } ifelse 464 | end 465 | } bind def 466 | 467 | /withcrosses true def 468 | % draftingshow : /glyph size -- 469 | /draftingshow { 470 | gsave 471 | 10 dict begin 472 | currentpoint translate 473 | 1000 div dup scale 474 | [1 0 10 tan 1 -100 -100] concat 475 | << 476 | /space { } 477 | /A { newpath 478 | 100 100 moveto 479 | 400 700 lineto 480 | 700 100 lineto 481 | 200 300 moveto 482 | 600 300 lineto 483 | pen stroke } 484 | /C { newpath 485 | 400 400 300 2 3 arccos -2 3 arccos 180 add arc 486 | pen stroke } 487 | /D { newpath 488 | 100 100 moveto 489 | 300 400 300 270 90 arc 490 | 100 700 lineto 491 | closepath 492 | pen stroke } 493 | /E { newpath 494 | 600 700 moveto 495 | 100 700 lineto 496 | 100 100 lineto 497 | 600 100 lineto 498 | 100 400 moveto 499 | 400 400 lineto 500 | pen stroke } 501 | /F { newpath 502 | 600 700 moveto 503 | 100 700 lineto 504 | 100 100 lineto 505 | 100 400 moveto 506 | 400 400 lineto 507 | pen stroke } 508 | /G { newpath 509 | 400 400 300 2 3 arccos -2 3 arccos 180 add arc 510 | 600 400 lineto 511 | 400 400 lineto 512 | pen stroke } 513 | /H { newpath 514 | 600 700 moveto 515 | 600 100 lineto 516 | 100 700 moveto 517 | 100 100 lineto 518 | 100 400 moveto 519 | 600 400 lineto 520 | pen stroke } 521 | /J { newpath 522 | 600 700 moveto 523 | matrix currentmatrix 524 | 100 100 translate 525 | 1 4 5 div scale 526 | 250 250 250 0 180 arcn 527 | setmatrix 528 | pen stroke } 529 | /L { newpath 530 | 100 700 moveto 531 | 100 100 lineto 532 | 600 100 lineto 533 | pen stroke } 534 | /K { newpath 535 | 600 700 moveto 536 | 100 300 lineto 537 | 100 700 moveto 538 | 100 100 lineto 539 | 300 460 moveto 540 | 600 100 lineto 541 | pen stroke } 542 | /M { newpath 543 | 100 100 moveto 544 | 100 700 lineto 545 | 400 100 lineto 546 | 700 700 lineto 547 | 700 100 lineto 548 | pen stroke } 549 | /N { newpath 550 | 600 700 moveto 551 | 600 100 lineto 552 | 100 700 lineto 553 | 100 100 lineto 554 | pen stroke } 555 | /P { newpath 556 | 100 400 moveto 557 | 450 550 150 270 90 arc 558 | 100 700 lineto 559 | 100 100 lineto 560 | pen stroke } 561 | /Q { newpath 562 | 400 400 300 0 360 arc 563 | 500 250 moveto 564 | 600 100 lineto 565 | pen stroke } 566 | /R { newpath 567 | 100 400 moveto 568 | 450 550 150 270 90 arc 569 | 100 700 lineto 570 | 100 100 lineto 571 | 400 400 moveto 572 | 600 100 lineto 573 | pen stroke } 574 | /S { newpath 575 | matrix currentmatrix 576 | 100 100 translate 577 | 5 3 div 1 scale 578 | 150 150 150 -90 1 3 arccos sub 90 arc 579 | setmatrix 580 | matrix currentmatrix 581 | 150 400 translate 582 | 4 3 div 1 scale 583 | 150 150 150 270 90 1 3 arccos sub arcn 584 | setmatrix 585 | withcrosses { 586 | 350 50 moveto 587 | 350 750 lineto 588 | } if 589 | pen stroke } 590 | /T { newpath 591 | 100 700 moveto 592 | 700 700 lineto 593 | 400 700 moveto 594 | 400 100 lineto 595 | pen stroke } 596 | /U { newpath 597 | 600 700 moveto 598 | matrix currentmatrix 599 | 100 100 translate 600 | 1 4 5 div scale 601 | 250 250 250 0 180 arcn 602 | setmatrix 603 | 100 700 lineto 604 | pen stroke } 605 | /V { newpath 606 | 100 700 moveto 607 | 400 100 lineto 608 | 700 700 lineto 609 | pen stroke } 610 | /W { newpath 611 | 100 700 moveto 612 | 300 100 lineto 613 | 500 700 lineto 614 | 700 100 lineto 615 | 900 700 lineto 616 | pen stroke } 617 | /X { newpath 618 | 100 100 moveto 619 | 650 700 lineto 620 | 150 700 moveto 621 | 700 100 lineto 622 | pen stroke } 623 | /Y { newpath 624 | 100 700 moveto 625 | 400 400 lineto 626 | 700 700 lineto 627 | 400 400 moveto 628 | 400 100 lineto 629 | pen stroke } 630 | /Z { newpath 631 | 100 700 moveto 632 | 600 700 lineto 633 | 100 100 lineto 634 | 600 100 lineto 635 | withcrosses { 636 | 200 400 moveto 637 | 500 400 lineto 638 | } if 639 | pen stroke } 640 | /zero { newpath 641 | matrix currentmatrix 642 | 100 100 translate 643 | 5 6 div 1 scale 644 | 300 300 300 0 360 arc 645 | setmatrix 646 | withcrosses { 647 | 100 100 moveto 648 | 600 700 lineto 649 | } if 650 | pen stroke } 651 | /two { newpath 652 | matrix currentmatrix 653 | 150 400 translate 654 | 4 3 div 1 scale 655 | 150 150 150 90 1 3 arccos add -90 arcn 656 | setmatrix 657 | matrix currentmatrix 658 | 100 -200 translate 659 | 5 6 div 1 scale 660 | 300 300 300 90 180 arc 661 | setmatrix 662 | 600 100 lineto 663 | pen stroke } 664 | /three { newpath 665 | matrix currentmatrix 666 | 100 100 translate 667 | 5 3 div 1 scale 668 | 150 150 150 -90 1 3 arccos sub 90 arc 669 | setmatrix 670 | matrix currentmatrix 671 | 150 400 translate 672 | 4 3 div 1 scale 673 | 150 150 150 -90 90 1 3 arccos add arc 674 | setmatrix 675 | pen stroke } 676 | /four { newpath 677 | 500 100 moveto 678 | 500 700 lineto 679 | 100 250 lineto 680 | 600 250 lineto 681 | pen stroke } 682 | /five { newpath 683 | matrix currentmatrix 684 | 100 100 translate 685 | 5 4 div 1 scale 686 | 200 200 200 -90 1 2 arccos sub 180 4 5 arccos sub arc 687 | setmatrix 688 | 150 700 lineto 689 | 550 700 lineto 690 | pen stroke } 691 | /six { newpath 692 | matrix currentmatrix 693 | 100 100 translate 694 | 5 6 div 1 scale 695 | 300 300 300 90 2 3 arccos sub 270 arc 696 | setmatrix 697 | matrix currentmatrix 698 | 100 100 translate 699 | 5 4 div 1 scale 700 | 200 200 200 -90 90 arc 701 | setmatrix 702 | pen stroke 703 | newpath 704 | matrix currentmatrix 705 | 100 100 translate 706 | 5 6 div 1 scale 707 | 300 300 300 0 360 arc 708 | setmatrix 709 | clip 710 | newpath 711 | matrix currentmatrix 712 | 100 100 translate 713 | 5 4 div 1 scale 714 | 200 200 200 90 180 arc 715 | setmatrix 716 | stroke } 717 | /seven { newpath 718 | 100 700 moveto 719 | 600 700 lineto 720 | 400 400 300 300 300 100 curveto 721 | withcrosses { 722 | 300 400 moveto 723 | 500 400 lineto 724 | } if 725 | pen stroke } 726 | /eight { newpath 727 | matrix currentmatrix 728 | 100 100 translate 729 | 5 3 div 1 scale 730 | 150 150 150 90 450 arc 731 | setmatrix 732 | matrix currentmatrix 733 | 150 400 translate 734 | 4 3 div 1 scale 735 | 150 150 150 -90 270 arc 736 | setmatrix 737 | pen stroke } 738 | /nine { newpath 739 | matrix currentmatrix 740 | 100 100 translate 741 | 5 6 div 1 scale 742 | 300 300 300 -90 2 3 arccos sub 90 arc 743 | setmatrix 744 | matrix currentmatrix 745 | 100 300 translate 746 | 5 4 div 1 scale 747 | 200 200 200 90 -90 arc 748 | setmatrix 749 | pen stroke 750 | newpath 751 | matrix currentmatrix 752 | 100 100 translate 753 | 5 6 div 1 scale 754 | 300 300 300 0 360 arc 755 | setmatrix 756 | clip 757 | newpath 758 | matrix currentmatrix 759 | 100 300 translate 760 | 5 4 div 1 scale 761 | 200 200 200 -90 0 arc 762 | setmatrix 763 | stroke } 764 | >> 765 | exch get exec 766 | end 767 | grestore 768 | } bind def 769 | 770 | /glyphwidth { 771 | gsave 772 | nulldevice newpath 0 0 moveto glyphshow currentpoint 773 | grestore 774 | } bind def 775 | 776 | /codexwidth { 777 | gsave 778 | nulldevice newpath 0 0 moveto codexshow currentpoint 779 | grestore 780 | } bind def 781 | 782 | /draftingwidth { 783 | exch 784 | 32 dict begin 785 | /M 800 def 786 | /N 700 def 787 | /W 1000 def 788 | /A M def 789 | /C N def 790 | /D N def 791 | /E N def 792 | /F N def 793 | /G N def 794 | /H N def 795 | /J N def 796 | /K N def 797 | /L N def 798 | /P N def 799 | /Q M def 800 | /R N def 801 | /S N def 802 | /T M def 803 | /U N def 804 | /V M def 805 | /X M def 806 | /Y M def 807 | /Z N def 808 | /zero N def 809 | /two N def 810 | /three N def 811 | /four N def 812 | /five N def 813 | /six N def 814 | /seven N def 815 | /eight N def 816 | /nine N def 817 | /space N def 818 | load 819 | end 820 | mul 1000 div 821 | 0 822 | } bind def 823 | 824 | /centreshow {dup stringwidth pop 2 div neg 0 rmoveto show} bind def 825 | 826 | /centrecodexshow {2 copy codexwidth pop 2 div neg 0 rmoveto codexshow} bind def 827 | 828 | /centresquare {dup neg 2 div dup rmoveto dup 0 rlineto dup 0 exch rlineto neg 0 rlineto closepath stroke} bind def 829 | 830 | /centredraftingshow {2 copy draftingwidth pop 2 div neg 0 rmoveto draftingshow} bind def 831 | 832 | % From BLUEBOOK Program #10 833 | /outsidecircletext 834 | { circtextdict begin 835 | /radius exch def 836 | /centerangle exch def 837 | /ptsize exch def 838 | /str exch def 839 | /xradius radius ptsize 4 div add def 840 | 841 | gsave 842 | centerangle str findhalfangle add rotate 843 | 844 | str 845 | { /charcode exch def 846 | ( ) dup 0 charcode put outsideplacechar 847 | } forall 848 | grestore 849 | end 850 | } def 851 | 852 | /insidecircletext 853 | { circtextdict begin 854 | /radius exch def /centerangle exch def 855 | /ptsize exch def /str exch def 856 | /xradius radius ptsize 3 div sub def 857 | gsave 858 | centerangle str findhalfangle sub rotate 859 | str 860 | { /charcode exch def 861 | ( ) dup 0 charcode put insideplacechar 862 | } forall 863 | grestore 864 | end 865 | } def 866 | 867 | /circtextdict 16 dict def 868 | circtextdict begin 869 | /findhalfangle 870 | { stringwidth pop 2 div 871 | 2 xradius mul pi mul div 360 mul 872 | } def 873 | 874 | /outsideplacechar 875 | { /char exch def 876 | /halfangle char findhalfangle def 877 | gsave 878 | halfangle neg rotate 879 | radius 0 translate 880 | -90 rotate 881 | char stringwidth pop 2 div neg 0 moveto 882 | char show 883 | grestore 884 | halfangle 2 mul neg rotate 885 | } def 886 | 887 | /insideplacechar 888 | { /char exch def 889 | /halfangle char findhalfangle def 890 | gsave 891 | halfangle rotate 892 | radius 0 translate 893 | 90 rotate 894 | char stringwidth pop 2 div neg 0 moveto 895 | char show 896 | grestore 897 | halfangle 2 mul rotate 898 | } def 899 | 900 | /pi 3.1415923 def 901 | end 902 | 903 | %****************** 904 | %* Volvelle and Slide Charts 905 | %* 906 | /magic 94 def % a magic angle for making nice looking spirals. 907 | /drawBottomWheelPage 908 | { 10 dict begin 909 | /outerperm exch def 910 | /outercode exch def 911 | /innercode exch def 912 | /title exch def 913 | /binop exch def 914 | /angle 360 outerperm length div neg def 915 | % Move cursor to center of page 916 | pgsize aload pop 2 div exch 2 div exch translate 917 | % Draw white interior circle 918 | newpath 0 0 6 40 mul 0 360 arc stroke 919 | gsave verythin line 920 | newpath 0 0 6 40 mul 28 add 0 360 arc stroke 921 | newpath 0 0 6 0 360 arc stroke 922 | grestore 923 | % Draw title (small text, repeated) 924 | /Helvetica findfont 12 scalefont setfont 925 | title 12 270 30 insidecircletext 926 | % Draw letters (using human-centric ABCD... permutation) 927 | /Helvetica findfont 6 scalefont setfont 928 | gsave 929 | 360 16 div 360 64 div sub rotate 930 | 0 360 8 div 360 {title 6 3 -1 roll 262 outsidecircletext} for 931 | grestore 932 | outerperm {0 38 sqrt 40 mul moveto outercode exch get 18 centrecodexshow angle rotate} forall 933 | % Draw inside contents 934 | 0 1 31 { % Draw 32 circles of increasing radius 935 | dup 1 add magic mul 24 add 936 | /theta exch def 937 | outerperm { 938 | 1 index 2 add sqrt 40 mul 2 sub 939 | /lam exch def 940 | lam theta sin mul 941 | lam theta cos mul neg 942 | moveto 943 | 0 -3 rmoveto 944 | 1 index 31 exch sub % 31 - inner index 945 | permV exch get binop % apply binary operation to the permuted letter and the inner index 946 | innercode exch get 12 centrecodexshow % display the result 947 | angle rotate % rotate one entry 948 | } forall pop 949 | } for 950 | end 951 | } bind def 952 | 953 | /showTopWheelPage 954 | { 955 | % Move cursor to center of page 956 | pgsize aload pop 2 div exch 2 div exch translate 957 | gsave verythin line 958 | newpath 0 0 6 40 mul 0 360 arc stroke 959 | grestore 960 | % Draw gray "handle" and white interior circle 961 | gsave 962 | 0.8 setgray 963 | newpath 0 0 7.25 40 mul 140 40 arc clip fill 964 | 1 setgray 965 | newpath 0 0 6 40 mul 0 360 arc fill 966 | 0 setgray 967 | newpath 0 0 6 40 mul 0 360 arc stroke 968 | grestore 969 | % Draw centre cross 970 | gsave verythin line 971 | newpath 0 6 moveto 0 -6 lineto stroke 972 | newpath 6 0 moveto -6 0 lineto stroke 973 | grestore 974 | % Draw indicator arrow 975 | newpath 0 6 40 mul moveto 10 -20 rlineto -20 0 rlineto closepath fill 976 | % Draw text 977 | 0 1 31 { 978 | dup 1 add magic mul 24 add 979 | /theta exch def 980 | dup 2 add sqrt 40 mul 2 sub % lam = 40*sqrt(idx + 0.5) - 2 981 | /lam exch def 982 | newpath 983 | lam theta sin mul 984 | lam theta cos mul neg 985 | 2 copy moveto 986 | 12 centresquare % draw square 987 | moveto % return to midpoint 988 | -26 -3 rmoveto % Move to the left 989 | 31 exch sub % 31 - loop index 990 | permV exch get code exch get % Permute index and extract 1-char substring of alphabet 991 | 12 codexshow % ...and draw it 992 | /Symbol findfont 12 scalefont setfont /arrowright glyphshow % Draw a right arrow 993 | } for 994 | } bind def 995 | 996 | % drawPointer : sz -- 997 | % draws a fillied triangle of sz pointing up (or down if sz is negative). 998 | /drawPointer { 999 | /sz exch def 1000 | 0 sz eq not { 1001 | sz 2 div sz neg rlineto sz neg 0 rlineto closepath fill 1002 | } if 1003 | } bind def 1004 | 1005 | % drawPin : sz -- 1006 | % draws a sylized brass fasstener 1007 | /drawPin { 1008 | gsave 1009 | /sz exch def 1010 | currentpoint newpath moveto 1011 | sz -2 div sz 3.5 mul rmoveto 1012 | sz 0 rlineto 1013 | sz 0 sz sz sz -2 div sz rcurveto 1014 | sz -1.5 mul 0 sz -1.5 mul sz neg sz -2 div sz neg rcurveto 1015 | 0 sz -3 mul rlineto 1016 | sz 2 div dup neg rlineto 1017 | sz 2 div dup rlineto 1018 | 0 sz 3 mul rlineto 1019 | closepath 1020 | gsave brass setrgbcolor fill grestore 1021 | thin line stroke 1022 | grestore 1023 | } bind def 1024 | % drawSplitPin : sz -- 1025 | % draws a sylaized open brass fastener. 1026 | /drawSplitPin { 1027 | /sz exch def 1028 | currentpoint 1029 | newpath moveto 1030 | 0 sz -3.5 mul rmoveto 1031 | sz -2 div sz 3.5 mul rmoveto 1032 | 0 sz -3 mul rlineto 1033 | sz 2 div dup neg rlineto 1034 | sz 2 div dup rlineto 1035 | 0 sz 3 mul rlineto 1036 | closepath 1037 | sz 2 div sz 3.5 mul rmoveto 1038 | sz 2 div sz -3.5 mul rmoveto 1039 | 0 sz 3 mul rlineto 1040 | sz -2 div dup neg rlineto 1041 | sz -2 div dup rlineto 1042 | 0 sz -3 mul rlineto 1043 | closepath 1044 | gsave brass setrgbcolor fill grestore 1045 | gsave thin line stroke grestore 1046 | } bind def 1047 | 1048 | % arrowHeadPath : x y r angle sz -- 1049 | % creates an arrow head path for the end of a arc 1050 | /arrowHeadPath { 1051 | 10 dict begin 1052 | { /sz /angle /r /y /x } {exch def} forall 1053 | matrix currentmatrix 1054 | x y translate 1055 | angle rotate 1056 | r sz add sz neg moveto 1057 | r 0 lineto 1058 | sz neg dup rlineto 1059 | setmatrix 1060 | end 1061 | } bind def 1062 | 1063 | 30 dict dup /multiplicationDisc exch def begin 1064 | /radius 200 def 1065 | /title (Multiplication) def 1066 | /outerTitleSz 6 def 1067 | /outerglyphSz 18 def 1068 | /outerPointerSz 0 def 1069 | /innerRadius { radius outerTitleSz outerglyphSz add sub outerPointerSz add } bind def 1070 | /innerglyphSz { outerglyphSz } def 1071 | /innerTitleSz 12 def 1072 | /innerPointerSz 6 def 1073 | /handlePointerSz -16 def 1074 | % the fold line for the bottom disc is slightly less that the disc radius. 1075 | /bottomfoldline { radius angle 2 div cos mul } bind def 1076 | /topfoldline { radius 27 add } bind def 1077 | /handlewidth 54 def 1078 | 1079 | /logbase 19 def 1080 | /coding code2 def 1081 | /numglyphs 31 def 1082 | /angle 360 numglyphs div def 1083 | /outerglyphs { [ [ 1 numglyphs 1 sub {dup logbase gf32mul} repeat ] {coding exch get} forall ] } bind def 1084 | /innerglyphs { outerglyphs } def 1085 | 1086 | /outlineBottomDisc { 1087 | radius 0 moveto 1088 | 0 0 radius 0 360 arc 1089 | 4.5 0 moveto 1090 | 0 0 4.5 360 0 arcn 1091 | } bind def 1092 | 1093 | /drawBottomDisc { 1094 | % Draw white interior circle 1095 | newpath 1096 | outlineBottomDisc 1097 | verythin line resetstroke 1098 | newpath 0 0 innerRadius 0 360 arc thick line resetstroke 1099 | % Draw letters 1100 | gsave 1101 | /Helvetica findfont outerTitleSz scalefont setfont 1102 | 360 8 div dup 2 div exch 360 {title outerTitleSz 3 -1 roll radius outerTitleSz sub outsidecircletext} for 1103 | grestore 1104 | outerglyphs { 1105 | 0 radius outerTitleSz outerglyphSz 0.8 mul add sub moveto outerglyphSz centrecodexshow 1106 | % Draw indicator pointer 1107 | 0 innerRadius moveto outerPointerSz drawPointer 1108 | angle neg rotate 1109 | } forall 1110 | } bind def 1111 | 1112 | /handleCapPath { 1113 | handlewidth 2 div topfoldline 5 sub moveto 1114 | handlewidth 2 div topfoldline handlewidth -2 div topfoldline 5 arct 1115 | handlewidth -2 div topfoldline 2 copy 5 sub 5 arct 1116 | } bind def 1117 | /outlineTopDisc { 1118 | handleCapPath 1119 | 0 0 innerRadius handlewidth 2 innerRadius mul arcsin dup 90 add exch 450 exch sub arc 1120 | closepath 1121 | /charwidth /space innerglyphSz codexwidth pop 1.2 mul def 1122 | charwidth 2 div innerRadius outerPointerSz sub moveto 1123 | charwidth 2 div neg innerRadius outerPointerSz sub lineto 1124 | charwidth 2 div neg radius outerTitleSz sub lineto 1125 | charwidth 2 div radius outerTitleSz sub lineto 1126 | closepath 1127 | } bind def 1128 | 1129 | /drawTopDisc { 1130 | gsave decorateTopDisc grestore 1131 | 1132 | /charwidth /space innerglyphSz codexwidth pop 1.2 mul def 1133 | 1134 | % Draw handle 1135 | gsave 1136 | newpath innerRadius topfoldline moveto 1137 | 0 0 innerRadius 0 180 arc 1138 | innerRadius neg topfoldline lineto 1139 | closepath 1140 | clip 1141 | newpath 1142 | handleCapPath 1143 | handlewidth -2 div 0 lineto 1144 | handlewidth 2 div 0 lineto 1145 | closepath 1146 | charwidth 2 div innerRadius outerPointerSz sub moveto 1147 | charwidth 2 div neg innerRadius outerPointerSz sub lineto 1148 | charwidth 2 div neg radius outerTitleSz sub lineto 1149 | charwidth 2 div radius outerTitleSz sub lineto 1150 | closepath 1151 | 0.8 setgray 1152 | fill 1153 | grestore 1154 | 1155 | % Draw indicator pointer 1156 | 0 radius outerTitleSz sub moveto handlePointerSz drawPointer 1157 | 1158 | gsave verythin line 1159 | % Draw white interior circle 1160 | newpath 0 0 innerRadius 0 360 arc resetstroke 1161 | % Draw centre cross 1162 | newpath 0 4.5 moveto 0 -4.5 lineto 4.5 0 moveto -4.5 0 lineto resetstroke 1163 | grestore 1164 | % Draw title 1165 | gsave 1166 | /Helvetica findfont innerTitleSz scalefont setfont 1167 | title innerTitleSz 270 30 insidecircletext 1168 | grestore 1169 | % Draw letters 1170 | innerglyphs { 1171 | 0 innerRadius innerglyphSz 0.8 mul sub innerPointerSz sub moveto innerglyphSz centrecodexshow 1172 | % Draw indicator pointer 1173 | 0 innerRadius moveto innerPointerSz drawPointer 1174 | angle neg rotate 1175 | } forall 1176 | } bind def 1177 | /decorateTopDisc {} def 1178 | end 1179 | 1180 | % /translationDisc is a modified copy of multiplicaitonDisc. 1181 | multiplicationDisc dup maxlength dict copy dup /translationDisc exch def begin 1182 | /title (Translation) def 1183 | /logbase 23 def 1184 | /coding code def 1185 | /handlePointerSz 0 def 1186 | /decorateTopDisc { 1187 | [/Q /arrowboth /Q] 1188 | dup 0 exch {outerglyphSz 2 mul codexwidth pop add} forall -2 div outerglyphSz 2 mul moveto 1189 | {outerglyphSz 2 mul codexshow} forall 1190 | } bind def 1191 | end 1192 | 1193 | % /recoveryDisc is a modified copy of multiplicaitonDisc. 1194 | multiplicationDisc dup maxlength dict copy dup /recoveryDisc exch def begin 1195 | /title (Recovery) def 1196 | /logbase 10 def 1197 | /outerglyphs { 1198 | [ [ 1 numglyphs 1 sub {dup logbase gf32mul} repeat ] 1199 | {16 xor code exch get} forall ] 1200 | } bind def 1201 | /outerPointerSz -6 def 1202 | /innerglyphs { 1203 | [ [ 1 numglyphs 1 sub {dup logbase gf32mul} repeat ] 1204 | { 16 xor [ exch 17 ] 16 17 3 -1 roll lagrange 1205 | dup 1 eq {pop 32} if 1206 | code2 exch get 1207 | } forall ] 1208 | } bind def 1209 | /innerPointerSz 0 def 1210 | /decorateTopDisc { 1211 | /littleR 1 def 1212 | /bigR innerRadius innerPointerSz sub innerglyphSz sub littleR sub def 1213 | thin line 1214 | 0.8 setgray 1215 | 1 1 numglyphs 2 idiv { 1216 | angle mul dup sin bigR mul exch cos bigR mul 1217 | newpath 2 copy littleR -180 180 arc 1218 | exch neg exch littleR 0 360 arc 1219 | resetstroke 1220 | } for 1221 | } bind def 1222 | end 1223 | 1224 | % foldingBottomDiscs: angle1 angle2 -- 1225 | % Renders an illustration of a folded multiplication and translation bottom disc pair. 1226 | /foldingBottomDiscs { 1227 | 10 dict begin 1228 | { /angle2 /angle1 } {exch def} forall 1229 | 1230 | matrix currentmatrix 1231 | dup determinant /det exch def 1232 | angle2 foldprojection concat 1233 | translationDisc begin 1234 | 0 bottomfoldline translate 1235 | 180 rotate 1236 | radius neg dup radius 2 mul radius bottomfoldline add rectclip 1237 | gsave newpath outlineBottomDisc 1 setgray fill grestore 1238 | matrix currentmatrix determinant det mul 0 gt 1239 | { drawBottomDisc } 1240 | { newpath outlineBottomDisc verythin line resetstroke } 1241 | ifelse 1242 | initclip 1243 | end 1244 | dup setmatrix 1245 | angle1 foldprojection concat 1246 | multiplicationDisc begin 1247 | 0 bottomfoldline neg translate 1248 | radius neg dup radius 2 mul radius bottomfoldline add rectclip 1249 | gsave newpath outlineBottomDisc 1 setgray fill grestore 1250 | drawBottomDisc 1251 | % draw fold line 1252 | initclip 1253 | 0 0 radius 0 360 arc clip 1254 | newpath radius bottomfoldline moveto radius -2 mul 0 rlineto verythin line resetstroke 1255 | initclip 1256 | end 1257 | setmatrix 1258 | end 1259 | } bind def 1260 | 1261 | %****************** 1262 | %* Reference Sheet 1263 | %* 1264 | 1265 | % Draws the data format box. Assumes that position 0 0 is the top-center of the page 1266 | /drawDataFormat { 1267 | 10 dict begin 1268 | /boxW 14 def 1269 | /boxH 14 def 1270 | 1271 | /Courier findfont boxH scalefont setfont 1272 | boxW -17 mul 0 moveto 1273 | % Draw left 13 boxes 1274 | 1 1 13 { 1275 | pop 1276 | gsave 1277 | [] 0 setdash 1278 | boxW 0 rlineto 1279 | 0 boxH neg rlineto 1280 | boxW neg 0 rlineto 1281 | closepath stroke 1282 | grestore 1283 | boxW 0 rmoveto 1284 | } for 1285 | % Draw dashed line (4 boxes wide) 1286 | boxW 2 div 0 rmoveto 1287 | 0 -7 rmoveto 1288 | [1 2] 0 setdash 1289 | boxW 4 mul 0 1290 | gsave 2 copy rlineto stroke grestore 1291 | rmoveto 1292 | 0 7 rmoveto 1293 | boxW 2 div 0 rmoveto 1294 | % Draw right 17 boxes 1295 | 1 1 17 { 1296 | pop 1297 | gsave 1298 | [] 0 setdash 1299 | boxW 0 rlineto 1300 | 0 boxH neg rlineto 1301 | boxW neg 0 rlineto 1302 | closepath stroke 1303 | grestore 1304 | boxW 0 rmoveto 1305 | } for 1306 | 1307 | % Draw a brace, moving the current point to the right 1308 | % by width many pts 1309 | /drawBrace { % width height -> nil 1310 | 10 dict begin 1311 | {/height /width} {exch def} forall 1312 | gsave 1313 | -2 height 2 div rmoveto 1314 | [] 0 setdash 1315 | height abs height rlineto 1316 | width 4 add 2 height mul abs sub 0 rlineto 1317 | height abs height neg rlineto 1318 | stroke 1319 | grestore 1320 | width 0 rmoveto 1321 | end 1322 | } bind def 1323 | 1324 | boxW -17 mul 0 moveto 1325 | /braceH -3 def 1326 | 3 boxW mul 0 rmoveto 1327 | [ 1 4 1 13 13 ] { 1328 | 0 5 braceH mul rmoveto 1329 | boxW exch mul braceH drawBrace 1330 | /braceH braceH neg store 1331 | } forall 1332 | 1333 | boxW -17 mul 0 moveto 1334 | % Draw example characters in boxes 1335 | 2 -12 rmoveto % manually center characters in box 1336 | /sampleString (MS12NAMEAXXXX XXXXCE43R337JKVTZ) def 1337 | 0 1 sampleString length 1 sub { 1338 | sampleString exch 1 getinterval gsave show grestore 1339 | boxW 0 rmoveto 1340 | } for 1341 | 1342 | boxW -17 mul -32 moveto 1343 | % Draw headings above/below braces 1344 | /Helvetica findfont 12 scalefont setfont 1345 | 3.5 boxW mul 0 rmoveto 1346 | gsave 1347 | gsave (recovery) centreshow grestore 1348 | 0 -12 rmoveto gsave (threshold) centreshow grestore 1349 | 0 -12 rmoveto gsave (\(k\)) centreshow grestore 1350 | grestore 1351 | 1352 | 2.5 boxW mul 40 rmoveto 1353 | gsave 1354 | gsave (ID) centreshow grestore 1355 | grestore 1356 | 1357 | 2.5 boxW mul -40 rmoveto 1358 | gsave 1359 | gsave (share) centreshow grestore 1360 | 0 -12 rmoveto gsave (index) centreshow grestore 1361 | grestore 1362 | 1363 | 6.5 boxW mul 40 rmoveto 1364 | gsave (data \(26 chars for 128 bits\)) centreshow grestore 1365 | 1366 | 13 boxW mul -40 rmoveto 1367 | gsave (checksum \(13 chars\)) centreshow grestore 1368 | 1369 | end 1370 | } bind def 1371 | 1372 | % Draws the bech32->binary conversion table. Assumes that position 0 0 is the top-center of the page 1373 | /drawBech32BinaryTable { 1374 | 10 dict begin 1375 | /invTable exch def 1376 | /Courier findfont 12 scalefont setfont 1377 | 1378 | /entryW 8 string stringwidth pop def 1379 | /entryS 36 def 1380 | 1381 | 0 0 moveto 1382 | entryW -2 mul entryS -1.5 mul add 0 rmoveto % centering logic 1383 | 0 4 { 1384 | gsave 8 { 1385 | dup 1386 | gsave 1387 | invTable { 1388 | 32 5 { 2 idiv 2 copy and 0 eq {(0)} {(1)} ifelse show } repeat pop 1389 | (: ) show code 1 index get 12 codexshow 1390 | } { 1391 | perm exch get 1392 | code 1 index get 12 codexshow (: ) show 1393 | 32 5 { 2 idiv 2 copy and 0 eq {(0)} {(1)} ifelse show } repeat pop 1394 | } ifelse 1395 | pop 1396 | grestore 1397 | 1 add 1398 | 0 -14 rmoveto 1399 | } repeat grestore 1400 | entryW entryS add 0 rmoveto 1401 | } repeat pop 1402 | 1403 | end 1404 | } bind def 1405 | 1406 | % Draws the symbol pronunciation table. Assumes that position 0 0 is the top-center of the page 1407 | /drawSymbolTable { 1408 | 10 dict begin 1409 | 1410 | /pronunciation << 1411 | /aleph (Aleph) /alpha (Alpha) /beta (Beta) /Gamma (Gamma) 1412 | /Delta (Delta) /epsilon (Epsilon) /eta (Eta) /Theta (Theta) 1413 | /Lambda (Lambda) /mu (Mu) /Xi (Xi) /Pi (Pi) 1414 | /rho (Rho) /Sigma (Sigma) /Phi (Phi) /Psi (Psi) 1415 | /Omega (Omega) /at (At) /numbersign (Hash) /percent (Percent) 1416 | /cent (Cent) /yen (Yen) /Euro (Euro) /currency (Scarab) 1417 | /circleplus (Earth) /dagger (Dagger) /daggerdbl (Double-dagger) /section (Section) 1418 | /paragraph (Paragraph) /diamond (Diamond) /heart (Heart) 1419 | >> def 1420 | 1421 | 0 0 moveto 1422 | 4 string stringwidth pop 90 2 mul add neg 0 rmoveto % goofy centering logic 1423 | gsave 1424 | 1 1 31 { 1425 | dup dup 1426 | code2 exch get 12 codexshow 1427 | ( ) show 1428 | gsave 1429 | /Helvetica findfont 12 scalefont setfont 1430 | code2 exch get pronunciation exch get show 1431 | grestore 1432 | 90 0 rmoveto 1433 | 1434 | 4 mod 0 eq { 1435 | grestore 0 -20 rmoveto gsave 1436 | } if 1437 | } for 1438 | grestore 1439 | 1440 | end 1441 | } bind def 1442 | 1443 | %****************** 1444 | %* Share Tables 1445 | %* 1446 | /showShareTable { 1447 | /offsety exch def 1448 | /offsetx exch def 1449 | /page exch def 1450 | /Courier findfont 10 scalefont setfont 1451 | 20 offsetx add offsety moveto (Page: ) show 1452 | /Courier-Bold findfont 8 scalefont setfont 1453 | code page get glyphshow 1454 | 2 1 31 { 1455 | dup 7 mul offsetx add offsety 10 sub moveto 1456 | permS exch get 1457 | code exch get glyphshow 1458 | } for 1459 | 1460 | 0 1 31 { 1461 | /Courier-Bold findfont 8 scalefont setfont 1462 | offsetx offsety 20 sub 2 index 8 mul sub moveto 1463 | dup code exch perm exch get get glyphshow 1464 | /Courier findfont 8 scalefont setfont 1465 | 2 1 31 { 1466 | dup 7 mul offsetx add offsety 20 sub 3 index 8 mul sub moveto 1467 | permS exch get 1468 | page exch perm 3 index get exch makeShare code exch get glyphshow 1469 | } for pop } for 1470 | } bind def 1471 | 1472 | /showShareTablePage { 1473 | 325 400 showShareTable 1474 | 50 400 showShareTable 1475 | 325 720 showShareTable 1476 | 50 720 showShareTable 1477 | } bind def 1478 | 1479 | %****************** 1480 | %* Checksum Table 1481 | %* 1482 | %* Draws the giant lookup table used by the checksum worksheet 1483 | %* 1484 | 1485 | /drawChecksumTable { 1486 | 10 dict begin 1487 | { /startVal /tHeight /tWidth /y /x } {exch def} forall 1488 | 1489 | /cellH tHeight 32 div def 1490 | /cellW tWidth 8 div def 1491 | 1492 | % Draw horizontal background lines: one black one for the heading then 1493 | % alternating 4-cell-height white/gray for the content background 1494 | x y moveto 1495 | 0 1 31 { 1496 | dup 0 eq { 0.808 0.923 0.953 } { 1497 | 8 mod 4 lt { 0.808 0.923 0.953 } { 1 1 1 } ifelse 1498 | } ifelse setrgbcolor 1499 | 1500 | gsave 1501 | tWidth 0 rlineto 1502 | 0 cellH neg rlineto tWidth neg 0 rlineto closepath fill 1503 | grestore 1504 | 0 cellH neg rmoveto 1505 | } for 1506 | 1507 | % Draw vertical background lines: one double-wide black one per column 1508 | 0 setgray 1509 | x y moveto 1510 | 0 1 7 { 1511 | gsave 1512 | 0 tHeight neg rlineto 1513 | cellW 2 mul 13 div 0 rlineto 0 tHeight rlineto closepath fill 1514 | grestore 1515 | cellW 0 rmoveto 1516 | } for 1517 | 1518 | % Draw content 1519 | startVal 1 startVal 7 add { 1520 | /xVal exch def 1521 | x xVal startVal sub cellW mul add 2 add y 1.5 add moveto 1522 | 0 1 31 { 1523 | /yVal exch def 1524 | 0 cellH neg rmoveto 1525 | 1526 | gsave 1527 | /Courier-Bold findfont 8.5 scalefont setfont 1528 | 1 setgray 1529 | code perm xVal get get glyphshow 1530 | code perm yVal get get glyphshow 1531 | grestore 1532 | 1533 | gsave 1534 | cellW 2 mul 13 div 0 rmoveto 1535 | perm xVal get perm yVal get polymodshift2 { 1536 | code exch get glyphshow 1537 | 0.25 0 rmoveto 1538 | } forall 1539 | grestore 1540 | } for 1541 | } for 1542 | 1543 | % Bounding box 1544 | 0.5 setlinewidth 1545 | x y moveto 1546 | 0 tHeight neg rlineto 1547 | tWidth 0 rlineto 1548 | 0 tHeight rlineto 1549 | closepath stroke 1550 | 1551 | end 1552 | } bind def 1553 | 1554 | %****************** 1555 | %* Checksum Worksheet 1556 | %* 1557 | %* Functionality for the checksum, addition, bit conversion, etc., worksheets, 1558 | %* to assist user manipulation of long bech32 strings 1559 | %* 1560 | 1561 | 30 dict dup /ladder exch def begin 1562 | /hrp (MS) def 1563 | /sharelen 48 def 1564 | /xsize 14 def 1565 | /xgap 2 def 1566 | /ysize -14 def 1567 | /ygap -1 def 1568 | /fsize 15 def 1569 | /fgap 2.5 def 1570 | /hrplen hrp length def 1571 | /checksumlen checksum length def 1572 | /numsteps sharelen hrplen sub checksumlen sub 2 idiv def 1573 | /firstrowlen sharelen hrplen sub 1 sub numsteps 2 mul sub def 1574 | /odd checksumlen firstrowlen sub def 1575 | /initresidue [hrp polymodhrp aload pop firstrowlen {0} repeat ] polymod0 def 1576 | /offset { 1577 | dup ysize mul exch 2 idiv ygap mul add exch 1578 | dup xsize mul exch 4 idiv xgap mul add exch 1579 | } bind def 1580 | /drawgrid { 1581 | 10 dict begin 1582 | gsave 1583 | pink setrgbcolor 1584 | 0 2 checksumlen { 1585 | /j exch def 1586 | 0 1 j checksumlen 2 mod add 1 sub { 1587 | /i exch def 1588 | i sharelen checksumlen sub add j numsteps 2 mul checksumlen 2 idiv 2 mul sub add offset xsize ysize rectfill 1589 | } for 1590 | } for 1591 | grestore 1592 | gsave 1593 | /Courier findfont 3 scalefont setfont 1594 | thick line 1595 | 0 1 firstrowlen hrplen add { 1596 | /i exch def 1597 | i 0 offset 2 copy xsize ysize rectstroke moveto 1598 | xsize 4.5 sub -3 rmoveto 1599 | /n i 1 add def 1600 | n 10 lt { ( ) show } if n 2 string cvs show 1601 | } for 1602 | 1603 | thin line 1604 | 0 1 firstrowlen 1 sub { 1605 | hrplen 1 add add 1 offset xsize ysize rectstroke 1606 | } for 1607 | 1608 | 0 1 checksumlen 1 sub { 1609 | sharelen checksumlen sub add /i exch def 1610 | thin line 1611 | i numsteps 1 add 2 mul offset xsize ysize rectstroke 1612 | } for 1613 | 1614 | 1 1 numsteps { 1615 | 2 mul /j exch def 1616 | 0 1 checksumlen 1 sub { 1617 | hrplen add j add 1 sub odd sub /i exch def 1618 | thin line 1619 | i j offset xsize ysize rectstroke 1620 | i 2 add j 1 add offset xsize ysize rectstroke 1621 | } for 1622 | thick line 1623 | i 1 add j offset 2 copy xsize ysize rectstroke moveto 1624 | xsize 4.5 sub -3 rmoveto 1625 | /Courier findfont 3 scalefont setfont 1626 | /n i 2 add def 1627 | n 10 lt { ( ) show } if n 2 string cvs show 1628 | i 2 add j offset 2 copy xsize ysize rectstroke moveto 1629 | xsize 4.5 sub -3 rmoveto 1630 | /n i 3 add def 1631 | n 10 lt { ( ) show } if n 2 string cvs show 1632 | } for 1633 | 1634 | /Helvetica-Bold findfont 10 scalefont setfont 1635 | 1 1 numsteps 1 add { 1636 | 2 mul /j exch def 1637 | j j offset moveto xsize 0.7 mul 5 rmoveto (+) centreshow 1638 | j j 1 add offset moveto xsize 0.7 mul 5 rmoveto (=) centreshow 1639 | } for 1640 | 1641 | /Courier findfont fsize scalefont setfont 1642 | 0 1 hrplen 1 sub { 1643 | dup 0 offset moveto xsize 2 div ysize fgap add rmoveto hrp exch 1 getinterval centreshow 1644 | } for 1645 | hrplen 0 offset moveto xsize 2 div ysize fgap add rmoveto (1) centreshow 1646 | 0 1 checksumlen 1 sub { 1647 | /i exch def 1648 | i hrplen add 1 add odd sub 1 0 i eq {odd add} if offset moveto xsize 2 div ysize fgap add rmoveto initresidue i get code exch get fsize centrecodexshow 1649 | } for 1650 | 0.85 setgray 1651 | 0 1 checksumlen 1 sub { 1652 | /i exch def 1653 | i sharelen checksumlen sub add numsteps 1 add 2 mul offset moveto xsize 2 div ysize fgap add rmoveto checksum i get code exch get fsize centrecodexshow 1654 | } for 1655 | grestore 1656 | end 1657 | } bind def 1658 | 1659 | /fillgrid { 1660 | 10 dict begin 1661 | gsave 1662 | /data exch decode def 1663 | /fsize fsize 2 sub def 1664 | 0 1 firstrowlen 1 sub { 1665 | /i exch def 1666 | i hrplen add 1 add 0 offset moveto xsize 2 div ysize fgap add rmoveto 1667 | code data i get get fsize centredraftingshow 1668 | } for 1669 | /residue 1670 | data 0 firstrowlen getinterval polymod0 1671 | initresidue gf32addarray 1672 | def 1673 | 0 2 numsteps 1 sub 2 mul { 1674 | /y exch def 1675 | /residue 1676 | [ residue polymod0 aload pop 1677 | data firstrowlen y add get 1678 | data firstrowlen y add 1 add get 1679 | ] def 1680 | 0 1 checksumlen 1 add { 1681 | /i exch def 1682 | i hrplen add y add 1 add odd sub 2 y add offset moveto xsize 2 div ysize fgap add rmoveto 1683 | odd 1 eq 0 i eq and 0 y eq and not {code residue i get get fsize centredraftingshow} if 1684 | } for 1685 | /addrow residue 0 get residue 1 get polymodshift2 def 1686 | 0 1 checksumlen 1 sub { 1687 | /i exch def 1688 | i hrplen add y add 3 add odd sub 3 y add offset moveto xsize 2 div ysize fgap add rmoveto 1689 | code addrow i get get fsize centredraftingshow 1690 | } for 1691 | } for 1692 | /residue residue polymod0 def 1693 | 0 1 checksumlen 1 sub { 1694 | /i exch def 1695 | i hrplen add numsteps 2 mul add 1 add 2 numsteps 2 mul add offset moveto xsize 2 div ysize fgap add rmoveto 1696 | code residue i get get fsize centredraftingshow 1697 | } for 1698 | grestore 1699 | end 1700 | } bind def 1701 | end 1702 | 1703 | /arraySpace 13 def 1704 | 1705 | /showParagraphs { 1706 | 10 dict begin 1707 | { /paragraphs /height /width } {exch def} forall 1708 | paragraphs { 1709 | /lines exch def 1710 | lines 0 lines length 1 sub getinterval { 1711 | /line exch def 1712 | % Compute amount of space needed for each /space character 1713 | width line stringwidth pop sub 0 line { 32 eq { 1 add } if} forall div 1714 | 0 32 line gsave widthshow grestore 1715 | 0 height neg rmoveto 1716 | } forall 1717 | lines lines length 1 sub get gsave show grestore 1718 | 0 height neg 2 mul rmoveto 1719 | } forall 1720 | end 1721 | } bind def 1722 | %%EndSetup 1723 | 1724 | %************************************************************************ 1725 | %************************************************************************ 1726 | %* 1727 | %* Section Three: Page Rendering 1728 | %* 1729 | %************************************************************************ 1730 | %************************************************************************ 1731 | 1732 | %**************************************************************** 1733 | %* 1734 | %* Title Page 1735 | %* 1736 | %**************************************************************** 1737 | %%Page: 1 1 1738 | %%BeginPageSetup 1739 | /pgsave save def 1740 | %%EndPageSetup 1741 | portraitPage begin 1742 | 1743 | /Times-Roman findfont 48 scalefont setfont 1744 | pgsize aload pop exch 2 div exch 300 sub moveto 1745 | title {gsave centreshow grestore 0 -70 rmoveto} forall 1746 | 1747 | /Times-Roman findfont 16 scalefont setfont 1748 | pgsize aload pop exch 2 div exch 700 sub moveto ver centreshow 1749 | 1750 | end 1751 | pgsave restore 1752 | showpage 1753 | 1754 | %**************************************************************** 1755 | %* 1756 | %* License Information 1757 | %* 1758 | %**************************************************************** 1759 | %%Page: 2 2 1760 | %%BeginPageSetup 1761 | /pgsave save def 1762 | %%EndPageSetup 1763 | portraitPage begin 1 drawPageContent 1764 | 1765 | /Helvetica findfont 6 scalefont setfont 1766 | marginX1 marginY1 16 sub moveto 1767 | MIT {gsave ((c)) search {show pop /copyright glyphshow} if show grestore 0 -8 rmoveto} forall 1768 | /Helvetica findfont 6 scalefont setfont 1769 | warning {gsave show grestore 0 -7 rmoveto} forall 1770 | 0 -16 rmoveto 1771 | /Helvetica findfont 8 scalefont setfont 1772 | README {gsave show grestore 0 -10 rmoveto} forall 1773 | 1774 | end 1775 | pgsave restore 1776 | showpage 1777 | 1778 | %**************************************************************** 1779 | %* 1780 | %* Reference Sheet 1781 | %* 1782 | %%Page: 3 3 1783 | %%BeginPageSetup 1784 | /pgsave save def 1785 | %%EndPageSetup 1786 | portraitPage begin 2 drawPageContent 1787 | 1788 | % Set 0 0 to top-center of page 1789 | centerX marginY1 16 sub translate 1790 | 1791 | % Header/data format 1792 | /Helvetica-Bold findfont 16 scalefont setfont 1793 | 0 0 moveto (Data Format) centreshow 1794 | 1795 | 0 -24 translate 1796 | drawDataFormat 1797 | 1798 | % bech32->binary chart 1799 | 0 -100 translate 1800 | /Helvetica-Bold findfont 16 scalefont setfont 1801 | 0 0 moveto (Bech32 to Binary Conversion) centreshow 1802 | 1803 | 0 -24 translate 1804 | false drawBech32BinaryTable 1805 | 1806 | 0 -150 translate 1807 | /Helvetica-Bold findfont 16 scalefont setfont 1808 | 0 0 moveto (Binary to Bech32 Conversion) centreshow 1809 | 1810 | 0 -24 translate 1811 | true drawBech32BinaryTable 1812 | 1813 | % Symbol pronunciation 1814 | 0 -150 translate 1815 | /Helvetica-Bold findfont 16 scalefont setfont 1816 | 0 0 moveto (Symbols) centreshow 1817 | 1818 | 0 -24 translate 1819 | drawSymbolTable 1820 | 1821 | end 1822 | pgsave restore 1823 | showpage 1824 | 1825 | %**************************************************************** 1826 | %* 1827 | %* Arithmetic Tables 1828 | %* 1829 | %%Page: 4 4 1830 | %%BeginPageSetup 1831 | /pgsave save def 1832 | %%EndPageSetup 1833 | 1834 | /Helvetica-Bold findfont 16 scalefont setfont 1835 | pgsize aload pop 48 sub exch 2 div exch 1836 | moveto (Principal Tables) centreshow 1837 | 1838 | pgsize aload pop 1839 | /tHeight exch 108 sub 2 div def 1840 | /tWidth exch 72 sub 2 div def 1841 | 1842 | /drawTable { 1843 | 10 dict begin 1844 | { /innerCode /topPerm /topCode /leftPerm /leftCode /title /binop /y /x } {exch def} forall 1845 | 1846 | % Top 16 pts are title 1847 | x y moveto 1848 | /Helvetica findfont 12 scalefont setfont 1849 | tWidth 2 div -12 rmoveto title centreshow 1850 | 1851 | % Remainder is split into the table (one extra row and column for heading) 1852 | /cellH tHeight 16 sub leftPerm length 1 add div def 1853 | /cellW tWidth topPerm length 1 add div def 1854 | 1855 | % Draw vertical background lines: one black one for the heading then 1856 | % alternating 3-cell-height white/gray for the content background 1857 | x y 16 sub moveto 1858 | 0 1 topPerm length { 1859 | dup 0 eq { pop 0 0 0 } { 1860 | 3 add 6 mod 3 lt { 0.808 0.923 0.953 } { 1 1 1 } ifelse 1861 | } ifelse setrgbcolor 1862 | 1863 | gsave 1864 | 0 tHeight 16 sub neg rlineto 1865 | cellW 0 rlineto 0 tHeight 16 sub rlineto closepath fill 1866 | grestore 1867 | cellW 0 rmoveto 1868 | } for 1869 | % Draw horizontal background line for top heading 1870 | 0 setgray 1871 | x y 16 sub moveto 1872 | x tWidth add y 16 sub lineto 1873 | 0 cellH neg rlineto 1874 | x y 16 cellH add sub lineto 1875 | closepath fill 1876 | % Draw vertical lines 1877 | 0.1 setlinewidth 1878 | % Draw horizontal lines 1879 | x y 16 sub moveto 1880 | 0 1 leftPerm length { 1881 | 0 cellH neg rmoveto 1882 | 3 mod 2 eq { 1883 | gsave tWidth 0 rlineto stroke grestore 1884 | } if 1885 | } for 1886 | 1887 | 1 setgray 1888 | % Draw top title 1889 | x cellW 2 div add y 16 sub cellH sub 2 add moveto 1890 | 0 1 topPerm length 1 sub { 1891 | cellW 0 rmoveto 1892 | topPerm exch get topCode exch get gsave 10 centrecodexshow grestore 1893 | } for 1894 | % Draw left title 1895 | x cellW 2 div add y 16 sub cellH sub 2 add moveto 1896 | 0 1 leftPerm length 1 sub { 1897 | 0 cellH neg rmoveto 1898 | leftPerm exch get leftCode exch get gsave 10 centrecodexshow grestore 1899 | } for 1900 | 1901 | 0 setgray 1902 | % Draw content 1903 | x cellW 2 div add y 16 sub cellH sub 2 add moveto 1904 | 0 1 topPerm length 1 sub { % x 1905 | cellW 0 rmoveto 1906 | gsave 1907 | 0 1 leftPerm length 1 sub { % y 1908 | 0 cellH neg rmoveto 1909 | 1 index 1910 | /xpos exch topPerm exch get def 1911 | /ypos exch leftPerm exch get def 1912 | innerCode xpos ypos binop get 1913 | gsave 10 centrecodexshow grestore 1914 | } for 1915 | pop 1916 | grestore 1917 | } for 1918 | 1919 | % Bounding box 1920 | 0.5 setlinewidth 1921 | x y 16 sub moveto 1922 | 0 tHeight 16 sub neg rlineto 1923 | tWidth 0 rlineto 1924 | 0 tHeight 16 sub rlineto 1925 | closepath stroke 1926 | 1927 | end 1928 | } bind def 1929 | 1930 | % top-left 1931 | 28 tHeight tHeight 56 add add 1932 | {gf32add} (Addition) code perm code perm code drawTable 1933 | % top-right 1934 | tWidth 44 add tHeight tHeight 56 add add 1935 | {gf32mul} (Translation) code perm code2 permId 1 31 getinterval code drawTable 1936 | % bot-left 1937 | 28 tHeight 48 add 1938 | { 10 dict begin 1939 | /in exch def 1940 | /out exch def 1941 | 16 out [ in out ] lagrange 1942 | dup 1 eq {pop 0} if % X out trying to recover a share with itself. 1943 | end 1944 | } 1945 | (Recovery) code permS 1 31 getinterval code permS 1 31 getinterval code2 drawTable 1946 | % bot-right 1947 | tWidth 44 add tHeight 48 add 1948 | {gf32mul} (Multiplication) code2 permId 1 31 getinterval code2 permId 1 31 getinterval code2 drawTable 1949 | 1950 | pgsave restore 1951 | showpage 1952 | 1953 | %%Page: 5 5 1954 | %%BeginPageSetup 1955 | /pgsave save def 1956 | %%EndPageSetup 1957 | {xor} (Addition) code dup perm drawBottomWheelPage 1958 | 1959 | pgsave restore 1960 | showpage 1961 | %%Page: 6 6 1962 | %%BeginPageSetup 1963 | /pgsave save def 1964 | %%EndPageSetup 1965 | showTopWheelPage 1966 | 1967 | pgsave restore 1968 | showpage 1969 | %%Page: 7 7 1970 | %%BeginPageSetup 1971 | /pgsave save def 1972 | %%EndPageSetup 1973 | 10 dict begin 1974 | 72 720 moveto 1975 | /Helvetica findfont 20 scalefont setfont 1976 | gsave (Constructing Shares) show grestore 1977 | 0 -20 rmoveto 1978 | /Times-Roman findfont 12 scalefont setfont 1979 | 468 12 1980 | [ [ 1981 | (Before constructing shares, you must choose your threshold value k, which is the number of) 1982 | (shares needed to reconstruct the secret, and must be between 2 and 31 inclusive. \(k = 1 can be) 1983 | (achieved by simply duplicating the original secret, and k > 31 is mathematically impossible with) 1984 | (this scheme.\) Be aware that the work required during recovery is quadratic in k, so values greater) 1985 | (than 4 are not recommended. You also cannot generate more than 31 shares total.) 1986 | ] [ 1987 | (Next, generate the "S" share, which is the unshared secret, as well as k - 1 more shares with) 1988 | (consecutive indices starting from "A". These initial shares should be generated randomly using) 1989 | (dice, affixed with a header, then checksummed. See the Dice Table page and the Checksum) 1990 | (Worksheet for more details.) 1991 | ] [ 1992 | (For the case k = 2, you will have only two shares, S and A, and there is a simplified method) 1993 | (available to generate the remaining shares. Use each character from your S share to select a table) 1994 | (from the following pages. Look up the row indexed by the corresponding character of your A) 1995 | (share, then read the corresponding character for the C share from the first column. The character) 1996 | (for the D share will be in the column after that, and so on.) 1997 | ] ] showParagraphs 1998 | [ 1999 | (The general scheme is more involved:) 2000 | ( 1. Choose the appropriate table from this page based on your choice of k.) 2001 | ( 2. Find the column corresponding to the share you wish to create.) 2002 | ( 3. Translate each initial share by its symbol from that column, using the Translation Wheel.) 2003 | ( 4. Add all the translated initial shares together using the Addition Wheel.) 2004 | () 2005 | (Tables for higher k can be easily generated by editing the source code of this file.) 2006 | ] 2007 | { gsave show grestore 0 -12 rmoveto } forall 2008 | 2009 | % EDITME 2010 | % Edit these values to draw tables for larger k. Be warned that the total work to recover 2011 | % a secret from k shares will be on the other of (48 + k - 1)k volvelle applications. For 2012 | % k = 8 this is already over 250. And this is not even considering the logistics of keeping 2013 | % eight distributed shares intact and available.. 2014 | /mink 2 def 2015 | /maxk 6 def 2016 | 2017 | /x 104 def 2018 | /y 400 def 2019 | /rowtitle 6 string def 2020 | mink 1 maxk { 2021 | /k exch def 2022 | 2023 | 0 1 k { 2024 | /rowidx exch def 2025 | x y moveto 2026 | /Courier-Bold findfont 12 scalefont setfont 2027 | rowidx 0 eq { 2028 | % First row (heading) 2029 | k (k = ) rowtitle copy 4 2 getinterval cvs pop 2030 | rowtitle show 2031 | 2032 | k mink sub { 12 0 rmoveto } repeat 2033 | k 1 31 { 2034 | permS exch get code exch get gsave glyphshow grestore 2035 | 12 0 rmoveto 2036 | } for % horizontal loop 2037 | } { 2038 | % Symbol rows 2039 | /xinterp rowidx 1 sub def % x coord to interpolate at 2040 | ( ) show 2041 | permS xinterp get code exch get glyphshow 2042 | ( ) show 2043 | 2044 | k mink sub { 12 0 rmoveto } repeat 2045 | k 1 31 { 2046 | permS exch get % x coord to evaluate at 2047 | permS xinterp get % x coord to interpolate at 2048 | permS 0 k getinterval % x coords to interpolate at 2049 | lagrange % symbol 2050 | code2 exch get gsave 12 codexshow grestore % print symbol 2051 | 12 0 rmoveto 2052 | } for % horizontal loop 2053 | } ifelse 2054 | 2055 | 2056 | /y y 11 sub def 2057 | } for % vertical loop 2058 | 2059 | /y y 20 sub def 2060 | } for 2061 | 2062 | end 2063 | 2064 | pgsave restore 2065 | showpage 2066 | %%Page: 8 8 2067 | %%BeginPageSetup 2068 | /pgsave save def 2069 | %%EndPageSetup 2070 | 29 24 13 25 showShareTablePage 2071 | 2072 | pgsave restore 2073 | showpage 2074 | %%Page: 9 9 2075 | %%BeginPageSetup 2076 | /pgsave save def 2077 | %%EndPageSetup 2078 | 9 8 23 18 showShareTablePage 2079 | 2080 | pgsave restore 2081 | showpage 2082 | %%Page: 10 10 2083 | %%BeginPageSetup 2084 | /pgsave save def 2085 | %%EndPageSetup 2086 | 22 31 27 19 showShareTablePage 2087 | 2088 | pgsave restore 2089 | showpage 2090 | %%Page: 11 11 2091 | %%BeginPageSetup 2092 | /pgsave save def 2093 | %%EndPageSetup 2094 | 1 0 3 16 showShareTablePage 2095 | 2096 | pgsave restore 2097 | showpage 2098 | %%Page: 12 12 2099 | %%BeginPageSetup 2100 | /pgsave save def 2101 | %%EndPageSetup 2102 | 11 28 12 14 showShareTablePage 2103 | 2104 | pgsave restore 2105 | showpage 2106 | %%Page: 13 13 2107 | %%BeginPageSetup 2108 | /pgsave save def 2109 | %%EndPageSetup 2110 | 6 4 2 15 showShareTablePage 2111 | 2112 | pgsave restore 2113 | showpage 2114 | %%Page: 14 14 2115 | %%BeginPageSetup 2116 | /pgsave save def 2117 | %%EndPageSetup 2118 | 10 17 21 20 showShareTablePage 2119 | 2120 | pgsave restore 2121 | showpage 2122 | %%Page: 15 15 2123 | %%BeginPageSetup 2124 | /pgsave save def 2125 | %%EndPageSetup 2126 | 26 30 7 5 showShareTablePage 2127 | 2128 | pgsave restore 2129 | showpage 2130 | %%Page: 16 16 2131 | %%BeginPageSetup 2132 | /pgsave save def 2133 | %%EndPageSetup 2134 | recoveryDisc begin 2135 | % Draw assembly diagram 2136 | 10 dict begin 2137 | /yscale 0.25 def 2138 | /pinoffset -55 def 2139 | gsave 2140 | 120 700 translate 2141 | 0.4 dup scale 2142 | matrix currentmatrix 2143 | 0 -60 translate 2144 | 0 pinoffset neg moveto 0 0 lineto 2145 | [4.5 4.5] 4.5 1.5 mul setdash stroke 2146 | 1 yscale scale 2147 | drawBottomDisc 2148 | dup setmatrix 2149 | 1 yscale scale 2150 | -90 rotate 2151 | gsave outlineTopDisc 1 setgray fill grestore 2152 | drawTopDisc 2153 | setmatrix 2154 | % pin size is the width of the cross 2155 | 0 pinoffset neg moveto 4.5 2 mul drawPin 2156 | 0 pinoffset neg moveto 0 0 lineto 2157 | [4.5 4.5] 4.5 1.5 mul setdash stroke 2158 | grestore 2159 | end 2160 | % Move cursor to center of page 2161 | pgsize aload pop 2 div exch 2 div exch translate 2162 | % angle the page 2163 | /pageangle pgsize aload pop radius angleinbox def 2164 | /buffer 2 def 2165 | pageangle rotate 2166 | gsave 2167 | 0 buffer innerRadius add neg translate 2168 | drawBottomDisc 2169 | grestore 2170 | 0 buffer radius add translate 2171 | 90 pageangle sub rotate 2172 | drawTopDisc 2173 | end 2174 | pgsave restore 2175 | showpage 2176 | %%Page: 17 17 2177 | %%BeginPageSetup 2178 | /pgsave save def 2179 | %%EndPageSetup 2180 | % Draw assembly diagram 2181 | gsave 2182 | 60 700 translate 2183 | 0.4 dup scale 2184 | 0 0 70 90 180 5 copy 5 copy 2185 | arc 2186 | pop -10 arrowHeadPath 2187 | exch pop 10 arrowHeadPath 2188 | 5 line stroke 2189 | 5 75 foldingBottomDiscs 2190 | grestore 2191 | gsave 2192 | 140 600 translate 2193 | 0.4 dup scale 2194 | multiplicationDisc begin 2195 | 0 0 radius 2 mul 255 260 5 copy arc 2196 | exch pop 10 arrowHeadPath 5 line stroke 2197 | 0 0 radius 2 mul 280 275 5 copy arcn 2198 | exch pop -10 arrowHeadPath 5 line stroke 2199 | end 2200 | 5 175 foldingBottomDiscs 2201 | grestore 2202 | /Helvetica findfont 14 scalefont setfont 2203 | 20 740 moveto (1.) show 2204 | 95 600 moveto (2.) show 2205 | % Move cursor to center of page 2206 | pgsize aload pop 2 div exch 2 div exch translate 2207 | % angle the page 2208 | multiplicationDisc begin 2209 | pgsize aload pop bottomfoldline angleinbox rotate 2210 | gsave 2211 | newpath 2212 | radius 1.1 mul 0 moveto 2213 | radius 1.1 mul neg 0 lineto 2214 | radius 1.1 mul neg radius -2.2 mul lineto 2215 | radius 1.1 mul radius -2.2 mul lineto 2216 | closepath 2217 | clip 2218 | 0 bottomfoldline neg translate 2219 | drawBottomDisc 2220 | grestore 2221 | end 2222 | 180 rotate 2223 | translationDisc begin 2224 | newpath 2225 | radius 1.1 mul 0 moveto 2226 | radius 1.1 mul neg 0 lineto 2227 | radius 1.1 mul neg radius -2.2 mul lineto 2228 | radius 1.1 mul radius -2.2 mul lineto 2229 | closepath 2230 | clip 2231 | 0 bottomfoldline neg translate 2232 | drawBottomDisc 2233 | end 2234 | 2235 | pgsave restore 2236 | showpage 2237 | %%Page: 18 18 2238 | %%BeginPageSetup 2239 | /pgsave save def 2240 | %%EndPageSetup 2241 | % Draw assembly diagram 2242 | gsave 2243 | 60 700 translate 2244 | 0.4 dup scale 2245 | 0 0 70 90 180 5 copy 5 copy 2246 | arc 2247 | pop -10 arrowHeadPath 2248 | exch pop 10 arrowHeadPath 2249 | 5 line stroke 2250 | matrix currentmatrix 2251 | 75 foldprojection concat 2252 | translationDisc begin 2253 | 0 topfoldline translate 2254 | 180 rotate 2255 | drawTopDisc 2256 | end 2257 | dup setmatrix 2258 | multiplicationDisc begin 2259 | 40 bottomfoldline topfoldline sub translate 2260 | end 2261 | 1 179 foldingBottomDiscs 2262 | setmatrix 2263 | 5 foldprojection concat 2264 | multiplicationDisc begin 2265 | 0 topfoldline neg translate 2266 | gsave 2267 | newpath outlineTopDisc 1 setgray fill 2268 | grestore 2269 | drawTopDisc 2270 | end 2271 | grestore 2272 | gsave 2273 | 170 600 translate 2274 | 0.4 dup scale 2275 | multiplicationDisc begin 2276 | 0 0 radius 2 mul 255 260 5 copy arc 2277 | exch pop 10 arrowHeadPath 5 line stroke 2278 | 0 0 radius 2 mul 280 275 5 copy arcn 2279 | exch pop -10 arrowHeadPath 5 line stroke 2280 | end 2281 | matrix currentmatrix 2282 | 175 foldprojection concat 2283 | translationDisc begin 2284 | 0 topfoldline translate 2285 | 180 rotate 2286 | newpath outlineTopDisc verythin line resetstroke 2287 | end 2288 | dup setmatrix 2289 | multiplicationDisc begin 2290 | 0 bottomfoldline topfoldline sub translate 2291 | end 2292 | 1 179 foldingBottomDiscs 2293 | setmatrix 2294 | 5 foldprojection concat 2295 | multiplicationDisc begin 2296 | 0 topfoldline neg translate 2297 | gsave 2298 | newpath outlineTopDisc 1 setgray fill 2299 | grestore 2300 | drawTopDisc 2301 | end 2302 | grestore 2303 | gsave 2304 | 500 230 translate 2305 | 0.4 dup scale 2306 | matrix currentmatrix 2307 | multiplicationDisc begin 2308 | 90 rotate 2309 | matrix currentmatrix 2310 | 0 topfoldline translate [1 0 2 tan 1 0 0] concat 0 topfoldline neg translate 2311 | 0.25 1 scale 2312 | newpath outlineTopDisc verythin line resetstroke 2313 | setmatrix 2314 | matrix currentmatrix 2315 | 0.25 1 scale 2316 | gsave newpath outlineBottomDisc 1 setgray fill grestore 2317 | drawBottomDisc 2318 | setmatrix 2319 | 0 topfoldline translate [1 0 -1 tan 1 0 0] concat 0 topfoldline neg translate 2320 | 0.25 1 scale 2321 | gsave newpath outlineTopDisc 1 setgray fill grestore 2322 | drawTopDisc 2323 | 0 0 moveto 2324 | end 2325 | setmatrix 2326 | 0 55 lineto 2327 | [4.5 4.5] 0 setdash stroke 2328 | 0 55 moveto 4.5 2 mul drawPin 2329 | grestore 2330 | gsave 2331 | 500 100 translate 2332 | 0.4 dup scale 2333 | translationDisc begin 2334 | drawBottomDisc 2335 | gsave 2336 | newpath outlineTopDisc 1 setgray fill 2337 | grestore 2338 | drawTopDisc 2339 | 90 rotate 2340 | 0 0 moveto 4.5 2 mul drawSplitPin 2341 | end 2342 | grestore 2343 | /Helvetica findfont 14 scalefont setfont 2344 | 20 740 moveto (3.) show 2345 | 125 600 moveto (4.) show 2346 | 410 260 moveto (5.) show 2347 | 410 175 moveto (6.) show 2348 | % Move cursor to center of page 2349 | pgsize aload pop 2 div exch 2 div exch translate 2350 | % angle the page 2351 | multiplicationDisc begin 2352 | pgsize aload pop topfoldline angleinbox rotate 2353 | gsave 2354 | 0 topfoldline neg translate 2355 | drawTopDisc 2356 | grestore 2357 | 180 rotate 2358 | translationDisc begin 2359 | 0 topfoldline neg translate 2360 | drawTopDisc 2361 | end 2362 | 2363 | pgsave restore 2364 | showpage 2365 | %%Page: 19 19 2366 | %%PageOrientation: Landscape 2367 | %%BeginPageSetup 2368 | /pgsave save def 2369 | %%EndPageSetup 2370 | 90 rotate 2371 | 10 dict begin 2372 | pgsize aload pop 2373 | /pageW exch def 2374 | /pageH exch def 2375 | 2376 | 0 pageH neg translate 2377 | 2378 | /Helvetica-Bold findfont 10 scalefont setfont 2379 | pageW 2 div pageH 48 sub moveto (MS32 Checksum Table) centreshow 2380 | 2381 | /Courier findfont 8.5 scalefont setfont 2382 | 36 pageH 64 sub % x y 2383 | pageW 64 sub pageH 144 sub 2 div % w h 2384 | 0 drawChecksumTable 2385 | 2386 | 36 pageH 2 div 16 sub % x y 2387 | pageW 64 sub pageH 144 sub 2 div % w h 2388 | 8 drawChecksumTable 2389 | 2390 | end 2391 | pgsave restore 2392 | showpage 2393 | 2394 | %%Page: 20 20 2395 | %%PageOrientation: Landscape 2396 | %%BeginPageSetup 2397 | /pgsave save def 2398 | %%EndPageSetup 2399 | 90 rotate 2400 | 10 dict begin 2401 | pgsize aload pop 2402 | /pageW exch def 2403 | /pageH exch def 2404 | 2405 | 0 pageH neg translate 2406 | 2407 | /Helvetica-Bold findfont 10 scalefont setfont 2408 | pageW 2 div pageH 48 sub moveto (MS32 Checksum Table) centreshow 2409 | 2410 | /Courier findfont 8.5 scalefont setfont 2411 | 36 pageH 64 sub % x y 2412 | pageW 64 sub pageH 144 sub 2 div % w h 2413 | 16 drawChecksumTable 2414 | 2415 | 36 pageH 2 div 16 sub % x y 2416 | pageW 64 sub pageH 144 sub 2 div % w h 2417 | 24 drawChecksumTable 2418 | 2419 | end 2420 | pgsave restore 2421 | showpage 2422 | 2423 | %%Page: 21 21 2424 | %%PageOrientation: Landscape 2425 | %%BeginPageSetup 2426 | /pgsave save def 2427 | %%EndPageSetup 2428 | 90 rotate 2429 | 2430 | % 0 pgsize aload pop pop neg translate 2431 | 0 -750 translate 2432 | 2433 | /Helvetica-Bold findfont 10 scalefont setfont 2434 | pgsize aload pop exch pop 2 div 700 2435 | moveto (ms32 Checksum Worksheet) centreshow 2436 | 2437 | gsave 2438 | 50 680 translate 2439 | ladder begin 2440 | drawgrid 2441 | % (2NAMES5GS8YDXGMLUW34LEN0PRDAK9GLF307N04SN6SKL) fillgrid 2442 | % (2NAMES5GS8YDXGMLUW34LEN0PRDAK9GL ) fillgrid 2443 | end 2444 | grestore 2445 | 2446 | 100 420 moveto 2447 | /Helvetica-Bold findfont 10 scalefont setfont 2448 | (Verifying Checksums) show 2449 | 100 400 moveto 2450 | /Helvetica findfont 9 scalefont setfont 2451 | (Write out the 45 character data portion in the) show 2452 | 100 390 moveto 2453 | (bold boxes, two at a time, starting on the top) show 2454 | 100 380 moveto 2455 | (row. Working from the top row down, look up) show 2456 | 100 370 moveto 2457 | (the first two characters of each odd row in the) show 2458 | 100 360 moveto 2459 | (ms32 Checksum Table and write the ) polymodulus length 10 string cvs concatstrings show 2460 | 100 350 moveto 2461 | (character word into the even row below it. Fill) show 2462 | 100 340 moveto 2463 | (in the odd rows by adding the two characters) show 2464 | 100 330 moveto 2465 | (above each cell. You may use either the) show 2466 | 100 320 moveto 2467 | (addition wheel table. The first few boxes are) show 2468 | 100 310 moveto 2469 | (already filled in for you. The last row will sum) show 2470 | 100 300 moveto 2471 | (to ) show checksumstring {glyphshow} forall ( if the checksum is valid.) show 2472 | 100 260 moveto 2473 | /Helvetica-Bold findfont 10 scalefont setfont 2474 | (Creating Checksums) show 2475 | 100 240 moveto 2476 | /Helvetica findfont 9 scalefont setfont 2477 | (Follow the "Verifying Checksums" instructions) show 2478 | 100 230 moveto 2479 | (to fill in everything but the shaded cells. To fill in) show 2480 | 100 220 moveto 2481 | (the shaded cells, write ) show checksumstring {glyphshow} forall ( into the bottom) show 2482 | 100 210 moveto 2483 | (row. Working from the bottom up, fill in the) show 2484 | 100 200 moveto 2485 | (shaded cells by adding the two characters below) show 2486 | 100 190 moveto 2487 | (each cell. The ) polymodulus length 10 string cvs ( characters in the bold shaded) concatstrings concatstrings show 2488 | 100 180 moveto 2489 | (boxes will be the checksum.) show 2490 | 2491 | 450 650 2492 | /offsety exch def 2493 | /offsetx exch def 2494 | /Courier findfont 10 scalefont setfont 2495 | 20 offsetx add offsety moveto (Addition Table) show 2496 | /Courier-Bold findfont 8 scalefont setfont 2497 | 0 1 31 { 2498 | dup 2 add 7 mul offsetx add offsety 10 sub moveto 2499 | perm exch get 2500 | code exch get glyphshow 2501 | } for 2502 | 2503 | 0 1 31 { 2504 | /Courier-Bold findfont 8 scalefont setfont 2505 | offsetx 34.5 7 mul add offsety 20 sub 2 index 8 mul sub moveto 2506 | dup code exch perm exch get get glyphshow 2507 | /Courier findfont 8 scalefont setfont 2508 | dup 1 31 { 2509 | dup 2 add 7 mul offsetx add offsety 20 sub 3 index 8 mul sub moveto 2510 | perm exch get 2511 | perm 2 index get gf32add code exch get glyphshow 2512 | } for pop } for 2513 | 2514 | pgsave restore 2515 | showpage 2516 | %%EOF 2517 | -------------------------------------------------------------------------------- /docs/wallets.md: -------------------------------------------------------------------------------- 1 | # For Wallet Developers 2 | 3 | Codex32 is a new format for BIP32 master seeds. It is essentially a replacement for 4 | BIP39 or SLIP39 seed words, and the user workflow should be much the same, except 5 | that: 6 | 7 | * The character set is different (bech32 characters rather than a wordlist). 8 | * Seeds may be split across multiple shares, rather than encoded as a single string. 9 | * It is possible to do specific error detection/correction during entry. 10 | 11 | There are two levels of wallet support: 12 | 13 | * The ability to import seeds/shares; here we essentially just have recommendations about dealing with errors. 14 | * The ability to generate seeds/shares on the device; here our guidance is more involved. 15 | 16 | We encourage every wallet to support importing seeds and shares, since 17 | * the technical difficulty is low (roughly on par with that of supporting Segwit addresses, plus optional error-correction support); 18 | * the added functionality is isolated from the rest of the wallet (once the seed is imported you don't care where it came from); and 19 | * beyond correctness of the code, there is little risk (no need to source randomness or execute potentially variable-time algorithms). 20 | 21 | Supporting seed generation is a little more involved so the tradeoff between 22 | implementation complexity and user value is less clear, especially since the 23 | Codex provides users instructions on doing generation themselves. 24 | 25 | ## Error Detection and Correction 26 | 27 | Wallets MUST support detection of errors using the codex32 checksum algorithm. 28 | Wallets SHOULD additionally support error correction; such wallets are referred to as "error-correcting wallets (ECWs)" and have additional requirements. 29 | 30 | An ECW: 31 | 32 | * MUST support correction of up to 4 substitution errors and erasures; 33 | * MAY also support correction of up to 8 erasures, up to 13 if contiguous; 34 | * MAY support correction of further errors, including insertion or deletion errors. 35 | 36 | If a wallet is unable to meet these specifications, it is not an ECW and it SHOULD NOT expose error-correction functionality to the user. 37 | 38 | ## Import Support 39 | 40 | Wallets SHOULD support import of 128- and 256-bit seeds; other lengths are optional. 128-bit seeds encode as 48-character codex32 strings, including the `MS1` prefix. 256-bit seeds encode as 74-character codex32 strings. For other bit-lengths, see the BIP. 41 | 42 | The process for entering codex32 strings is: 43 | 44 | 1. The user should enter the first string. To the extent possible given screen limitations, data should be displayed in uppercase with visually distinct four-character windows. The first four-character window should include the `MS1` prefix, which SHOULD be pre-filled. 45 | * The user MUST be able to enter all bech32 characters. 46 | * ECWs MUST also allow entry of `?` which indicates an erasure (unknown character). 47 | * The user SHOULD NOT be able to enter mixed-case characters. 48 | * If the header is invalid, the wallet SHOULD highlight the problem and request confirmation from the user before allowing additional data to be entered. 49 | * An invalid header is one that starts with a character other than `0` or `2` through `9`, or one which starts with `0` but whose share index is not `S`. For shares after the first, a header is also invalid if its threshold and identifier do not match those of the first share or whose share index matches any previous share. 50 | * ECWs MAY replace the offending characters of the header with `?`. 51 | * Wallets MAY: 52 | * Allow users to enter invalid characters, at their discretion. (This may be useful to guide error correction, by attempting to replace commonly confused characters.) 53 | * Use predictive text for on-screen keyboards to suggest the codex32 checksum characters but if so MUST require user to manually accept the prediction. 54 | * Indicate when the entry has a valid checksum, e.g. by highlighting the string green or displaying the "Submit" option but they MUST NOT submit a string with a valid checksum without user request. 55 | * ECWs MAY additionally indicate when an entry of sufficient length to correct has an invalid checksum, e.g. by highlighting the string red or displaying an "Attempt Correction" option. 56 | 57 | 58 | 1. Once the first string is fully entered, the wallet MUST validate the checksum and header before accepting it. 59 | * If the checksum does not pass, then an ECW: 60 | * MUST attempt error correction of substitution errors and erasures. 61 | * MAY attempt correction by deleting and/or inserting characters, as long as the resulting string has a valid length for a codex32 string. ECWs MAY assume the correct length is the closest of 48 or 74. 62 | * MUST show any valid correction candidate found to the user for confirmation rather than silently applying it. 63 | * If insertion and/or deletion correction candidates are found, the shortest edit distance valid string SHOULD be displayed. 64 | * This is the sum of all edits with erasures and deletes weighted 1 and substitutions and insertions weighted 2. 65 | * ECWs displaying a candidate correction MAY highlight corrected 4-character windows and/or specific correction locations. 66 | 1. After the first string has been entered and accepted, the wallet now knows the identifier, threshold value and valid length. 67 | * If the first string had index `S`, this was the codex32 secret and the import process is complete. 68 | * Otherwise, the fourth character of the share will be a numeric character between `2` and `9` inclusive. The user must enter this many shares in total. 69 | * Wallets MAY encrypt and store recovery progress, to allow recovery without having all shares available at once. The details of this are currently outside of the scope of this specification. 70 | 1. The user should then enter the remaining shares, in the same manner as the first. 71 | * The wallet SHOULD pre-fill the header (threshold value and identifier). 72 | * If the user tries to repeat an already-entered share index, they SHOULD be prevented from entering additional data until it is corrected. 73 | * The wallet MAY guide the user by indicating that a share index has been repeated; 74 | * ECWs may use `?` as a share index arbitrarily many times. If the user indicates they are not repeating the share, the share index SHOULD be replaced by `?`. 75 | * If the checksum fails, the wallet MAY attempt correction by deleting and/or inserting characters. However, the wallet MUST assume the valid length of all subsequent shares is equal to the valid length of the first share, so the number of characters inserted and deleted must net out to the correct length. 76 | 1. For all invalid codex32 strings entered, if an ECW is able to correct the errors (by deletion, insertion, substitution and/or filling erasures), it MUST show the corrected string to the user and request confirmation that the corrected string **exactly matches** the user's copy of the data. It MUST NOT silently apply corrections without approval from the user. 77 | * If no valid string is found with a correct hrp, header and unique index within correction distance limits or within 10 seconds of search, give up. 78 | * ECWs MAY warn the user they've repeated a share if the only valid string found exactly matches a previously entered share. 79 | 1. Once all shares are entered, the wallet should recover the master seed and import this. 80 | 81 | **The master seed should be used directly as a master seed, as specified in BIP32.** 82 | Unlike in BIP39 or other specifications, no PBKDF or other pre-processing should be applied. 83 | 84 | ## Generate Support 85 | 86 | TODO 87 | 88 | -------------------------------------------------------------------------------- /images/volvelles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockstreamResearch/codex32/1a1c22aa895d78f2d385303feb9491d155e14cf7/images/volvelles.jpg -------------------------------------------------------------------------------- /mathematical-companion/images/addition-wheel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockstreamResearch/codex32/1a1c22aa895d78f2d385303feb9491d155e14cf7/mathematical-companion/images/addition-wheel.jpg -------------------------------------------------------------------------------- /mathematical-companion/images/fusion-fold.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockstreamResearch/codex32/1a1c22aa895d78f2d385303feb9491d155e14cf7/mathematical-companion/images/fusion-fold.jpg -------------------------------------------------------------------------------- /mathematical-companion/images/fusion-wheel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockstreamResearch/codex32/1a1c22aa895d78f2d385303feb9491d155e14cf7/mathematical-companion/images/fusion-wheel.jpg -------------------------------------------------------------------------------- /mathematical-companion/images/recovery-wheel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockstreamResearch/codex32/1a1c22aa895d78f2d385303feb9491d155e14cf7/mathematical-companion/images/recovery-wheel.jpg -------------------------------------------------------------------------------- /mathematical-companion/images/translation-wheel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockstreamResearch/codex32/1a1c22aa895d78f2d385303feb9491d155e14cf7/mathematical-companion/images/translation-wheel.jpg -------------------------------------------------------------------------------- /mathematical-companion/main.tex: -------------------------------------------------------------------------------- 1 | % Default course lecture note template by asp 2 | \documentclass[letterpaper]{article} 3 | \usepackage[utf8]{inputenc} 4 | \usepackage[T1]{fontenc} 5 | \usepackage[english]{babel} 6 | \usepackage[top=3cm, bottom=3cm, left=3.85cm, right=3.85cm]{geometry} 7 | \usepackage[onehalfspacing]{setspace} 8 | \usepackage{hyperref} 9 | \usepackage{amsmath,amsthm,amssymb,wasysym,fdsymbol,marvosym} 10 | \usepackage{graphicx} 11 | \usepackage[usenames,dvipsnames]{color} 12 | 13 | \newcommand{\ftwo}{\mathbb{F}_2} 14 | \newcommand{\fttwo}{\mathbb{F}_{32}} 15 | \newcommand{\ftttwo}{\mathbb{F}_{1024}} 16 | \newcommand{\binrep}[1]{\left\{#1\right\}} 17 | \newcommand{\vc}[1]{\texttt{#1}} % volvelle character 18 | 19 | \title{ 20 | \emph{The} codex32 \emph{Mathematical Companion} 21 | } 22 | \author{Pearlwort Snead} 23 | \date{ 24 | %currentdate %gitcommit 25 | } 26 | 27 | \begin{document} 28 | \maketitle 29 | \tableofcontents 30 | 31 | % Actual content start 32 | \section{Mathematical Preliminaries} 33 | 34 | In this first section we give a crash course in field theory. Readers familiar 35 | with this material should have no problem skipping or skimming this section, 36 | at least up to Section~\ref{sec:sss}. 37 | Readers who are completely unfamiliar with this material are unlikely to be 38 | able to follow the condensed exposition, and are encouraged to consult 39 | standard algebra textbooks (e.g.~the one by Dummit and Foote) or Wikipedia. 40 | 41 | \subsection{Fields and $\ftwo$} 42 | Consider the integers modulo 2. This is a set consisting of two equivalence 43 | classes, the evens and the odds, which hereafter we will refer to as 0 and 1. 44 | This set is a \textbf{field}, which means that when we define addition and 45 | multiplication in the obvious way, it satisfies the following axioms: 46 | \begin{enumerate} 47 | \item The set is closed under addition; addition is associative, commutative, 48 | has an identity element 0, and all elements have additive inverses. In other 49 | words it is an \textbf{abelian group} under addition. 50 | \item Similarly, the nonzero elements form an abelian group under multiplication, with identity 1. 51 | \item The \textbf{distributive law} holds, which means that $a(b + c)$ always 52 | equals $ab + ac$. 53 | \end{enumerate} 54 | 55 | We refer to this field as $\ftwo$. For any field, we refer to its nonzero 56 | elements as the \textbf{multiplicative group} of the field. We observe that 57 | the multiplicative group of $\ftwo$ has only the identity element. 58 | 59 | \subsection{Polynomial Rings} 60 | 61 | Since $\ftwo$ has only two elements, it is hard to do interesting algebra on 62 | it. But it is a fact that, by adjoining a formal symbol $x$ to a field, we 63 | can obtain a much bigger (in fact, countably infinite) set of \emph{polynomials} 64 | in $\ftwo$. We denote this set $\ftwo[x]$ and call it the \textbf{polynomial 65 | ring} of the field. 66 | 67 | Formally, the set of polynomials is defined as 68 | 69 | \[ \left\{ \sum_{i=0}^n a_{i}x^i : n\in\mathbb{N}\cup\{0\}, a_i\in \ftwo \right\} \] 70 | 71 | A ring, for our purposes, is defined the same way as a field except that we do 72 | not require multiplication to be invertible. In particular, we will only consider 73 | commutative rings. It is easy to check that the 74 | polynomial ring, endowed with addition and multiplication in the obvious ways, 75 | is in fact a ring. 76 | 77 | For a polynomial of the form $\sum_{i=0}^n a_{i}x^i$ with $a_n\neq0$ we refer to $n$ as the 78 | \textbf{degree} of the polynomial. It is an elementary fact that the product 79 | of polynomials has degree equal to the sum of the degrees of the factors. 80 | 81 | We refer to polynomials of degree 0 as \textbf{constant polynomials}. It is 82 | also a fact that a polynomial has a multiplicative inverse, i.e.~it is a 83 | \textbf{unit}, if and only if it is nonzero constant. 84 | 85 | A polynomial $r$ is \textbf{irreducible} if, whenever it is written as the product 86 | of two polynomials $r=pq$, either $p$ or $q$ is a unit (i.e.~degree 0). Otherwise, 87 | $r$ is \textbf{reducible}. 88 | 89 | \subsection{Quotient Fields} 90 | 91 | Just like we can consider the integers modulo some integer $n$, thus obtaining 92 | $n$ equivalence classes which inherit (roughly) the original ring structure of 93 | the integers, we can consider a polynomial ring modulo some polynomial $p$. In 94 | this case, we will get $m^n$ equivalence classes, where $m$ is the number of 95 | elements in the underlying field and $n$ is the degree of the polynomial. 96 | 97 | We call the set of equivalence classes a \textbf{quotient ring}, and its addition 98 | and multiplication are defined in the obvious way. 99 | 100 | Just like in the integer case, if our polynomial $p$ can be factored into 101 | nonconstant polynomials as $p=p_1p_2$, then the images of $p_1$ and $p_2$ in the quotient ring will 102 | be nonzero but satisfy $p_1p_2 = 0$. In other words they are \textbf{zero 103 | divisors} and imply that multiplication in the ring is not invertible. 104 | 105 | We do not like zero divisors, so from here on out we will be sure to mod out 106 | our polynomial ring only by irreducible polynomials. It is a fact that the 107 | resulting quotient ring will then be a field, and we term it a \textbf{quotient 108 | field}. It is a fact that $x^5 + x^3 + 1$ is irreducible in $\ftwo$, so that 109 | $\ftwo/(x^5 + x^3 + 1)$ is a quotient field with 32 elements. The original 110 | field, $\ftwo$, we refer to as the \textbf{base field}. 111 | 112 | In this field the object $x$ is a field element with a distinct identity and 113 | algebraic properties, so we rename it $\alpha$ to preserve the symbol $x$ to 114 | be an indeterminate used for writing polynomials. 115 | 116 | For any element $\delta$ in the quotient field, we can talk about its 117 | \textbf{minimal polynomial} over the base field. This is a monic polynomial 118 | (one whose highest-degree coefficient is 1) over the base field, of minimal 119 | degree, such that $\delta$ is a root when the polynomial is considered over 120 | the extension field. It is a fact of field theory that for any element 121 | $\delta$, a unique such minimal polynomial exists. We sometimes refer to the 122 | \textbf{degree} of $\delta$ as being the degree of its minimal polynomial. 123 | 124 | Whenever we walk about minimal polynomials or degrees of field elementns, it 125 | is understood that we are considering the elements relative to some base field, 126 | but it will always be clear from context what this base field is, so that we 127 | can use these terms unambiguously. 128 | 129 | It is a fact that, for this specific polynomial, that $\alpha$ is a 130 | \textbf{generator} of the multiplicative group of the quotient field, meaning 131 | that the field in its entirety is equal to 132 | \[ \left\{ \alpha^i : i \in \{0,1,\ldots,30\} \right\} ~\cup~ \left\{ 0 \right\}. \] 133 | 134 | We observe that the order of the multiplicative group is 31, a prime, and therefore 135 | every element of the group except 1 is a generator of the group. Furthermore there 136 | are no nontrivial proper subgroups. These are elementary facts of group theory. 137 | 138 | We refer to this new field as $\fttwo$. It is a fact of field theory that all 139 | groups with 32 elements are isomorphic to this one, which justifies the name. 140 | But bear in mind that, for our purposes, the field was constructed as $\ftwo[x]/ 141 | (x^5 + x^3 + 1)$ and has a distinguished generator $\alpha$ which is a root 142 | of that polynomial. 143 | 144 | \subsection{Vector Spaces} 145 | 146 | We observe that $\fttwo$ is a \textbf{vector space} over $\ftwo$. A vector 147 | space $V$ over a field $\mathbb{F}$ is defined by the following axioms: 148 | 149 | \begin{enumerate} 150 | \item $V$ is an abelian group with operation $+$ and identity $0_V$. 151 | \item $(a + b)v = av + bv$ and $a(u + v) = au + av$ for all $a,b\in \mathbb{F}$ and 152 | $u,v\in V$. 153 | \end{enumerate} 154 | 155 | We refer to a finite sum of the form $\sum_i f_{i}v_i$ with $f_i\in \mathbb{F}$ and 156 | $v_i\in V$ as a \textbf{linear combination}. We observe that every element 157 | of $\fttwo$ is a linear combination of the elements $\{1,\alpha,\alpha^2, 158 | \alpha^3,\alpha^4\}$, and that no smaller set of elements has this property. 159 | We call such a set a \textbf{basis} for $\fttwo$. 160 | 161 | \subsection{Lagrange Interpolation and Shamir's Secret Sharing\label{sec:sss}} 162 | 163 | Let $\mathbb{F}$ be a field and $p$ a polynomial of degree $n$ in $\mathbb{F}[x]$. It is a 164 | standard theorem of algebra that $p$'s value on all points of $\mathbb{F}$ is 165 | implied by its values on any $n+1$ distinct points. 166 | 167 | As discovered by Edward Waring in 168 | 1779\footnote{Waring, Edward (1779). ``Problems concerning interpolations''. 169 | \emph{Philosophical Transactions of the Royal Society}. 69: 59–67. 170 | doi:10.1098/rstl.1779.0008.}, 171 | and later by Joseph-Louis Lagrange in 1795\footnote{Lagrange, Joseph-Louis (1795). 172 | ``Leçon Cinquième. Sur l'usage des courbes dans la solution des problèmes''. 173 | \emph{Leçons Elémentaires sur les Mathématiques}}\footnote{Both citations taken 174 | from Wikipedia's ``Lagrange Interpolation'' page, March 2023.}, 175 | it is actually possible to compute 176 | the value of a degree $n$ polynomial at a field element $x$ explicitly in terms of 177 | its values at $n + 1$ given distinct points $x_i$. 178 | 179 | Specifically, suppose that $p(x_i) = y_i$. Then 180 | \begin{equation} 181 | p(x) = \sum_{i=1}^{n+1} y_i \ell_i(x) \label{eq:linterp} 182 | \end{equation} 183 | where $\ell_i$ is determined entirely by the $x_i$'s, as 184 | \[ \ell_i(x) = \prod_{j\neq i} \frac{x - x_j}{x_i - x_j} \] 185 | 186 | There are several very interesting observations to be made here: 187 | \begin{enumerate} 188 | \item First, for a fixed set of $x_i$'s, we see that the vector space of 189 | $n$-degree polynomials over $\mathbb{F}$ is spanned by the set $\{\ell_i(x)\}$. Since 190 | there are $n+1$ polynomials and this space has dimension $n+1$ (an obvious 191 | basis for it is $\{ 1,x,x^2,\ldots,x^n\}$), this means that the set 192 | $\{\ell_i(x)\}_i$ forms a basis for this space. 193 | \item Further, these basis polynomials satisfy the equality 194 | $\sum_i \ell_i(x) = 1$. (One way to see this is by using 195 | equation~\eqref{eq:linterp} to interpolate the constant one polynomial.) 196 | 197 | This means that equation~\eqref{eq:linterp} is an \textbf{affine 198 | combination} of the $y_i$'s, a strengthening of the familiar notion 199 | of linear combination. This property will become important, as we 200 | will see. 201 | \item If we further fix $x$, we see that knowing $p$'s evaluation at 202 | every $x_i$ is sufficient to determine $p(x)$, while knowing any fewer 203 | evaluations provides zero information about $x$: suppose for example 204 | that $y_n$ is unknown. Then by a suitable choice of $y_n$ in~\eqref{eq:linterp} 205 | we can cause $p(x)$ to take any of the $|\mathbb{F}|$ possible values. 206 | \end{enumerate} 207 | 208 | Putting these facts together, we obtain \textbf{Shamir's Secret Sharing 209 | Scheme} (SSSS) for splitting a secret element of $\mathbb{F}$ into up to $|\mathbb{F}|-1$ 210 | shares, such that a fixed threshold number $k$ of them are sufficient 211 | to reconstruct the secret: 212 | 213 | \begin{enumerate} 214 | \item First, fix an index $s\in \mathbb{F}$ to be the secret index. 215 | \item Generate a random $(k-1)$-degree polynomial $p$ by choosing $k$ 216 | random values and assigning them to be the evaluation of $p$ at specific 217 | points $x_i\in \mathbb{F}$. 218 | 219 | (If the secret is known beforehand, then fix $p(s)$ to be the secret 220 | and generate $k-1$ other evaluations of $p$ randomly.) 221 | \item Distribute the points $x_i$ along with their evaluations $p(x_i)$ 222 | to multiple parties. 223 | \item Then if any $k$ of them come together, they can use equation~\eqref{eq:linterp} 224 | to reconstruct the secret $p(s)$. 225 | \end{enumerate} 226 | 227 | We call the $k$ randomly generated values \textbf{initial shares} and 228 | every other evaluation of $p$ a \textbf{derived share}. 229 | 230 | There are several interesting observations here: 231 | \begin{itemize} 232 | \item If we have a sequence $F_x = \{f_i\}$ of elements of $\mathbb{F}$, we can use 233 | SSSS in parallel on all of them, choosing independently random polynomials 234 | $\{p_i\}$ and distributing the sequence $\{p_i(x)\}$ along with the evaluation 235 | point $x$. 236 | \item If, for some particular $i$, $f_i$ is constant across our $k$ initial 237 | shares $F_{x_1},\ldots,F_{x_k}$, Lagrange interpolation will cause the same 238 | constant to appear in the same position for all derived shares. So you can 239 | have, say, a fixed header on all of your shares which will be preserved by 240 | the secret-sharing mechanism. 241 | \item Similarly, for some particular $i$, you set $f_i=x$, i.e.~you encode the 242 | evaluation point in a fixed place in your sequence, then Lagrange interpolation 243 | will interpolate the polynomial $p(x) = x$ here and place the correct value 244 | of $x$ in the correct place for all shares. 245 | \item Going even further, suppose that for each initial share $F_x=\{f_i\}$, 246 | a particular character can be described as a particular \textbf{affine 247 | transformation} of the others, like 248 | \[ f_j = \sum_{i\neq j} \alpha_i f_i + \beta \] 249 | for a fixed index $j$ and fixed $\beta,\alpha_i\in \mathbb{F}$. 250 | 251 | Then \emph{all derived shares will satisfy the same equation}! 252 | 253 | This is not immediately obvious but can be shown by interpolating the polynomial 254 | \[ q(x) = \sum_{i\neq j} \alpha_i p_i(x) + \beta - p_j(x). \] 255 | By assumption, this polynomial is zero at each evaluation point $x$ and is 256 | therefore zero everywhere. 257 | \end{itemize} 258 | 259 | This fact is so important that we term it the \textbf{Fundamental Theorem of 260 | Computing SSSS with Volvelles}. 261 | 262 | Error correcting codes can be characterized in terms of affine transformations. 263 | For example, the codex32 error correcting code is computed by adding 264 | thirteen extra ``checksum'' characters to our data, each of which is a particular 265 | affine transformation of the other characters. 266 | 267 | The Fundamental Theorem therefore implies that if we apply any checksum derived from 268 | such a code to our initial shares, that the derived shares will automatically 269 | be checksummed as well. 270 | 271 | For more information about volvelles, see the next two sections. 272 | 273 | \section{Volvelles and Tables} 274 | 275 | The basic tools of hand computation are lookup tables for operations in $\fttwo$. 276 | With only 32 elements, we can represent binary operations using reasonably-sized 277 | 1024-element tables. 278 | 279 | The four basic operations are provided in the booklet as ``Principal Tables'' 280 | and also implemented as \textbf{volvelles}, which are simple computers constructed 281 | by two sheets of paper, cut into circles and affixed at the center so that they 282 | are able to rotate relative to each other. We frequently refer to volvelles just 283 | as \emph{wheels}. 284 | 285 | This section explains the underlying operations, the encodings, and the volvelles. 286 | 287 | \subsection{The Bech32 Alphabet} 288 | 289 | The previous section indicated that if $\beta\in\fttwo$, then we can write 290 | \[ \beta = b_4\alpha^4 + b_3\alpha^3 + b_2\alpha^2 + b_1\alpha + b_0 \] 291 | where each $b_i\in\{0, 1\}$ and the choices for $b_i$ are unique. 292 | We can therefore encode $\beta$ as a 5-bit 293 | number by directly encoding the bits $b_i$. Alternately, since there are 294 | only 32 such $\beta$s, we assign them all alphanumeric symbols, with four 295 | symbols to spare. This is the premise behind the \textbf{bech32 alphabet}, 296 | defined in BIP 173, and reproduced on the following page. 297 | 298 | In addition to the bech32 alphabet, which uses Latin characters, we also use 299 | an alternate alphabet using Greek letters and various symbols. 300 | 301 | We have ordered all the symbols in three ways --- $\alpha$betically, 302 | alphabetically, and by their ``numeric'' binary value. These three 303 | representations are useful in different contexts: 304 | \begin{enumerate} 305 | \item Representing elements as a power of $\alpha$ makes multiplication 306 | very easy, since multiplication is just addition mod 31 in the exponent. 307 | 308 | This is how our multiplication wheel can be implemented as a circular slide rule. 309 | \item Representing alphabetically makes it easy for humans to scan and sort. 310 | \item Representing in binary is how the elements are typically stored in 311 | computers, can be used to convert data from other encodings. Addition is 312 | simply xor in this format. 313 | \end{enumerate} 314 | 315 | The following page enumerates all 32 elements of $\fttwo$, ordered by 316 | their different encodings. This page can be used as a reference when 317 | doing operations not supported by the volvelles. (All the operations in 318 | the booklet, including checksumming, secret splitting and recovery, can 319 | be done with the volvelles. But readers of this document may want to 320 | play with the algebra more directly, in which case they will need this 321 | table.) 322 | 323 | \clearpage 324 | ~\\~\\~\\~\\ % vertical centering 325 | 326 | \label{pg:f32table} 327 | % Ordered by power 328 | \begin{minipage}[b]{0.3\linewidth} 329 | \begin{center} 330 | \begin{tabular}{|cccc|} 331 | \hline 332 | Q & $\times$ & - & 00000 \\ 333 | P & $\aleph$ & $\alpha^0$ & 00001 \\ 334 | Z & $\alpha$ & $\alpha^1$ & 00010 \\ 335 | Y & $\Gamma$ & $\alpha^2$ & 00100 \\ 336 | G & $\Theta$ & $\alpha^3$ & 01000 \\ 337 | S & $\Psi$ & $\alpha^4$ & 10000 \\ 338 | F & $\Lambda$ & $\alpha^5$ & 01001 \\ 339 | J & @ & $\alpha^6$ & 10010 \\ 340 | D & $\rho$ & $\alpha^7$ & 01101 \\ 341 | 6 & \textdagger& $\alpha^8$ & 11010 \\ 342 | A & $\P$ & $\alpha^9$ & 11101 \\ 343 | N & \# & $\alpha^{10}$ & 10011 \\ 344 | 0 & $\Phi$ & $\alpha^{11}$ & 01111 \\ 345 | 7 & $\blacklozenge$ & $\alpha^{12}$ & 11110 \\ 346 | 4 & $\cent$ & $\alpha^{13}$ & 10101 \\ 347 | R & $\beta$ & $\alpha^{14}$ & 00011 \\ 348 | X & $\epsilon$ & $\alpha^{15}$ & 00110 \\ 349 | V & $\Pi$ & $\alpha^{16}$ & 01100 \\ 350 | C & \textcurrency{} & $\alpha^{17}$ & 11000 \\ 351 | E & $\oplus$ & $\alpha^{18}$ & 11001 \\ 352 | M & \textdaggerdbl{} & $\alpha^{19}$ & 11011 \\ 353 | L & $\varheartsuit$ & $\alpha^{20}$ & 11111 \\ 354 | H & \EUR{} & $\alpha^{21}$ & 10111 \\ 355 | 8 & $\eta$ & $\alpha^{22}$ & 00111 \\ 356 | W & $\Sigma$ & $\alpha^{23}$ & 01110 \\ 357 | U & $\S$ & $\alpha^{24}$ & 11100 \\ 358 | 3 & $\Omega$ & $\alpha^{25}$ & 10001 \\ 359 | T & $\Xi$ & $\alpha^{26}$ & 01011 \\ 360 | K & \yen{} & $\alpha^{27}$ & 10110 \\ 361 | 9 & $\Delta$ & $\alpha^{28}$ & 00101 \\ 362 | 2 & $\mu$ & $\alpha^{29}$ & 01010 \\ 363 | 5 & \% & $\alpha^{30}$ & 10100 \\ 364 | \hline 365 | \end{tabular} 366 | \end{center} 367 | \end{minipage} 368 | % Ordered by alphabet 369 | \begin{minipage}[b]{0.3\linewidth} 370 | \begin{center} 371 | \begin{tabular}{|ccc|} 372 | \hline 373 | A & $\alpha^9$ & 11101 \\ 374 | C & $\alpha^{17}$ & 11000 \\ 375 | D & $\alpha^7$ & 01101 \\ 376 | E & $\alpha^{18}$ & 11001 \\ 377 | F & $\alpha^5$ & 01001 \\ 378 | G & $\alpha^3$ & 01000 \\ 379 | H & $\alpha^{21}$ & 10111 \\ 380 | J & $\alpha^6$ & 10010 \\ 381 | K & $\alpha^{27}$ & 10110 \\ 382 | L & $\alpha^{20}$ & 11111 \\ 383 | M & $\alpha^{19}$ & 11011 \\ 384 | N & $\alpha^{10}$ & 10011 \\ 385 | P & $\alpha^0$ & 00001 \\ 386 | Q & - & 00000 \\ 387 | R & $\alpha^{14}$ & 00011 \\ 388 | S & $\alpha^4$ & 10000 \\ 389 | T & $\alpha^{26}$ & 01011 \\ 390 | U & $\alpha^{24}$ & 11100 \\ 391 | V & $\alpha^{16}$ & 01100 \\ 392 | W & $\alpha^{23}$ & 01110 \\ 393 | X & $\alpha^{15}$ & 00110 \\ 394 | Y & $\alpha^2$ & 00100 \\ 395 | Z & $\alpha^1$ & 00010 \\ 396 | 0 & $\alpha^{11}$ & 01111 \\ 397 | 2 & $\alpha^{29}$ & 01010 \\ 398 | 3 & $\alpha^{25}$ & 10001 \\ 399 | 4 & $\alpha^{13}$ & 10101 \\ 400 | 5 & $\alpha^{30}$ & 10100 \\ 401 | 6 & $\alpha^8$ & 11010 \\ 402 | 7 & $\alpha^{12}$ & 11110 \\ 403 | 8 & $\alpha^{22}$ & 00111 \\ 404 | 9 & $\alpha^{28}$ & 00101 \\ 405 | \hline 406 | \end{tabular} 407 | \end{center} 408 | \end{minipage} 409 | % Ordered by binary 410 | \begin{minipage}[b]{0.3\linewidth} 411 | \begin{center} 412 | \begin{tabular}{|ccc|} 413 | \hline 414 | Q & - & 00000 \\ 415 | P & $\alpha^0$ & 00001 \\ 416 | Z & $\alpha^1$ & 00010 \\ 417 | R & $\alpha^{14}$ & 00011 \\ 418 | Y & $\alpha^2$ & 00100 \\ 419 | 9 & $\alpha^{28}$ & 00101 \\ 420 | X & $\alpha^{15}$ & 00110 \\ 421 | 8 & $\alpha^{22}$ & 00111 \\ 422 | G & $\alpha^3$ & 01000 \\ 423 | F & $\alpha^5$ & 01001 \\ 424 | 2 & $\alpha^{29}$ & 01010 \\ 425 | T & $\alpha^{26}$ & 01011 \\ 426 | V & $\alpha^{16}$ & 01100 \\ 427 | D & $\alpha^7$ & 01101 \\ 428 | W & $\alpha^{23}$ & 01110 \\ 429 | 0 & $\alpha^{11}$ & 01111 \\ 430 | S & $\alpha^4$ & 10000 \\ 431 | 3 & $\alpha^{25}$ & 10001 \\ 432 | J & $\alpha^6$ & 10010 \\ 433 | N & $\alpha^{10}$ & 10011 \\ 434 | 5 & $\alpha^{30}$ & 10100 \\ 435 | 4 & $\alpha^{13}$ & 10101 \\ 436 | K & $\alpha^{27}$ & 10110 \\ 437 | H & $\alpha^{21}$ & 10111 \\ 438 | C & $\alpha^{17}$ & 11000 \\ 439 | E & $\alpha^{18}$ & 11001 \\ 440 | 6 & $\alpha^8$ & 11010 \\ 441 | M & $\alpha^{19}$ & 11011 \\ 442 | U & $\alpha^{24}$ & 11100 \\ 443 | A & $\alpha^9$ & 11101 \\ 444 | 7 & $\alpha^{12}$ & 11110 \\ 445 | L & $\alpha^{20}$ & 11111 \\ 446 | \hline 447 | \end{tabular} 448 | \end{center} 449 | \end{minipage} 450 | 451 | \clearpage 452 | 453 | \subsection{The Addition Wheel} 454 | 455 | Our first volvelle, the Addition Wheel, is a \textbf{slide chart}, which is 456 | essentially a lookup table. The top sheet has 32 holes cut into it, one for 457 | each character of $\fttwo$, and an index pointer. The bottom sheet has 1024 458 | results, of which 32 are revealed at each index. 459 | 460 | \begin{center}\includegraphics[scale=0.25]{images/addition-wheel.jpg}\end{center} 461 | 462 | This volvelle computes addition in $\fttwo$. To compute $x+y$, rotate so that 463 | the pointer is pointing at either $x$ or $y$, then look up the other one on 464 | the front page. It is instructive to observe that the expected symmetries are 465 | there: $x+y=y+x$, $x+x=Q$, $x+Q=x$, etc. 466 | 467 | \paragraph{Volvelles and Algebraic Structure.} The addition wheel has 32 holes cut 468 | in the face, corresponding to the 32 bech32 characters. If all 32 rotations 469 | of the volvelle revealed distinct locations on the bottom wheel, it would 470 | require 1024 symbols to be printed on the bottom wheel --- but if there 471 | were algebraic structure relating the results of different volvelle positions, 472 | we could reduce this number. 473 | 474 | We will return to this idea in the next section, about slide rules, but for now 475 | we simply observe that we did \textbf{not} reduce the number of symbols from 476 | the maximum 1024. 477 | 478 | Why not? Well, observe that the way to reduce symbols is to have two windows at 479 | the same radius from the center of the volvelle. Then on the bottom sheet, a 480 | single circle of values would provide the revealed symbols for both windows. 481 | Let's say that one window is labeled $y\to$, and the other labeled $z\to$. Then 482 | since the windows are at a fixed angle $\theta$ from each other (being printed 483 | on the same solid sheet of paper), we would require the bottom circle of values 484 | to satisfy 485 | \[ \textnormal{for all } x\in\fttwo:\qquad x + y \textnormal{ and } x + z \textnormal{ are at angle $\theta$ to each other} \] 486 | Note that if $x$ ranges over all values in $\fttwo$ then so does $x + y$. Furthermore, 487 | we have $x + z = (x + y) + (y + z)$ (recall we are in characteristic 2). 488 | Thus substituting $x + y$ in place of $x$ gives us 489 | \[ \textnormal{for all } x\in\fttwo:\qquad x \textnormal{ and } x + (y + z) \textnormal{ are at angle $\theta$ to each other} \] 490 | Since $x + (y + z) + (y + z) = x$, two applications of the above condition gives us 491 | \[ \textnormal{for all } x\in\fttwo:\qquad x \textnormal{ is at angle $2\theta$ from itself} \] 492 | It is now clear that we either need to repeat characters (defeating the goal 493 | of reducing the amount of symbols on the bottom wheel) or have $\theta=180^\circ$. 494 | 495 | Okay, so perhaps we can get a 50\% reduction in density for the bottom wheel, by 496 | setting $\theta=180^\circ$ and having the windows on opposite sides of the top 497 | wheel be at the same radius and use the same set of bottom-wheel symbols. 498 | 499 | Let's play this out. Suppose we place the \vc{A} and \vc{T} windows at the 500 | same radius on opposite sides of the top wheel. Note that \vc{A} and \vc{T} 501 | differ by \vc{K}. Now suppose that when the pointer is at some arbitrary symbol $x$, 502 | the two opposite windows at \vc{A} and \vc{T} show $x + A = y$ and $x + T = z$. 503 | Adding these two equations gives $y + z = A + T = K$. 504 | 505 | In other words, for this compression to work with the choice of \vc{A} and \vc{T} 506 | windows being at the same radius, we need every pair of opposing 507 | symbols to add to \vc{K}; i.e.~we need to take the sixteen 2-element cosets 508 | obtained by modding out by \vc{K} and then order the symbols so that each 509 | coset's members appear opposite each other. 510 | 511 | It can be seen, by modding out by every possible symbol, and trying various 512 | orderings of the resulting cosets, that no such choice will lead to a 513 | ``natural'' ordering\footnote{There are 16 cosets, so $15!\approxeq2^{40}$ 514 | different arrangements around a circle. Then you can exchange the members 515 | in each coset, for another $2^{15}$ possibilities. So an exhaustive search 516 | would require about $2^{55}$ work. I did not do an exhaustive search, so I 517 | may be wrong in claiming that ``no such choice'' works. But I spent several 518 | hours starting from random permutations and then looking for local optima 519 | and never got very close. My measure of ``naturalness'' was to take the 520 | distance $d$ between each character and its alphanumeric successor, and to 521 | sum all the $2^d$s.}. This means that to get this compression, we'd need 522 | to reorder the wheel such that users wouldn't know which direction to spin 523 | to find a desired symbol, and the resulting harm to usability would exceed 524 | the benefit of having larger windows. 525 | 526 | If this argument was too abstract, take the addition volvelle and spin it to 527 | \vc{C} (one right of \vc{A}) and look at the symbols in the \vc{A} and \vc{T} 528 | windows. Then spin it $180^\circ$ to \vc{U} (one right of \vc{T}) and look 529 | at the same symbols. You will see different symbols. For this scheme to work, 530 | they would need to be the same symbols. Ergo, we'd have to reorder the symbols 531 | in a confusing order to make this work. 532 | 533 | (By the way, this \emph{could} be made to work if we rearranged our mapping 534 | between bech32 symbols and $\fttwo$ objects so that the \vc{K}-cosets, or 535 | whatever, were naturally ordered. But deviating from the bech32 spec in this 536 | way, for such a minor benefit in volvelle layout, doesn't seem worth the 537 | potential confusion/incompatibility between the schemes.) 538 | 539 | \subsection{The Fusion-Translation Wheel} 540 | 541 | \paragraph{Fusion.} 542 | While the addition volvelle could not be re-arranged to reduce the number of 543 | symbols beyond 1024, let's consider the second operation we might like to do: 544 | \emph{multiplication}. 545 | 546 | For reasons that we will describe later, when multiplying in $\fttwo$ it turns 547 | out that we want to use the alternate symbol alphabet rather than the bech32 548 | alphabet. We also don't care so much about multiplication by zero, which always 549 | results in zero, which we can tell the user rather than putting it into a 550 | volvelle. 551 | 552 | Now, we have 31 nonzero elements, so a volvelle would naively have $31^2=961$ 553 | entries. Can we do better? Using the same reasoning as with the addition volvelle, 554 | if we wanted two windows $y\to$ and $z\to$ to share a radius, we'd need that 555 | \[ \textnormal{for all } x\in\fttwo:\qquad xy \textnormal{ and } xz \textnormal{ are at angle $\theta$ to each other} \] 556 | We have a group under multiplication with 31 elements in it. Since 31 is prime, it is then a fact 557 | that if we choose any element $\alpha\in\fttwo^*$ except 1, that $\alpha$ 558 | \textbf{generates} the group. Meaning that every element $x$, including 1, 559 | can be written as $z=\alpha^{i_x}$ where $i_x$ is some integer modulo 31. So we 560 | may write 561 | \[ \textnormal{for all } \alpha^{i_x}\in\fttwo:\qquad \alpha^{i_x}\alpha^{i_y} = \alpha^{i_x+i_y} \textnormal{ and } \alpha^{i_x}\alpha^{i_z} = \alpha^{i_x+i_z} \textnormal{ are at angle $\theta$ to each other} \] 562 | 563 | By squinting at this for a moment, you can observe that if $\theta$ is one 31th 564 | of a full rotation, and we make sure that each $\alpha^i$ on the front wheel is 565 | followed by $\alpha^{i+1}$, then \emph{every single window can have the same 566 | radius}. In fact, we don't need windows, since the bottom wheel now has only a 567 | single circle of symbols, all of which are always visible. 568 | 569 | This is the intuition behind the \textbf{multiplication wheel}, which is actually 570 | a \textbf{circular slide rule}: 571 | 572 | \begin{center}\includegraphics[scale=1.00]{images/fusion-wheel.jpg}\end{center} 573 | 574 | We use the term \textbf{fusion} rather than \emph{multiplication} because we were 575 | concerned that by having wheels labeled both ``addition'' and ``multiplication'', 576 | that users may use their intuition about these operations on the integers, and 577 | take incorrect shortcuts. 578 | 579 | \paragraph{Translation.} There are actually two kinds of multiplication that 580 | we might want to do: symbol-by-symbol multiplication and symbol-by-bech32-character 581 | multiplication. The former we called fusion, and latter we refer to as 582 | \textbf{translation}. 583 | 584 | Algebraically, fusion and translation are identical, of course. But in 585 | practice, fusion is used to multiply $k=2$ Lagrange basis polynomials 586 | (encoded as symbols) to get $k>2$ basis polynomials (also symbols). 587 | Meanwhile transalation is used to multiple the basis polynomials (symbols) by 588 | share values (characters) to get translated shares (characters). 589 | 590 | So by using different names for the wheels, and different encodings of the 591 | underlying field elements, we have implemented a ``type system'' which makes 592 | it hard for users to do operations in an incorrect order. 593 | 594 | Since translation is identical to fusion, we might hope that we could 595 | construct the translation slide rule by simply relabelling the fusion one. 596 | Indeed, we could do this by changing the inner wheel to use bech32 characters 597 | rather than symbols. Then to translate a character $c$ by a symbol $\sigma$, 598 | for example, the user would turn the wheel to point to $c$, look for 599 | $\sigma$, and find what it points to. 600 | 601 | There are a couple minor issues with this approach: 602 | \begin{itemize} 603 | \item Because the correspondence between bech32 characters and $\fttwo$ 604 | does not have any algebraic structure, ordering alphanumeric characters by 605 | increasing powers of $\alpha$ results in an unintuitive ordering. 606 | 607 | We have chosen to just live with this problem. All the characters are visible 608 | at the same time, so it isn't nearly as a bad a usability burden as it would've 609 | been with a volvelle. 610 | \item Since zero (\vc{Q}) is a valid share value, we actually do need to think 611 | about multiplication by 0. This differs from the fusion case, since zero is not 612 | a valid Lagrange basis polynomial. (0 can only be obtained by trying to use the 613 | \vc{S} share as an \emph{input} rather than \emph{output}. But as we will see 614 | in the next section, the Recovery Wheel won't let you do this.) 615 | 616 | We solve this by just printing $\vc{Q}\leftrightarrow\vc{Q}$ on the handle of 617 | the slide rule. 618 | \end{itemize} 619 | 620 | The biggest issue with just relabeling the inner wheel is that the user is 621 | trying to map bech32 characters to bech32 characters, but indexing this mapping by 622 | a symbol. So if she wants to translate a share by $\sigma$ say, for all 48 623 | characters in her share, she'll need to rotate the wheel to the input 624 | character then check where $\sigma$ points to find the output character. 625 | 626 | This is tiring and error prone. 627 | It would be better if she could just turn the wheel to $\sigma$ and then look 628 | up every character without further rotations. How can we achieve this? 629 | 630 | After several abortive attempts to add a third circle of characters to the 631 | volvelle, Leon had the idea to put the Translation Wheel \emph{on the back of the 632 | Fusion Wheel}. Then the user can use the Fusion side to index the mapping, then 633 | flip over the wheel and use the Translation side to actually do the mapping! 634 | 635 | Of course, we are mathematicians, so while we have brass fasteners, we have 636 | no glue. So the actual assembly method is to print both sides attached to 637 | each other, then fold them together. We then have two slide rules, whose 638 | top and bottom wheels are now the ``outer'' and ``inner'' wheels, and which 639 | are on opposite sides of the same pages. 640 | 641 | \begin{center}\includegraphics[scale=1.00]{images/fusion-fold.jpg}\end{center} 642 | \begin{center}\includegraphics[scale=0.80]{images/translation-wheel.jpg}\end{center} 643 | 644 | (The wheel in the photo is incorrectly labeled ``Recovery'', because I messed 645 | up the PostScript and didn't notice before printing.) 646 | 647 | \subsection{The Recovery Slide Rule} 648 | 649 | There is one remaining paper computer to design. This one is the \textbf{Recovery 650 | Wheel}, which computes Lagrange basis polynomials, evaluated at \vc{S}. That is, 651 | it computes the map 652 | \[ (p, r) \mapsto \frac{r + S}{r + p} = \frac{r + S}{r + S + p + S} \eqqcolon \frac{\hat{r}}{\hat{r}+\hat{p}} = \frac{1}{1 + \hat{p}/\hat{r}} \] 653 | (where $\hat{c}\coloneqq c+\vc{S}$ is just a relabeling of our character set). 654 | 655 | Note that both inputs are bech32-encoded share indices, while the output is 656 | a symbol-encoded Lagrange basis polynomial evaluated at \vc{S}. 657 | 658 | The idea is that the user would turn the wheel so that the pointer points at 659 | share index $p$, looks on the wheel for the arrow labeled $r$, and the resulting 660 | symbol is the Lagrange basis polynomial. We notice that the result is undefined 661 | when $r = p$, which corresponds to the case when you are trying to use the same 662 | share twice, which makes intuitive sense. 663 | 664 | Now, as before we may write $\hat{p}=\alpha^{i_{\hat{p}}}$ and 665 | $\hat{r}=\alpha^{i_{\hat{r}}}$, where $\alpha$ is a generator of our multiplicative 666 | group. Then we have 667 | \[ (p, r) \mapsto \left[ 1 + \alpha^{i_{\hat{p}} - i_{\hat{r}}} \right]^{-1} \] 668 | 669 | The addition of 1 and the multiplicative inversion can be accomplished by more 670 | relabeling, and the fact that we have a difference rather than sum in the exponent 671 | of $\alpha$ can be accomodated by reversing the direction of our arrows relative 672 | to the arrows on the multiplication wheel. 673 | 674 | As with the Translation Wheel, we are indexing by a different set than either 675 | our input or output. However, the Recovery Wheel only needs to be used once per 676 | share, so it is fine for it to be a bit less convienent to use. The real usability 677 | concern is that the use of bech32 characters for both indexing and output makes 678 | it quite easy to accidentally use the wheel backward. 679 | 680 | Putting it all together, to get a recovery slide rule, we 681 | \begin{enumerate} 682 | \item Reverse the arrows in our multiplication slide rule, as we are doing 683 | division rather than multiplication. 684 | \item Apply $x\mapsto x+\vc{S}$ to the inputs (which are now on the bottom 685 | wheel). 686 | \item Apply $x\mapsto[1+x]^{-1}$ to the output (on the top wheel). 687 | \item Since 0 is not a possible output value, there is a blank space on the top 688 | wheel, which conveniently is located directly below the handle. We can take 689 | advantage of this blank space to label the handle ``share to translate'', which 690 | hopefully will guide users to use the device correctly. 691 | \end{enumerate} 692 | 693 | \begin{center}\includegraphics[scale=1.00]{images/recovery-wheel.jpg}\end{center} 694 | 695 | Finally, we observe that, if we center our slide rule on the $\alpha^0$ output 696 | slot (the one with the pointer on it, rather than a symbol), looking $j$ positions 697 | to the left and right we have the values 698 | \[ \left[ 1 + \alpha^{-j} \right]^{-1} \textnormal{ and } \left[ 1 + \alpha^j \right]^{-1} \] 699 | and it can be computed directly that these sum to 1. 700 | In fact, this is exactly the relation between pairs of Lagrange basis polynomials 701 | for the case $k=2$. (There are only two of them, and they add up to one because 702 | Lagrange basis polynomials form affine sets.) So on the plain (non-art-covered) 703 | version of this wheel, we can draw horizontal lines between these pairs, making 704 | the wheel visually distinct from the other plain wheels and also guiding the user 705 | in the $k=2$ case.\footnote{This was another innovation from Leon, who observed that 706 | to look up the basis polynomials for $k=2$, you turn to one symbol and look up the 707 | other; then swap the symbols and repeat. So by some sort of symmetry of turning, the 708 | resulting outputs will be pairs of symbols opposite each other. 709 | 710 | I'm not sure I see the symmetry he was referring to, but there is a simpler 711 | algebraic reason: when you exchange $r$ and $p$, as you do when computing the 712 | two Lagrange basis polynomials for the $k=2$ case, you replace $\hat{r}/\hat{p}$ 713 | with its reciprocal, which is the same as mirroring it over the $\alpha^0$ 714 | position on a slide rule. Then the fact that these add to 1 is just a restatement 715 | of the fact that Lagrange basis polynomials form an affine set.} 716 | 717 | \paragraph{The two alphabets.} 718 | Now that we have all four of our paper computers defined, we can see the 719 | justification for having two alphabets: \emph{share data} and \emph{share 720 | indices}, which are secret and need to be stored, are represented by bech32 721 | characters, while \emph{Lagrange multipliers}, which are not secret and 722 | never stored, are represented by symbols. 723 | 724 | Because share indices are actually encoded as share data, these arguably-distinct 725 | kinds of data must be encoded the same way. There does not seem to be any ergonimic 726 | way to avoid this. 727 | 728 | This user-centric categorization is conveniently reflected in the operations that 729 | need to be done: 730 | 731 | \begin{itemize} 732 | \item Share data may be added to other share data, but it is never multiplied 733 | by other share data, only by Lagrange basis polynomials (i.e.~``translation''). 734 | \item Lagrange polynomials never added to each other, only multiplied by 735 | each other (i.e.~``fusion'') or by share data (i.e.~``translation''). In these 736 | two cases the output data is different. 737 | \item To do share derivation or recovery, a user cannot even start without 738 | obtaining a Lagrange multiplier, which will come either from the Recovery Wheel 739 | or from tables provided in the booklet. This ensures the user has obtained all 740 | the necessary data before being able to start secret sharing. 741 | \end{itemize} 742 | 743 | More explicitly, both share creation and secret recovery are implemented as 744 | Lagrange interpolation using Equation~\eqref{eq:linterp} on page~\pageref{eq:linterp}. 745 | In that equation, we are performing a linear combination of $y_i$ values, which 746 | are share data (bech32 characters), multiplied by evaluated Lagrange basis 747 | polynomials $\ell_i(x)$s (symbols). 748 | 749 | The Lagrange basis polynomials are always products of the form $(x + y)/(x + z)$. 750 | The Recovery Wheel and Fusion Wheel allow the user to compute these products when 751 | they are evaluated at \vc{S}, regardless of which of the combinatorially-many sets 752 | of shares they may have started with. During share derivation, the complete products 753 | are pre-computed and provided in tables, and require the user to begin with a 754 | prescribed set of initial shares. 755 | 756 | \section{BCH Codes} 757 | 758 | Now we have our mathematical foundation, from field arithmetic to Lagrange 759 | interpolation, and have seen how to express this in volvelles. The final 760 | component of our scheme is the checksum, which is a \textbf{BCH code}. There is a 761 | rich and enormous theory underlying BCH codes, and linear codes in general, 762 | but we will take an operationalist point of view and summarize just the facts 763 | that we need: 764 | 765 | An \textbf{error-correcting code} is a mapping from a set of raw data, called 766 | \textbf{messages} into a larger set of \textbf{codewords} which have 767 | some extra algebraic structure. In particular, between every pair of 768 | codewords there is a minimum \textbf{distance} $d$, which measures the 769 | number of characters at which the two codewords differ. 770 | 771 | Since all codewords differ by at least the minimum distance $d$, if there 772 | are at most $d-1$ errors in a codeword, the result is guaranteed not to 773 | be a valid codeword, and to be detected as an error pattern. If there are fewer 774 | than $d/2$ errors, the correct codeword is uniquely determined by being 775 | the closest codeword to the error pattern, which means that this many 776 | errors can, in principle, be corrected. 777 | 778 | A \textbf{BCH code} is a specific type of error-correcting code. In a BCH 779 | code, codewords are constructed by encoding data as the coefficients of a 780 | large polynomial, then affixing some number of \textbf{checksum characters}. 781 | The checksum characters are chosen so that the full result is in 782 | a specific residue class modulo a \textbf{generator polynomial} $G(x)$. 783 | 784 | Given a potential codeword, there is a unique minimum-degree polynomial 785 | equivalent to this codeword modulo $G(x)$, which can be found by modular 786 | reduction. We refer to this reduced polynomial as the \textbf{residue} 787 | of the codeword. 788 | 789 | There are several properties of a BCH code that we will need: 790 | \begin{itemize} 791 | \item The \textbf{degree} of the code is the maximum degree of the roots of 792 | its generator polynomial. (Since the generator polynomial is not, in general, 793 | irreducible, this is \emph{not} the degree of the generator polynomial.) 794 | 795 | The degree of our BCH codes, and that of bech32, is 2. Degree-1 BCH codes are 796 | called \textbf{Reed-Solomon codes}. 797 | 798 | The degree indicates the dimension of the extension field you need to work 799 | in to do error correction. So for our codes, you would need to work in a 800 | quadratic extension of $\fttwo$, i.e.~$\ftttwo$. 801 | 802 | \item The \textbf{length} of the code is how long a coded message can be 803 | (including the checksum!) for the code to retain its error-correcting 804 | properties. 805 | 806 | The length $\ell$ can be computed as the smallest polynomial of the form 807 | $x^\ell - x$ that the generator divides. This can be seen by observing 808 | that if you have a message of this length, that the $\ell$th character 809 | will be interpreted as the coefficient of $x^{\ell-1} \equiv 1$ mod $G(x)$, 810 | meaning that it will just mask your first character rather than being 811 | checksummed independently. 812 | 813 | We have two codes -- the ``normal'' codex32 has a length of 93. For 512-bit 814 | seeds, we need more than 93 characters, so we use an alternate code of 815 | length 1023. The length of bech32 is also 1023 --- although through 816 | an exhaustive search, it was found to have better error detection properties 817 | than the algebra would suggest, up to length 71. 818 | 819 | \item The $m$-value, or \textbf{target residue}, is the specific residue that 820 | all codewords must must have modulo $G$. All $m$ values are equivalent, in 821 | the sense that it is easy to convert from one $m$ value to another (simply 822 | add the difference to the checksum characters of every codeword). 823 | 824 | But $m=0$ has the particularly bad property that any codeword can be extended 825 | by addition of an arbitrary number of 0s to get another valid codeword. (This 826 | highlights the fact that BCH codes are designed to handle only \emph{substitution} 827 | or \emph{erasure} errors, not insertions or deletions.) Other small values of 828 | $m$ have similar issues; bech32 was originally defined to have $m=1$ but later 829 | needed to be modified to bech32m for this reason. bech32m uses a large $m$ 830 | instead\footnote{For more details, see 831 | \url{https://gist.github.com/sipa/14c248c288c3880a3b191f978a34508e}}. 832 | 833 | On the other hand, $m=0$ makes a BCH code a \textbf{linear code}, and brings 834 | with it a ton of algebraic properties which are needed for analysis, so this 835 | is what is used in the literature. 836 | 837 | In practice it is common to use a string of all-bits-one for $m$. For our code, 838 | we chose characters which spell out \vc{SECRETSHARE32} for the standard code, 839 | and \vc{SECRETSHARE32EX} for the alternate one. 840 | 841 | \item The \textbf{checksum length} is the number of extra characters that 842 | need to be added to a string to ensure that it has the correct residue. 843 | This value \emph{is} the degree of the generator polynomial. 844 | 845 | Our standard code, of length 93, has a checksum length of 13. The alternate 846 | code has a checksum length of 15. 847 | \end{itemize} 848 | 849 | We will return to BCH codes in the next section, when we discuss the Checksum 850 | Worksheet and the properties of BCH codes which make them compatible with 851 | Shamir's Secret Sharing. 852 | 853 | \subsection{The \texttt{codex32} Checksum} 854 | 855 | We now get into the meat of the document, where we descibe how the actual 856 | user processes are implemented. We start with the checksum. 857 | 858 | Our primary checksum is defined by a BCH code with generator polynomial 859 | \begin{align*} 860 | G(x) = x^{13} 861 | &+ \vc{E}x^{12} + \vc{M}x^{11} + \vc{3}x^{10} + \vc{G}x^9 + \vc{Q}x^8 + \vc{E}x^7 \\ 862 | &+ \vc{E}x^6 + \vc{E}x^5 + \vc{L}x^4 + \vc{M}x^3 + \vc{C}x^2 + \vc{S}x + \vc{S} 863 | \end{align*} 864 | Our alternate checksum has generator polynomial 865 | \begin{align*} 866 | \hat{G}(x) = x^{15} 867 | &+ \vc{0}x^{14} + \vc{2}x^{13} + \vc{E}x^{12} + \vc{6}x^{11} + \vc{F}x^{10} + \vc{E}x^9 \\ 868 | &+ \vc{4}x^8 + \vc{X}x^7 + \vc{H}x^6 + \vc{4}x^5 + \vc{X}x^4 + \vc{9}x^3 \\ 869 | &+ \vc{K}x^2 + \vc{Y}x^1 + \vc{H} 870 | \end{align*} 871 | Because the alternate code has substantially the same properties as the primary 872 | one, and the primary one is easier to work with, we will only focus on the 873 | primary one from here on. 874 | 875 | To interpret the $\fttwo$ elements such as $\vc{E}$, $\vc{M}$, etc., recall that 876 | there is a table on page~\pageref{pg:f32table} containing all the elements and 877 | their alternate encodings. 878 | 879 | To checksum a string of bech32 characters $\{v_i\}_{i=1}^n$, we want to encode it 880 | as a polynomial 881 | \[ p(x) = x^n + \sum_{i=0}^{n-1} v^{n-i} x^i \] 882 | such that $p(x)$ mod $G(x)$ is equal to \vc{SECRETSHARE32}, i.e. 883 | \begin{align*} 884 | \vc{S}x^{12} + \vc{E}x^{11} + \vc{C}x^{10} + \vc{R}x^9 + \vc{E}x^8 + \vc{T}x^7 885 | + \vc{S}x^6 + \vc{H}x^5 + \vc{A}x^4 + \vc{R}x^3 + \vc{E}x^2 + \vc{3}x + \vc{2}. 886 | \end{align*} 887 | 888 | Remembering throughout that an $n$-degree polynomial has $(n+1)$ terms, the way that 889 | we achieve this for an arbitrary message $\{ v_i \}$ is to concatenate the target 890 | residue \vc{SECRETSHARE32} to $\{ v_i \}$ as 891 | \begin{equation} 892 | \hat{p}(x) = x^{n+13} + \sum_{i=0}^{n+13-1} v^{n+13-i} x^{i} \textnormal{ mod } G(x) 893 | \label{eq:modg} 894 | \end{equation} 895 | We call the thirteen resulting characters the \textbf{checksum} and replace our 896 | copy of \vc{SECRETSHARE32} with them. 897 | 898 | Why does this work? Basically, to get our residue to match some specific target, we 899 | just need to subtract the existing residue from our string, then add the target. Since 900 | both the existing residue and the target value have degree 12, this will affect only 901 | the rightmost 13 charactes of our string. 902 | 903 | So we multiply by $x^{13}$, moving our actual data to the left 13 spaces, and add 904 | \vc{SECRETSHARE32}. Then the above instructions ``subtract the existing residue from 905 | our string, then add the target'' are easy. First we add the target, which since we 906 | are in characteristic 2, means zeroing out the \vc{SECRETSHARE32} we just added by 907 | adding another one. Then we subtract the existing residue from this 0, which (again 908 | because of characteristic 2, where negation is a no-op) means just copying it into place. 909 | 910 | \subsection{The Checksum Worksheet} 911 | 912 | As described in the last section, our checksum verification algorithm is: encode the 913 | message as a polynomial, take it mod $G(x)$, and compare it against the fixed string 914 | \vc{SECRETSHARE32}. The checksum generation algorithm is essentially the same: 915 | append \vc{SECRETSHARE32} in place of the checksum, take the resulting polynomial 916 | mod $G(x)$, then use the result as the actual checksum. 917 | 918 | How do we do this in practice? In short, we build up our polynomial two characters 919 | as a time: multiply by $x^2$, add two new characters, reduce, repeat. In detail: 920 | 921 | \paragraph{Checksum Verification.} 922 | 923 | \begin{enumerate} 924 | \item Take the prefix \vc{ms1} and encode it as a bech32 human readable part (HRP), 925 | which works out to \vc{RRQDN} (the process is to take the high 3 bits of each 926 | 8-bit ASCII character, followed by 0, followed by the low 5 bits of each character). 927 | Prefix a 1, or \vc{P} in bech32. The resulting initial polynomial is \vc{PRRQDN}, or 928 | \[ x^5 + \vc{R}x^4 + \vc{R}x^3 + \vc{D}x + \vc{N} \] 929 | Multiply this by $x^{13}$ and reduce it mod $G(x)$. The result will be 930 | \vc{33XW87RRYLJG}. This string is initially filled in in the checksum 931 | worksheet. 932 | 933 | \item Fill in the first 13 characters of the data to be checksummed. Add this to 934 | the pre-filled values. The result will be the residue of \vc{ms1}, 935 | and this is what the first three lines of the checksum worksheet compute. 936 | 937 | \item Multiply by $x^2$ and fill in the next two characters of data. The multiplication 938 | is done by shifting the characters two spaces to the left, equivalently, shifting the 939 | entire rest of the worksheet 2 spaces to the right. This accounts for the diagonal 940 | shape of the worksheet. 941 | 942 | The resulting 3rd row has 15 characters, where the leftmost characters $\ell_1$ 943 | and $\ell_2$ are the coefficients of $x^{13}$ and $x^{14}$. 944 | 945 | \item Reduce the whole string mod $G(x)$: first, compute the reduction of the 946 | leftmost characters, $\ell_1x^{14} + \ell_2x^{13}$. This is a nontrivial 947 | computation, so we simply provide a giant ``Checksum Table'' in which the user 948 | can look up $\ell_1$ and $\ell_2$. 949 | 950 | Copy the table entry into line 4. Then add the remaining 13 characters, which 951 | are unaffected by reduction since $G$ has degree 13, to this. 952 | 953 | It may be instructive to read the PostScript code for the Checksum Table, which 954 | enumerates all the two-character possibilities, and for each one, computes 955 | the reduction mod $G$. 956 | 957 | \item Repeat the previous two steps: add the next two characters of data to the 958 | right of the just-filled-in line, lookup the leftmost two characters in the 959 | Checksum Table to fill in the next line, and add the results. 960 | 961 | Repeat until you run out of data. 962 | \item Check that the final result is \vc{SECRETSHARE32}. 963 | \end{enumerate} 964 | 965 | \paragraph{Checksum Generation.} This is basically identical to checksum verification, 966 | except that the final 13 characters are initially not available. On the worksheet these 967 | are colored pink to indicate to the user that she should stop filling random data into 968 | the cells. 969 | 970 | In the mathematical description we suggest concatenating \vc{SECRETSHARE} to the data 971 | itself, then replacing it. It is equivalent to instead write \vc{SECRETSHARE} in the 972 | in the bottom-most row (whose cells lie below the final 13 data cells), and then 973 | ``backsolve'' by subtracting (i.e.~adding) all the rows above it. This will place 974 | the residue, our desired checksum, in the correct place. 975 | 976 | \section{Secret Sharing} 977 | 978 | In the Mathematical Preliminaries section we described Lagrange interpolation and 979 | Shamir's Secret Sharing Scheme (SSSS). We defined the Fundamental Theorem of Computing 980 | SSSS With Volvelles as the observation that Lagrange interpolation allows evaluating 981 | a polynomial at a fixed point as an affine combination of its evaluations at other 982 | fixed points. 983 | 984 | As a consequence, any linear or affine relationship between the original evalutations 985 | will be preserved. We will come to this in a moment, but first let's describe the SSSS 986 | process. 987 | 988 | \subsection{Computing SSSS} 989 | 990 | There are two places where we use Lagrange interpolation: 991 | \begin{itemize} 992 | \item When creating a share with index $x$, we start with $k$ initial shares $x_1$, 993 | $x_2$, \ldots, $x_k$ whose indices are always the first $k$ symbols of the bech32 994 | alphabet \vc{A}, \vc{C}, \vc{D}, etc. 995 | 996 | (There is an alternate process where the user starts with a fixed secret, in which 997 | case $x_1$ is \vc{S} and the other $x_i$'s are shifted, but the rest is exactly 998 | the same.) 999 | \item When recovering a secret, we start with $k$ shares with indices $\{x_i\}_{i=1}^k$ 1000 | which are fixed during recovery but unpredictable in advance, and compute the 1001 | \vc{S} share. 1002 | \end{itemize} 1003 | 1004 | In both cases, the computation is straightforward: evaluate~\eqref{eq:linterp} as 1005 | \[ p(x) = \sum_{i=1}^k \ell_i(x) y_i \] 1006 | Here $y_i$ is the data of the $i$th share and $\ell_i(x)$ is a Lagrange multiplier 1007 | which must be computed by the user. We refer to these multiplies as ``Recovery 1008 | Symbols'' in the text. To avoid confusion, they are always encoded using the symbol 1009 | alphabet rather than bech32. The process for obtaining these is: 1010 | 1011 | \begin{itemize} 1012 | \item During share creation, the evaluation points $\{x_i\}$, which are the 1013 | user's initial share indices, are always fixed. Therefore $\ell_i(x)$ is purely 1014 | a function of $x$ (the index of the share to be created) and $k$. There aren't 1015 | that many possibilities so we simply provide tables on the ``Constructing Shares'' 1016 | page. 1017 | 1018 | (It is instructive to modify the PostScript source for these tables to allow 1019 | ``deriving'' the initial shares from themselves. You will find that the index for the initial 1020 | share under question becomes 1 ($\aleph$) while the index for all the other shares 1021 | is 0 ($\times$).) 1022 | 1023 | \item During recovery, there are up to 31 outstanding non-\vc{S} shares and 1024 | the user has an arbitrary subset of $k$ of them. There are $\binom{31}k$ 1025 | possibilities, which for $k=2$ is a reasonable number --- 465 --- but for 1026 | $k\geq3$ quickly grows out of hand, to 4495, then 31465, and beyond. 1027 | 1028 | For $k = 2$ we provide a table, both in tabular form and in the form of a volvelle. 1029 | This is what the Recovery Wheel ultimately is, and at position $p$, window $w$ it 1030 | computes 1031 | \[ \ell_i(\vc{S}) = \frac{w + \vc{S}}{w + p} \] 1032 | Here position $p$ is the index of share $y_i$ whose multiplier is being computed, 1033 | and $w$ is the index of the other share. To avoid getting $p$ and $w$ confused, 1034 | we have written ``Share to Translate'' on the handle of the disc. 1035 | 1036 | For $k \geq 3$, for target share $p$ (index of $y_i$) with other shares 1037 | $\{w_i\}_{i=1}^{k-1}$, we need to compute 1038 | \[ \ell_i(\vc{S}) = \prod_{i=1}^{k-1} \frac{w_i + \vc{S}}{w_i + p} \] 1039 | with notation chosen to highlight that \emph{the Lagrange basis polynomials for 1040 | $k\geq3$ are products of the basis polynomials for $k=2$}. 1041 | 1042 | This fortuitous fact means that the user can compute polynomials $\ell_i(\vc{S})$ 1043 | by looking up factors using the Recovery Wheel (rotate it to index $p$ then 1044 | read all the $w_i$'s off the front), and then multiplying all the results using 1045 | the Fusion Wheel. (Turn the wheel so it points to the first symbol, then look up 1046 | the second. Turn it to whatever the second symbol is pointing to, look up the 1047 | third, and so on.) 1048 | 1049 | The Fusion Wheel is the only one which takes two symbols as input, and multiplication 1050 | is commutative so the order of inputs doesn't matter, so it is hard for the user 1051 | to do the wrong thing here. 1052 | \end{itemize} 1053 | 1054 | Once the user has obtained the $\ell_i(x)$'s, the rest is simple: multiply each 1055 | cell of $y_i$ (the value of the $i$th share, encoded in the bech32 alphabet) by 1056 | $\ell_i(x)$ (computed above, encoded in the symbol alphabet) using the Translation 1057 | Wheel. 1058 | 1059 | Again, the choice of alphabets and commutativity of multiplication make it hard 1060 | for the user to do the wrong thing. 1061 | 1062 | This will give the user $k$ translated shares $\hat{y}_i$, which she should then 1063 | add together using the Addition volvelle. We have provided a Translation Worksheet 1064 | to help keep everything straight. 1065 | 1066 | \subsection{SSSS and Checksumming} 1067 | 1068 | Once the user has computed her derived share(s) or recovered her secret \vc{S} 1069 | share, she will likely notice that the produced share header is of the correct form 1070 | and has the correct index. This is oddly thrilling but not mathematically surprising: 1071 | the fixed parts of the header are produced by interpolating a constant polynomial 1072 | and the share index is produced by interpolating the polynomial $f(x) = x$. 1073 | 1074 | What is more mathematically impressive is that the last 13 symbols of the polynomial 1075 | will constitute a valid checksum for the resulting share. This is because each checksum 1076 | symbol is defined as an affine transformation of the other characters, and the 1077 | Fundamental Theorem says that any such relationships will be preserved. 1078 | 1079 | In fact, in a complete Checksum Worksheet, \emph{every single cell} that the user 1080 | fills in is an affine function of the share data. This means that if you pick an 1081 | arbitrary cell, say, the fourth cell of the tenth row, you can use the above process 1082 | to combine those cells from the initial shares' worksheets and produce the 1083 | corresponding cell on the derived share's worksheet. 1084 | 1085 | This means in particular, that the final row (\vc{SECRETSHARE32}) will be 1086 | preserved, meaning that the checksum of the derived share will be correct. But it 1087 | also means that, if the user uses the checksum worksheet to verify her share 1088 | translation (which she should!) she can ``sanity check'' her work by pre-deriving 1089 | cells. 1090 | 1091 | This is was an exciting realization, because normally the Checksum Worksheet takes 1092 | a long time to fill out and provides the user no feedback until the very end, at 1093 | which point the result might just be ``wrong residue, start over''. To avoid this 1094 | frustration, we recommend the following process for derived shares and recovery: 1095 | \begin{enumerate} 1096 | \item Before deriving any shares, complete Checksum Worksheets for the inital 1097 | shares. (During construction these should be readily available; during recovery 1098 | it's worth doing as a sanity check.) 1099 | \item Derive the actual share, using the above method. 1100 | \item Copy the share into the top diagonal, i.e.~the bolded data cells, of a fresh 1101 | Checksum Worksheet. 1102 | \item Derive the cells of the \emph{bottom} diagonal directly, in the same way that 1103 | you derived the top diagonal. 1104 | \item Start working through the worksheet. 1105 | \end{enumerate} 1106 | 1107 | If, at any point, the computed value of a bottom diagonal square doesn't match 1108 | the precomputed value, it means you made a mistake in that column. Re-derive the 1109 | top and bottom values and redo the additions before continuing. 1110 | 1111 | \section{Quickchecks} 1112 | 1113 | \paragraph{Important Note.} {\color{BrickRed}This section describes a series of 1114 | worksheets which, as of March 2023, do not actually exist.} 1115 | 1116 | Recall that the generating polynomial for our primary code is 1117 | \begin{align*} 1118 | G(x) = x^{13} 1119 | &+ \vc{E}x^{12} + \vc{M}x^{11} + \vc{3}x^{10} + \vc{G}x^9 + \vc{Q}x^8 + \vc{E}x^7 \\ 1120 | &+ \vc{E}x^6 + \vc{E}x^5 + \vc{L}x^4 + \vc{M}x^3 + \vc{C}x^2 + \vc{S}x + \vc{S}. 1121 | \end{align*} 1122 | Previously we simply took this polynomial as a given. But in this section and the 1123 | next, we need to dig a bit deeper into how it was constructed. 1124 | 1125 | To this end we first construct the extension field $\ftttwo$. Just as we 1126 | produced $\fttwo$ by adding a root of the irreducible 5th-degree polynomial 1127 | $x^5 + x^3 + 1$ over $\ftwo$, we can adjoin a root of the irreducible 1128 | 2nd-degree polynomial $x^2 + x + 1$ to $\fttwo$ to get a new field $\ftttwo$. 1129 | We will call this new root $\zeta$. By construction, $\zeta$ satisfies 1130 | the equation $\zeta^2 = \zeta + 1$. 1131 | 1132 | We can write any element of $\ftttwo$ as $a + b\zeta$, where $a$ and $b$ are 1133 | in $\fttwo$. Multiplication and addition happen in the obvious way, with 1134 | every $\zeta^2$ factor simply replaced by $\zeta + 1$. 1135 | 1136 | The new field $\ftttwo$ has 1024 elements; its multiplicative group has 1137 | $1023 = 3\cdot11\cdot31$ elements, so unlike the case of $\fttwo$ where every 1138 | non-unit element has order 31, in $\ftttwo$ non-unit elements might have any 1139 | order in the set $\{3, 11, 31, 33, 93, 341, 1023\}$. 1140 | 1141 | You may recognize the numbers 93 and 1023 as the length of our primary and 1142 | alternate code (and 1023 as the length of bech32). This is not a coincidence. 1143 | 1144 | Consider the element $\beta = \vc{G}\zeta$, which has order $93$. In fact, our 1145 | generator $G$ has roots which are all powers of $\beta$!\footnote{This is no 1146 | accident --- to construct $G$, we started with 8 consecutive powers of $\beta$, 1147 | took the minimal polynomials of these, and took the least common multiple of 1148 | these. The exact choice of $\beta$ and its powers came down to an exhaustive 1149 | search of which values led us to a code with our desired properties: distance 1150 | 9, checksum length 13, maximal length, and three repeated coefficients in the 1151 | generator polynomial, which cause the entries in the checksum table to have 1152 | repeated digits, which we believe make transcribing easier for human eyes.} 1153 | We can write it as 1154 | \begin{align*} 1155 | G(x) = \prod_{i\in\{17, 20, 46, 49, 52, 77, 78, 79, 80, 81, 82, 83, 84\}} (x - \beta^i). 1156 | \end{align*} 1157 | 1158 | We can now see why our code has length 93 --- all its roots satisfy $x^{93}-1$, 1159 | so $G$ is a factor of $x^{93}-1$, which means that $x^{93}\equiv1$ mod $G$. So 1160 | we cannot distinguish the codewords $x^{93}$ and $1$, even though they are 1161 | distance 2 from each other. 1162 | 1163 | The run of 8 consecutive roots is the reason that this code has distance 9, though 1164 | the reason is not one we can casually state.\footnote{It can be easily 1165 | found online, e.g.~on the Wikipedia page for BCH codes.} 1166 | 1167 | Now, recall $G$ has degree 2, so all of its roots have degree at most 2, or 1168 | equivalently, it can be factored in $\fttwo$ into factors which are all linear 1169 | or quadratic. Specifically: 1170 | \begin{align*} 1171 | G(x) = (x &+ \vc{T})(x + \vc{S})(x + \vc{C}) \\ 1172 | &\times (x^2 + \vc{Z}x + \vc{Y})(x^2 + \vc{R}x + \vc{9}) 1173 | (x^2 + \vc{2}x + \vc{K})(x^2 + \vc{W}x + \vc{X})(x^2 + \vc{L}x + \vc{A}) 1174 | \end{align*} 1175 | 1176 | Recall further that our codewords are defined by their membership in a specific 1177 | equivalence class modulo $G$, the equivalence class of \vc{SECRETSHARE32}. By 1178 | the Chinese Remainder Theorem, this equivalence class is also characterized by 1179 | sets of equivalence classes modulo these factors. In particular, for a given $v(x)$, 1180 | \begin{align} 1181 | v(x) &\equiv \vc{2W} \label{eq:quickstart} 1182 | &\mod (x + \vc{S})(x + \vc{T}) 1183 | &= (x - \beta^{84})(x - \beta^{81}) \\ 1184 | v(x) &\equiv \vc{LD} 1185 | &\mod (x^2 + \vc{Z}x + \vc{Y}) 1186 | &= (x - \beta^{83})(x - \beta^{52}) \\ 1187 | v(x) &\equiv \vc{XK} 1188 | &\mod (x^2 + \vc{2}x + \vc{K}) 1189 | &= (x - \beta^{82})(x - \beta^{20}) \\ 1190 | v(x) &\equiv \vc{9X} 1191 | &\mod (x^2 + \vc{W}x + \vc{X}) 1192 | &= (x - \beta^{80})(x - \beta^{49}) \\ 1193 | v(x) &\equiv \vc{LT} 1194 | &\mod (x^2 + \vc{L}x + \vc{A}) 1195 | &= (x - \beta^{79})(x - \beta^{17}) \\ 1196 | v(x) &\equiv \vc{WU} 1197 | &\mod (x + \vc{C})(x + \vc{T}) 1198 | &= (x - \beta^{78})(x - \beta^{81}) \\ 1199 | v(x) &\equiv \vc{UM} \label{eq:quickend} 1200 | &\mod (x^2 + \vc{R}x + \vc{9}) 1201 | &= (x - \beta^{77})(x - \beta^{46}) 1202 | \end{align} 1203 | if and only if $v(x)\equiv\vc{SECRETSHARE32}$ modulo $G$. 1204 | 1205 | In fact, we have ordered these checks so that if the user checks each one of 1206 | them, in order, the CRT will fix the equivalence class of her codeword modulo 1207 | a polynomial $\hat{G}$ with progressively many consecutive roots of $\beta$, 1208 | so she will be guaranteed to detect progressively many errors. 1209 | 1210 | This is the intuition behind the \textbf{quickcheck} method of verifying the 1211 | checksum. Rather than, say, doing a full checksum worksheet every year to verify 1212 | their codeword module $G$, the user can instead verify each of 1213 | Equations~\eqref{eq:quickstart} through~\eqref{eq:quickend}, doing one check 1214 | ever month. Once the final check~\eqref{eq:quickend} is done, the user starts 1215 | back over with~\eqref{eq:quickstart}. 1216 | 1217 | You may notice that the factor $(x + \vc{S}) = (x - \beta^{81})$ appears twice in 1218 | this list. For a user doing all the checks, its second appearance is strictly 1219 | redundant. But by including it, we ensure that no matter where in the list the 1220 | user is starting from, they consistently accumulate consecutive roots, so that 1221 | if their data isn't corrupted between individual checks, they gain progressive 1222 | error detection ability. 1223 | 1224 | Another reason to include $(x+\vc{S})$ twice is to make all the quickchecks look 1225 | the same, so that the process is as consistent as possible. The point of this 1226 | scheme is to encourage the user to frequently engage with their secret data, so 1227 | that they gain and maintain familiarity with the checksum verification process. 1228 | 1229 | Even more important that consistency, by making each quickcheck use a quadratic 1230 | generator polynomial, we get two checksum digits, providing 10 bits of protection 1231 | against random errors. This means that if even a single quickcheck passes, the 1232 | user has 99.9\% assurance (1023/1024) that their data is intact. If we'd used a 1233 | linear generator polynomial, we would get only 5 bits, so a passing check would 1234 | give the user only 97\% (31/32) assurance. 1235 | 1236 | Each quickcheck is merely a modular reduction of the user's data; it differs 1237 | from the Checksum Worksheet only in that we are reducing modulo a quadratic 1238 | rather than modulo the full degree-13 generator. This allows us to rearrange 1239 | the worksheet in a more aesthetically-pleasing way, and fit the 2-page Checksum 1240 | Table into a single page (since it is mapping pairs of characters to pairs of 1241 | characters, rather than pairs of characters to 13-character strings). But the 1242 | underlying mechanism is exactly the same. 1243 | 1244 | (All of the considerations in this section apply also to our alternate length-1023 1245 | code, which is used for 400+-bit seeds. It uses the order-1023 element 1246 | $\gamma=\vc{E} + \vc{X}\zeta$ to generate its roots, in place of $\beta$. But since 1247 | we do not expect anybody to manipulate such large seeds by hand, we will not 1248 | bother do the equivalent calculations. The motivated user will be able to construct 1249 | everything merely from knowledge of the generator polynomial and of $\gamma$.) 1250 | 1251 | \section{Error Correction} 1252 | 1253 | \paragraph{Important Note.} {\color{BrickRed}This section describes a correction 1254 | table which, as of March 2023, does not actually exist.} 1255 | 1256 | Suppose that a user's share data has up to 8 errors in it. We can write their data 1257 | in polynomial form as 1258 | \[ \hat{d}(x) = d(x) + e(x) \] 1259 | where $d(x)$ is their actual share data, $e(x)$ is some error polynomial with up 1260 | to 8 terms, and $\hat{d}$ is their actual data. 1261 | 1262 | Taking this modulo $G$, we see that 1263 | \[ \hat{d}(x) \equiv d(x) + e(x) \equiv \vc{SECRETSHARE32} + e(x) \mod G. \] 1264 | That is, in the process of modding out by $G$, the actual secret data is completely 1265 | erased and is replaced by the target residue. This is why the booklet advises users 1266 | that they may enter the residue into an electronic computer --- it has no secret 1267 | data in it --- and why this is a useful thing to do --- it is completely determined 1268 | by the error pattern. 1269 | 1270 | The task of \textbf{error correction} is to undo the reduction process. That is, 1271 | given a residue $e(x)$ mod $G$, figure out what $e(x)$ really is. Then the user 1272 | can recover their share data by computing 1273 | \[ \hat{d}(x) + e(x) = d(x) + e(x) + e(e) = d(x) \] 1274 | 1275 | Because our checksum has distance 9, if there are up to 8 errors, the reduction 1276 | is guaranteed not to produce the value \vc{SECRETSHARE32}, so that the error 1277 | pattern will be recognized as an error. 1278 | 1279 | Furthermore, in all cases where there are 4 or fewer errors, the reduction will 1280 | produce unique values, so that it can (in principle) be undone. 1281 | 1282 | But doing this is fairly involved. The standard way of doing this is to use the 1283 | Berlekamp-Massey algorithm to determine an ``error locator polynomial'', to 1284 | find roots of this polynomial to determine the error locations, and then to use 1285 | Forney's algorithm to compute the error values. All of these steps involve 1286 | arithmetic in $\ftttwo$ which does not lend itself nicely to volvelles and 1287 | worksheets. We believe that it possible, but as of March 2023, we have not 1288 | figured out how. 1289 | 1290 | However, one thing we \emph{can} do is produce a lookup table. In the case that 1291 | the user has a 48-element share, of which the first 3 elements are definitely 1292 | the characters \vc{ms1}, then there are 1395 ways in which there may be one 1293 | mistake in the remaining 45 characters. Each of these 1395 ways produce a unique 1294 | residue, and we have provided a 3-page table listing them all. Therefore, if 1295 | the user makes only a single error, they can simply look up their residue to 1296 | learn how to correct it. 1297 | 1298 | A final observation is that errors might not mean that the share data itself are 1299 | corrupted. An error in location $y$ actually means that \emph{column $y$ in the 1300 | Checksum Worksheet was not computed correctly}. This may mean that the top cell, 1301 | which contains share data, was wrong. But it could also be that the user made an 1302 | arithmetic error. We therefore advise users, when doing error correction, to 1303 | completely recompute any erroneous columns of the Checksum Worksheet before they 1304 | consider modifying their data. 1305 | 1306 | \section{Conclusion and Acknowledgements} 1307 | 1308 | We thank the Russell O'Connor for noticing the remarkable compatibility between 1309 | SSSS and BCH codes (or any linear code), which enable user-computed SSSS\footnote{ 1310 | See \url{https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-August/018070.html}}. 1311 | Even if SSSS were otherwise tractable to do by hand, without hand-verifiable checksums it 1312 | would be hopeless for users to notice or recover from arithmetic and transcription 1313 | mistakes, and this whole project would be unworkable. 1314 | 1315 | We thank the authors of SLIP39, which also uses both SSSS and BCH codes, and 1316 | inspired us to attempt a hand-computable version of it exploiting the compatibility 1317 | between the two. 1318 | 1319 | We thank Dr.~Curr for then noticing that by using a code over $\fttwo$ 1320 | rather than $\mathbb{F}_{1024}$, it is possible to do these computations by hand, 1321 | and for putting together the initial prototype of this project which included 1322 | the PostScript fundamentals to do computations with BCH codes, to draw 32-by-32 1323 | volvelles, and to do 2-of-$n$ secret sharing. 1324 | 1325 | We thank Micaela Paez for the amazing artwork that adorns the illustrated version 1326 | of the volvelles. 1327 | 1328 | We thank Peter Todd for his mailing list post in which he suggested replacing the 1329 | checksum with a single-character one obtained by summing all the share data\footnote{ 1330 | \url{https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2023-February/021498.html}}. 1331 | Without this we would not have discovered the ``quickcheck'' method of verifying 1332 | the checksum. 1333 | 1334 | From that point onward it was a real trip to bring everything together, optimizing 1335 | the layout of the volvelles and worksheets for user experience, reducing the 1336 | total number of volvelles, introducing the slide rules, discovering how to localize 1337 | Checksum Worksheet errors, and adding artwork and color. 1338 | 1339 | The result has been a remarkable, and even mathematically novel, project which 1340 | fits together much better than any of us expected. 1341 | 1342 | We hope you will appreciate the mathematical beauty of this construction, or 1343 | at least appreciate the peace of mind that comes with being able to redundantly 1344 | back up your Bitcoin secrets without the use of electronic computing devices. 1345 | 1346 | \vfill 1347 | \begin{center} 1348 | \emph{Never trust anything that can think for itself, if you can’t see where it 1349 | keeps its brain!}\\ 1350 | \hfill---Arthur Weasley, Harry Potter and the Chamber of Secrets 1351 | \end{center} 1352 | 1353 | \end{document} 1354 | 1355 | -------------------------------------------------------------------------------- /polymod.md: -------------------------------------------------------------------------------- 1 | # SSS32 2 | 3 | ## Polymod 4 | 5 | See also and . 6 | 7 | I requested a generator for a BCH code that can correct 4 errors on 57 data characters (supporting at least 285 bits) from sipa, who suggested three available generators with a 13 character checksum: 8 | 9 | 1. `x^13 + {10}x^12 + {30}x^11 + {14}x^10 + {26}x^9 + {17}x^7 + {28}x^6 + {18}x^5 + {18}x^4 + {5}x^3 + {26}x^2 + {6}x + {22}` 10 | 1. `x^13 + {27}x^12 + {7}x^11 + {15}x^10 + {3}x^9 + {20}x^8 + {30}x^7 + {10}x^6 + {17}x^5 + x^4 + {6}x^3 + {3}x^2 + {15}x + {15}` 11 | 1. `x^13 + {29}x^12 + {30}x^11 + {16}x^10 + {26}x^9 + {16}x^7 + {8}x^6 + {4}x^5 + {29}x^4 + {16}x^3 + {11}x^2 + {16}x + {8}` 12 | 13 | These are all distance 9 (can correct 4 errors) BCH codes of degree 2 over GF(32) of length 93. 14 | They can support payloads upto 80 characters (50 bytes; 400 bits). (Note: codewords of length 93 with a 13 character checksum leaves 80 characters for the payload) 15 | 16 | ### Derivation 17 | 18 | The Bech32 character set encodes `F := GF(32)` as polynomials over `GF(2)` modulo `x^5 + x^3 + 1`. 19 | We define a generator for `F` as `gen[F]` where `gen[F]^5 + gen[F]^3 + 1 = 0`. 20 | We define `{n}` for 5-bit values `n`, which represent the value `b[0] + b[1]gen[F] + b[2]gen[F]^2 + b[3]gen[F]^3 + b[4]gen[F]^4` where `b[0]` is the least signficant bit of `n` and `b[4]` is the most sigificant bit of `n`. 21 | 22 | The above BCH generators are all of degree 2, so requires us to consider the field extesion `E := GF(32^2) = GF(1024)` which we define as polynomials over `GF(32)` modulo `x^2 + x + {3}`. 23 | We define a generator for `E` as `gen[E]` where `gen[E]^2 + gen[E] + {3} = 0`. 24 | 25 | The first generator is specified by the minimal polynomials of `alpha[1]^2, alpha[1]^3, ... alpha[1]^9` where `alpha[1] = gen[E]^((1024-1)/93) = gen[E]^11 = {6}gen[E] + {22}`. 26 | These minimal polynomials are as follows: 27 | 28 | - `m(alpha[1]^2) = x^2 + {20}x + {10}` 29 | - `m(alpha[1]^3) = x + {3}` 30 | - `m(alpha[1]^4) = x^2 + {10}x + {22}` 31 | - `m(alpha[1]^5) = x^2 + {21}x + {11}` 32 | - `m(alpha[1]^6) = x + {5}` 33 | - `m(alpha[1]^7) = x^2 + {30}x + {28}` 34 | - `m(alpha[1]^8) = x^2 + {22}x + {14}` 35 | - `m(alpha[1]^9) = x + {15}` 36 | 37 | The LCM of these 7 polynomials is the first listed generator, and thus `alpha[1]^2, ... alpha[1]^9` are all roots of that generating polynomial. 38 | 39 | ## Other polymods 40 | 41 | Sipa said that to correct 3 errors would require a checksum of 10 characters on length 93 (which would support 83 characters for the payload). 42 | To correct 5 errors would require a checksum of 16 characters on length 93 (which would support 77 characters for the payload). 43 | -------------------------------------------------------------------------------- /reference/rust-codex32/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codex32" 3 | version = "0.1.0" 4 | edition = "2018" 5 | description = "Rust reference implementation of the codex32 spec" 6 | license = "CC0-1.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | -------------------------------------------------------------------------------- /reference/rust-codex32/LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | -------------------------------------------------------------------------------- /reference/rust-codex32/src/checksum.rs: -------------------------------------------------------------------------------- 1 | // Rust Codex32 Library and Reference Implementation 2 | // Written in 2023 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! Checksums 16 | //! 17 | //! Validates specific checksums 18 | //! 19 | 20 | use super::{Case, Error}; 21 | use crate::gf32::Fe; 22 | 23 | /// An engine which consumes one GF32 character at a time, and produces 24 | /// a residue modulo some generator 25 | #[derive(Clone, PartialEq, Eq, Debug)] 26 | pub struct Engine { 27 | case: Option, 28 | generator: Vec, 29 | residue: Vec, 30 | target: Vec, 31 | } 32 | 33 | impl Engine { 34 | // An engine which computes the normal codex32 checksum 35 | pub fn new_codex32_short() -> Engine { 36 | Engine { 37 | case: None, 38 | #[rustfmt::skip] 39 | generator: vec![ 40 | Fe::E, Fe::M, Fe::_3, Fe::G, Fe::Q, Fe::E, 41 | Fe::E, Fe::E, Fe::L, Fe::M, Fe::C, Fe::S, 42 | Fe::S, 43 | ], 44 | #[rustfmt::skip] 45 | residue: vec![ 46 | Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, 47 | Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, 48 | Fe::P, 49 | ], 50 | #[rustfmt::skip] 51 | target: vec![ 52 | Fe::S, Fe::E, Fe::C, Fe::R, Fe::E, Fe::T, 53 | Fe::S, Fe::H, Fe::A, Fe::R, Fe::E, Fe::_3, 54 | Fe::_2, 55 | ], 56 | } 57 | } 58 | 59 | // An engine which computes the "long" codex32 checksum 60 | pub fn new_codex32_long() -> Engine { 61 | // hyk9x4hx4ef6e20p 62 | Engine { 63 | case: None, 64 | #[rustfmt::skip] 65 | generator: vec![ 66 | Fe::_0, Fe::_2, Fe::E, Fe::_6, Fe::F, Fe::E, 67 | Fe::_4, Fe::X, Fe::H, Fe::_4, Fe::X, Fe::_9, 68 | Fe::K, Fe::Y, Fe::H, 69 | ], 70 | #[rustfmt::skip] 71 | residue: vec![ 72 | Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, 73 | Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, 74 | Fe::Q, Fe::Q, Fe::P, 75 | ], 76 | #[rustfmt::skip] 77 | target: vec![ 78 | Fe::S, Fe::E, Fe::C, Fe::R, Fe::E, Fe::T, 79 | Fe::S, Fe::H, Fe::A, Fe::R, Fe::E, Fe::_3, 80 | Fe::_2, Fe::E, Fe::X, 81 | ], 82 | } 83 | } 84 | 85 | /// Accessor for the generator polynomial, as a big-endian (highest powers 86 | /// first) vector of coefficients 87 | pub fn generator(&self) -> &[Fe] { 88 | &self.generator 89 | } 90 | 91 | /// When computing checksums of "diffs" you do may want to set 92 | /// the highest-degree coefficient of the polynomial to 1. 93 | /// 94 | /// If you do not know exactly why you are using this function, 95 | /// you should not use it. 96 | pub fn force_residue_to_zero(&mut self) { 97 | self.residue = vec![Fe::Q; self.residue.len()]; 98 | } 99 | 100 | /// Extracts the residue from a checksum engine 101 | pub fn into_residue(self) -> Vec { 102 | self.residue 103 | } 104 | 105 | /// Determines whether the residue matches the target value 106 | /// for the checksum 107 | /// 108 | /// If you need the actual residue, e.g. for error correction, 109 | /// call the `into_residue` function (which will consume the 110 | /// engine). 111 | pub fn is_valid(&self) -> bool { 112 | self.residue == self.target 113 | } 114 | 115 | /// Initializes the checksum engine by loading an HRP into it 116 | pub fn input_hrp(&mut self, hrp: &str) -> Result<(), Error> { 117 | for ch in hrp.chars() { 118 | self.set_check_case(ch)?; 119 | self.input_fe(Fe::from_int(u32::from(ch.to_ascii_lowercase()) >> 5)?); 120 | } 121 | self.input_fe(Fe::Q); 122 | for ch in hrp.chars() { 123 | self.input_fe(Fe::from_int(u32::from(ch.to_ascii_lowercase()) & 0x1f)?); 124 | } 125 | Ok(()) 126 | } 127 | 128 | /// Adds a single character to the checksum engine 129 | pub fn input_char(&mut self, c: char) -> Result<(), Error> { 130 | self.set_check_case(c)?; 131 | self.input_fe(Fe::from_char(c)?); 132 | Ok(()) 133 | } 134 | 135 | /// Adds an entire string to the engine, counting each character as a data character 136 | /// (not an HRP). 137 | pub fn input_data_str(&mut self, s: &str) -> Result<(), Error> { 138 | for ch in s.chars() { 139 | self.input_char(ch)?; 140 | } 141 | Ok(()) 142 | } 143 | 144 | /// Adds the target residue to the end of the input string 145 | pub fn input_own_target(&mut self) { 146 | // Need to clone self.target to iterate over it while calling self.input_fe, 147 | // which rustc worries may modify self.target. 148 | let sigh_borrowck = self.target.clone(); 149 | for u in sigh_borrowck { 150 | self.input_fe(u); 151 | } 152 | } 153 | 154 | /// Helper function to check that the whole input has consistent case 155 | fn set_check_case(&mut self, c: char) -> Result<(), Error> { 156 | if !c.is_ascii() { 157 | Err(Error::InvalidChar(c)) 158 | } else if c.is_numeric() { 159 | // numbers don't affect case, nor are they affected by case 160 | Ok(()) 161 | } else { 162 | let is_lower = c.is_ascii_lowercase(); 163 | match (self.case, is_lower) { 164 | (Some(Case::Lower), true) | (Some(Case::Upper), false) => Ok(()), 165 | (Some(case @ Case::Lower), false) | (Some(case @ Case::Upper), true) => { 166 | Err(Error::InvalidCase(case, c)) 167 | } 168 | (None, true) => { 169 | self.case = Some(Case::Lower); 170 | Ok(()) 171 | } 172 | (None, false) => { 173 | self.case = Some(Case::Upper); 174 | Ok(()) 175 | } 176 | } 177 | } 178 | } 179 | 180 | /// Adds a single gf32 element to the checksum engine 181 | /// 182 | /// This is where the real magic happens. 183 | #[rustfmt::skip] 184 | pub fn input_fe(&mut self, e: Fe) { 185 | let res_len = self.residue.len(); // needed for borrowck 186 | // Store current coefficient of x^{n-1}, which will become 187 | // x^n (and get reduced) 188 | let xn = self.residue[0]; 189 | // Simply shift x^0 through x^{n-1} up one, and set x^0 to the new input 190 | for i in 1..res_len { 191 | self.residue[i - 1] = self.residue[i]; 192 | } 193 | self.residue[res_len - 1] = e; 194 | // Then reduce x^n mod the generator. 195 | for i in 0..res_len { 196 | self.residue[i] += self.generator[i] * xn; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /reference/rust-codex32/src/gf32.rs: -------------------------------------------------------------------------------- 1 | // Rust Codex32 Library and Reference Implementation 2 | // Written in 2023 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! Field Implementation 16 | //! 17 | //! Implements GF32 arithmetic, defined and encoded as in BIP-0173 "bech32" 18 | //! 19 | 20 | use std::{ 21 | convert::{TryFrom, TryInto}, 22 | fmt, num, ops, str, 23 | }; 24 | 25 | /// Locarithm table of each bech32 element, as a power of alpha = Z. 26 | /// 27 | /// Includes Q as 0 but this is false; you need to exclude Q because 28 | /// it has no discrete log. If we could have a 1-indexed array that 29 | /// would panic on a 0 index that would be better. 30 | #[rustfmt::skip] 31 | const LOG: [isize; 32] = [ 32 | 0, 0, 1, 14, 2, 28, 15, 22, 33 | 3, 5, 29, 26, 16, 7, 23, 11, 34 | 4, 25, 6, 10, 30, 13, 27, 21, 35 | 17, 18, 8, 19, 24, 9, 12, 20, 36 | ]; 37 | 38 | /// Mapping of powers of 2 to the numeric value of the element 39 | #[rustfmt::skip] 40 | const LOG_INV: [u8; 31] = [ 41 | 1, 2, 4, 8, 16, 9, 18, 13, 42 | 26, 29, 19, 15, 30, 21, 3, 6, 43 | 12, 24, 25, 27, 31, 23, 7, 14, 44 | 28, 17, 11, 22, 5, 10, 20, 45 | ]; 46 | 47 | /// Mapping from numeric value to bech32 character 48 | #[rustfmt::skip] 49 | const CHARS_LOWER: [char; 32] = [ 50 | 'q', 'p', 'z', 'r', 'y', '9', 'x', '8', // +0 51 | 'g', 'f', '2', 't', 'v', 'd', 'w', '0', // +8 52 | 's', '3', 'j', 'n', '5', '4', 'k', 'h', // +16 53 | 'c', 'e', '6', 'm', 'u', 'a', '7', 'l', // +24 54 | ]; 55 | 56 | /// Mapping from bech32 character (either case) to numeric value 57 | #[rustfmt::skip] 58 | const CHARS_INV: [i8; 128] = [ 59 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 60 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62 | 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, 63 | -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 64 | 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, 65 | -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 66 | 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, 67 | ]; 68 | 69 | /// Field-related error 70 | #[derive(Debug)] 71 | pub enum Error { 72 | /// Tried to decode a GF32 element from a string, but got more than one character 73 | ExtraChar(char), 74 | /// Tried to interpret an integer as a GF32 element but it could not be 75 | /// converted to an u8. 76 | NotAByte(num::TryFromIntError), 77 | /// Tried to interpret a byte as a GF32 element but its numeric value was 78 | /// outside of [0, 32). 79 | InvalidByte(u8), 80 | } 81 | 82 | /// An element of GF32 83 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 84 | pub struct Fe(u8); 85 | 86 | impl ops::Add for Fe { 87 | type Output = Fe; 88 | fn add(self, other: Fe) -> Fe { 89 | Fe(self.0 ^ other.0) 90 | } 91 | } 92 | 93 | impl ops::AddAssign for Fe { 94 | fn add_assign(&mut self, other: Fe) { 95 | *self = *self + other; 96 | } 97 | } 98 | 99 | // Subtraction is the same as addition in a char-2 field 100 | impl ops::Sub for Fe { 101 | type Output = Fe; 102 | fn sub(self, other: Fe) -> Fe { 103 | self + other 104 | } 105 | } 106 | 107 | impl ops::SubAssign for Fe { 108 | fn sub_assign(&mut self, other: Fe) { 109 | *self = *self - other; 110 | } 111 | } 112 | 113 | impl ops::Mul for Fe { 114 | type Output = Fe; 115 | fn mul(self, other: Fe) -> Fe { 116 | if self.0 == 0 || other.0 == 0 { 117 | Fe(0) 118 | } else { 119 | let log1 = LOG[self.0 as usize]; 120 | let log2 = LOG[other.0 as usize]; 121 | Fe(LOG_INV[((log1 + log2) % 31) as usize]) 122 | } 123 | } 124 | } 125 | 126 | impl ops::MulAssign for Fe { 127 | fn mul_assign(&mut self, other: Fe) { 128 | *self = *self * other; 129 | } 130 | } 131 | 132 | impl ops::Div for Fe { 133 | type Output = Fe; 134 | fn div(self, other: Fe) -> Fe { 135 | if self.0 == 0 { 136 | Fe(0) 137 | } else if other.0 == 0 { 138 | panic!("Attempt to divide {} by 0 in GF32", self); 139 | } else { 140 | let log1 = LOG[self.0 as usize]; 141 | let log2 = LOG[other.0 as usize]; 142 | Fe(LOG_INV[((31 + log1 - log2) % 31) as usize]) 143 | } 144 | } 145 | } 146 | 147 | impl ops::DivAssign for Fe { 148 | fn div_assign(&mut self, other: Fe) { 149 | *self = *self / other; 150 | } 151 | } 152 | 153 | impl Fe { 154 | // These are a little gratuitous for a reference implementation, 155 | // but it makes me happy to do it 156 | pub const Q: Fe = Fe(0); 157 | pub const P: Fe = Fe(1); 158 | #[allow(dead_code)] 159 | pub const Z: Fe = Fe(2); 160 | pub const R: Fe = Fe(3); 161 | pub const Y: Fe = Fe(4); 162 | pub const _9: Fe = Fe(5); 163 | pub const X: Fe = Fe(6); 164 | pub const _8: Fe = Fe(7); 165 | pub const G: Fe = Fe(8); 166 | pub const F: Fe = Fe(9); 167 | pub const _2: Fe = Fe(10); 168 | pub const T: Fe = Fe(11); 169 | #[allow(dead_code)] 170 | pub const V: Fe = Fe(12); 171 | #[allow(dead_code)] 172 | pub const D: Fe = Fe(13); 173 | #[allow(dead_code)] 174 | pub const W: Fe = Fe(14); 175 | pub const _0: Fe = Fe(15); 176 | pub const S: Fe = Fe(16); 177 | pub const _3: Fe = Fe(17); 178 | #[allow(dead_code)] 179 | pub const J: Fe = Fe(18); 180 | #[allow(dead_code)] 181 | pub const N: Fe = Fe(19); 182 | pub const _5: Fe = Fe(20); 183 | pub const _4: Fe = Fe(21); 184 | pub const K: Fe = Fe(22); 185 | pub const H: Fe = Fe(23); 186 | pub const C: Fe = Fe(24); 187 | pub const E: Fe = Fe(25); 188 | pub const _6: Fe = Fe(26); 189 | pub const M: Fe = Fe(27); 190 | #[allow(dead_code)] 191 | pub const U: Fe = Fe(28); 192 | pub const A: Fe = Fe(29); 193 | pub const _7: Fe = Fe(30); 194 | pub const L: Fe = Fe(31); 195 | 196 | /// Iterator over all field elements, in alphabetical order 197 | pub fn iter_alpha() -> impl Iterator { 198 | [ 199 | Fe::A, Fe::C, Fe::D, Fe::E, Fe::F, Fe::G, Fe::H, Fe::J, 200 | Fe::K, Fe::L, Fe::M, Fe::N, Fe::P, Fe::Q, Fe::R, Fe::S, 201 | Fe::T, Fe::U, Fe::V, Fe::W, Fe::X, Fe::Y, Fe::Z, Fe::_0, 202 | Fe::_2, Fe::_3, Fe::_4, Fe::_5, Fe::_6, Fe::_7, Fe::_8, Fe::_9, 203 | ].iter().copied() 204 | } 205 | 206 | /// Creates a field element from an integer type 207 | pub fn from_u8(byte: u8) -> Result { 208 | if byte < 32 { 209 | Ok(Fe(byte)) 210 | } else { 211 | Err(super::Error::Field(Error::InvalidByte(byte))) 212 | } 213 | } 214 | 215 | /// Creates a field element from an integer type 216 | pub fn from_int(i: I) -> Result 217 | where 218 | I: TryInto, 219 | { 220 | i.try_into() 221 | .map_err(|e| super::Error::Field(Error::NotAByte(e))) 222 | .and_then(Self::from_u8) 223 | } 224 | 225 | /// Creates a field element from a single bech32 character 226 | pub fn from_char(c: char) -> Result { 227 | let byte = i8::try_from(u32::from(c)).map_err(|_| super::Error::InvalidChar(c))?; 228 | let byte = byte as u8; // cast guaranteed to be ok since we started with an unsigned value 229 | let u5 = 230 | u8::try_from(CHARS_INV[usize::from(byte)]).map_err(|_| super::Error::InvalidChar(c))?; 231 | Ok(Fe(u5)) 232 | } 233 | 234 | /// Converts the field element to a lowercase bech32 character 235 | pub fn to_char(self) -> char { 236 | // casting and indexing fine as we have self.0 in [0, 32) as an invariant 237 | CHARS_LOWER[self.0 as usize] 238 | } 239 | 240 | /// Converts the field element to a 5-bit u8, with bits representing the coefficients 241 | /// of the polynomial representation. 242 | pub fn to_u8(self) -> u8 { 243 | self.0 244 | } 245 | } 246 | 247 | impl fmt::Display for Fe { 248 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 249 | fmt::Display::fmt(&self.to_char(), f) 250 | } 251 | } 252 | 253 | /// We abuse `UpperHex` in this library to display notably non-hex field elements 254 | /// in uppercase. 255 | impl fmt::UpperHex for Fe { 256 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 257 | fmt::Display::fmt(&self.to_char().to_ascii_uppercase(), f) 258 | } 259 | } 260 | 261 | impl str::FromStr for Fe { 262 | type Err = super::Error; 263 | fn from_str(s: &str) -> Result { 264 | let mut chs = s.chars(); 265 | match (chs.next(), chs.next()) { 266 | (Some(c), None) => Fe::from_char(c), 267 | (Some(_), Some(c)) => Err(super::Error::Field(Error::ExtraChar(c))), 268 | (None, _) => Err(super::Error::InvalidLength(0)), 269 | } 270 | } 271 | } 272 | 273 | #[cfg(test)] 274 | mod tests { 275 | use super::*; 276 | 277 | #[test] 278 | fn numeric_string() { 279 | let s: String = (0..32).map(Fe).map(Fe::to_char).collect(); 280 | assert_eq!(s, "qpzry9x8gf2tvdw0s3jn54khce6mua7l"); 281 | } 282 | 283 | #[test] 284 | fn translation_wheel() { 285 | // 1. Produce the translation wheel by multiplying 286 | let logbase = Fe(20); 287 | let mut init = Fe(1); 288 | let mut s = String::new(); 289 | for _ in 0..31 { 290 | s.push(init.to_char()); 291 | init *= logbase; 292 | } 293 | // Can be verified against the multiplication disk, starting with P and moving 294 | // clcockwise 295 | assert_eq!(s, "p529kt3uw8hlmecvxr470na6djfsgyz"); 296 | 297 | // 2. By dividing 298 | let logbase = Fe(20); 299 | let mut init = Fe(1); 300 | let mut s = String::new(); 301 | for _ in 0..31 { 302 | s.push(init.to_char()); 303 | init /= logbase; 304 | } 305 | // Same deal, but counterclockwise 306 | assert_eq!(s, "pzygsfjd6an074rxvcemlh8wu3tk925"); 307 | } 308 | 309 | #[test] 310 | fn recovery_wheel() { 311 | // Remarkably, the recovery wheel can be produced in the same way as the 312 | // multiplication wheel, though with a different log base and with every 313 | // element added by S. 314 | // 315 | // We spent quite some time deriving this, but honestly we probably could've 316 | // just guessed it if we'd known a priori that a wheel existed. 317 | let logbase = Fe(10); 318 | let mut init = Fe(1); 319 | let mut s = String::new(); 320 | for _ in 0..31 { 321 | s.push((init + Fe(16)).to_char()); 322 | init *= logbase; 323 | } 324 | // To verify, start with 3 and move clockwise on the Recovery Wheel 325 | assert_eq!(s, "36xp78tgk9ldaecjy4mvh0funwr2zq5"); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /reference/rust-codex32/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Rust Codex32 Library and Reference Implementation 2 | // Written in 2023 by 3 | // Andrew Poelstra 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all 6 | // copyright and related and neighboring rights to this software to 7 | // the public domain worldwide. This software is distributed without 8 | // any warranty. 9 | // 10 | // You should have received a copy of the CC0 Public Domain Dedication 11 | // along with this software. 12 | // If not, see . 13 | // 14 | 15 | //! codex32 Reference Implementation 16 | //! 17 | //! This project is a reference implementation of BIP-XXX "codex32", a project 18 | //! by Leon Olson Curr and Pearlwort Snead to produce checksummed and secret-shared 19 | //! BIP32 master seeds. 20 | //! 21 | //! References: 22 | //! * BIP-XXX 23 | //! * The codex32 website 24 | //! * BIP-0173 "bech32" 25 | //! * BIP-0032 "BIP 32" 26 | //! 27 | 28 | // This is the shittiest lint ever and has literally never been correct when 29 | // it has fired, and somehow in rust-bitcoin managed NOT to fire in the one 30 | // case where it might've been useful. 31 | // https://github.com/rust-bitcoin/rust-bitcoin/pull/1701 32 | #![allow(clippy::suspicious_arithmetic_impl)] 33 | 34 | mod checksum; 35 | mod gf32; 36 | 37 | use std::{cmp, fmt}; 38 | pub use checksum::Engine as ChecksumEngine; 39 | pub use gf32::Fe as Fe32; 40 | 41 | #[derive(Debug)] 42 | pub enum Error { 43 | /// Error related to a single bech32 character 44 | Field(gf32::Error), 45 | /// Identifier had wrong length when creating a share 46 | IdNotLength4(usize), 47 | /// When translating from u5 to u8, there was an incomplete group of 48 | /// size greater than 4 bits, meaning an entirely extraneous character. 49 | IncompleteGroup(usize), 50 | /// Tried a codex32 string of an illegal length 51 | InvalidLength(usize), 52 | /// Tried to decode a character which was not part of the bech32 alphabet, 53 | /// or, if in the HRP, was not ASCII. 54 | InvalidChar(char), 55 | /// Tried to decode a character but its case did not match the expected case 56 | InvalidCase(Case, char), 57 | /// String had an invalid checksum 58 | InvalidChecksum { 59 | /// Checksum we used, "long" or "short" 60 | checksum: &'static str, 61 | /// The string with the bad checksum 62 | string: String, 63 | }, 64 | /// Threshold was not an allowed value (2 through 9, or 0) 65 | InvalidThreshold(char), 66 | /// Threshold was not an allowed value (2 through 9, or 0) 67 | InvalidThresholdN(usize), 68 | /// Share index was not an allowed value (only S if the threshold is 0, 69 | /// otherwise anything goes) 70 | InvalidShareIndex(Fe32), 71 | /// A set of shares to be interpolated did not all have the same length 72 | MismatchedLength(usize, usize), 73 | /// A set of shares to be interpolated did not all have the same HRP 74 | MismatchedHrp(String, String), 75 | /// A set of shares to be interpolated did not all have the same threshold 76 | MismatchedThreshold(usize, usize), 77 | /// A set of shares to be interpolated did not all have the same ID 78 | MismatchedId(String, String), 79 | /// A share index was repeated in the set of shares to interpolate. 80 | RepeatedIndex(Fe32), 81 | /// A set of shares to be interpolated did not have enough shares 82 | ThresholdNotPassed { threshold: usize, n_shares: usize }, 83 | } 84 | 85 | impl From for Error { 86 | fn from(e: gf32::Error) -> Error { 87 | Error::Field(e) 88 | } 89 | } 90 | 91 | /// Lowercase or uppercase (as applied to the bech32 alphabet) 92 | #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] 93 | pub enum Case { 94 | /// qpzr... 95 | Lower, 96 | /// QPZR... 97 | Upper, 98 | } 99 | 100 | /// A codex32 string, containing a valid checksum 101 | #[derive(Clone, PartialEq, Eq, Hash, Debug)] 102 | pub struct Codex32String(String); 103 | 104 | impl fmt::Display for Codex32String { 105 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 106 | fmt::Display::fmt(&self.0, f) 107 | } 108 | } 109 | 110 | impl Codex32String { 111 | fn sanity_check(&self) -> Result<(), Error> { 112 | let parts = self.parts_inner()?; 113 | let incomplete_group = (parts.payload.len() * 5) % 8; 114 | if incomplete_group > 4 { 115 | return Err(Error::IncompleteGroup(incomplete_group)); 116 | } 117 | Ok(()) 118 | } 119 | 120 | /// Construct a codex32 string from a not-yet-checksummed string 121 | pub fn from_unchecksummed_string(mut s: String) -> Result { 122 | // Determine what checksum to use and extend the string 123 | let (len, mut checksum) = if s.len() < 81 { 124 | (13, checksum::Engine::new_codex32_short()) 125 | } else { 126 | (15, checksum::Engine::new_codex32_long()) 127 | }; 128 | s.reserve_exact(len); 129 | 130 | // Split out the HRP 131 | let (hrp, real_string) = match s.rsplit_once('1') { 132 | Some((s1, s2)) => (s1, s2), 133 | None => ("", &s[..]), 134 | }; 135 | // Compute the checksum 136 | checksum.input_hrp(hrp)?; 137 | checksum.input_data_str(real_string)?; 138 | for ch in checksum.into_residue() { 139 | s.push(ch.to_char()); 140 | } 141 | 142 | let ret = Codex32String(s); 143 | ret.sanity_check()?; 144 | Ok(ret) 145 | } 146 | 147 | /// Construct a codex32 string from an already-checksummed string 148 | pub fn from_string(s: String) -> Result { 149 | let (name, mut checksum) = if s.len() >= 48 && s.len() < 94 { 150 | ("short", checksum::Engine::new_codex32_short()) 151 | } else if s.len() >= 125 && s.len() < 128 { 152 | ("long", checksum::Engine::new_codex32_long()) 153 | } else { 154 | return Err(Error::InvalidLength(s.len())); 155 | }; 156 | 157 | // Split out the HRP 158 | let (hrp, real_string) = match s.rsplit_once('1') { 159 | Some((s1, s2)) => (s1, s2), 160 | None => ("", &s[..]), 161 | }; 162 | checksum.input_hrp(hrp)?; 163 | checksum.input_data_str(real_string)?; 164 | if !checksum.is_valid() { 165 | return Err(Error::InvalidChecksum { 166 | checksum: name, 167 | string: s, 168 | }); 169 | } 170 | // Looks good, return 171 | let ret = Codex32String(s); 172 | ret.sanity_check()?; 173 | Ok(ret) 174 | } 175 | 176 | /// Break the string up into its constituent parts 177 | fn parts_inner(&self) -> Result { 178 | let (hrp, s) = match self.0.rsplit_once('1') { 179 | Some((s1, s2)) => (s1, s2), 180 | None => ("", &self.0[..]), 181 | }; 182 | let checksum_len = if self.0.len() > 93 { 15 } else { 13 }; 183 | let ret = Parts { 184 | hrp, 185 | threshold: match s.as_bytes()[0] { 186 | b'0' => 0, 187 | b'2' => 2, 188 | b'3' => 3, 189 | b'4' => 4, 190 | b'5' => 5, 191 | b'6' => 6, 192 | b'7' => 7, 193 | b'8' => 8, 194 | b'9' => 9, 195 | _ => return Err(Error::InvalidThreshold(s.as_bytes()[0].into())), 196 | }, 197 | id: &s[1..5], 198 | share_index: Fe32::from_char(s.as_bytes()[5].into()).unwrap(), 199 | payload: &s[6..s.len() - checksum_len], 200 | checksum: &s[s.len() - checksum_len..], 201 | }; 202 | if ret.threshold == 0 && ret.share_index != Fe32::S { 203 | return Err(Error::InvalidShareIndex(ret.share_index)); 204 | } 205 | Ok(ret) 206 | } 207 | 208 | /// Break the string up into its constituent parts 209 | pub fn parts(&self) -> Parts { 210 | // unwrap OK since we validated the input on parse 211 | self.parts_inner().unwrap() 212 | } 213 | 214 | /// Interpolate a set of shares to derive a share at a specific index. 215 | /// 216 | /// Using the index `Fe32::S` will recover the master seed. 217 | pub fn interpolate_at( 218 | shares: &[Codex32String], 219 | target: Fe32, 220 | ) -> Result { 221 | // Collect indices and sanity check 222 | if shares.is_empty() { 223 | return Err(Error::ThresholdNotPassed { 224 | threshold: 1, 225 | n_shares: 0, 226 | }); 227 | } 228 | let mut indices = Vec::with_capacity(shares.len()); 229 | let s0_parts = shares[0].parts(); 230 | if s0_parts.threshold > shares.len() { 231 | return Err(Error::ThresholdNotPassed { 232 | threshold: s0_parts.threshold, 233 | n_shares: shares.len(), 234 | }); 235 | } 236 | for share in shares { 237 | let parts = share.parts(); 238 | if shares[0].0.len() != share.0.len() { 239 | return Err(Error::MismatchedLength(shares[0].0.len(), share.0.len())); 240 | } 241 | if s0_parts.hrp != parts.hrp { 242 | return Err(Error::MismatchedHrp(s0_parts.hrp.into(), parts.hrp.into())); 243 | } 244 | if s0_parts.threshold != parts.threshold { 245 | return Err(Error::MismatchedThreshold( 246 | s0_parts.threshold, 247 | parts.threshold, 248 | )); 249 | } 250 | if s0_parts.id != parts.id { 251 | return Err(Error::MismatchedId(s0_parts.id.into(), parts.id.into())); 252 | } 253 | indices.push(parts.share_index); 254 | } 255 | 256 | // Do lagrange interpolation 257 | let mut mult = Fe32::P; 258 | for i in 0..shares.len() { 259 | if indices[i] == target { 260 | // If we're trying to output an input share, just output it directly. 261 | // Naive Lagrange multiplication would otherwise multiply by 0. 262 | return Ok(shares[i].clone()); 263 | } 264 | 265 | mult *= indices[i] + target; 266 | } 267 | 268 | let payload_len = 6 + s0_parts.payload.len() + s0_parts.checksum.len(); 269 | let hrp_len = shares[0].0.len() - payload_len; 270 | let mut result = vec![Fe32::Q; payload_len]; 271 | 272 | for i in 0..shares.len() { 273 | let mut inv = Fe32::P; 274 | for j in 0..shares.len() { 275 | inv *= indices[j] 276 | + if i == j { 277 | target 278 | } else { 279 | // If there is a repeated index, just call this an error. Technically 280 | // speaking, we could reject the other one and re-do the threshold 281 | // check in case we had enough unique ones .. but easier to just make 282 | // it the user's responsibility to provide unique indices to begin with. 283 | if indices[i] == indices[j] { 284 | return Err(Error::RepeatedIndex(indices[i])); 285 | } 286 | indices[i] 287 | } 288 | } 289 | 290 | for (j, res_j) in result.iter_mut().enumerate() { 291 | let ch_at_i = char::from(shares[i].0.as_bytes()[hrp_len + j]); 292 | *res_j += mult / inv * Fe32::from_char(ch_at_i).unwrap(); 293 | } 294 | } 295 | 296 | let mut s = s0_parts.hrp.to_owned(); 297 | s.push('1'); 298 | if s0_parts.hrp.chars().all(char::is_uppercase) { 299 | s.extend( 300 | result 301 | .into_iter() 302 | .map(Fe32::to_char) 303 | .map(|c| c.to_ascii_uppercase()), 304 | ); 305 | } else { 306 | s.extend(result.into_iter().map(Fe32::to_char)); 307 | } 308 | Ok(Codex32String(s)) 309 | } 310 | 311 | /// Creates a S share from bare seed data 312 | pub fn from_seed( 313 | hrp: &str, 314 | threshold: usize, 315 | id: &str, 316 | share_idx: Fe32, 317 | data: &[u8], 318 | ) -> Result { 319 | if id.len() != 4 { 320 | return Err(Error::IdNotLength4(id.len())); 321 | } 322 | 323 | let mut ret = String::with_capacity(hrp.len() + 6 + (data.len() * 8 + 4) / 5); 324 | ret.push_str(hrp); 325 | ret.push('1'); 326 | let k = match threshold { 327 | 0 => Fe32::_0, 328 | 2 => Fe32::_2, 329 | 3 => Fe32::_3, 330 | 4 => Fe32::_4, 331 | 5 => Fe32::_5, 332 | 6 => Fe32::_6, 333 | 7 => Fe32::_7, 334 | 8 => Fe32::_8, 335 | 9 => Fe32::_9, 336 | x => return Err(Error::InvalidThresholdN(x)), 337 | }; 338 | // FIXME correct case to match HRP 339 | ret.push(k.to_char()); 340 | ret.push_str(id); 341 | ret.push(share_idx.to_char()); 342 | 343 | // Convert byte data to base 32 344 | let mut next_u5 = 0; 345 | let mut rem = 0; 346 | for byte in data { 347 | // Each byte provides at least one u5. Push that. 348 | let u5 = (next_u5 << (5 - rem)) | byte >> (3 + rem); 349 | ret.push(Fe32::from_u8(u5).unwrap().to_char()); 350 | next_u5 = byte & ((1 << (3 + rem)) - 1); 351 | // If there were 2 or more bits from the last iteration, then 352 | // this iteration will push *two* u5s. 353 | if rem >= 2 { 354 | ret.push(Fe32::from_u8(next_u5 >> (rem - 2)).unwrap().to_char()); 355 | next_u5 &= (1 << (rem - 2)) - 1; 356 | } 357 | rem = (rem + 8) % 5; 358 | } 359 | if rem > 0 { 360 | ret.push(Fe32::from_u8(next_u5 << (5 - rem)).unwrap().to_char()); 361 | } 362 | 363 | // Initialize checksum engine with HRP and header 364 | let mut checksum = if data.len() < 51 { 365 | checksum::Engine::new_codex32_short() 366 | } else { 367 | checksum::Engine::new_codex32_long() 368 | }; 369 | checksum.input_hrp(hrp)?; 370 | checksum.input_data_str(&ret[hrp.len() + 1..])?; 371 | // Now, to compute the checksum, we stick the target residue onto the end 372 | // of the input string, the take the resulting residue as the checksum 373 | checksum.input_own_target(); 374 | ret.extend(checksum.into_residue().into_iter().map(Fe32::to_char)); 375 | 376 | let mut checksum = checksum::Engine::new_codex32_short(); 377 | checksum.input_hrp(hrp)?; 378 | checksum.input_data_str(&ret[hrp.len() + 1..])?; 379 | Ok(Codex32String(ret)) 380 | } 381 | } 382 | 383 | /// A codex32 string, split into its constituent partrs 384 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 385 | pub struct Parts<'s> { 386 | hrp: &'s str, 387 | threshold: usize, 388 | id: &'s str, 389 | share_index: Fe32, 390 | payload: &'s str, 391 | checksum: &'s str, 392 | } 393 | 394 | impl<'s> Parts<'s> { 395 | /// Extract the binary data from a checksummed string 396 | /// 397 | /// If the string does not have a multiple-of-8 number of bits, right-pad the 398 | /// final byte with 0s. 399 | pub fn data(&self) -> Vec { 400 | let mut ret = Vec::with_capacity((self.payload.len() * 5 + 7) / 8); 401 | 402 | let mut next_byte = 0; 403 | let mut rem = 0; 404 | for ch in self.payload.chars() { 405 | let fe = Fe32::from_char(ch).unwrap(); // unwrap ok since string is valid bech32 406 | match rem.cmp(&3) { 407 | cmp::Ordering::Less => { 408 | // If we are within 3 bits of the start we can fit the whole next char in 409 | next_byte |= fe.to_u8() << (3 - rem); 410 | } 411 | cmp::Ordering::Equal => { 412 | // If we are exactly 3 bits from the start then this char fills in the byte 413 | ret.push(next_byte | fe.to_u8()); 414 | next_byte = 0; 415 | } 416 | cmp::Ordering::Greater => { 417 | // Otherwise we have to break it in two 418 | let overshoot = rem - 3; 419 | assert!(overshoot > 0); 420 | ret.push(next_byte | (fe.to_u8() >> overshoot)); 421 | next_byte = fe.to_u8() << (8 - overshoot); 422 | } 423 | } 424 | rem = (rem + 5) % 8; 425 | } 426 | debug_assert!(rem <= 4); // checked when parsing the string 427 | ret 428 | } 429 | } 430 | 431 | #[cfg(test)] 432 | mod tests { 433 | use super::*; 434 | 435 | // hex-encoding is a niche requirement and does not belong in the standard 436 | // library of a systems programming language -- rust IRC 437 | fn hex(data: &[u8]) -> String { 438 | let mut ret = String::new(); 439 | for byte in data { 440 | ret.push_str(&format!("{:02x}", byte)); 441 | } 442 | ret 443 | } 444 | 445 | #[test] 446 | fn bip_vector_1() { 447 | let secret = "ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"; 448 | let c32 = Codex32String::from_string(secret.into()).unwrap(); 449 | let c32_parts = c32.parts(); 450 | assert_eq!(c32_parts.hrp, "ms"); 451 | // Don't test the separator "1" which is not stored anywhere 452 | assert_eq!(c32_parts.threshold, 0); 453 | assert_eq!(c32_parts.share_index, Fe32::S); 454 | assert_eq!(c32_parts.id, "test"); 455 | assert_eq!(c32_parts.payload, "xxxxxxxxxxxxxxxxxxxxxxxxxx"); 456 | assert_eq!(c32_parts.checksum, "4nzvca9cmczlw"); 457 | assert_eq!(hex(&c32_parts.data()), "318c6318c6318c6318c6318c6318c631"); 458 | // Don't check master node xpriv; this is implied by the master seed 459 | // and would require extra dependencies to compute 460 | } 461 | 462 | #[test] 463 | fn bip_vector_2() { 464 | let share_ac = [ 465 | Codex32String::from_string("MS12NAMEA320ZYXWVUTSRQPNMLKJHGFEDCAXRPP870HKKQRM".into()) 466 | .unwrap(), 467 | Codex32String::from_string("MS12NAMECACDEFGHJKLMNPQRSTUVWXYZ023FTR2GDZMPY6PN".into()) 468 | .unwrap(), 469 | ]; 470 | 471 | let share_d = Codex32String::interpolate_at(&share_ac, Fe32::D).unwrap(); 472 | assert_eq!( 473 | share_d.to_string(), 474 | "MS12NAMEDLL4F8JLH4E5VDVULDLFXU2JHDNLSM97XVENRXEG" 475 | ); 476 | 477 | let seed = Codex32String::interpolate_at(&share_ac, Fe32::S).unwrap(); 478 | assert_eq!( 479 | seed.to_string(), 480 | "MS12NAMES6XQGUZTTXKEQNJSJZV4JV3NZ5K3KWGSPHUH6EVW" 481 | ); 482 | assert_eq!( 483 | hex(&seed.parts().data()), 484 | "d1808e096b35b209ca12132b264662a5" 485 | ); 486 | } 487 | 488 | #[test] 489 | fn bip_vector_3() { 490 | let share_sac = [ 491 | Codex32String::from_string("ms13cashsllhdmn9m42vcsamx24zrxgs3qqjzqud4m0d6nln".into()) 492 | .unwrap(), 493 | Codex32String::from_string("ms13casha320zyxwvutsrqpnmlkjhgfedca2a8d0zehn8a0t".into()) 494 | .unwrap(), 495 | Codex32String::from_string("ms13cashcacdefghjklmnpqrstuvwxyz023949xq35my48dr".into()) 496 | .unwrap(), 497 | ]; 498 | 499 | let share_def = [ 500 | Codex32String::interpolate_at(&share_sac, Fe32::D).unwrap(), 501 | Codex32String::interpolate_at(&share_sac, Fe32::E).unwrap(), 502 | Codex32String::interpolate_at(&share_sac, Fe32::F).unwrap(), 503 | ]; 504 | assert_eq!( 505 | share_def[0].to_string(), 506 | "ms13cashd0wsedstcdcts64cd7wvy4m90lm28w4ffupqs7rm", 507 | ); 508 | assert_eq!( 509 | share_def[1].to_string(), 510 | "ms13casheekgpemxzshcrmqhaydlp6yhms3ws7320xyxsar9", 511 | ); 512 | assert_eq!( 513 | share_def[2].to_string(), 514 | "ms13cashf8jh6sdrkpyrsp5ut94pj8ktehhw2hfvyrj48704", 515 | ); 516 | } 517 | 518 | #[test] 519 | fn bip_vector_4() { 520 | #[rustfmt::skip] 521 | let seed_b = [ 522 | 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 523 | 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 524 | 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 525 | 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 526 | ]; 527 | let seed = Codex32String::from_seed("ms", 0, "leet", Fe32::S, &seed_b).unwrap(); 528 | assert_eq!( 529 | seed.to_string(), 530 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma", 531 | ); 532 | // Our code sticks 0s onto the bitstring to get a multiple of 5 bits. Confirm that 533 | // other choices would've worked. 534 | assert_eq!(seed.parts().data(), seed_b); 535 | let alt_encodings = [ 536 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqqtum9pgv99ycma", 537 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqpj82dp34u6lqtd", 538 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqzsrs4pnh7jmpj5", 539 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqrfcpap2w8dqezy", 540 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqy5tdvphn6znrf0", 541 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyq9dsuypw2ragmel", 542 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqx05xupvgp4v6qx", 543 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyq8k0h5p43c2hzsk", 544 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqgum7hplmjtr8ks", 545 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqf9q0lpxzt5clxq", 546 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyq28y48pyqfuu7le", 547 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqt7ly0paesr8x0f", 548 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqvrvg7pqydv5uyz", 549 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqd6hekpea5n0y5j", 550 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyqwcnrwpmlkmt9dt", 551 | "ms10leetsllhdmn9m42vcsamx24zrxgs3qrl7ahwvhw4fnzrhve25gvezzyq0pgjxpzx0ysaam", 552 | ]; 553 | for alt in alt_encodings { 554 | let seed = Codex32String::from_string(alt.into()).unwrap(); 555 | assert_eq!(seed.parts().data(), seed_b); 556 | } 557 | } 558 | 559 | #[test] 560 | fn bip_vector_5() { 561 | let long_seed = Codex32String::from_string( 562 | "MS100C8VSM32ZXFGUHPCHTLUPZRY9X8GF2TVDW0S3JN54KHCE6MUA7LQPZYGSFJD6AN074RXVCEMLH8WU3TK925ACDEFGHJKLMNPQRSTUVWXY06FHPV80UNDVARHRAK".into() 563 | ).unwrap(); 564 | assert_eq!( 565 | hex(&long_seed.parts().data()), 566 | "dc5423251cb87175ff8110c8531d0952d8d73e1194e95b5f19d6f9df7c01111104c9baecdfea8cccc677fb9ddc8aec5553b86e528bcadfdcc201c17c638c47e9", 567 | ); 568 | } 569 | 570 | #[test] 571 | fn bip_invalid_bad_checksums() { 572 | let bad_checksums = [ 573 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxve740yyge2ghq", 574 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxve740yyge2ghp", 575 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxlk3yepcstwr", 576 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxx6pgnv7jnpcsp", 577 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxx0cpvr7n4geq", 578 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxm5252y7d3lr", 579 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxrd9sukzl05ej", 580 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxc55srw5jrm0", 581 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxgc7rwhtudwc", 582 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxx4gy22afwghvs", 583 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxme084q0vpht7pe0", 584 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxme084q0vpht7pew", 585 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxqyadsp3nywm8a", 586 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxzvg7ar4hgaejk", 587 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxcznau0advgxqe", 588 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxch3jrc6j5040j", 589 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx52gxl6ppv40mcv", 590 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx7g4g2nhhle8fk", 591 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx63m45uj8ss4x8", 592 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy4r708q7kg65x", 593 | ]; 594 | for chk in bad_checksums { 595 | if let Err(Error::InvalidChecksum { .. }) = Codex32String::from_string(chk.into()) { 596 | // ok 597 | } else { 598 | panic!( 599 | "Accepted {} with bad checksum, or raised a different error", 600 | chk 601 | ); 602 | } 603 | } 604 | 605 | let wrong_checksums = [ 606 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxurfvwmdcmymdufv", 607 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxcsyppjkd8lz4hx3", 608 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxu6hwvl5p0l9xf3c", 609 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwqey9rfs6smenxa", 610 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxv70wkzrjr4ntqet", 611 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3hmlrmpa4zl0v", 612 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxrfggf88znkaup", 613 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxpt7l4aycv9qzj", 614 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxus27z9xtyxyw3", 615 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxcwm4re8fs78vn", 616 | ]; 617 | for chk in wrong_checksums { 618 | let err = Codex32String::from_string(chk.into()); 619 | if let Err(Error::InvalidChecksum { .. }) = err { 620 | // ok 621 | } else if let Err(Error::InvalidLength { .. }) = err { 622 | // also ok 623 | } else { 624 | panic!( 625 | "Accepted {} with bad checksum, or raised a different error", 626 | chk 627 | ); 628 | } 629 | } 630 | } 631 | 632 | #[test] 633 | fn bip_invalid_improper_length() { 634 | let bad_length = [ 635 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxw0a4c70rfefn4", 636 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxk4pavy5n46nea", 637 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxx9lrwar5zwng4w", 638 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxr335l5tv88js3", 639 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxvu7q9nz8p7dj68v", 640 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxpq6k542scdxndq3", 641 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkmfw6jm270mz6ej", 642 | "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxzhddxw99w7xws", 643 | "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx42cux6um92rz", 644 | "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxarja5kqukdhy9", 645 | "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxky0ua3ha84qk8", 646 | "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx9eheesxadh2n2n9", 647 | "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx9llwmgesfulcj2z", 648 | "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx02ev7caq6n9fgkf", 649 | ]; 650 | for chk in bad_length { 651 | let err = Codex32String::from_string(chk.into()); 652 | if let Err(Error::InvalidLength { .. }) = err { 653 | // ok 654 | } else if let Err(Error::IncompleteGroup(..)) = err { 655 | // ok 656 | } else { 657 | panic!( 658 | "Accepted {} with invalid length, or raised a different error: {:?}", 659 | chk, err 660 | ); 661 | } 662 | } 663 | } 664 | 665 | #[test] 666 | fn bip_invalid_misc() { 667 | if let Err(Error::InvalidShareIndex(..)) = 668 | Codex32String::from_string("ms10fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx0z26tfn0ulw3p".into()) 669 | { 670 | // ok 671 | } else { 672 | panic!("bad error, expected 'invalid share index'"); 673 | } 674 | 675 | if let Err(Error::InvalidThreshold(..)) = 676 | Codex32String::from_string("ms1fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxda3kr3s0s2swg".into()) 677 | { 678 | // ok 679 | } else { 680 | panic!("bad error, expected 'invalid share index'"); 681 | } 682 | } 683 | 684 | // Skip tho "missing ms prefix" tests because this library is HRP-agnostic 685 | // FIXME it probably should not be, and should probably enforce the ms 686 | 687 | #[test] 688 | fn bip_invalid_case() { 689 | let bad_case = [ 690 | "Ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxuqxkk05lyf3x2", 691 | "mS10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxuqxkk05lyf3x2", 692 | "MS10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxuqxkk05lyf3x2", 693 | "ms10FAUXsxxxxxxxxxxxxxxxxxxxxxxxxxxuqxkk05lyf3x2", 694 | "ms10fauxSxxxxxxxxxxxxxxxxxxxxxxxxxxuqxkk05lyf3x2", 695 | "ms10fauxsXXXXXXXXXXXXXXXXXXXXXXXXXXuqxkk05lyf3x2", 696 | "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxUQXKK05LYF3X2", 697 | ]; 698 | for chk in bad_case { 699 | let err = Codex32String::from_string(chk.into()); 700 | if let Err(Error::InvalidCase { .. }) = err { 701 | // ok 702 | } else { 703 | panic!( 704 | "Accepted {} with invalid length, or raised a different error: {:?}", 705 | chk, err 706 | ); 707 | } 708 | } 709 | } 710 | } 711 | --------------------------------------------------------------------------------