Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COLDCARD edge taproot #725

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/actions/install-sim/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,25 @@ runs:
apt-get install -y libsdl2-image-2.0-0 libusb-1.0-0
tar -xvf trezor-firmware.tar.gz

- if: inputs.device == 'coldcard'
- if: startsWith(inputs.device, 'coldcard')
shell: bash
run: |
apt-get update
apt-get install -y libpcsclite-dev libusb-1.0-0 swig
git config --global user.email "[email protected]"
git config --global user.name "ci"
pushd test/work; git clone --recursive https://github.com/Coldcard/firmware.git; popd
tar -xvf coldcard-mpy.tar.gz
pushd test/work/firmware; git am ../../data/coldcard-multisig.patch; popd
poetry run pip install -r test/work/firmware/requirements.txt
pip install -r test/work/firmware/requirements.txt
if [[ inputs.device == "coldcard-edge"]]; then
archive="coldcard-edge-mpy.tar.gz
dir="firmware-edge"
else
archive="coldcard-mpy.tar.gz"
dir="firmware"
fi
tar -xvf ${archive}
pushd test/work/${dir}; git am ../../data/coldcard-multisig.patch; popd
poetry run pip install -r test/work/${dir}/requirements.txt
pip install -r test/work/${dir}/requirements.txt

- if: inputs.device == 'bitbox01'
shell: bash
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ jobs:
- { name: 'trezor-1', archive: 'trezor-firmware', paths: 'test/work/trezor-firmware' }
- { name: 'trezor-t', archive: 'trezor-firmware', paths: 'test/work/trezor-firmware' }
- { name: 'coldcard', archive: 'coldcard-mpy', paths: 'test/work/firmware/external/micropython/ports/unix/coldcard-mpy test/work/firmware/unix/coldcard-mpy test/work/firmware/unix/l-mpy test/work/firmware/unix/l-port' }
- { name: 'coldcard-edge', archive: 'coldcard-edge-mpy', paths: 'test/work/firmware-edge/external/micropython/ports/unix/coldcard-mpy test/work/firmware-edge/unix/coldcard-mpy test/work/firmware-edge/unix/l-mpy test/work/firmware-edge/unix/l-port' }
- { name: 'bitbox01', archive: 'mcu', paths: 'test/work/mcu' }
- { name: 'jade', archive: 'jade', paths: 'test/work/jade/simulator' }
- { name: 'ledger', archive: 'speculos', paths: 'test/work/speculos' }
Expand Down Expand Up @@ -213,6 +214,7 @@ jobs:
- 'trezor-1'
- 'trezor-t'
- 'coldcard'
- 'coldcard-edge'
- 'bitbox01'
- 'jade'
- 'ledger'
Expand Down Expand Up @@ -281,6 +283,7 @@ jobs:
- 'trezor-1'
- 'trezor-t'
- 'coldcard'
- 'coldcard-edge'
- 'bitbox01'
- 'jade'
- 'ledger'
Expand Down
2 changes: 1 addition & 1 deletion hwilib/devices/ckcc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This is a stripped down and modified version of the official [ckcc-protocol](https://github.com/Coldcard/ckcc-protocol) library.

This stripped down version was made at commit [ca8d2b7808784a9f4927f3250bf52d2623a4e15b](https://github.com/Coldcard/ckcc-protocol/tree/ca8d2b7808784a9f4927f3250bf52d2623a4e15b).
This stripped down version was made at commit [f924f6d35ca0a6804b9e25d476cb53ae2f8ae8d6](https://github.com/Coldcard/ckcc-protocol/commit/f924f6d35ca0a6804b9e25d476cb53ae2f8ae8d6).

## Changes

Expand Down
3 changes: 2 additions & 1 deletion hwilib/devices/ckcc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.

__version__ = '1.0.2'
__version__ = '1.4.0'

__all__ = [ "client", "protocol", "constants" ]

Expand Down
49 changes: 30 additions & 19 deletions hwilib/devices/ckcc/client.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# (c) Copyright 2021 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# client.py
#
Expand All @@ -8,9 +9,10 @@
#
# - ec_mult, ec_setup, aes_setup, mitm_verify
#
import hid, sys, os, platform
import hid, sys, os
from binascii import b2a_hex, a2b_hex
from hashlib import sha256
from .constants import USB_NCRY_V1, USB_NCRY_V2
from .protocol import CCProtocolPacker, CCProtocolUnpacker, CCProtoError, MAX_MSG_LEN, MAX_BLK_LEN
from .utils import decode_xpub, get_pubkey_string

Expand All @@ -22,13 +24,11 @@
CKCC_SIMULATOR_PATH = '/tmp/ckcc-simulator.sock'

class ColdcardDevice:
def __init__(self, sn=None, dev=None, encrypt=True):
def __init__(self, sn=None, dev=None, encrypt=True, ncry_ver=USB_NCRY_V1):
# Establish connection via USB (HID) or Unix Pipe
self.is_simulator = False

if not dev and sn and '/' in sn:
if platform.system() == 'Windows':
raise RuntimeError("Cannot connect to simulator. Is it running?")
dev = UnixSimulatorPipe(sn)
found = 'simulator'
self.is_simulator = True
Expand All @@ -49,7 +49,7 @@ def __init__(self, sn=None, dev=None, encrypt=True):
break

if not dev:
raise KeyError("Could not find Coldcard!"
raise KeyError("Could not find Coldcard!"
if not sn else ('Cannot find CC with serial: '+sn))
else:
found = dev.get_serial_number_string()
Expand All @@ -58,6 +58,7 @@ def __init__(self, sn=None, dev=None, encrypt=True):
self.serial = found

# they will be defined after we've established a shared secret w/ device
self.ncry_ver = ncry_ver
self.session_key = None
self.encrypt_request = None
self.decrypt_response = None
Expand All @@ -67,7 +68,7 @@ def __init__(self, sn=None, dev=None, encrypt=True):
self.resync()

if encrypt:
self.start_encryption()
self.start_encryption(version=self.ncry_ver)

def close(self):
# close underlying HID device
Expand Down Expand Up @@ -101,17 +102,21 @@ def send_recv(self, msg, expect_errors=False, verbose=0, timeout=3000, encrypt=T
# first byte of each 64-byte packet encodes length or packet-offset
assert 4 <= len(msg) <= MAX_MSG_LEN, "msg length: %d" % len(msg)

if not self.encrypt_request:
if self.encrypt_request is None:
# disable encryption if not already enabled for this connection
encrypt = False

if self.encrypt_request and self.ncry_ver == USB_NCRY_V2:
# ncry version 2 - everything needs to be encrypted
encrypt = True

if encrypt:
msg = self.encrypt_request(msg)

left = len(msg)
offset = 0
while left > 0:
# Note: first byte always zero (HID report number),
# Note: first byte always zero (HID report number),
# [1] is framing header (length+flags)
# [2:65] payload (63 bytes, perhaps including padding)
here = min(63, left)
Expand Down Expand Up @@ -224,7 +229,7 @@ def aes_setup(self, session_key):
self.encrypt_request = pyaes.AESModeOfOperationCTR(session_key, pyaes.Counter(0)).encrypt
self.decrypt_response = pyaes.AESModeOfOperationCTR(session_key, pyaes.Counter(0)).decrypt

def start_encryption(self):
def start_encryption(self, version=USB_NCRY_V1):
# setup encryption on the link
# - pick our own key pair, IV for AES
# - send IV and pubkey to device
Expand All @@ -233,10 +238,12 @@ def start_encryption(self):

pubkey = self.ec_setup()

msg = CCProtocolPacker.encrypt_start(pubkey)
msg = CCProtocolPacker.encrypt_start(pubkey, version=version)

his_pubkey, fingerprint, xpub = self.send_recv(msg, encrypt=False)

self.ncry_ver = version

self.session_key = self.ec_mult(his_pubkey)

# capture some public details of remote side's master key
Expand All @@ -248,7 +255,6 @@ def start_encryption(self):
self.aes_setup(self.session_key)

def mitm_verify(self, sig, expected_xpub):
# If Pycoin is not available, do it using ecdsa
from ecdsa import BadSignatureError, SECP256k1, VerifyingKey
# of the returned (pubkey, chaincode) tuple, chaincode is not used
pubkey, _ = decode_xpub(expected_xpub)
Expand Down Expand Up @@ -318,14 +324,15 @@ def download_file(self, length, checksum, blksize=1024, file_number=1):

return data

def hash_password(self, text_password):
def hash_password(self, text_password, v3=False):
# Turn text password into a key for use in HSM auth protocol
# - changed from pbkdf2_hmac_sha256 to pbkdf2_hmac_sha512 in version 4 of CC firmware
from hashlib import pbkdf2_hmac, sha256
from .constants import PBKDF2_ITER_COUNT

salt = sha256(b'pepper' + self.serial.encode('ascii')).digest()

return pbkdf2_hmac('sha256', text_password, salt, PBKDF2_ITER_COUNT)
return pbkdf2_hmac('sha256' if v3 else 'sha512', text_password, salt, PBKDF2_ITER_COUNT)[:32]


class UnixSimulatorPipe:
Expand All @@ -341,15 +348,19 @@ def __init__(self, path):
self.close()
raise RuntimeError("Cannot connect to simulator. Is it running?")

instance = 0
while instance < 10:
last_err = None
for instance in range(5):
pn = '/tmp/ckcc-client-%d-%d.sock' % (os.getpid(), instance)
try:
self.pipe.bind(pn) # just needs any name
break
except OSError:
instance += 1
except OSError as err:
last_err = err
if os.path.exists(pn):
os.remove(pn)
continue
else:
raise last_err # raise whatever was raised last in the loop

self.pipe_name = pn
atexit.register(self.close)
Expand Down Expand Up @@ -383,7 +394,7 @@ def close(self):
pass

def get_serial_number_string(self):
return 'simulator'
return 'F1'*6


# EOF
# EOF
Loading