Skip to content

Commit

Permalink
Add find-wallet-depths command
Browse files Browse the repository at this point in the history
  • Loading branch information
jokpine committed May 27, 2022
1 parent 9f5e6c1 commit f51bbf4
Showing 1 changed file with 80 additions and 12 deletions.
92 changes: 80 additions & 12 deletions webcash/walletclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import secrets
import hashlib
import datetime
import string
import struct
import os
import sys
Expand All @@ -45,6 +46,9 @@
# unused?
FEE_AMOUNT = 0

BATCH_SIZE = 25
STOP_AFTER_N_TESTED_UNKNOWN_TOKENS = 5 * BATCH_SIZE

WALLET_NAME = "default_wallet.webcash"

CHAIN_CODES = {
Expand All @@ -54,6 +58,22 @@
"MINING": 3,
}


def is_master_secret(s):
return len(s) == 64 and all(c in string.hexdigits for c in s)


def get_deterministic_secret(master_secret_hex, chain_code, wallet_depth):
assert is_master_secret(master_secret_hex)
assert chain_code == chain_code.upper()
assert wallet_depth >= 0
tag = hashlib.sha256(b"webcashwalletv1").digest()
secret = hashlib.sha256(tag + tag)
secret.update(convert_secret_hex_to_bytes(master_secret_hex))
secret.update(struct.pack(">Q", CHAIN_CODES[chain_code])) # big-endian
secret.update(struct.pack(">Q", wallet_depth))
return secret.hexdigest()

def convert_secret_hex_to_bytes(secret):
"""
Convert a string secret to bytes.
Expand All @@ -71,15 +91,7 @@ def generate_new_secret(webcash_wallet=None, chain_code="RECEIVE", walletdepth=N
else:
walletdepth = walletdepth

master_secret = webcash_wallet["master_secret"]
master_secret_bytes = convert_secret_hex_to_bytes(master_secret)

tag = hashlib.sha256(b"webcashwalletv1").digest()
new_secret = hashlib.sha256(tag + tag)
new_secret.update(master_secret_bytes)
new_secret.update(struct.pack(">Q", CHAIN_CODES[chain_code.upper()])) # big-endian
new_secret.update(struct.pack(">Q", walletdepth))
new_secret = new_secret.hexdigest()
new_secret = get_deterministic_secret(webcash_wallet["master_secret"], chain_code, walletdepth)

# Record the change in walletdepth, but don't record the new secret
# because (1) it can be re-constructed even if it is lost, and (2) the
Expand Down Expand Up @@ -239,7 +251,7 @@ def webcash_server_request(url, json_data):
raise Exception(f"Something went wrong on the server: {response.content}")
json_response = response.json()
if json_response.get("status", "") != "success":
raise Exception(f"Something went wrong on the server: {response}")
raise Exception(f"Something went wrong on the server: {response.content}")
return json_response

def check_wallet():
Expand All @@ -255,9 +267,9 @@ def check_wallet():
outputs[str(sk.to_public().hashed_value)] = webcash

while outputs:
# Batch into no more than 25 at a time
# Batch into no more than BATCH_SIZE at a time
batch = {}
while outputs and len(batch) < 25:
while outputs and len(batch) < BATCH_SIZE:
item = outputs.popitem()
batch[item[0]] = item[1]

Expand Down Expand Up @@ -297,6 +309,62 @@ def check_wallet():
def check():
return check_wallet()

@cli.command(
"find-wallet-depths", short_help="[advanced] Find the 'walletdepths' parameters of a given master secret key by scanning."
)
@click.argument("master_secret")
def find_wallet_depths(master_secret):
if not is_master_secret(master_secret):
raise click.ClickException(
"Invalid master secret. Hex string of length 64 expected."
)

resulting_wallet_depths = {}
for chain_code in CHAIN_CODES.keys():
max_used_wallet_depth = -1
for step_size in [100000, 10000, 1000, 100, 10, 1]:
wallet_depth = max(0, max_used_wallet_depth)
unknown_count = 0
while unknown_count < STOP_AFTER_N_TESTED_UNKNOWN_TOKENS:
hash_to_webcash_and_depth = {}
while len(hash_to_webcash_and_depth) < BATCH_SIZE:
hex_secret = get_deterministic_secret(
master_secret, chain_code, wallet_depth
)
secret_webcash = SecretWebcash.deserialize(f"e1:secret:{hex_secret}")
public_webhash = secret_webcash.to_public()
hash_to_webcash_and_depth[
public_webhash.hashed_value
] = (secret_webcash, wallet_depth)
wallet_depth += step_size

health_check_request = [
str(o[0].to_public()) for o in hash_to_webcash_and_depth.values()
]
response = webcash_server_request(
WEBCASH_ENDPOINT_HEALTH_CHECK, health_check_request
)

assert len(hash_to_webcash_and_depth) == BATCH_SIZE
assert len(response["results"]) == BATCH_SIZE

found_new = False
for public_webcash, result in response["results"].items():
secret_webcash, found_at_wallet_depth = hash_to_webcash_and_depth[PublicWebcash.deserialize(public_webcash).hashed_value]
spent = result["spent"]
amount = result.get("amount")
token_is_unknown = spent is None and amount is None
if token_is_unknown:
unknown_count += 1
continue
if found_at_wallet_depth > max_used_wallet_depth:
max_used_wallet_depth = found_at_wallet_depth
found_new = True
if found_new:
print(f"Scanning... Found used walled depth {chain_code}:{max_used_wallet_depth}")
resulting_wallet_depths[chain_code] = max_used_wallet_depth + 1
print(f"\"walletdepths\": {resulting_wallet_depths}")

@cli.command("recover", short_help="Recover webcash using the wallet's master secret.")
@click.option("--gaplimit", default=20)
@lock_wallet
Expand Down

0 comments on commit f51bbf4

Please sign in to comment.