Skip to content

Commit

Permalink
Merge pull request #434 from 3rdIteration/main
Browse files Browse the repository at this point in the history
Add additional "Tiny Seed Like" Binary seed options
  • Loading branch information
odudex authored Aug 2, 2024
2 parents 268cc73 + e40639a commit 2c08417
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 24 deletions.
25 changes: 25 additions & 0 deletions docs/getting-started/features/tinyseed.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Background
The examples below have been crated so that you can test the workflow for scanning both 12 and 24 word mnemonics. (Scanning the left plate for a 12 word mnemonic and both plates for 24) The resulting fingerprint from an successful scan is also incldued in the image.

### TinySeed
![](../../img/tinyseed_binarygrid/tinyseed.jpg)

### OneKey KeyTag
![](../../img/tinyseed_binarygrid/onekey_keytag.jpg)

### Binary Grid
![](../../img/tinyseed_binarygrid/binarygrid.jpg)

## Size, Offset and Padding Reference
The general logic for how these are processed is:
1. Krux first looks for a square (Which works best if with a well lit square, with clean edges, on a dark background)
2. This square is checked and if the ratio of length to height is within a defined range for the given seed type, the square is further processed. (Uses the aspect_high and aspect_low variables)
3. An X and Y offset are applied to work out the corner of the seed grid within the seed plate. Some devices like the Maix Amigo use a mirrored coordinate system and some seed types will have a slightly different layout on the front and back of the plate. (Uses the x_offset and y_offset variables, p0 for the front face and p1 for the reverse face)
4. The location of each cell within the 12x12 grid is calculated. (This uses the xpad and ypad variables)
5. Krux uses the grid created in 4 to evaluate which cells are marked and which are blank, once a seed with a valid checksum is detected, the user can then confirm the dots.

If you have a different type of grid that you want to use, you will need to edit the offsets and padding numbers in tiny_seed.py. (All of the sizes are scaled based on the size of the square detected in step 1...)

You can match the pre-sets for supported key-types to the physical dimensions of the tag as shown below. (The numbers for these offsets are in 1/10th of a millimeter)

![](../../img/tinyseed_binarygrid/size_reference.jpg)
16 changes: 13 additions & 3 deletions docs/getting-started/usage/loading-a-mnemonic.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,20 @@ It's unpleasant having to manually enter 12 or 24 words every time you want to u
After opening your wallet via one of the manual methods you can use Krux to create QR codes of all types above, transcript them to paper or metal using the transcription helpers or attach a thermal printer to your Krux and print out the mnemonic. Check out the [Printing section](../features/printing.md) for more information.
You can also use [an offline QR code generator for this](https://iancoleman.io/bip39/) (ideally on an airgapped device).

#### Tiny Seed
#### Tiny Seed, OneKey KeyTag or Binary Grid
Tiny Seed (and similar methods) directly encode a seed as binary, allowing for a very compact mnemonic storage method when compared to SeedQR and Compact SeedQR.

[Tiny Seed](https://tinyseed.io/) is a compact metal plate mnemonic backup method.
Krux devices have machine vision capabilities that allow users to scan these metal plates and instantly load mnemonics engraved on them. To properly scan them place the Tiny Seed over a black background and paint the punched bits black to increase contrast. You can also scan the thermally printed version, or a filled template. Find templates to scan or print [here](https://github.com/odudex/krux_binaries/tree/main/templates).
Krux devices have machine vision capabilities that allow users to scan these metal plates and instantly load mnemonics engraved on them. (This feature is not available in Krux on Android)

To properly scan them place the Tiny Seed (or similar) over a black background and paint the punched bits black to increase contrast. You can also scan the thermally printed version, or a filled template.

[You can find some examples of seeds encoded with each of the supported formats here.](../features/tinyseed.md)

Retail versions of this type of seed can be purchased here:
[Tiny Seed](https://tinyseed.io/)
[Onekey KeyTag](https://onekey.so/products/onekey-keytag/)

Alternatively, you can find templates to scan or print [here](https://github.com/odudex/krux_binaries/tree/main/templates).

### Via Manual Input
<img src="../../../img/maixpy_m5stickv/load-mnemonic-manual-options-125.png" align="right">
Expand Down
Binary file added docs/img/tinyseed_binarygrid/binarygrid.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/tinyseed_binarygrid/onekey_keytag.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/tinyseed_binarygrid/size_reference.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/tinyseed_binarygrid/tinyseed.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions src/krux/pages/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,9 @@ def load_key_from_camera(self):
self.ctx,
[
(t("QR Code"), self.load_key_from_qr_code),
(
"Tiny Seed",
self.load_key_from_tiny_seed_image,
),
("Tiny Seed", lambda: self.load_key_from_tiny_seed_image("TinySeed")),
("OneKey KeyTag", lambda: self.load_key_from_tiny_seed_image("OneKey KeyTag")),
(t("Binary Grid"), lambda: self.load_key_from_tiny_seed_image("Binary Grid")),
],
)
index, status = submenu.run_loop()
Expand Down Expand Up @@ -676,7 +675,7 @@ def load_key_from_tiny_seed(self):
return self._load_key_from_words(words)
return MENU_CONTINUE

def load_key_from_tiny_seed_image(self):
def load_key_from_tiny_seed_image(self, grid_type = "TinySeed"):
"""Menu handler to scan key from Tiny Seed sheet metal storage method"""
from .tiny_seed import TinyScanner

Expand All @@ -691,8 +690,9 @@ def load_key_from_tiny_seed_image(self):
if not self.prompt(t("Proceed?"), BOTTOM_PROMPT_LINE):
return MENU_CONTINUE

tiny_scanner = TinyScanner(self.ctx)
words = tiny_scanner.scanner(len_mnemonic == 24)
w24 = index == 1
tiny_scanner = TinyScanner(self.ctx, grid_type)
words = tiny_scanner.scanner(w24)
del tiny_scanner
if words is None:
self.flash_error(t("Failed to load mnemonic"))
Expand Down
77 changes: 63 additions & 14 deletions src/krux/pages/tiny_seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,49 @@ def _editable_bit():
class TinyScanner(Page):
"""Uses camera sensor to detect punch pattern on a Tiny Seed, in metal or paper"""

def __init__(self, ctx):
"""Settings for different binary grid types"""
binary_grid_settings = {
'TinySeed' : {'xpad_factor' : (240 / (12 * 345)),
'ypad_factor' : (210 / (12 * 272)),
'x_offset_factor_amigo_p0': 39 / 345,
'y_offset_factor_amigo_p0': 44 / 272,
'x_offset_factor_amigo_p1' : 42 / 345,
'y_offset_factor_amigo_p1' : 41 / 272,
'x_offset_factor_p0' : 65 / 345,
'y_offset_factor_p0' : 17 / 272,
'x_offset_factor_p1' : 62 / 345,
'y_offset_factor_p1' : 22 / 272,
'aspect_high' : 1.3,
'aspect_low' : 1.1},
'OneKey KeyTag': {'xpad_factor': 240 / (12 * 360),
'ypad_factor': 240 / (12 * 335),
'x_offset_factor_amigo_p0': 50/360,
'y_offset_factor_amigo_p0': 67/335,
'x_offset_factor_amigo_p1': 50/360,
'y_offset_factor_amigo_p1': 67/335,
'x_offset_factor_p0' : 68/360,
'y_offset_factor_p0' : 30/335,
'x_offset_factor_p1' : 68/360,
'y_offset_factor_p1' : 30/335,
'aspect_high' : 1.1,
'aspect_low' : 0.9},
'Binary Grid': {'xpad_factor': 1/14,
'ypad_factor': 1/14,
'x_offset_factor_amigo_p0': 1/14,
'y_offset_factor_amigo_p0': 1/14,
'x_offset_factor_amigo_p1': 1/14,
'y_offset_factor_amigo_p1': 1/14,
'x_offset_factor_p0': 1/14,
'y_offset_factor_p0': 1/14,
'x_offset_factor_p1': 1/14,
'y_offset_factor_p1': 1/14,
'aspect_high': 1.1,
'aspect_low': 0.9},
}

grid_settings = None

def __init__(self, ctx, grid_type="TinySeed"):
super().__init__(ctx, None)
self.ctx = ctx
# Capturing flag used for first page of 24 words seed
Expand All @@ -591,6 +633,7 @@ def __init__(self, ctx):
self.time_frame = time.ticks_ms()
self.previous_seed_numbers = [1] * 12
self.tiny_seed = TinySeed(self.ctx)
self.grid_settings = self.binary_grid_settings[grid_type]

def _map_punches_region(self, rect_size, page=0):
# Think in portrait mode, with Tiny Seed tilted 90 degrees
Expand All @@ -599,22 +642,22 @@ def _map_punches_region(self, rect_size, page=0):
if not page:
if board.config["type"] == "amigo":
# Amigo has mirrored coordinates
x_offset = rect_size[0] + (rect_size[2] * 39) / 345
y_offset = rect_size[1] + (rect_size[3] * 44) / 272
x_offset = rect_size[0] + rect_size[2] * self.grid_settings['x_offset_factor_amigo_p0']
y_offset = rect_size[1] + rect_size[3] * self.grid_settings['y_offset_factor_amigo_p0']
else:
x_offset = rect_size[0] + (rect_size[2] * 65) / 345
y_offset = rect_size[1] + (rect_size[3] * 17) / 272
x_offset = rect_size[0] + rect_size[2] * self.grid_settings['x_offset_factor_p0']
y_offset = rect_size[1] + rect_size[3] * self.grid_settings['y_offset_factor_p0']
else:
if board.config["type"] == "amigo":
x_offset = rect_size[0] + (rect_size[2] * 42) / 345
y_offset = rect_size[1] + (rect_size[3] * 41) / 272
x_offset = rect_size[0] + rect_size[2] * self.grid_settings['x_offset_factor_amigo_p1']
y_offset = rect_size[1] + rect_size[3] * self.grid_settings['y_offset_factor_amigo_p1']
else:
x_offset = rect_size[0] + (rect_size[2] * 62) / 345
y_offset = rect_size[1] + (rect_size[3] * 22) / 272
x_offset = rect_size[0] + rect_size[2] * self.grid_settings['x_offset_factor_p1']
y_offset = rect_size[1] + rect_size[3] * self.grid_settings['y_offset_factor_p1']
self.x_regions.append(int(x_offset))
self.y_regions.append(int(y_offset))
x_pad = rect_size[2] * 240 / (12 * 345)
y_pad = rect_size[3] * 210 / (12 * 272)
x_pad = rect_size[2] * self.grid_settings['xpad_factor']
y_pad = rect_size[3] * self.grid_settings['ypad_factor']
for _ in range(12):
x_offset += x_pad
y_offset += y_pad
Expand Down Expand Up @@ -744,6 +787,10 @@ def _gradient_value(self, index, gradient_corners):
def _detect_tiny_seed(self, img):
"""Detects Tiny Seed as a bright blob against a dark surface"""

# Load Settings for the grid type we are using
aspect_low = self.grid_settings['aspect_low']
aspect_high = self.grid_settings['aspect_high']

def _choose_rect(rects):
for rect in rects:
aspect = rect[2] / rect[3]
Expand All @@ -752,13 +799,15 @@ def _choose_rect(rects):
and rect[1]
and (rect[0] + rect[2]) < img.width()
and (rect[1] + rect[3]) < img.height()
and aspect_low < aspect < 1.3
and aspect_low < aspect < aspect_high
):
return rect
return None

# Big lenses cameras seems to distor aspect ratio to 1.1
aspect_low = 1.1 if self.ctx.camera.cam_id in (OV2640_ID, OV5642_ID) else 1.2
# Big lenses cameras seems to distort aspect ratio
if self.ctx.camera.cam_id not in (OV2640_ID, OV5642_ID):
aspect_low += 0.1

stats = img.get_statistics()
# # Debug stats
# img.draw_string(10,10,"Mean:"+str(stats.mean()))
Expand Down

0 comments on commit 2c08417

Please sign in to comment.