Skip to content

Commit

Permalink
feat(emulator): Shamir walkthrough with configurable shares and thres…
Browse files Browse the repository at this point in the history
…hold
  • Loading branch information
grdddj authored and tsusanka committed Jul 22, 2021
1 parent a6d9809 commit d0d5d43
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 28 deletions.
5 changes: 4 additions & 1 deletion docs/controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@
- **action**: simulates the Single backup process

- **emulator-read-and-confirm-shamir-mnemonic**
- **action**: simulates the Shamir backup process
- **action**: simulates the Shamir backup process for chosen amount of shares and threshold
- **arguments**:
- **shares**: `int` (defaults to 1)
- **threshold**: `int` (defaults to 1)

- **emulator-allow-unsafe-paths**
- **action**: allow unsafe path on emulator
Expand Down
10 changes: 8 additions & 2 deletions src/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,14 @@ def run_command_and_get_its_response(self) -> dict:
emulator.read_and_confirm_mnemonic()
return {"response": "Read and confirm mnemonic"}
elif self.command == "emulator-read-and-confirm-shamir-mnemonic":
emulator.read_and_confirm_mnemonic_shamir()
return {"response": "Read and confirm Shamir mnemonic"}
shares = self.request_dict.get("shares") or 1
threshold = self.request_dict.get("threshold") or 1
emulator.read_and_confirm_shamir_mnemonic(
shares=shares, threshold=threshold
)
return {
"response": f"Read and confirm Shamir mnemonic for {shares} shares and threshold {threshold}."
}
elif self.command == "emulator-allow-unsafe-paths":
emulator.allow_unsafe()
return {"response": "Allowed unsafe path"}
Expand Down
6 changes: 6 additions & 0 deletions src/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ <h3>Emulator commands</h3>
</div>
<div>
<button onclick="readAndConfirmMnemonic();">Read and confirm mnemonic</button>
</div>
<div>
<label for="shares-input">Shares</label>
<input id="shares-input" type="number" value="3" size="3" min="1" max="20"/>
<label for="threshold-input">Threshold</label>
<input id="threshold-input" type="number" value="2" size="3" min="1" max="20"/>
<button onclick="readAndConfirmMnemonicShamir();">Read and confirm mnemonic Shamir</button>
</div>
</section>
Expand Down
6 changes: 5 additions & 1 deletion src/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,12 @@ function readAndConfirmMnemonic() {
}

function readAndConfirmMnemonicShamir() {
const shares = parseInt(document.getElementById("shares-input").value);
const threshold = parseInt(document.getElementById("threshold-input").value);
_send({
type: 'emulator-read-and-confirm-mnemonic-shamir',
type: 'emulator-read-and-confirm-shamir-mnemonic',
shares,
threshold,
});
}

Expand Down
106 changes: 82 additions & 24 deletions src/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ def swipe(direction: str) -> None:


def read_and_confirm_mnemonic() -> None:
# Connecting to the device
client = DebugLink(get_device().find_debug())
client.open()
time.sleep(SLEEP)
Expand Down Expand Up @@ -252,34 +253,83 @@ def read_and_confirm_mnemonic() -> None:
client.input(mnemonic[index])
time.sleep(SLEEP)

# Click Continue to finish the quiz
client.press_yes()
time.sleep(SLEEP)

# Click Continue to finish the backup
client.press_yes()
time.sleep(SLEEP)

client.close()


def read_and_confirm_mnemonic_shamir():
def read_and_confirm_shamir_mnemonic(shares: int = 1, threshold: int = 1) -> None:
"""Performs a walkthrough of the whole Shamir backup on the device.
NOTE: does not support Super Shamir.
"""
MIN_SHARES = 1
MAX_SHARES = 16
if shares < MIN_SHARES or shares > MAX_SHARES:
raise RuntimeError(
f"Number of shares must be between {MIN_SHARES} and {MAX_SHARES}."
)
if threshold > shares:
raise RuntimeError("Threshold cannot be bigger than number of shares.")

# For setting the right amount of shares/thresholds, we need location of buttons
MINUS_BUTTON_COORDS = (60, 70)
PLUS_BUTTON_COORDS = (180, 70)

# Connecting to the device
client = DebugLink(get_device().find_debug())
client.open()
time.sleep(SLEEP)

# Click Continue to begin Shamir setup process
client.press_yes()
time.sleep(SLEEP)

# Clicking the minus button four times to set only one share (it starts at 5)
minus_button_coords = (60, 70)
for _ in range(4):
client.click(minus_button_coords)
time.sleep(SLEEP)
# Clicking the minus/plus button to set right number of shares (it starts at 5)
DEFAULT_SHARES = 5
needed_clicks = abs(shares - DEFAULT_SHARES)
if needed_clicks > 0:
if shares < DEFAULT_SHARES:
button_coords_to_click = MINUS_BUTTON_COORDS
else:
button_coords_to_click = PLUS_BUTTON_COORDS

for _ in range(needed_clicks):
client.click(button_coords_to_click)
time.sleep(SLEEP)

# Click Continue to confirm one share
# Click Continue to confirm the number of shares
client.press_yes()
time.sleep(SLEEP)

# Click Continue to set threshold
client.press_yes()
time.sleep(SLEEP)

# Click Continue to set threshold to one
# When we have 1 or 2 shares, the threshold is set and cannot be changed
# (it will be 1 and 2 respectively)
# Otherise assign it correctly by clicking the plus/minus button
if shares not in [1, 2]:
# Default threshold can be calculated from the share number
default_threshold = shares // 2 + 1
needed_clicks = abs(threshold - default_threshold)
if needed_clicks > 0:
if threshold < default_threshold:
button_coords_to_click = MINUS_BUTTON_COORDS
else:
button_coords_to_click = PLUS_BUTTON_COORDS

for _ in range(needed_clicks):
client.click(button_coords_to_click)
time.sleep(SLEEP)

# Click Continue to confirm our chosen threshold
client.press_yes()
time.sleep(SLEEP)

Expand All @@ -291,29 +341,37 @@ def read_and_confirm_mnemonic_shamir():
client.press_yes()
time.sleep(SLEEP)

# Scrolling through all the 20 words on next 5 pages
# While doing so, saving all the words on the screen for the "quiz" later
mnemonic = []
for _ in range(5):
# Loop through all the shares and fulfill all checks
for _ in range(shares):
# Scrolling through all the 20 words on next 5 pages
# While doing so, saving all the words on the screen for the "quiz" later
mnemonic = []
for _ in range(5):
mnemonic.extend(client.read_reset_word().split())
client.swipe_up()
time.sleep(SLEEP)

mnemonic.extend(client.read_reset_word().split())
client.swipe_up()
assert len(mnemonic) == 20

# Confirming that I have written the seed down
client.press_yes()
time.sleep(SLEEP)

mnemonic.extend(client.read_reset_word().split())
assert len(mnemonic) == 20
# Answering 3 questions asking for a specific word
for _ in range(3):
index = client.read_reset_word_pos()
client.input(mnemonic[index])
time.sleep(SLEEP)

# Confirming that I have written the seed down
client.press_yes()
time.sleep(SLEEP)

# Answering 3 questions asking for a specific word
for _ in range(3):
index = client.read_reset_word_pos()
client.input(mnemonic[index])
# Click Continue to finish this quiz
client.press_yes()
time.sleep(SLEEP)

# Click Continue to finish the backup
client.press_yes()
client.press_yes()
time.sleep(SLEEP)

client.close()


Expand Down

0 comments on commit d0d5d43

Please sign in to comment.