-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathxor.py
200 lines (152 loc) · 5.76 KB
/
xor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# print some charts
from hashlib import sha256
from mnemonic import Mnemonic
from random import Random
from textwrap import TextWrapper
wordlist = Mnemonic('english').wordlist
WIDTH = 110
def xor_table(indent=' '*8):
print(indent + '| XOR |' + '|'.join(' %X ' % x for x in range(16)))
print(indent + '|----:' + '|---'*16)
for y in range(16):
print(indent + '|**%X**|' % y, end='')
print('|'.join(' %X ' % (x^y) for x in range(16)))
print('''\
- XOR = EOR = ⊕ = Exclusive OR [Wikipedia](https://en.wikipedia.org/wiki/Exclusive_or)
- values in table are: x ⊕ y in hex
- go sideways for first digit, then look down for second digit
- in fact, doesn't matter if you do row or column first
- example: 2 XOR 6 => 4 same as 6 XOR 2 => 4
- any values XOR itself is zero (diagonal on this table)
- alternative view: (x) XOR (y) = flip bits of (x) that are set in (y)
- XOR with zero does nothing (flips no bits)
- XOR with 0xF flips all four bits
- XOR with self flips all set bits, so gives zero
- to XOR three values together, do (a⊕b)=X then (X⊕c)=answer
- right to A, down to B ... take that number, and go to that column
- down to C, that is answer: a ⊕ b ⊕ c
''')
def get_words(entropy):
e_len = len(entropy)
assert e_len in [16, 20, 24, 28, 32]
csum_len = int(e_len / 4)
num_words = int(((e_len * 8) + csum_len) / 11)
v = int.from_bytes(entropy, 'big') << csum_len
indexes = []
for i in range(num_words):
v, m = divmod(v, 2048)
indexes.insert(0, m)
# final csum_len bits are a checksum
indexes[-1] += sha256(entropy).digest()[0] >> (8 - csum_len)
return indexes
def calc_check(words):
words_len = len(words)
csum_len = int(words_len / 3)
e_len = int(((words_len * 11) - csum_len) / 8)
x = 0
for i in range(words_len):
x <<= 11
x |= words[i]
x >>= csum_len
raw = x.to_bytes(e_len, 'big')
cb = sha256(raw).digest()[0] >> (8 - csum_len)
x <<= csum_len
x |= cb
return raw, (x % 0x800)
def entropy_length_to_word_length(entropy_len):
entropy_bit_len = entropy_len * 8
cs_bit_len = entropy_bit_len // 32
return (entropy_bit_len + cs_bit_len) // 11
def print_phrase(raw, indent=' '*4, from_entropy=False):
tw = TextWrapper(width=WIDTH, initial_indent=indent, subsequent_indent=indent)
if from_entropy:
words = get_words(raw)
else:
words = list(raw)
x = ['%d=%s_[%03X]'%(n+1, wordlist[i],i) for n,i in enumerate(words)]
msg = ', '.join(x)
eng = [ln.replace('_', ' ') for ln in tw.wrap(msg)]
hx = ' '.join('%03X'%i for i in words)
return eng, hx, words
def worked_example(count=3, entropy_bytes=32):
rng = Random(123 * count)
num_words = entropy_length_to_word_length(entropy_bytes)
print(f'# {num_words} Words XOR Seed Example Using {count} Parts\n')
indent = ' '*6
digits = []
for n in range(count):
raw = bytearray(rng.randint(0, 255) for _ in range(entropy_bytes))
eng, hx, words = print_phrase(raw, indent, from_entropy=True)
print(f'## Seed {n+10:X} ({n+1} of {count})\n')
print('\n'.join(eng))
print(f'\n{indent}{n+10:X} = {hx}\n\n')
digits.append(hx)
print('## Calculation (XOR each hex digit)\n')
for n in range(count):
print(f'{indent}{n+10:X} = {digits[n]}')
if entropy_bytes == 32:
constant = 2
elif entropy_bytes == 16:
constant = 1
else:
constant = 0
xor = ''
for pos in range(len(digits[0]) - constant):
if digits[0][pos] == ' ':
xor += ' '
continue
here = 0
for n in range(count):
here ^= int(digits[n][pos], 16)
assert 0 <= here < 16, here
xor += '%X' % here
xor += 'x' * constant
lst = list(range(0, 100, 16)) + [23*4]
align = ''.join(('|' if n in lst else ' ') for n in range(len(xor)))
print(f'{indent} {align}')
print(f'{indent[2:]}XOR = {xor}')
print('\n\n## Resulting Seed Phrase\n')
rw = [int(i, 16) for i in xor.split(' ')[:-1]]
eng, hx, _ = print_phrase(rw, indent)
print('\n'.join(eng))
if len(rw) == 11:
_range = 0x10
chk = int(xor.split(' ')[-1][0:2], 16) * _range
elif len(rw) == 23:
_range = 0x100
chk = int(xor.split(' ')[-1][0], 16) * _range
else:
# for these cases - bitwise operations required
# as their checksums have length in (5,6,7)
lw = xor.split(' ')[-1]
lw_int = int(lw, 16)
chk_len = int((len(rw) + 1) / 3)
_range = (2 ** chk_len) - 1
chk = (lw_int >> chk_len) << chk_len
inclusive_range = (chk+_range) - 1
print(f'\n{indent}final word between: %s [%03X] - %s [%03X]' % (
wordlist[chk], chk, wordlist[inclusive_range], inclusive_range))
secret, final = calc_check(rw + [chk])
print(f'{indent}correct final word: %s [%03X]' % (wordlist[final], final))
# check our checksum math
_, chk_hx, _ = print_phrase(secret, indent, from_entropy=True)
assert chk_hx.split(' ')[-1] == '%03X'%final
def footer_text():
print('''\
- It's not possible to calculate the checksum of the final seed phrase on paper (needs SHA256).
- But it must start with the indicated digit(s). If using 24 words XOR, there will be only one
suitable choice offered by the Coldcard in that range (x00 to xFF),
once you have entered the other 23 words.
- The checksum of each of the XOR-parts protects the final result, assuming your XOR
math is correct.
''')
if 1:
print('## XOR Lookup Table\n\n')
xor_table('')
print('---\n')
if 1:
worked_example(3, entropy_bytes=32)
print("\n\n")
worked_example(3, entropy_bytes=16)
print("\n\n")
footer_text()