add 12 words XOR
This commit is contained in:
parent
3274dc2987
commit
50fa6f6e9b
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,3 @@
|
||||
*.DS_Store
|
||||
*.DS_Store
|
||||
ENV
|
||||
.idea
|
||||
@ -36,7 +36,7 @@ def left_label(x):
|
||||
def top_label(x):
|
||||
return Paragraph(x, labelStyleCenter)
|
||||
|
||||
def doit(fname='worksheet.pdf'):
|
||||
def doit(fname='worksheet.pdf', word_length=24):
|
||||
doc = SimpleDocTemplate(fname, pagesize=landscape(letter))
|
||||
|
||||
doc.leftMargin = doc.rightMargin = \
|
||||
@ -44,10 +44,10 @@ def doit(fname='worksheet.pdf'):
|
||||
|
||||
# container for the 'Flowable' objects
|
||||
data = []
|
||||
blanks = ['' for i in range(24*3)]
|
||||
blanks = ['' for _ in range(word_length*3)]
|
||||
for part in 'ABCD':
|
||||
row = [part, 'Word #']
|
||||
for i in range(24):
|
||||
for i in range(word_length):
|
||||
row.append(str(i+1))
|
||||
row.append('')
|
||||
row.append('')
|
||||
@ -63,7 +63,12 @@ def doit(fname='worksheet.pdf'):
|
||||
xor = '(A⊕B)⊕C'
|
||||
else:
|
||||
xor = '...⊕' + part
|
||||
data.append([xor, ''] + blanks[:-2] + ['X', 'X'])
|
||||
if word_length == 24:
|
||||
data.append([xor, ''] + blanks[:-2] + ['X', 'X'])
|
||||
elif word_length == 12:
|
||||
data.append([xor, ''] + blanks[:-1] + ['X'])
|
||||
else:
|
||||
data.append([xor, ''] + blanks)
|
||||
|
||||
conf = [
|
||||
#('BACKGROUND',(1,1),(-2,-2),colors.green),
|
||||
@ -94,14 +99,14 @@ def doit(fname='worksheet.pdf'):
|
||||
('LINEABOVE', (0,y+3), (-1, y+3), 1.0, colors.grey),
|
||||
])
|
||||
|
||||
for x in range(24):
|
||||
for x in range(word_length):
|
||||
pos = 2 + (x*3)
|
||||
conf.extend([
|
||||
('SPAN', (pos,y), (pos+2, y)),
|
||||
('SPAN', (pos,y+1), (pos+2, y+1)),
|
||||
])
|
||||
|
||||
for x in range(3, 24*3, 6):
|
||||
for x in range(3, word_length*3, 6):
|
||||
conf.extend([
|
||||
('BACKGROUND', (2+x,0), (2+x+2, -1), colors.lightgrey),
|
||||
])
|
||||
@ -110,7 +115,7 @@ def doit(fname='worksheet.pdf'):
|
||||
|
||||
W1 = 10
|
||||
W2 = W1 + 2
|
||||
t = Table(data, repeatRows=0, colWidths=[W2, None]+[W1 for i in range(24*3)])
|
||||
t = Table(data, repeatRows=0, colWidths=[W2, None]+[W1 for _ in range(word_length*3)])
|
||||
t.setStyle(TableStyle(conf))
|
||||
|
||||
seed_samples = [[cell(w) for w in
|
||||
@ -163,4 +168,5 @@ def doit(fname='worksheet.pdf'):
|
||||
|
||||
doc.build(elements)
|
||||
|
||||
doit()
|
||||
doit("worksheet12.pdf", 12)
|
||||
doit("worksheet.pdf", 24)
|
||||
|
||||
@ -42,7 +42,7 @@ endobj
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Author (\(anonymous\)) /CreationDate (D:20210428142813+05'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20210428142813+05'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Author (\(anonymous\)) /CreationDate (D:20230320161339-01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20230320161339-01'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@ -74,7 +74,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<807392c73ac4a44365403987194c69a5><807392c73ac4a44365403987194c69a5>]
|
||||
[<e090d63c3b5e914fd51680575e942cb0><e090d63c3b5e914fd51680575e942cb0>]
|
||||
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
||||
|
||||
/Info 8 0 R
|
||||
|
||||
86
worksheet12.pdf
Normal file
86
worksheet12.pdf
Normal file
File diff suppressed because one or more lines are too long
@ -37,7 +37,7 @@
|
||||
|
||||
---
|
||||
|
||||
# XOR Seed Example Using 3 Parts
|
||||
# 24 Words XOR Seed Example Using 3 Parts
|
||||
|
||||
## Seed A (1 of 3)
|
||||
|
||||
@ -85,10 +85,58 @@
|
||||
13=nurse [4BC], 14=find [2B5], 15=fish [2BD], 16=scene [604], 17=bench [0A8], 18=asthma [070],
|
||||
19=bike [0B1], 20=wage [7B1], 21=world [7ED], 22=quit [57E], 23=primary [555]
|
||||
|
||||
final word between: gas [300] - lend [3FF]
|
||||
final word between: gas [300] - length [400]
|
||||
correct final word: indoor [398]
|
||||
|
||||
|
||||
|
||||
# 12 Words XOR Seed Example Using 3 Parts
|
||||
|
||||
## Seed A (1 of 3)
|
||||
|
||||
1=romance [5DC], 2=wink [7DE], 3=lottery [420], 4=autumn [07D], 5=shop [635], 6=bring [0E1],
|
||||
7=dawn [1BF], 8=tongue [723], 9=range [58E], 10=crater [194], 11=truth [74E], 12=ability [001]
|
||||
|
||||
A = 5DC 7DE 420 07D 635 0E1 1BF 723 58E 194 74E 001
|
||||
|
||||
|
||||
## Seed B (2 of 3)
|
||||
|
||||
1=boat [0C6], 2=unfair [768], 3=shell [62B], 4=violin [7A2], 5=tree [73F], 6=robust [5DA], 7=open [4D9],
|
||||
8=ride [5CB], 9=visual [7A7], 10=forest [2D9], 11=vintage [7A1], 12=approve [056]
|
||||
|
||||
B = 0C6 768 62B 7A2 73F 5DA 4D9 5CB 7A7 2D9 7A1 056
|
||||
|
||||
|
||||
## Seed C (3 of 3)
|
||||
|
||||
1=lion [411], 2=misery [46D], 3=divide [1FF], 4=hurry [37D], 5=latin [3EB], 6=fluid [2CD], 7=camp [106],
|
||||
8=advance [01F], 9=illegal [388], 10=lab [3E0], 11=pyramid [578], 12=unhappy [76A]
|
||||
|
||||
C = 411 46D 1FF 37D 3EB 2CD 106 01F 388 3E0 578 76A
|
||||
|
||||
|
||||
## Calculation (XOR each hex digit)
|
||||
|
||||
A = 5DC 7DE 420 07D 635 0E1 1BF 723 58E 194 74E 001
|
||||
B = 0C6 768 62B 7A2 73F 5DA 4D9 5CB 7A7 2D9 7A1 056
|
||||
C = 411 46D 1FF 37D 3EB 2CD 106 01F 388 3E0 578 76A
|
||||
| | |
|
||||
XOR = 10B 4DB 3F4 4A2 2E1 7F6 460 2F7 1A1 0AD 597 73x
|
||||
|
||||
|
||||
## Resulting Seed Phrase
|
||||
|
||||
1=cannon [10B], 2=opinion [4DB], 3=leader [3F4], 4=nephew [4A2], 5=found [2E1], 6=yard [7F6],
|
||||
7=metal [460], 8=galaxy [2F7], 9=crouch [1A1], 10=between [0AD], 11=real [597]
|
||||
|
||||
final word between: toward [730] - trend [740]
|
||||
correct final word: trade [735]
|
||||
|
||||
|
||||
|
||||
- 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, and there will be only one
|
||||
- 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
|
||||
|
||||
102
xor.py
102
xor.py
@ -34,46 +34,54 @@ def xor_table(indent=' '*8):
|
||||
- down to C, that is answer: a ⊕ b ⊕ c
|
||||
''')
|
||||
|
||||
def get_words(h):
|
||||
# Apply BIP39 to convert into seed words
|
||||
v = int.from_bytes(h, 'big') << 8
|
||||
w = []
|
||||
for i in range(24):
|
||||
|
||||
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)
|
||||
w.insert(0, m)
|
||||
assert not v
|
||||
indexes.insert(0, m)
|
||||
# final csum_len bits are a checksum
|
||||
indexes[-1] += sha256(entropy).digest()[0] >> (8 - csum_len)
|
||||
return indexes
|
||||
|
||||
# final 8 bits are a checksum
|
||||
w[-1] |= sha256(h).digest()[0]
|
||||
|
||||
return w
|
||||
|
||||
def calc_check(words):
|
||||
assert len(words) == 24
|
||||
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(24):
|
||||
for i in range(words_len):
|
||||
x <<= 11
|
||||
x |= words[i]
|
||||
x >>= 8
|
||||
raw = x.to_bytes(32, 'big')
|
||||
x >>= csum_len
|
||||
raw = x.to_bytes(e_len, 'big')
|
||||
|
||||
cb = sha256(raw).digest()[0]
|
||||
|
||||
x <<= 8
|
||||
cb = sha256(raw).digest()[0] >> (8 - csum_len)
|
||||
x <<= csum_len
|
||||
x |= cb
|
||||
|
||||
return raw, (x % 0x800)
|
||||
|
||||
def print_phrase(raw, indent=' '*4):
|
||||
|
||||
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 len(raw) == 32:
|
||||
if from_entropy:
|
||||
words = get_words(raw)
|
||||
assert len(words) == 24
|
||||
else:
|
||||
words = list(raw)
|
||||
assert len(words) in {23, 24}
|
||||
|
||||
x = ['%d=%s_[%03X]'%(n+1, wordlist[i],i) for n,i in enumerate(words)]
|
||||
msg = ', '.join(x)
|
||||
@ -83,18 +91,18 @@ def print_phrase(raw, indent=' '*4):
|
||||
|
||||
return eng, hx, words
|
||||
|
||||
def worked_example(count=3):
|
||||
def worked_example(count=3, entropy_bytes=32):
|
||||
|
||||
rng = Random(123 * count)
|
||||
|
||||
print(f'# XOR Seed Example Using {count} Parts\n')
|
||||
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 i in range(32))
|
||||
raw = bytearray(rng.randint(0, 255) for _ in range(entropy_bytes))
|
||||
|
||||
eng, hx, words = print_phrase(raw, indent)
|
||||
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))
|
||||
@ -106,8 +114,14 @@ def worked_example(count=3):
|
||||
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]) - 2):
|
||||
for pos in range(len(digits[0]) - constant):
|
||||
if digits[0][pos] == ' ':
|
||||
xor += ' '
|
||||
continue
|
||||
@ -118,7 +132,7 @@ def worked_example(count=3):
|
||||
assert 0 <= here < 16, here
|
||||
xor += '%X' % here
|
||||
|
||||
xor += 'xx'
|
||||
xor += 'x' * constant
|
||||
|
||||
lst = list(range(0, 100, 16)) + [23*4]
|
||||
align = ''.join(('|' if n in lst else ' ') for n in range(len(xor)))
|
||||
@ -132,20 +146,36 @@ def worked_example(count=3):
|
||||
|
||||
eng, hx, _ = print_phrase(rw, indent)
|
||||
print('\n'.join(eng))
|
||||
chk = int(xor.split(' ')[-1][0], 16) * 0x100
|
||||
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
|
||||
|
||||
print(f'\n{indent}final word between: %s [%03X] - %s [%03X]' % (
|
||||
wordlist[chk], chk, wordlist[chk+0xff], chk+0xff))
|
||||
wordlist[chk], chk, wordlist[chk+_range], chk+_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)
|
||||
_, 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, and there will be only one
|
||||
- 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
|
||||
@ -159,6 +189,10 @@ if 1:
|
||||
print('---\n')
|
||||
|
||||
if 1:
|
||||
worked_example()
|
||||
worked_example(3, entropy_bytes=32)
|
||||
print("\n\n")
|
||||
worked_example(3, entropy_bytes=16)
|
||||
print("\n\n")
|
||||
footer_text()
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user