Skip to content

Commit

Permalink
Add Support for BitGo KeyCard UserKey - Wallet Password Recovery (#517)
Browse files Browse the repository at this point in the history
* Add Bitgo Keycard User key Support, documentation and CI tests
* Bump Ubuntu and Python versions for CI tests
  • Loading branch information
3rdIteration authored Nov 18, 2024
1 parent 06ecfb9 commit abf267e
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 11 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/Latest-Run-All-Tests_Base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
os: [ubuntu-22.04] # Test Ubuntu Only
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] # Test all suppoorted versions of Python
os: [ubuntu-24.04] # Test Ubuntu Only
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] # Test all suppoorted versions of Python

steps:
- uses: actions/checkout@v2
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/Latest-Run-All-Tests_Full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

name: Last Push - All Tests (Full Modules)

env:
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1

on:
push:
branches: [ master ]
Expand All @@ -13,8 +16,8 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
os: [ubuntu-22.04] # Test Ubuntu Only
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] # Test all suppoorted versions of Python
os: [ubuntu-24.04] # Test Ubuntu Only
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] # Test all suppoorted versions of Python

steps:
- uses: actions/checkout@v2
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/Weekly-Run-All-Tests-Full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

name: Weekly - All Tests (+Optional Modules)

env:
PYO3_USE_ABI3_FORWARD_COMPATIBILITY: 1

on:
schedule:
- cron: "0 0 * * 6"
Expand All @@ -15,8 +18,8 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
os: [ubuntu-22.04, windows-latest, macos-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] # Test all suppoorted versions of Python
os: [ubuntu-24.04, windows-latest, macos-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] # Test all suppoorted versions of Python

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/Weekly-Run-All-Tests_Base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
os: [ubuntu-22.04, windows-latest, macos-latest] # Test all supported operating systems
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] # Test all suppoorted versions of Python
os: [ubuntu-24.04, windows-latest, macos-latest] # Test all supported operating systems
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] # Test all suppoorted versions of Python

steps:
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions addressdb-checklists/ETH.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
0xB0158EF4cb3293dd1d2897aF805Aa2F96586c1CA #ERC20 Transactions Only (No Eth)
0x0Bfd366A8C800C61ca76B6C2866Cc0BEf2f01ef9 #First seen Apr-24-2024
0xc9e6dBcE15E0fAcBd20ce2bbF4ecBab8C5Dd767C #First seen Sep-26-2023
0x0584Da0B6cf278B6750dcF9Fa52e564737FdE5c7 #First seen Apr-07-2023
Expand Down
73 changes: 72 additions & 1 deletion btcrecover/btcrpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ def error(s: str) -> None:
except:
pass

# Modules dependant on SJCL
sjcl_available = False
try:
from sjcl import SJCL
sjcl_available = True
except:
pass

searchfailedtext = "\nAll possible passwords (as specified in your tokenlist or passwordlist) have been checked and none are correct for this wallet. You could consider trying again with a different password list or expanded tokenlist..."

Expand Down Expand Up @@ -2753,6 +2760,65 @@ def return_verified_password_or_false(self, arg_passwords): # block.io Main Pas

return False, count

############### Bitgo User Key ###############

@register_wallet_class
class WalletBitGo(object):
opencl_algo = -1
_savepossiblematches = False

_dump_privkeys_file = None
_dump_wallet_file = None
_using_extract = False

def __init__(self):
if not sjcl_available:
exit(
"\nERROR: Cannot load SJCL module which is required for BitGo wallets... You can install it with the command 'pip3 install sjcl")

@staticmethod
def is_wallet_file(wallet_file):
wallet_file.seek(0)
try:
walletdata = wallet_file.read()
json.loads(walletdata) # Check if it's a valid JSON
except: return False

return (b"adata" in walletdata and b"aes" in walletdata)

def passwords_per_seconds(self, seconds):
return 5

# Load a Dogechain wallet file
@classmethod
def load_from_filename(cls, wallet_filename):
self = cls()
with open(wallet_filename, "rb") as wallet_file:
wallet_data = wallet_file.read()

self.user_key = json.loads(wallet_data)

return self

def difficulty_info(self):
iter_count = self.user_key['iter']
hash_function = str(self.user_key['ks'])
return str(iter_count) + " SHA" + hash_function + " Iterations"

# This is the time-consuming function executed by worker thread(s). It returns a tuple: if a password
# is correct return it, else return False for item 0; return a count of passwords checked for item 1
def return_verified_password_or_false(self, arg_passwords): # block.io Main Password

for count, password in enumerate(arg_passwords, 1):
try:
key = SJCL().decrypt(self.user_key, password)
return password, count

except ValueError:
pass

return False, count

############### Dogechain.info ###############

@register_wallet_class
Expand Down Expand Up @@ -4897,7 +4963,12 @@ def return_verified_password_or_false(self, passwords): # Raw Privatekey
else:
privcompress = bytes([])

pubkey = pubkey_from_secret(privkey).format(compressed = isCompressed)
# Sometimes it's possible that a privatekey (with a valid checksum) will still be invalid in terms of generating a usable address
try:
pubkey = pubkey_from_secret(privkey).format(compressed = isCompressed)
except Exception as e:
print("Exception for Privkey: ", password, " : ", e)
continue

if self.crypto == 'ethereum':
pubkey_hash160 = keccak(pubkey[1:])[-20:]
Expand Down
1 change: 1 addition & 0 deletions btcrecover/test/test-wallets/bitgo_keycard_userkey.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"iv":"ZG0B0bPyYIl6cD49i3uBlg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"l/A+yUyoDnA=","ct":"2MJDfwQ2yP9g3Vf9e5msp5ROQNmOa6iBMrFNvR/I6FzqFqObjfBWHShaCtS8c8s1CC2IvlI8DRTNJR2QXzZy/XIjOKlCRL1XACe1ZFmppY8jBy0IYBy35SVqoXxFil9hO+r4aauvrxzTXnDF1CLvyf1ZYYANv/s="}
16 changes: 16 additions & 0 deletions btcrecover/test/test_passwords.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,18 @@ def can_load_staking_deposit():
eth2_staking_deposit_available = False
return eth2_staking_deposit_available

# Modules dependant on SJCL
sjcl_available = None
def can_load_sjcl():
global sjcl_available
if sjcl_available is None:
try:
from sjcl import SJCL
sjcl_available = True
except:
sjcl_available = False
return sjcl_available

# Wrapper for btcrpass.init_worker() which clears btcrpass.loaded_wallet to simulate the way
# multiprocessing works on Windows (even on other OSs) and permits pure python library testing
def init_worker(wallet, char_mode, force_purepython, force_kdf_purepython):
Expand Down Expand Up @@ -1404,6 +1416,10 @@ def test_block_io_privkeyrequest_data_cpu(self):
def test_block_io_pinchange_data_cpu(self):
self.wallet_tester("block.io.change.json", correct_pass="btcrtestpassword2022")

@skipUnless(can_load_sjcl, "requires SJCL")
def bitgo_keycard_userkey(self):
self.wallet_tester("bitgo_keycard_userkey.json", correct_pass="btcr-test-password")

@skipUnless(can_load_leveldb, "Unable to load LevelDB module, requires Python 3.8+")
def test_metamask_leveldb_chrome_cpu(self):
self.wallet_tester("metamask/nkbihfbeogaeaoehlefnkodbefgpgknn")
Expand Down
6 changes: 6 additions & 0 deletions docs/Usage_Examples/basic_password_recoveries.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,12 @@ Do a basic recovery with a command like the one below. (This command uses a samp
python btcrecover.py --wallet ./btcrecover/test/test-wallets/imtoken-identity.json --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```

## Bitgo KeyCard User Key
Do a basic recovery with a command like the one below. (This command uses a sample wallet file bunlded with BTCRecover)
```
python btcrecover.py --wallet ./btcrecover/test/test-wallets/bitgo_keycard_userkey.json --passwordlist ./docs/Usage_Examples/common_passwordlist.txt
```

## SLIP39 Passphrases
This uses much of the same syntax as recovering BIP39 passphrases. BTCRecover currently supports most of the coins that are supported by the Trezor T.

Expand Down
5 changes: 3 additions & 2 deletions requirements-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ pycryptodome~=3.19.1
ecdsa==0.17.0
groestlcoin_hash~=1.0.3
eth-keyfile~=0.6.1
py_crypto_hd_wallet~=1.1.0
pynacl~=1.4.0
py_crypto_hd_wallet~=1.3.3
pynacl~=1.5.0
bitstring~=3.1.9
shamir-mnemonic[cli]~=0.3.0
bitcoin-utils~=0.5.5
pylibscrypt~=2.0.0
git+https://github.com/ethereum/staking-deposit-cli.git@v2.5.0
py_ecc~=6.0.0
sjcl~=0.2.1
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ coincurve~=19.0.0
green~=3.3.0
protobuf~=3.19.1
pycryptodome~=3.19.1
sjcl~=0.2.1

0 comments on commit abf267e

Please sign in to comment.