add PDF worksheet
This commit is contained in:
parent
cf1e05b222
commit
bd2b9107f8
19
README.md
19
README.md
@ -1,7 +1,6 @@
|
||||
|
||||
# BIP39 Wordlist on Paper
|
||||
|
||||
Uses python's "reportlab" to make a PDF file of 4 pages, containing every English
|
||||
Use python's "reportlab" to make a PDF file of 4 pages, containing every English
|
||||
BIP-39 seed word, and their hex value.
|
||||
|
||||
Could be cut into squares for drawing from a hat... or for looking up values to do
|
||||
@ -11,13 +10,23 @@ Hexidecimal number that each words represents is shown in the corner.
|
||||
|
||||
Look here for the final PDF ready to go: [wordlist.pdf](wordlist.pdf)
|
||||
|
||||
|
||||
# Seed XOR Worksheets and Explainer
|
||||
|
||||
This repo is also holding some code to explain how to XOR two or more
|
||||
This repo is also holds some code to explain how to XOR two or more
|
||||
seed phrases together, using your hands... fully airgapped.
|
||||
|
||||
See [xor-example.md](xor-example.md)
|
||||
|
||||
The worksheet to go with this is still in progress.
|
||||
To follow along, or to use this paper method yourself, you may want to use this worksheet:
|
||||
|
||||
[worksheet.pdf](worksheet.pdf)
|
||||
|
||||
You will need the wordlist as reference as well.
|
||||
|
||||
# Security
|
||||
|
||||
Be sure to completely destroy any paper materials used in your work. They
|
||||
contain your master secret and could be used to steal your funds! It's best
|
||||
to burn them after use.
|
||||
|
||||
|
||||
|
||||
166
pdf-worksheet.py
Normal file
166
pdf-worksheet.py
Normal file
@ -0,0 +1,166 @@
|
||||
# print a large worksheet
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.pagesizes import letter, landscape
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib.enums import *
|
||||
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
|
||||
from mnemonic import Mnemonic
|
||||
wordlist = Mnemonic('english').wordlist
|
||||
|
||||
styleSheet = getSampleStyleSheet()
|
||||
|
||||
labelStyle = ParagraphStyle('llab', fontFace='courier', alignment=TA_RIGHT)
|
||||
labelStyleCenter = ParagraphStyle('tlab', fontFace='courier', alignment=TA_CENTER)
|
||||
|
||||
commentStyle = ParagraphStyle('com', alignment=TA_CENTER)
|
||||
|
||||
hex_style = ParagraphStyle('tlab', fontName='Courier-Bold', alignment=TA_LEFT, fontSize=6, spaceAfter=2, spaceBefore=0, leading=4)
|
||||
word_style = ParagraphStyle('cell', alignment=TA_CENTER, fontSize=8, spaceBefore=0, spaceAfter=0, leading=8)
|
||||
|
||||
def cell(w):
|
||||
assert 0 <= w < 0x800
|
||||
word = wordlist[w]
|
||||
hex = ('%03x' % w).upper()
|
||||
|
||||
#rv = Paragraph(f'{word}\n<br/><font face="courier">{hex}</font>', cellStyle)
|
||||
rv = []
|
||||
rv.append(Paragraph(hex, hex_style))
|
||||
rv.append(Paragraph(word, word_style))
|
||||
|
||||
return rv
|
||||
|
||||
def left_label(x):
|
||||
return Paragraph(x, labelStyle)
|
||||
def top_label(x):
|
||||
return Paragraph(x, labelStyleCenter)
|
||||
|
||||
def doit(fname='worksheet.pdf'):
|
||||
doc = SimpleDocTemplate(fname, pagesize=landscape(letter))
|
||||
|
||||
doc.leftMargin = doc.rightMargin = \
|
||||
doc.topMargin = doc.bottomMargin = 0.1 * inch
|
||||
|
||||
# container for the 'Flowable' objects
|
||||
data = []
|
||||
blanks = ['' for i in range(24*3)]
|
||||
for part in 'ABCD':
|
||||
row = [part, 'Word #']
|
||||
for i in range(24):
|
||||
row.append(str(i+1))
|
||||
row.append('')
|
||||
row.append('')
|
||||
|
||||
data.append(row)
|
||||
data.append(['', 'Word'] + blanks)
|
||||
data.append(['', 'Hex Digit'] + blanks)
|
||||
|
||||
if part != 'A':
|
||||
if part == 'B':
|
||||
xor = 'A⊕B'
|
||||
elif part == 'C':
|
||||
xor = '(A⊕B)⊕C'
|
||||
else:
|
||||
xor = '...⊕' + part
|
||||
data.append([xor, ''] + blanks[:-2] + ['X', 'X'])
|
||||
|
||||
conf = [
|
||||
#('BACKGROUND',(1,1),(-2,-2),colors.green),
|
||||
#('TEXTCOLOR',(0,0),(0,-1),colors.red),
|
||||
('VALIGN', (0,0),(0,-1), 'MIDDLE'),
|
||||
#('ALIGN', (0,0),(0,-1), 'RIGHT'),
|
||||
('ALIGN', (0,0),(0,1), 'CENTER'),
|
||||
('ALIGN', (1,0),(2,-1), 'RIGHT'),
|
||||
('ALIGN', (2,0),(-1,-1), 'CENTER'),
|
||||
('VALIGN', (0,0),(0,-1), 'MIDDLE'),
|
||||
('LEFTPADDING', (0,0),(-1,-1), 2),
|
||||
('RIGHTPADDING', (0,0),(-1,-1), 2),
|
||||
#('TOPPADDING', (0,0),(-1,-1), 0),
|
||||
#('BOTTOMPADDING', (0,0),(-1,-1), 4.25),
|
||||
('GRID', (0,0),(-1,-1), 0.5, colors.grey),
|
||||
('LINEBEFORE', (2,0), (2, -1), 1.0, colors.grey),
|
||||
]
|
||||
for y in [0, 3, 3+4, 3+4+4]:
|
||||
conf.extend([
|
||||
('SPAN', (0,y), (0, y+2)),
|
||||
('BACKGROUND', (0,y), (0, y+2), colors.lightgrey),
|
||||
('LINEABOVE', (0,y), (-1, y), 1.0, colors.grey),
|
||||
])
|
||||
if y:
|
||||
conf.extend([
|
||||
('SPAN', (0,y+3), (1, y+3)),
|
||||
('ALIGN', (0,y+3),(1,y+3), 'RIGHT'),
|
||||
('LINEABOVE', (0,y+3), (-1, y+3), 1.0, colors.grey),
|
||||
])
|
||||
|
||||
for x in range(24):
|
||||
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):
|
||||
conf.extend([
|
||||
('BACKGROUND', (2+x,0), (2+x+2, -1), colors.lightgrey),
|
||||
])
|
||||
|
||||
|
||||
|
||||
W1 = 10
|
||||
W2 = W1 + 2
|
||||
t = Table(data, repeatRows=0, colWidths=[W2, None]+[W1 for i in range(24*3)])
|
||||
t.setStyle(TableStyle(conf))
|
||||
|
||||
seed_samples = [[cell(w) for w in
|
||||
sorted(list(range(0, 0x800, 0x100)) + [0, 1, 2, 3, 0x7fd, 0x7fe, 0x7ff])]]
|
||||
|
||||
t2 = Table(seed_samples, colWidths=50)
|
||||
t2.setStyle(TableStyle([
|
||||
('LEFTPADDING', (0,0),(-1,-1), 2),
|
||||
('RIGHTPADDING', (0,0),(-1,-1), 2),
|
||||
('TOPPADDING', (0,0),(-1,-1), 0),
|
||||
('BOTTOMPADDING', (0,0),(-1,-1), 4.25),
|
||||
('GRID', (0,0),(-1,-1), 0.5, colors.grey),
|
||||
]),
|
||||
)
|
||||
|
||||
hex_table = [['⊕'] + ['%X'%i for i in range(16)]]
|
||||
for y in range(16):
|
||||
hex_table.append( ['%X'%y] + ['%X'%(x^y) for x in range(16)] )
|
||||
|
||||
t3 = Table(hex_table, colWidths=None)
|
||||
t3s = TableStyle([
|
||||
('ALIGN', (1,1),(-1,-1), 'CENTER'),
|
||||
('ALIGN', (1,1),(-1,-1), 'RIGHT'),
|
||||
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
|
||||
('GRID', (0,0),(-1,-1), 0.5, colors.grey),
|
||||
('FONT', (0,0), (-1, -1), 'Courier-Bold', 6),
|
||||
('FONT', (1,1), (-1, -1), 'Courier', 6),
|
||||
])
|
||||
for x in range(0, 16, 2):
|
||||
t3s.add('BACKGROUND', (2+x,0), (2+x, -1), colors.lightgrey)
|
||||
t3.setStyle(t3s)
|
||||
|
||||
# page top-to-bottom
|
||||
elements = []
|
||||
elements.append(Paragraph('Seed XOR Worksheet',
|
||||
ParagraphStyle('tlab2', alignment=TA_LEFT, fontSize=16, spaceAfter=20, spaceBefore=20)))
|
||||
elements.append(t)
|
||||
|
||||
elements.append(Spacer(0, 20))
|
||||
elements.append(t3)
|
||||
|
||||
if 1:
|
||||
elements.append(Paragraph('IMPORTANT: After use, burn with fire!',
|
||||
ParagraphStyle('tlab', alignment=TA_LEFT, fontSize=8, spaceAfter=2, spaceBefore=4)))
|
||||
|
||||
|
||||
if 0:
|
||||
elements.append(Spacer(0, 10))
|
||||
elements.append(t2)
|
||||
|
||||
doc.build(elements)
|
||||
|
||||
doit()
|
||||
86
worksheet.pdf
Normal file
86
worksheet.pdf
Normal file
File diff suppressed because one or more lines are too long
@ -1,3 +1,42 @@
|
||||
## XOR Lookup Table
|
||||
|
||||
|
||||
| XOR | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
||||
|----:|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---
|
||||
|**0**| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
||||
|**1**| 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6 | 9 | 8 | B | A | D | C | F | E
|
||||
|**2**| 2 | 3 | 0 | 1 | 6 | 7 | 4 | 5 | A | B | 8 | 9 | E | F | C | D
|
||||
|**3**| 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | B | A | 9 | 8 | F | E | D | C
|
||||
|**4**| 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | C | D | E | F | 8 | 9 | A | B
|
||||
|**5**| 5 | 4 | 7 | 6 | 1 | 0 | 3 | 2 | D | C | F | E | 9 | 8 | B | A
|
||||
|**6**| 6 | 7 | 4 | 5 | 2 | 3 | 0 | 1 | E | F | C | D | A | B | 8 | 9
|
||||
|**7**| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | F | E | D | C | B | A | 9 | 8
|
||||
|**8**| 8 | 9 | A | B | C | D | E | F | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
|**9**| 9 | 8 | B | A | D | C | F | E | 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6
|
||||
|**A**| A | B | 8 | 9 | E | F | C | D | 2 | 3 | 0 | 1 | 6 | 7 | 4 | 5
|
||||
|**B**| B | A | 9 | 8 | F | E | D | C | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4
|
||||
|**C**| C | D | E | F | 8 | 9 | A | B | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3
|
||||
|**D**| D | C | F | E | 9 | 8 | B | A | 5 | 4 | 7 | 6 | 1 | 0 | 3 | 2
|
||||
|**E**| E | F | C | D | A | B | 8 | 9 | 6 | 7 | 4 | 5 | 2 | 3 | 0 | 1
|
||||
|**F**| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
|
||||
|
||||
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
# XOR Seed Example Using 3 Parts
|
||||
|
||||
## Seed A (1 of 3)
|
||||
@ -55,44 +94,3 @@
|
||||
- The checksum of each of the XOR-parts protects the final result, assuming your XOR
|
||||
math is correct.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## XOR Lookup Table
|
||||
|
||||
|
||||
| XOR | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
||||
|----:|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---
|
||||
|**0**| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
||||
|**1**| 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6 | 9 | 8 | B | A | D | C | F | E
|
||||
|**2**| 2 | 3 | 0 | 1 | 6 | 7 | 4 | 5 | A | B | 8 | 9 | E | F | C | D
|
||||
|**3**| 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | B | A | 9 | 8 | F | E | D | C
|
||||
|**4**| 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | C | D | E | F | 8 | 9 | A | B
|
||||
|**5**| 5 | 4 | 7 | 6 | 1 | 0 | 3 | 2 | D | C | F | E | 9 | 8 | B | A
|
||||
|**6**| 6 | 7 | 4 | 5 | 2 | 3 | 0 | 1 | E | F | C | D | A | B | 8 | 9
|
||||
|**7**| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | F | E | D | C | B | A | 9 | 8
|
||||
|**8**| 8 | 9 | A | B | C | D | E | F | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
|**9**| 9 | 8 | B | A | D | C | F | E | 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6
|
||||
|**A**| A | B | 8 | 9 | E | F | C | D | 2 | 3 | 0 | 1 | 6 | 7 | 4 | 5
|
||||
|**B**| B | A | 9 | 8 | F | E | D | C | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4
|
||||
|**C**| C | D | E | F | 8 | 9 | A | B | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3
|
||||
|**D**| D | C | F | E | 9 | 8 | B | A | 5 | 4 | 7 | 6 | 1 | 0 | 3 | 2
|
||||
|**E**| E | F | C | D | A | B | 8 | 9 | 6 | 7 | 4 | 5 | 2 | 3 | 0 | 1
|
||||
|**F**| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
|
||||
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user