dev squashed

This commit is contained in:
Peter D. Gray 2020-11-18 14:19:14 -05:00
parent d88f14ea92
commit b6b9191145
114 changed files with 716 additions and 464 deletions

44
COPYING-CC Normal file
View File

@ -0,0 +1,44 @@
(c) Copyright 2020 by Coinkite Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject
to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"Commons Clause" License Condition v1.0
The Software is provided to you by the Licensor under the License,
as defined below, subject to the following condition.
Without limiting other conditions in the License, the grant of
rights under the License will not include, and the License does not
grant to you, the right to Sell the Software.
For purposes of the foregoing, "Sell" means practicing any or all
of the rights granted to you under the License to provide to third
parties, for a fee or other consideration (including without
limitation fees for hosting or consulting/ support services related
to the Software), a product or service whose value derives, entirely
or substantially, from the functionality of the Software. Any license
notice or attribution required by the License must also include
this Commons Clause License Condition notice.
Software: All Coldcard associated files.
License: MIT
Licensor: Coinkite Inc.

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# based on <http://click.pocoo.org/5/setuptools/#setuptools-integration> # based on <http://click.pocoo.org/5/setuptools/#setuptools-integration>
# #

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Repackage and sign the firmware image. # Repackage and sign the firmware image.
# #

View File

@ -63,6 +63,8 @@
- we always store xpubs in BIP32 format, although we can read SLIP132 format (Ypub/Zpub/etc) - we always store xpubs in BIP32 format, although we can read SLIP132 format (Ypub/Zpub/etc)
- change outputs (indicated with paths, scripts in output section) must correspond to - change outputs (indicated with paths, scripts in output section) must correspond to
the active multisig wallet, and cannot be used to describe an unrelated (multisig) wallet. the active multisig wallet, and cannot be used to describe an unrelated (multisig) wallet.
- derivation path for each cosigner must be known and consistent with PSBT
- fixed: XFP values (fingerprints) for each of the co-signers must be unique (limitation removed)
# SIGHASH types # SIGHASH types

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
all: graphics.py all: graphics.py

View File

@ -1,5 +1,11 @@
## 3.1.10 - Nov 9, 2020 ## 3.2.0 - Nov , 2020
- Major Multisig improvements:
Tracks derivation path for each co-signer and no longer assumes
they all use a shared derivation prefix. Blocks multiple instances of same XFP in the wallet
(not supported anymore, bad idea). Various displays updated to reflect derivation path change.
Text file import: "Derivation:" line can be repeated, applies too all following xpubs.
Show Ypub/Zpub formated values from SLIP-132 when viewing details of wallet.
- Enhancement: Add support for signing Payjoin PSBT files based on - Enhancement: Add support for signing Payjoin PSBT files based on
[BIP-78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki). [BIP-78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki).
- Enhancement: Promoted the address explorer to the main menu. It's useful! - Enhancement: Promoted the address explorer to the main menu. It's useful!
@ -10,6 +16,7 @@
an attacker could socially-engineer you to sign a transaction on Testnet, which an attacker could socially-engineer you to sign a transaction on Testnet, which
corresponds to real UTXO being stolen. Only developers should be using Testnet. corresponds to real UTXO being stolen. Only developers should be using Testnet.
- Bugfix: Display of amounts could be incorrect by a few sats in final digits. - Bugfix: Display of amounts could be incorrect by a few sats in final digits.
- License changed from GPL to MIT+CC on files for which the GPL doesn't apply.
## 3.1.9 - Aug 6, 2020 ## 3.1.9 - Aug 6, 2020

View File

@ -1,3 +1,5 @@
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# Just a little helper for CK devs ... so we can do "make up" in this dir. # Just a little helper for CK devs ... so we can do "make up" in this dir.
all: all:

View File

@ -8,7 +8,7 @@
import ckcc, pyb, version import ckcc, pyb, version
from ux import ux_show_story, the_ux, ux_confirm, ux_dramatic_pause, ux_poll_once, ux_aborted from ux import ux_show_story, the_ux, ux_confirm, ux_dramatic_pause, ux_poll_once, ux_aborted
from ux import ux_enter_number from ux import ux_enter_number
from utils import imported, pretty_short_delay from utils import imported, pretty_short_delay, problem_file_line
from main import settings from main import settings
from uasyncio import sleep_ms from uasyncio import sleep_ms
from files import CardSlot, CardMissingError from files import CardSlot, CardMissingError
@ -1524,7 +1524,6 @@ async def import_multisig(*a):
fn = await file_picker('Pick multisig wallet file to import (.txt)', suffix='.txt', fn = await file_picker('Pick multisig wallet file to import (.txt)', suffix='.txt',
min_size=100, max_size=20*200, taster=possible) min_size=100, max_size=20*200, taster=possible)
if not fn: return if not fn: return
try: try:
@ -1540,7 +1539,9 @@ async def import_multisig(*a):
possible_name = (fn.split('/')[-1].split('.'))[0] possible_name = (fn.split('/')[-1].split('.'))[0]
maybe_enroll_xpub(config=data, name=possible_name) maybe_enroll_xpub(config=data, name=possible_name)
except Exception as e: except Exception as e:
await ux_show_story('Failed to import.\n\n\n'+str(e)) import sys
sys.print_exception(e)
await ux_show_story('Failed to import.\n\n\n'+problem_file_line(e))
async def start_hsm_menu_item(*a): async def start_hsm_menu_item(*a):
from hsm_ux import start_hsm_approval from hsm_ux import start_hsm_approval

View File

@ -1043,17 +1043,14 @@ def start_show_p2sh_address(M, N, addr_format, xfp_paths, witdeem_script):
raise AssertionError('Unknown/unsupported addr format') raise AssertionError('Unknown/unsupported addr format')
# Search for matching multisig wallet that we must already know about # Search for matching multisig wallet that we must already know about
xfps = [i[0] for i in xfp_paths] xs = list(xfp_paths)
xs.sort()
idx = MultisigWallet.find_match(M, N, xfps) ms = MultisigWallet.find_match(M, N, xs)
assert idx >= 0, 'Multisig wallet with those fingerprints not found' assert ms, 'Multisig wallet with those fingerprints not found'
ms = MultisigWallet.get_by_idx(idx)
assert ms
assert ms.M == M assert ms.M == M
assert ms.N == N assert ms.N == N
UserAuthorizedAction.check_busy(ShowAddressBase) UserAuthorizedAction.check_busy(ShowAddressBase)
UserAuthorizedAction.active_request = ShowP2SHAddress(ms, addr_format, xfp_paths, witdeem_script) UserAuthorizedAction.active_request = ShowP2SHAddress(ms, addr_format, xfp_paths, witdeem_script)

View File

@ -40,7 +40,7 @@ import uasyncio.core as asyncio
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
print("---\nColdcard Wallet from Coinkite Inc. (c) 2018.\n") print("---\nColdcard Wallet from Coinkite Inc. (c) 2020.\n")
# Setup OLED and get something onto it. # Setup OLED and get something onto it.
from display import Display from display import Display

View File

@ -5,7 +5,7 @@
# #
import stash, chains, ustruct, ure, uio, sys import stash, chains, ustruct, ure, uio, sys
#from ubinascii import hexlify as b2a_hex #from ubinascii import hexlify as b2a_hex
from utils import xfp2str, str2xfp, swab32, cleanup_deriv_path from utils import xfp2str, str2xfp, swab32, cleanup_deriv_path, keypath_to_str, str_to_keypath
from ux import ux_show_story, ux_confirm, ux_dramatic_pause, ux_clear_keys from ux import ux_show_story, ux_confirm, ux_dramatic_pause, ux_clear_keys
from files import CardSlot, CardMissingError from files import CardSlot, CardMissingError
from public_constants import AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH, AFC_SCRIPT, MAX_PATH_DEPTH from public_constants import AF_P2SH, AF_P2WSH_P2SH, AF_P2WSH, AFC_SCRIPT, MAX_PATH_DEPTH
@ -23,6 +23,10 @@ TRUST_VERIFY = const(0)
TRUST_OFFER = const(1) TRUST_OFFER = const(1)
TRUST_PSBT = const(2) TRUST_PSBT = const(2)
# when we aren't sure of the derivation of an xpub we are holding
# - should only happen from older version data
UNSURE_DERIV = '*'
class MultisigOutOfSpace(RuntimeError): class MultisigOutOfSpace(RuntimeError):
pass pass
@ -101,19 +105,22 @@ class MultisigWallet:
(AF_P2WSH_P2SH, 'p2wsh-p2sh'), (AF_P2WSH_P2SH, 'p2wsh-p2sh'),
] ]
def __init__(self, name, m_of_n, xpubs, addr_fmt=AF_P2SH, common_prefix=None, chain_type='BTC'): def __init__(self, name, m_of_n, xpubs, addr_fmt=AF_P2SH, chain_type='BTC'):
self.storage_idx = -1 self.storage_idx = -1
self.name = name self.name = name
assert len(m_of_n) == 2 assert len(m_of_n) == 2
self.M, self.N = m_of_n self.M, self.N = m_of_n
self.chain_type = chain_type or 'BTC' self.chain_type = chain_type or 'BTC'
self.xpubs = xpubs # list of (xfp(int), xpub(str)) assert len(xpubs[0]) == 3
self.common_prefix = common_prefix # example: "45'" for BIP45 .. no m/ prefix self.xpubs = xpubs # list of (xfp(int), deriv, xpub(str))
self.addr_fmt = addr_fmt # not clear how useful that is. self.addr_fmt = addr_fmt # not clear how useful that is.
# useful cache value # calc useful cache value: numeric xfp+subpath, with lookup
self.xfps = sorted(k for k,v in self.xpubs) self.xfp_paths = {}
for xfp, deriv, _ in self.xpubs:
self.xfp_paths[xfp] = str_to_keypath(xfp, deriv)
assert len(self.xfp_paths) == self.N, 'dup XFP' # not supported
@classmethod @classmethod
def render_addr_fmt(cls, addr_fmt): def render_addr_fmt(cls, addr_fmt):
@ -122,19 +129,6 @@ class MultisigWallet:
return v.upper() return v.upper()
return '?' return '?'
def serialize(self):
# return a JSON-able object
opts = dict()
if self.addr_fmt != AF_P2SH:
opts['ft'] = self.addr_fmt
if self.chain_type != 'BTC':
opts['ch'] = self.chain_type
if self.common_prefix:
opts['pp'] = self.common_prefix
return (self.name, (self.M, self.N), self.xpubs, opts)
@property @property
def chain(self): def chain(self):
return chains.get_chain(self.chain_type) return chains.get_chain(self.chain_type)
@ -150,64 +144,130 @@ class MultisigWallet:
return which return which
def serialize(self):
# return a JSON-able object
opts = dict()
if self.addr_fmt != AF_P2SH:
opts['ft'] = self.addr_fmt
if self.chain_type != 'BTC':
opts['ch'] = self.chain_type
# Data compression: most legs will all use same derivation.
# put a int(0) in place and set option 'pp' to be deriation
# (used to be common_prefix assumption)
pp = list(sorted(set(d for _,d,_ in self.xpubs)))
opts['d'] = pp
xp = [(a, pp.index(deriv),c) for a,deriv,c in self.xpubs]
return (self.name, (self.M, self.N), xp, opts)
@classmethod @classmethod
def deserialize(cls, vals, idx=-1): def deserialize(cls, vals, idx=-1):
# take json object, make instance. # take json object, make instance.
name, m_of_n, xpubs, opts = vals name, m_of_n, xpubs, opts = vals
if len(xpubs[0]) == 2:
# promote from old format to new: assume common prefix is the derivation
# for all of them
# PROBLEM: we don't have enough info if no common prefix can be assumed
common_prefix = opts.get('pp', None) or UNSURE_DERIV
xpubs = [(a, common_prefix, b) for a,b in xpubs]
else:
# new format decompression
if 'd' in opts:
derivs = opts.get('d', None)
xpubs = [(a, derivs[b], c) for a,b,c in xpubs]
rv = cls(name, m_of_n, xpubs, addr_fmt=opts.get('ft', AF_P2SH), rv = cls(name, m_of_n, xpubs, addr_fmt=opts.get('ft', AF_P2SH),
common_prefix=opts.get('pp', None),
chain_type=opts.get('ch', 'BTC')) chain_type=opts.get('ch', 'BTC'))
rv.storage_idx = idx rv.storage_idx = idx
return rv return rv
@classmethod @classmethod
def find_match(cls, M, N, fingerprints): def find_match(cls, M, N, xfp_paths):
# Find index of matching wallet. Don't de-serialize everything. # Find index of matching wallet. Don't de-serialize more than needed.
# - returns index, or -1 if not found # - xfp_paths is list of lists: [xfp, *path] like in psbt files
# - fingerprints are iterable of uint32's: may not be unique! # - M and N must be known
# - M, N must be known. # - returns instance, or None if not found
from main import settings from main import settings
lst = settings.get('multisig', []) lst = settings.get('multisig', [])
fingerprints = sorted(fingerprints) fingerprints = set(f[0] for f in xfp_paths)
assert N == len(fingerprints)
for idx, rec in enumerate(lst): for idx, rec in enumerate(lst):
name, m_of_n, xpubs, opts = rec name, m_of_n, xpubs, opts = rec
if tuple(m_of_n) != (M, N): continue
if sorted(f for f,_ in xpubs) != fingerprints: continue
return idx
return -1 if tuple(m_of_n) != (M, N):
continue
if set(f[0] for f in xpubs) != fingerprints: continue
rv = cls.deserialize(rec, idx)
if rv.matching_subpaths(xfp_paths):
return rv
del rv
return None
@classmethod @classmethod
def find_candidates(cls, fingerprints): def find_candidates(cls, xfp_paths, addr_fmt=None):
# Find index of matching wallet and M value. Don't de-serialize everything. # Return a list of matching wallets for various M values.
# - returns set of matches, each with M value # - xpfs_paths hsould already be sorted
# - fingerprints are iterable of uint32's # - returns set of matches, of any M value
from main import settings from main import settings
lst = settings.get('multisig', []) lst = settings.get('multisig', [])
fingerprints = sorted(fingerprints) # we know N, but not M at this point.
N = len(fingerprints) N = len(xfp_paths)
rv = []
rv = []
for idx, rec in enumerate(lst): for idx, rec in enumerate(lst):
name, m_of_n, xpubs, opts = rec name, m_of_n, xpubs, opts = rec
if m_of_n[1] != N: continue if m_of_n[1] != N: continue
if sorted(f for f,_ in xpubs) != fingerprints: continue
rv.append(idx) if addr_fmt is not None:
af = opts.get('ft', AF_P2SH)
if af != addr_fmt: continue
maybe = cls.deserialize(rec, idx)
if maybe.matching_subpaths(xfp_paths):
rv.append(maybe)
else:
del maybe
return rv return rv
def assert_matching(self, M, N, fingerprints): def matching_subpaths(self, xfp_paths):
# Does this wallet use same set of xfp values, and
# the same prefix path per-each xfp, as indicated
# xfp_paths (unordered)?
# - could also check non-prefix part is all non-hardened
for x in xfp_paths:
if x[0] not in self.xfp_paths:
return False
prefix = self.xfp_paths[x[0]]
if len(x) < len(prefix):
# PSBT specs a path shorter than wallet's xpub
#print('path len: %d vs %d' % (len(prefix), len(x)))
return False
comm = len(prefix)
if tuple(prefix[:comm]) != tuple(x[:comm]):
# xfp => maps to wrong path
#print('path mismatch:\n%r\n%r\ncomm=%d' % (prefix[:comm], x[:comm], comm))
return False
return True
def assert_matching(self, M, N, xfp_paths):
# compare in-memory wallet with details recovered from PSBT # compare in-memory wallet with details recovered from PSBT
# - xfp_paths must be sorted already
assert (self.M, self.N) == (M, N), "M/N mismatch" assert (self.M, self.N) == (M, N), "M/N mismatch"
assert len(fingerprints) == N, "XFP count" assert len(xfp_paths) == N, "XFP count"
assert sorted(fingerprints) == self.xfps, "wrong XFPs" assert self.matching_subpaths(xfp_paths), "wrong XFP/derivs"
@classmethod @classmethod
def quick_check(cls, M, N, xfp_xor): def quick_check(cls, M, N, xfp_xor):
@ -222,7 +282,7 @@ class MultisigWallet:
if m_of_n[1] != N: continue if m_of_n[1] != N: continue
x = 0 x = 0
for xfp, _ in xpubs: for xfp, _, _ in xpubs:
x ^= xfp x ^= xfp
if x != xfp_xor: continue if x != xfp_xor: continue
@ -291,30 +351,34 @@ class MultisigWallet:
raise MultisigOutOfSpace raise MultisigOutOfSpace
def has_similar(self):
def has_dup(self):
# check if we already have a saved duplicate to this proposed wallet # check if we already have a saved duplicate to this proposed wallet
# - also, flag if it's a dangerous/fraudulent attempt to replace it. # - return (name_change, diff_items) where:
# - name_change is existing wallet that has exact match, different name
# - diff_items: text list of similarity/differences
idx = MultisigWallet.find_match(self.M, self.N, self.xfps) similar = MultisigWallet.find_candidates(list(self.xfp_paths.values()))
if idx == -1: if not similar:
# no matches # no matches
return False, 0 return None, []
# See if the xpubs are changing, which is risky... other differences like # See if the xpubs are changing, which is risky... other differences like
# name are okay. # name are okay.
o = self.get_by_idx(idx) diffs = set()
name_diff = None
for c in similar:
if c.M != self.M:
diffs.add('M differs')
if c.addr_fmt != self.addr_fmt:
diffs.add('address type')
if c.name != self.name and c.matching_subpaths(c):
diffs.add('name')
name_diff = c
# Calc apx. number of xpub changes. if name_diff and len(diffs) == 1:
diffs = 0 return name_diff, []
a = sorted(self.xpubs)
b = sorted(o.xpubs)
assert len(a) == len(b) # because same N
for idx in range(self.N):
if a[idx] != b[idx]:
diffs += 1
return o, diffs return None, diffs
def delete(self): def delete(self):
# remove saved entry # remove saved entry
@ -324,8 +388,9 @@ class MultisigWallet:
assert self.storage_idx >= 0 assert self.storage_idx >= 0
# safety check # safety check
expect_idx = self.find_match(self.M, self.N, self.xfps) existing = self.find_match(self.M, self.N, list(self.xfp_paths.values()))
assert expect_idx == self.storage_idx assert existing
assert existing.storage_idx == self.storage_idx
lst = settings.get('multisig', []) lst = settings.get('multisig', [])
del lst[self.storage_idx] del lst[self.storage_idx]
@ -336,7 +401,7 @@ class MultisigWallet:
def xpubs_with_xfp(self, xfp): def xpubs_with_xfp(self, xfp):
# return set of indexes of xpubs with indicated xfp # return set of indexes of xpubs with indicated xfp
return set(xp_idx for xp_idx, (wxfp, _) in enumerate(self.xpubs) return set(xp_idx for xp_idx, (wxfp, _, _) in enumerate(self.xpubs)
if wxfp == xfp) if wxfp == xfp)
def validate_script(self, redeem_script, subpaths=None, xfp_paths=None): def validate_script(self, redeem_script, subpaths=None, xfp_paths=None):
@ -346,7 +411,6 @@ class MultisigWallet:
# redeem_script: what we expect and we were given # redeem_script: what we expect and we were given
# subpaths: pubkey => (xfp, *path) # subpaths: pubkey => (xfp, *path)
# xfp_paths: (xfp, *path) in same order as pubkeys in redeem script # xfp_paths: (xfp, *path) in same order as pubkeys in redeem script
from psbt import path_to_str
subpath_help = [] subpath_help = []
used = set() used = set()
@ -361,10 +425,11 @@ class MultisigWallet:
if subpaths: if subpaths:
# in PSBT, we are given a map from pubkey to xfp/path, use it # in PSBT, we are given a map from pubkey to xfp/path, use it
# while remembering it's potentially one-2-many # while remembering it's potentially one-2-many
# TODO: this could be simpler now
assert pubkey in subpaths, "unexpected pubkey" assert pubkey in subpaths, "unexpected pubkey"
xfp, *path = subpaths[pubkey] xfp, *path = subpaths[pubkey]
for xp_idx, (wxfp, xpub) in enumerate(self.xpubs): for xp_idx, (wxfp, _, xpub) in enumerate(self.xpubs):
if wxfp != xfp: continue if wxfp != xfp: continue
if xp_idx in used: continue # only allow once if xp_idx in used: continue # only allow once
check_these.append((xp_idx, path)) check_these.append((xp_idx, path))
@ -383,7 +448,7 @@ class MultisigWallet:
too_shallow = False too_shallow = False
for xp_idx, path in check_these: for xp_idx, path in check_these:
# matched fingerprint, try to make pubkey that needs to match # matched fingerprint, try to make pubkey that needs to match
xpub = self.xpubs[xp_idx][1] xpub = self.xpubs[xp_idx][-1]
node = ch.deserialize_node(xpub, AF_P2SH); assert node node = ch.deserialize_node(xpub, AF_P2SH); assert node
dp = node.depth() dp = node.depth()
@ -405,7 +470,7 @@ class MultisigWallet:
# part of the path from fingerprint to here. # part of the path from fingerprint to here.
here = '(m=%s)\n' % xfp2str(xfp) here = '(m=%s)\n' % xfp2str(xfp)
if dp != len(path): if dp != len(path):
here += 'm' + ('/_'*dp) + path_to_str(path[dp:], '/', 0) here += 'm' + ('/_'*dp) + keypath_to_str(path[dp:], '/', 0)
if found_pk != pubkey: if found_pk != pubkey:
# Not a match but not an error by itself, since might be # Not a match but not an error by itself, since might be
@ -444,6 +509,8 @@ class MultisigWallet:
# where label is: # where label is:
# name: nameforwallet # name: nameforwallet
# policy: M of N # policy: M of N
# format: p2sh (+etc)
# derivation: m/45'/0 (common prefix)
# (8digithex): xpub of cosigner # (8digithex): xpub of cosigner
# #
# quick checks: # quick checks:
@ -454,11 +521,10 @@ class MultisigWallet:
from main import settings from main import settings
my_xfp = settings.get('xfp') my_xfp = settings.get('xfp')
common_prefix = None deriv = None
xpubs = [] xpubs = []
path_tops = set()
M, N = -1, -1 M, N = -1, -1
has_mine = False has_mine = 0
addr_fmt = AF_P2SH addr_fmt = AF_P2SH
expect_chain = chains.current_chain().ctype expect_chain = chains.current_chain().ctype
@ -479,7 +545,7 @@ class MultisigWallet:
value = ln value = ln
else: else:
# complain? # complain?
if ln: print("no colon: " + ln) #if ln: print("no colon: " + ln)
continue continue
else: else:
label, value = ln.split(':') label, value = ln.split(':')
@ -501,11 +567,9 @@ class MultisigWallet:
raise AssertionError('bad policy line') raise AssertionError('bad policy line')
elif label == 'derivation': elif label == 'derivation':
# reveal the **common** path derivation for all keys # reveal the path derivation for following key(s)
try: try:
cp = cleanup_deriv_path(value) deriv = cleanup_deriv_path(value)
# - not storing "m/" prefix, nor 'm' case which doesn't add any info
common_prefix = None if cp == 'm' else cp[2:]
except BaseException as exc: except BaseException as exc:
raise AssertionError('bad derivation line: ' + str(exc)) raise AssertionError('bad derivation line: ' + str(exc))
@ -527,11 +591,9 @@ class MultisigWallet:
continue continue
# deserialize, update list and lots of checks # deserialize, update list and lots of checks
xfp = cls.check_xpub(xfp, value, expect_chain, xpubs, path_tops) is_mine = cls.check_xpub(xfp, value, deriv, expect_chain, my_xfp, xpubs)
if is_mine:
if xfp == my_xfp: has_mine += 1
# not conclusive, but enough for error catching.
has_mine = True
assert len(xpubs), 'need xpubs' assert len(xpubs), 'need xpubs'
@ -555,30 +617,27 @@ class MultisigWallet:
# check we're included... do not insert ourselves, even tho we # check we're included... do not insert ourselves, even tho we
# have enough info, simply because other signers need to know my xpubkey anyway # have enough info, simply because other signers need to know my xpubkey anyway
assert has_mine, 'my key not included' assert has_mine != 0, 'my key not included'
assert has_mine == 1 # 'my key included more than once'
if not common_prefix and len(path_tops) == 1:
# fill in the common prefix iff we can deduce it from xpubs
common_prefix = path_tops.pop()
# done. have all the parts # done. have all the parts
return cls(name, (M, N), xpubs, addr_fmt=addr_fmt, return cls(name, (M, N), xpubs, addr_fmt=addr_fmt, chain_type=expect_chain)
chain_type=expect_chain, common_prefix=common_prefix)
@classmethod @classmethod
def check_xpub(cls, xfp, xpub, expect_chain, xpubs, path_tops): def check_xpub(cls, xfp, xpub, deriv, expect_chain, my_xfp, xpubs):
# Shared code: consider an xpub for inclusion into a wallet, if ok, append # Shared code: consider an xpub for inclusion into a wallet, if ok, append
# to list: xpubs, and path_tops # to list: xpubs with a tuple: (xfp, deriv, xpub)
# return T if it's our own key
# - deriv can be None, and in very limited cases can recover derivation path
try: try:
# Note: addr fmt detected here via SLIP-132 isn't useful # Note: addr fmt detected here via SLIP-132 isn't useful
node, chain, _ = import_xpub(xpub) node, chain, _ = import_xpub(xpub)
except: except:
print(xpub)
raise AssertionError('unable to parse xpub') raise AssertionError('unable to parse xpub')
assert node.private_key() == None, 'no privkeys plz' assert node.private_key() == None # 'no privkeys plz'
assert chain.ctype == expect_chain, 'wrong chain' assert chain.ctype == expect_chain # 'wrong chain'
# NOTE: could enforce all same depth, and/or all depth >= 1, but # NOTE: could enforce all same depth, and/or all depth >= 1, but
# seems like more restrictive than needed. # seems like more restrictive than needed.
@ -590,23 +649,36 @@ class MultisigWallet:
# generally cannot check fingerprint values, but if we can, do. # generally cannot check fingerprint values, but if we can, do.
assert swab32(node.fingerprint()) == xfp, 'xfp depth=1 wrong' assert swab32(node.fingerprint()) == xfp, 'xfp depth=1 wrong'
assert xfp, 'need fingerprint' assert xfp # 'need fingerprint'
# detect, when possible, if it follows BIP45 ... find the path # In most cases, we cannot verify the derivation path because it's hardened
path_top = None # and we know none of the private keys involved.
if node.depth() == 1: if node.depth() == 1:
# but derivation is implied at depth==1
cn = node.child_num() cn = node.child_num()
path_top = str(cn & 0x7fffffff) guess = 'm/%d' % (cn & 0x7fffffff)
if cn & 0x80000000: if cn & 0x80000000:
path_top += "'" guess += "'"
path_tops.add(path_top) if deriv:
assert guess == deriv, '%s != %s' % (guess, deriv)
else:
deriv = guess # reachable? doubt it
assert deriv, 'need deriv path'
if xfp == my_xfp:
# its supposed to be my key, so I should be able to generate pubkey
# - might indicate collision on xfp value between co-signers, and that's not supported
with stash.SensitiveValues() as sv:
chk_node = sv.derive_path(deriv)
assert node.public_key() == chk_node.public_key(), "XFP non-unique"
# serialize xpub w/ BIP32 standard now. # serialize xpub w/ BIP32 standard now.
# - this has effect of stripping SLIP-132 confusion away # - this has effect of stripping SLIP-132 confusion away
xpubs.append((xfp, chain.serialize_public(node, AF_P2SH))) xpubs.append((xfp, deriv, chain.serialize_public(node, AF_P2SH)))
return xfp return (xfp == my_xfp)
def make_fname(self, prefix, suffix='txt'): def make_fname(self, prefix, suffix='txt'):
rv = '%s-%s.%s' % (prefix, self.name, suffix) rv = '%s-%s.%s' % (prefix, self.name, suffix)
@ -623,7 +695,7 @@ class MultisigWallet:
ch = self.chain ch = self.chain
# the important stuff. # the important stuff.
for idx, (xfp, xpub) in enumerate(self.xpubs): for idx, (xfp, deriv, xpub) in enumerate(self.xpubs):
if self.addr_fmt != AF_P2SH: if self.addr_fmt != AF_P2SH:
# CHALLENGE: we must do slip-132 format [yz]pubs here when not p2sh mode. # CHALLENGE: we must do slip-132 format [yz]pubs here when not p2sh mode.
@ -632,11 +704,13 @@ class MultisigWallet:
else: else:
xp = xpub xp = xpub
assert deriv != UNSURE_DERIV # also checked above
rv['x%d/' % (idx+1)] = dict( rv['x%d/' % (idx+1)] = dict(
hw_type='coldcard', type='hardware', hw_type='coldcard', type='hardware',
ckcc_xfp=xfp, ckcc_xfp=xfp,
label='Coldcard %s' % xfp2str(xfp), label='Coldcard %s' % xfp2str(xfp),
derivation='m/'+self.common_prefix, xpub=xp) derivation=deriv, xpub=xp)
return rv return rv
@ -675,15 +749,15 @@ class MultisigWallet:
def render_export(self, fp): def render_export(self, fp):
print("Name: %s\nPolicy: %d of %d" % (self.name, self.M, self.N), file=fp) print("Name: %s\nPolicy: %d of %d" % (self.name, self.M, self.N), file=fp)
if self.common_prefix:
print("Derivation: m/%s" % self.common_prefix, file=fp)
if self.addr_fmt != AF_P2SH: if self.addr_fmt != AF_P2SH:
print("Format: " + self.render_addr_fmt(self.addr_fmt), file=fp) print("Format: " + self.render_addr_fmt(self.addr_fmt), file=fp)
print("", file=fp) last_deriv = None
for xfp, deriv, val in self.xpubs:
if last_deriv != deriv:
print("\nDerivation: %s\n" % deriv, file=fp)
last_deriv = deriv
for xfp, val in self.xpubs:
print('%s: %s' % (xfp2str(xfp), val), file=fp) print('%s: %s' % (xfp2str(xfp), val), file=fp)
@classmethod @classmethod
@ -702,6 +776,7 @@ class MultisigWallet:
raise FatalPSBTIssue("XPUBs in PSBT do not match any existing wallet") raise FatalPSBTIssue("XPUBs in PSBT do not match any existing wallet")
# build up an in-memory version of the wallet. # build up an in-memory version of the wallet.
# TODO: capture address format from an input?
assert N == len(xpubs_list) assert N == len(xpubs_list)
assert 1 <= M <= N <= MAX_SIGNERS, 'M/N range' assert 1 <= M <= N <= MAX_SIGNERS, 'M/N range'
@ -709,28 +784,37 @@ class MultisigWallet:
expect_chain = chains.current_chain().ctype expect_chain = chains.current_chain().ctype
xpubs = [] xpubs = []
has_mine = False has_mine = 0
path_tops = set()
for k, v in xpubs_list: for k, v in xpubs_list:
xfp, *path = ustruct.unpack_from('<%dI' % (len(k)/4), k, 0) xfp, *path = ustruct.unpack_from('<%dI' % (len(k)//4), k, 0)
xpub = tcc.codecs.b58_encode(v) xpub = tcc.codecs.b58_encode(v)
xfp = cls.check_xpub(xfp, xpub, expect_chain, xpubs, path_tops) is_mine = cls.check_xpub(xfp, xpub, keypath_to_str(path, skip=0),
if xfp == my_xfp: expect_chain, my_xfp, xpubs)
has_mine = True if is_mine:
has_mine += 1
assert has_mine # 'my key not included' assert has_mine == 1 # 'my key not included'
name = 'PSBT-%d-of-%d' % (M, N) name = 'PSBT-%d-of-%d' % (M, N)
ms = cls(name, (M, N), xpubs, chain_type=expect_chain)
prefix = path_tops.pop() if len(path_tops) == 1 else None
ms = cls(name, (M, N), xpubs, chain_type=expect_chain, common_prefix=prefix)
# may just keep just in-memory version, no approval required, if we are # may just keep just in-memory version, no approval required, if we are
# trusting PSBT's today, otherwise caller will need to handle UX w.r.t new wallet # trusting PSBT's today, otherwise caller will need to handle UX w.r.t new wallet
return ms, (trust_mode != TRUST_PSBT) return ms, (trust_mode != TRUST_PSBT)
def format_deriv_paths(self):
# show either single common derivation path, or indented list of them
ds = set(d for _,d,_ in self.xpubs)
unsure = (UNSURE_DERIV in ds)
if len(ds) == 1:
ds = ds.pop()
else:
ds = ' ' + '\n '.join(sorted(ds))
return unsure, ds
async def confirm_import(self): async def confirm_import(self):
# prompt them about a new wallet, let them see details and then commit change. # prompt them about a new wallet, let them see details and then commit change.
M, N = self.M, self.N M, N = self.M, self.N
@ -745,17 +829,21 @@ class MultisigWallet:
exp = '{M} signatures, from {N} possible co-signers, will be required to approve spends.'.format(M=M, N=N) exp = '{M} signatures, from {N} possible co-signers, will be required to approve spends.'.format(M=M, N=N)
# Look for duplicate case. # Look for duplicate case.
is_dup, diff_count = self.has_dup() name_change, diff_items = self.has_similar()
if not is_dup: if name_change:
story = 'Create new multisig wallet?' story = 'Update NAME only of existing multisig wallet?'
elif diff_count: elif diff_items:
# Concern here is overwrite when similar, but we don't overwrite anymore, so
# more of a warning about funny business.
story = '''\ story = '''\
CAUTION: This updated wallet has %d different XPUB values, but matching fingerprints \ WARNING: This new wallet is very similar to an existing wallet, but will NOT replace it. Consider deleting previous wallet first. Differences: \
and same M of N. Perhaps the derivation path has changed legitimately, otherwise, much \ ''' + ', '.join(diff_items)
DANGER!''' % diff_count
else: else:
story = 'Update existing multisig wallet?' story = 'Create new multisig wallet?'
_, ds = self.format_deriv_paths()
story += '''\n story += '''\n
Wallet Name: Wallet Name:
{name} {name}
@ -764,43 +852,63 @@ Policy: {M} of {N}
{exp} {exp}
Addresses:
{at}
Derivation: Derivation:
m/{deriv} {deriv}
Press (1) to see extended public keys, \ Press (1) to see extended public keys, \
OK to approve, X to cancel.'''.format(M=M, N=N, name=self.name, exp=exp, OK to approve, X to cancel.'''.format(M=M, N=N, name=self.name, exp=exp, deriv=ds,
deriv=self.common_prefix or 'unknown') at=self.render_addr_fmt(self.addr_fmt))
ux_clear_keys(True) ux_clear_keys(True)
while 1: while 1:
ch = await ux_show_story(story, escape='1') ch = await ux_show_story(story, escape='1')
if ch == '1': if ch == '1':
# Show the xpubs; might be 2k or more rendered. await self.show_detail(verbose=False)
msg = uio.StringIO()
for idx, (xfp, xpub) in enumerate(self.xpubs):
if idx:
msg.write('\n\n')
# Not showing index numbers here because order
# is non-deterministic both here, our storage, and in usage.
msg.write('%s:\n%s' % (xfp2str(xfp), xpub))
await ux_show_story(msg, title='%d of %d' % (self.M, self.N))
continue continue
if ch == 'y': if ch == 'y':
# save to nvram, may raise MultisigOutOfSpace # save to nvram, may raise MultisigOutOfSpace
if is_dup: if name_change:
is_dup.delete() name_change.delete()
self.commit() self.commit()
await ux_dramatic_pause("Saved.", 2) await ux_dramatic_pause("Saved.", 2)
break break
return ch return ch
async def show_detail(self, verbose=True):
# Show the xpubs; might be 2k or more rendered.
msg = uio.StringIO()
if verbose:
msg.write('''
Policy: {M} of {N}
Blockchain: {ctype}
Addresses:
{at}\n\n'''.format(M=self.M, N=self.N, ctype=self.chain_type,
at=self.render_addr_fmt(self.addr_fmt)))
# concern: the order of keys here is non-deterministic
for idx, (xfp, deriv, xpub) in enumerate(self.xpubs):
if idx:
msg.write('\n---===---\n\n')
msg.write('%s:\n %s\n\n%s\n' % (xfp2str(xfp), deriv, xpub))
if self.addr_fmt != AF_P2SH:
# SLIP-132 format [yz]pubs here when not p2sh mode.
# - has some info as proper bitcoin serialization, but useful still
node = self.chain.deserialize_node(xpub, AF_P2SH)
xp = self.chain.serialize_public(node, self.addr_fmt)
msg.write('\nSLIP-132 equiv:\n%s\n' % xp)
return await ux_show_story(msg, title=self.name)
async def no_ms_yet(*a): async def no_ms_yet(*a):
# action for 'no wallets yet' menu item # action for 'no wallets yet' menu item
await ux_show_story("You don't have any multisig wallets yet.") await ux_show_story("You don't have any multisig wallets yet.")
@ -940,13 +1048,13 @@ async def ms_wallet_electrum_export(menu, label, item):
ms = item.arg ms = item.arg
from actions import electrum_export_story from actions import electrum_export_story
prefix = ms.common_prefix unsure, derivs = ms.format_deriv_paths()
if not prefix : if unsure:
return await ux_show_story("We don't know the common derivation path for " return await ux_show_story("We don't know all the derivation paths for "
"these keys, so cannot create Electrum wallet.") "these keys, so cannot create Electrum wallet.")
msg = 'The new wallet will have derivation path:\n %s\n and use %s addresses.\n' % ( msg = 'The new wallet will have derivation path:\n %s\n and use %s addresses.\n' % (
prefix, MultisigWallet.render_addr_fmt(ms.addr_fmt) ) derivs, MultisigWallet.render_addr_fmt(ms.addr_fmt) )
if await ux_show_story(electrum_export_story(msg)) != 'y': if await ux_show_story(electrum_export_story(msg)) != 'y':
return return
@ -955,35 +1063,11 @@ async def ms_wallet_electrum_export(menu, label, item):
async def ms_wallet_detail(menu, label, item): async def ms_wallet_detail(menu, label, item):
# show details of single multisig wallet, offer to delete # show details of single multisig wallet
import chains
ms = item.arg ms = item.arg
msg = uio.StringIO()
msg.write(''' return await ms.show_detail()
Policy: {M} of {N}
Blockchain: {ctype}
Addresses:
{at}
'''.format(M=ms.M, N=ms.N, ctype=ms.chain_type,
at=MultisigWallet.render_addr_fmt(ms.addr_fmt)))
if ms.common_prefix:
msg.write('''\
Derivation:
m/{der}
'''.format(der=ms.common_prefix))
msg.write('\n')
# concern: the order of keys here is non-deterministic
for idx, (xfp, xpub) in enumerate(ms.xpubs):
if idx:
msg.write('\n')
msg.write('%s:\n%s\n' % (xfp2str(xfp), xpub))
await ux_show_story(msg, title=ms.name)
async def export_multisig_xpubs(*a): async def export_multisig_xpubs(*a):
@ -1095,7 +1179,7 @@ async def ondevice_multisig_create(mode='p2wsh', addr_fmt=AF_P2WSH):
xpubs = [] xpubs = []
files = [] files = []
has_mine = False has_mine = 0
deriv = None deriv = None
try: try:
with CardSlot() as card: with CardSlot() as card:
@ -1128,16 +1212,15 @@ async def ondevice_multisig_create(mode='p2wsh', addr_fmt=AF_P2WSH):
# value in file is BE32, but we want LE32 internally # value in file is BE32, but we want LE32 internally
xfp = str2xfp(vals['xfp']) xfp = str2xfp(vals['xfp'])
if not deriv: if not deriv:
deriv = vals[mode+'_deriv'] deriv = cleanup_deriv_path(vals[mode+'_deriv'])
else: else:
assert deriv == vals[mode+'_deriv'], "wrong derivation" assert deriv == vals[mode+'_deriv'], "wrong derivation"
node, _, _ = import_xpub(ln) is_mine = MultisigWallet.check_xpub(xfp, ln, deriv,
chain.ctype, my_xfp, xpubs)
if is_mine:
has_mine += 1
if xfp == my_xfp:
has_mine = True
xpubs.append( (xfp, chain.serialize_public(node, AF_P2SH)) )
files.append(fn) files.append(fn)
except CardMissingError: except CardMissingError:
@ -1171,7 +1254,9 @@ async def ondevice_multisig_create(mode='p2wsh', addr_fmt=AF_P2WSH):
if not has_mine: if not has_mine:
with stash.SensitiveValues() as sv: with stash.SensitiveValues() as sv:
node = sv.derive_path(deriv) node = sv.derive_path(deriv)
xpubs.append( (my_xfp, chain.serialize_public(node, AF_P2SH)) ) xpubs.append( (my_xfp, deriv, chain.serialize_public(node, AF_P2SH)) )
else:
assert has_mine == 1, "same coldcard included"
N = len(xpubs) N = len(xpubs)
@ -1213,8 +1298,7 @@ Coldcard multisig setup file and an Electrum wallet file will be created automat
assert 1 <= M <= N <= MAX_SIGNERS assert 1 <= M <= N <= MAX_SIGNERS
name = 'CC-%d-of-%d' % (M, N) name = 'CC-%d-of-%d' % (M, N)
ms = MultisigWallet(name, (M, N), xpubs, chain_type=chain.ctype, ms = MultisigWallet(name, (M, N), xpubs, chain_type=chain.ctype, addr_fmt=addr_fmt)
common_prefix=deriv[2:], addr_fmt=addr_fmt)
from auth import NewEnrollRequest, UserAuthorizedAction from auth import NewEnrollRequest, UserAuthorizedAction

View File

@ -3,19 +3,19 @@
# #
# psbt.py - understand PSBT file format: verify and generate them # psbt.py - understand PSBT file format: verify and generate them
# #
from serializations import ser_compact_size, deser_compact_size, hash160, hash256
from serializations import CTxIn, CTxInWitness, CTxOut, SIGHASH_ALL, ser_uint256
from serializations import ser_sig_der, uint256_from_str, ser_push_data, uint256_from_str
from serializations import ser_string
from ustruct import unpack_from, unpack, pack from ustruct import unpack_from, unpack, pack
from ubinascii import hexlify as b2a_hex from ubinascii import hexlify as b2a_hex
from utils import xfp2str, B2A from utils import xfp2str, B2A, keypath_to_str, problem_file_line
import tcc, stash, gc, history import tcc, stash, gc, history, sys
from uio import BytesIO from uio import BytesIO
from sffile import SizerFile from sffile import SizerFile
from sram2 import psbt_tmp256 from sram2 import psbt_tmp256
from multisig import MultisigWallet, MAX_SIGNERS, disassemble_multisig, disassemble_multisig_mn from multisig import MultisigWallet, MAX_SIGNERS, disassemble_multisig, disassemble_multisig_mn
from exceptions import FatalPSBTIssue, FraudulentChangeOutput from exceptions import FatalPSBTIssue, FraudulentChangeOutput
from serializations import ser_compact_size, deser_compact_size, hash160, hash256
from serializations import CTxIn, CTxInWitness, CTxOut, SIGHASH_ALL, ser_uint256
from serializations import ser_sig_der, uint256_from_str, ser_push_data, uint256_from_str
from serializations import ser_string
from public_constants import ( from public_constants import (
PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_XPUB, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_XPUB, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO,
@ -59,10 +59,6 @@ def read_varint(v):
return unpack_from("<Q", v, 1)[0] return unpack_from("<Q", v, 1)[0]
return nit return nit
def path_to_str(bin_path, prefix='m/', skip=1):
return prefix + '/'.join(str(i & 0x7fffffff) + ("'" if i & 0x80000000 else "")
for i in bin_path[skip:])
def seq_to_str(seq): def seq_to_str(seq):
# take a set or list of numbers and show a tidy list in order. # take a set or list of numbers and show a tidy list in order.
return ', '.join(str(i) for i in sorted(seq)) return ', '.join(str(i) for i in sorted(seq))
@ -169,7 +165,7 @@ class psbtProxy:
def __getattr__(self, nm): def __getattr__(self, nm):
if nm in self.blank_flds: if nm in self.blank_flds:
return None return None
raise AttributeError raise AttributeError(nm)
def parse(self, fd): def parse(self, fd):
self.fd = fd self.fd = fd
@ -260,8 +256,7 @@ class psbtProxy:
# promote to a list of ints # promote to a list of ints
v = self.get(self.subpaths[pk]) v = self.get(self.subpaths[pk])
here = list(unpack_from('<I', v, off)[0] for off in range(0, vl, 4)) here = list(unpack_from('<%dI' % (vl//4), v))
assert len(here) == vl // 4
# update in place # update in place
self.subpaths[pk] = here self.subpaths[pk] = here
@ -269,8 +264,8 @@ class psbtProxy:
if here[0] == my_xfp: if here[0] == my_xfp:
num_ours += 1 num_ours += 1
else: else:
# Address that isn't based on my seed; might be another leg in a p2sh # Address that isn't based on my seed; might be another leg in a p2sh,
# and that's okay, or an input we're not supposed to be able to sign # or an input we're not supposed to be able to sign... and that's okay.
pass pass
self.num_our_keys = num_ours self.num_our_keys = num_ours
@ -283,7 +278,7 @@ class psbtProxy:
class psbtOutputProxy(psbtProxy): class psbtOutputProxy(psbtProxy):
no_keys = { PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT } no_keys = { PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_WITNESS_SCRIPT }
blank_flds = ('unknown', 'subpaths', 'redeem_script', 'witness_script', blank_flds = ('unknown', 'subpaths', 'redeem_script', 'witness_script',
'is_change', 'is_p2sh_change', 'num_our_keys') 'is_change', 'num_our_keys')
def __init__(self, fd, idx): def __init__(self, fd, idx):
super().__init__() super().__init__()
@ -296,9 +291,6 @@ class psbtOutputProxy(psbtProxy):
# this flag is set when we are assuming output will be change (same wallet) # this flag is set when we are assuming output will be change (same wallet)
#self.is_change = False #self.is_change = False
# output is change into same multisig wallet
#self.is_p2sh_change = False
self.parse(fd) self.parse(fd)
@ -406,8 +398,8 @@ class psbtOutputProxy(psbtProxy):
# it cannot be change if it doesn't precisely match our multisig setup # it cannot be change if it doesn't precisely match our multisig setup
if not active_multisig: if not active_multisig:
# might be a p2sh output for another wallet that isn't us # - might be a p2sh output for another wallet that isn't us
# not fraud, just an output with more details than we need. # - not fraud, just an output with more details than we need.
self.is_change = False self.is_change = False
return return
@ -423,9 +415,6 @@ class psbtOutputProxy(psbtProxy):
raise FraudulentChangeOutput(out_idx, raise FraudulentChangeOutput(out_idx,
"P2WSH or P2SH change output script: %s" % exc) "P2WSH or P2SH change output script: %s" % exc)
# mark pubkeys as already checked.
self.is_p2sh_change = True
if is_segwit: if is_segwit:
# p2wsh case # p2wsh case
# - need witness script and check it's hash against proposed p2wsh value # - need witness script and check it's hash against proposed p2wsh value
@ -715,23 +704,25 @@ class psbtInputProxy(psbtProxy):
#print("redeem: %s" % b2a_hex(redeem_script)) #print("redeem: %s" % b2a_hex(redeem_script))
M, N = disassemble_multisig_mn(redeem_script) M, N = disassemble_multisig_mn(redeem_script)
xfps = [lst[0] for lst in self.subpaths.values()] xfp_paths = list(self.subpaths.values())
xfp_paths.sort()
if not psbt.active_multisig: if not psbt.active_multisig:
# search for multisig wallet # search for multisig wallet
wal = MultisigWallet.find_match(M, N, xfps) wal = MultisigWallet.find_match(M, N, xfp_paths)
if wal < 0: if not wal:
raise FatalPSBTIssue('Unknown multisig wallet') raise FatalPSBTIssue('Unknown multisig wallet')
psbt.active_multisig = MultisigWallet.get_by_idx(wal) psbt.active_multisig = wal
else: else:
# check consistent w/ already selected wallet # check consistent w/ already selected wallet
psbt.active_multisig.assert_matching(M, N, xfps) psbt.active_multisig.assert_matching(M, N, xfp_paths)
# validate redeem script, by disassembling it and checking all pubkeys # validate redeem script, by disassembling it and checking all pubkeys
try: try:
psbt.active_multisig.validate_script(redeem_script, subpaths=self.subpaths) psbt.active_multisig.validate_script(redeem_script, subpaths=self.subpaths)
except BaseException as exc: except BaseException as exc:
sys.print_exception(exc)
raise FatalPSBTIssue('Input #%d: %s' % (my_idx, exc)) raise FatalPSBTIssue('Input #%d: %s' % (my_idx, exc))
if not which_key and DEBUG: if not which_key and DEBUG:
@ -1019,18 +1010,27 @@ class psbtObject(psbtProxy):
async def handle_xpubs(self): async def handle_xpubs(self):
# Lookup correct wallet based on xpubs in globals # Lookup correct wallet based on xpubs in globals
# - only happens if they volunteered this 'extra' data # - only happens if they volunteered this 'extra' data
# - do not assume multisig
assert not self.active_multisig assert not self.active_multisig
xfps = [unpack_from('<I', k)[0] for k,_ in self.xpubs] xfp_paths = []
if self.my_xfp not in xfps: has_mine = 0
for k,_ in self.xpubs:
h = unpack_from('<%dI' % (len(k)//4), k, 0)
assert len(h) >= 1
xfp_paths.append(h)
if h[0] == self.my_xfp:
has_mine += 1
if not has_mine:
raise FatalPSBTIssue('My XFP not involved') raise FatalPSBTIssue('My XFP not involved')
candidates = MultisigWallet.find_candidates(xfps) candidates = MultisigWallet.find_candidates(xfp_paths)
match_idx = -1
if len(candidates) == 1: if len(candidates) == 1:
# exact match (by xfp set) .. normal case # exact match (by xfp+deriv set) .. normal case
self.active_multisig = MultisigWallet.get_by_idx(candidates[0]) self.active_multisig = candidates[0]
else: else:
# don't want to guess M if not needed, but we need it # don't want to guess M if not needed, but we need it
M, N = self.guess_M_of_N() M, N = self.guess_M_of_N()
@ -1041,13 +1041,15 @@ class psbtObject(psbtProxy):
# - too slow to re-derive it here, so nothing more to validate at this point # - too slow to re-derive it here, so nothing more to validate at this point
return return
assert N == len(xfps) assert N == len(xfp_paths)
if candidates: for c in candidates:
# maybe narrowed down to single match now if c.M == M:
match_idx = MultisigWallet.find_match(M, N, xfps) assert c.N == N
if match_idx != -1: self.active_multisig = c
self.active_multisig = MultisigWallet.get_by_idx(match_idx) break
del candidates
if not self.active_multisig: if not self.active_multisig:
# Maybe create wallet, for today, forever, or fail, etc. # Maybe create wallet, for today, forever, or fail, etc.
@ -1063,6 +1065,16 @@ class psbtObject(psbtProxy):
raise FatalPSBTIssue("Refused to import new wallet") raise FatalPSBTIssue("Refused to import new wallet")
self.active_multisig = proposed self.active_multisig = proposed
else:
# Validate good match here. The xpubs must be exactly right, but
# we're going to use our own values from setup time anyway and not trusting
# new values without user interaction.
# Check:
# - chain codes match what we have stored already
# - pubkey vs. path will be checked later
# - xfp+path already checked above when selecting wallet
# Any issue here is a fraud attempt in some way, not innocent
self.active_multisig.validate_psbt_xpubs(self.xpubs)
if not self.active_multisig: if not self.active_multisig:
# not clear if an error... might be part-way to importing, and # not clear if an error... might be part-way to importing, and
@ -1070,9 +1082,6 @@ class psbtObject(psbtProxy):
# we should not reach this point (ie. raise something to abort signing) # we should not reach this point (ie. raise something to abort signing)
return return
# Could validate good match here? The xpubs must be exactly right, but
# we're going to use our own values from setup time anyway and not trusting
# these values without user interaction.
async def validate(self): async def validate(self):
# Do a first pass over the txn. Raise assertions, be terse tho because # Do a first pass over the txn. Raise assertions, be terse tho because
@ -1194,8 +1203,8 @@ class psbtObject(psbtProxy):
continue continue
probs.append("Output#%d: %s: %s not %s/{0~1}%s/{0~%d}%s expected" probs.append("Output#%d: %s: %s not %s/{0~1}%s/{0~%d}%s expected"
% (nout, iss, path_to_str(path, skip=0), % (nout, iss, keypath_to_str(path, skip=0),
path_to_str(path_prefix, skip=0), keypath_to_str(path_prefix, skip=0),
"'" if hard_pattern[-2] else "", "'" if hard_pattern[-2] else "",
idx_max, "'" if hard_pattern[-1] else "", idx_max, "'" if hard_pattern[-1] else "",
)) ))
@ -1370,8 +1379,7 @@ class psbtObject(psbtProxy):
# Double check the change outputs are right. This is slow, but critical because # Double check the change outputs are right. This is slow, but critical because
# it detects bad actors, not bugs or mistakes. # it detects bad actors, not bugs or mistakes.
# - equivilent check already done for p2sh outputs when we re-built the redeem script # - equivilent check already done for p2sh outputs when we re-built the redeem script
change_outs = [n for n,o in enumerate(self.outputs) change_outs = [n for n,o in enumerate(self.outputs) if o.is_change]
if o.is_change and not o.is_p2sh_change]
if change_outs: if change_outs:
dis.fullscreen('Change Check...') dis.fullscreen('Change Check...')
@ -1379,17 +1387,27 @@ class psbtObject(psbtProxy):
# only expecting single case, but be general # only expecting single case, but be general
dis.progress_bar_show(count / len(change_outs)) dis.progress_bar_show(count / len(change_outs))
for pubkey, subpath in self.outputs[out_idx].subpaths.items(): oup = self.outputs[out_idx]
# derive it
skp = path_to_str(subpath) good = 0
for pubkey, subpath in oup.subpaths.items():
if subpath[0] != self.my_xfp:
# for multisig, will be N paths, and exactly one will
# be our key. For single-signer, should always be my XFP
continue
# derive actual pubkey from private
skp = keypath_to_str(subpath)
node = sv.derive_path(skp) node = sv.derive_path(skp)
# check the pubkey of this BIP32 node # check the pubkey of this BIP32 node
pu = node.public_key() if pubkey == node.public_key():
if pu != pubkey: good += 1
raise FraudulentChangeOutput(out_idx,
"Deception regarding change output. " if not good:
"BIP32 path doesn't match actual address.") raise FraudulentChangeOutput(out_idx,
"Deception regarding change output. "
"BIP32 path doesn't match actual address.")
# progress # progress
dis.fullscreen('Signing...') dis.fullscreen('Signing...')
@ -1430,7 +1448,7 @@ class psbtObject(psbtProxy):
# need to consider a set of possible keys, since xfp may not be unique # need to consider a set of possible keys, since xfp may not be unique
for which_key in inp.required_key: for which_key in inp.required_key:
# get node required # get node required
skp = path_to_str(inp.subpaths[which_key]) skp = keypath_to_str(inp.subpaths[which_key])
node = sv.derive_path(skp, register=False) node = sv.derive_path(skp, register=False)
# expensive test, but works... and important # expensive test, but works... and important
@ -1453,7 +1471,7 @@ class psbtObject(psbtProxy):
continue continue
# get node required # get node required
skp = path_to_str(inp.subpaths[which_key]) skp = keypath_to_str(inp.subpaths[which_key])
node = sv.derive_path(skp, register=False) node = sv.derive_path(skp, register=False)
# expensive test, but works... and important # expensive test, but works... and important

View File

@ -247,6 +247,30 @@ def cleanup_deriv_path(bin_path, allow_star=False):
return 'm/' + '/'.join(parts) return 'm/' + '/'.join(parts)
def keypath_to_str(bin_path, prefix='m/', skip=1):
# take binary path, like from a PSBT and convert into text notation
return prefix + '/'.join(str(i & 0x7fffffff) + ("'" if i & 0x80000000 else "")
for i in bin_path[skip:])
def str_to_keypath(xfp, path):
# Take a numeric xfp, and string derivation, and make a list of numbers,
# like occurs in a PSBT.
# - no error checking ehre
rv = [xfp]
for i in path.split('/'):
if i == 'm': continue
if not i: continue # trailing or duplicated slashes
if i[-1] == "'":
here = int(i[:-1]) | 0x80000000
else:
here = int(i)
rv.append(here)
return rv
def match_deriv_path(patterns, path): def match_deriv_path(patterns, path):
# check for exact string match, or wildcard match (star in last position) # check for exact string match, or wildcard match (star in last position)
# - both args must be cleaned by cleanup_deriv_path() already # - both args must be cleaned by cleanup_deriv_path() already

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images. # Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images.
# #
@ -29,7 +28,7 @@ BOOTLOADER_BASE = 0x08000000
FILESYSTEM_BASE = 0x080e0000 FILESYSTEM_BASE = 0x080e0000
# Our version for this release. # Our version for this release.
VERSION_STRING = 3.1.10 VERSION_STRING = 3.2.1
# #
# Sign and merge various parts # Sign and merge various parts

View File

@ -1,3 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# #
# "Bootloader" Makefile # "Bootloader" Makefile
# #

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#include "basics.h" #include "basics.h"
#include "ae.h" #include "ae.h"

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once
// //

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Plan: # Plan:
# - take a fixed background image and compose a number of info screens ontop # - take a fixed background image and compose a number of info screens ontop

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
/* /*
* This file is part of the MicroPython project, http://micropython.org/ * This file is part of the MicroPython project, http://micropython.org/

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#include "constant_time.h" #include "constant_time.h"

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once
#include <stdbool.h> #include <stdbool.h>

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* delay.c -- Software delay loops (we have no interrupts) * delay.c -- Software delay loops (we have no interrupts)
* *

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* dispatch.c * dispatch.c
* *

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once

View File

@ -1,6 +1,5 @@
// //
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> // (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
// and is covered by GPLv3 license found in COPYING.
// //
// //
// enable.c // enable.c

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#define NUM_KNOWN_PUBKEYS 6 #define NUM_KNOWN_PUBKEYS 6

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* gpio.c -- setup and control GPIO pins (and one button) * gpio.c -- setup and control GPIO pins (and one button)
* *

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#include "oled.h" #include "oled.h"
#include "delay.h" #include "delay.h"

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#include "basics.h" #include "basics.h"

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* pins.c -- PIN codes and security issues * pins.c -- PIN codes and security issues
* *

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* pins.h -- everything to do with PIN's and their policies * pins.h -- everything to do with PIN's and their policies
* *

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#include <string.h> #include <string.h>
#include "rng.h" #include "rng.h"

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once
#include "basics.h" #include "basics.h"

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Secure Element Config Area. # Secure Element Config Area.
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Secure Element Config debugging tools. # Secure Element Config debugging tools.
# #

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* sflash.c -- talk to the serial flash * sflash.c -- talk to the serial flash
* *

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#include "basics.h" #include "basics.h"

View File

@ -1,5 +1,4 @@
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> // (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
// and is covered by GPLv3 license found in COPYING.
// //
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>

View File

@ -1,7 +1,6 @@
# Autogen'ed file, don't edit. See bootloader/sigheader.h for original # Autogen'ed file, don't edit. See bootloader/sigheader.h for original
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# Our simple firmware header. # Our simple firmware header.
# Although called a header, this data is placed into the middle of the binary. # Although called a header, this data is placed into the middle of the binary.

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* NOTE: heavily trimmed! * NOTE: heavily trimmed!
* *

View File

@ -1,6 +1,4 @@
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> // (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
// and is covered by GPLv3 license found in COPYING.
//
// //
// storage.c -- manage flash and its sensitive contents. // storage.c -- manage flash and its sensitive contents.
// //

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
* *
* verify.c -- Check signatures on firmware images in flash. * verify.c -- Check signatures on firmware images in flash.
* *

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once
#include "basics.h" #include "basics.h"

View File

@ -1,5 +1,4 @@
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> // (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
// and is covered by GPLv3 license found in COPYING.
// //
// Version string. Careful with changes because parsed by python code and probably others. // Version string. Careful with changes because parsed by python code and probably others.
// //

View File

@ -1,6 +1,5 @@
/* /*
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> * (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
* and is covered by GPLv3 license found in COPYING.
*/ */
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>

View File

@ -1,4 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
all: all:
pytest . pytest .

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Access a local bitcoin-Qt/bitcoind on testnet # Access a local bitcoin-Qt/bitcoind on testnet
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
import pytest, glob, time, sys, random, re import pytest, glob, time, sys, random, re
from pprint import pprint from pprint import pprint

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
SIM_PATH = '/tmp/ckcc-simulator.sock' SIM_PATH = '/tmp/ckcc-simulator.sock'

View File

@ -2,6 +2,7 @@
# #
Name: CC-2-of-4 Name: CC-2-of-4
Policy: 2 of 4 Policy: 2 of 4
Derivation: m/45' Derivation: m/45'
0F056943: tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n 0F056943: tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n

View File

@ -2,9 +2,10 @@
# #
Name: CC-2-of-4 Name: CC-2-of-4
Policy: 2 of 4 Policy: 2 of 4
Derivation: m/48'/1'/0'/2'
Format: P2WSH Format: P2WSH
Derivation: m/48'/1'/0'/2'
0F056943: tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP 0F056943: tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP
6BA6CFD0: tpubDFcrvj5n7gyaxWQkoX69k2Zij4vthiAwvN2uhYjDrE6wktKoQaE7gKVZRiTbYdrAYH1UFPGdzdtWJc6WfR2gFMq6XpxA12gCdQmoQNU9mgm 6BA6CFD0: tpubDFcrvj5n7gyaxWQkoX69k2Zij4vthiAwvN2uhYjDrE6wktKoQaE7gKVZRiTbYdrAYH1UFPGdzdtWJc6WfR2gFMq6XpxA12gCdQmoQNU9mgm
747B698E: tpubDExj5FnaUnPAn7sHGUeBqD3buoNH5dqmjAT6884vbDpH1iDYWigb7kFo2cA97dc8EHb54u13TRcZxC4kgRS9gc3Ey2xc8c5urytEzTcp3ac 747B698E: tpubDExj5FnaUnPAn7sHGUeBqD3buoNH5dqmjAT6884vbDpH1iDYWigb7kFo2cA97dc8EHb54u13TRcZxC4kgRS9gc3Ey2xc8c5urytEzTcp3ac

View File

@ -2,9 +2,10 @@
# #
Name: CC-2-of-4 Name: CC-2-of-4
Policy: 2 of 4 Policy: 2 of 4
Derivation: m/48'/1'/0'/1'
Format: P2WSH-P2SH Format: P2WSH-P2SH
Derivation: m/48'/1'/0'/1'
0F056943: tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP 0F056943: tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP
6BA6CFD0: tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax 6BA6CFD0: tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax
747B698E: tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM 747B698E: tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM

View File

@ -1 +1 @@
["CC-2-of-4", [2, 4], [[1130956047, "tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n"], [3503269483, "tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9"], [2389277556, "tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc"], [3190206587, "tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa"]], {"ch": "XTN", "pp": "45'"}] ["CC-2-of-4", [2, 4], [[1130956047, "m/45'", "tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n"], [3503269483, "m/45'", "tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9"], [2389277556, "m/45'", "tpubD97nVL37v5tWyMf9ofh5rznwhh1593WMRg6FT4o6MRJkKWANtwAMHYLrcJFsFmPfYbY1TE1LLQ4KBb84LBPt1ubvFwoosvMkcWJtMwvXgSc"], [3190206587, "m/45'", "tpubD9ArfXowvGHnuECKdGXVKDMfZVGdephVWg8fWGWStH3VKHzT4ph3A4ZcgXWqFu1F5xGTfxncmrnf3sLC86dup2a8Kx7z3xQ3AgeNTQeFxPa"]], {"ch": "XTN"}]

View File

@ -1 +1 @@
["CC-2-of-4", [2, 4], [[1130956047, "tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP"], [3503269483, "tpubDFcrvj5n7gyaxWQkoX69k2Zij4vthiAwvN2uhYjDrE6wktKoQaE7gKVZRiTbYdrAYH1UFPGdzdtWJc6WfR2gFMq6XpxA12gCdQmoQNU9mgm"], [2389277556, "tpubDExj5FnaUnPAn7sHGUeBqD3buoNH5dqmjAT6884vbDpH1iDYWigb7kFo2cA97dc8EHb54u13TRcZxC4kgRS9gc3Ey2xc8c5urytEzTcp3ac"], [3190206587, "tpubDFiuHYSJhNbHcbLJoxWdbjtUcbKR6PvLq53qC1Xq6t93CrRx78W3wcng8vJyQnY3giMJZEgNCRVzTojLb8RqPFpW5Ms2dYpjcJYofN1joyu"]], {"pp": "48'/1'/0'/2'", "ch": "XTN", "ft": 14}] ["CC-2-of-4", [2, 4], [[1130956047, "m/48'/1'/0'/2'", "tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP"], [3503269483, "m/48'/1'/0'/2'", "tpubDFcrvj5n7gyaxWQkoX69k2Zij4vthiAwvN2uhYjDrE6wktKoQaE7gKVZRiTbYdrAYH1UFPGdzdtWJc6WfR2gFMq6XpxA12gCdQmoQNU9mgm"], [2389277556, "m/48'/1'/0'/2'", "tpubDExj5FnaUnPAn7sHGUeBqD3buoNH5dqmjAT6884vbDpH1iDYWigb7kFo2cA97dc8EHb54u13TRcZxC4kgRS9gc3Ey2xc8c5urytEzTcp3ac"], [3190206587, "m/48'/1'/0'/2'", "tpubDFiuHYSJhNbHcbLJoxWdbjtUcbKR6PvLq53qC1Xq6t93CrRx78W3wcng8vJyQnY3giMJZEgNCRVzTojLb8RqPFpW5Ms2dYpjcJYofN1joyu"]], {"ch": "XTN", "ft": 14}]

View File

@ -1 +1 @@
["CC-2-of-4", [2, 4], [[1130956047, "tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP"], [3503269483, "tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax"], [2389277556, "tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM"], [3190206587, "tpubDFiuHYSJhNbHaGtB5skiuDLg12tRboh2uVZ6KGXxr8WVr28pLcS7F3gv8SsHFa2tm1jtx3VAuw56YfgRkdo6DXyfp51oygTKY3nJFT5jBMt"]], {"pp": "48'/1'/0'/1'", "ch": "XTN", "ft": 26}] ["CC-2-of-4", [2, 4], [[1130956047, "m/48'/1'/0'/1'", "tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP"], [3503269483, "m/48'/1'/0'/1'", "tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax"], [2389277556, "m/48'/1'/0'/1'", "tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM"], [3190206587, "m/48'/1'/0'/1'", "tpubDFiuHYSJhNbHaGtB5skiuDLg12tRboh2uVZ6KGXxr8WVr28pLcS7F3gv8SsHFa2tm1jtx3VAuw56YfgRkdo6DXyfp51oygTKY3nJFT5jBMt"]], {"ch": "XTN", "ft": 26}]

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# abort menu system and return to top menu # abort menu system and return to top menu
from main import pa, numpad from main import pa, numpad

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. All rights reserved.
#
# Unit test for shared/backups.py # Unit test for shared/backups.py
# #
# this will run on the simulator only # this will run on the simulator only

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
from main import dis from main import dis
from ubinascii import hexlify as b2a_hex from ubinascii import hexlify as b2a_hex

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import sim_display import sim_display
RV.write('\n'.join(sim_display.read_menu())) RV.write('\n'.join(sim_display.read_menu()))

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import sim_display import sim_display
RV.write(sim_display.full_contents) RV.write(sim_display.full_contents)

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import sim_display import sim_display
if sim_display.story: if sim_display.story:

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# check we decoded the PSBT correctly, by looking under the covers. # check we decoded the PSBT correctly, by looking under the covers.
import main import main
expect = main.EXPECT expect = main.EXPECT

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# quickly main wipe seed; don't install anything new # quickly main wipe seed; don't install anything new
from main import pa, settings, numpad, dis from main import pa, settings, numpad, dis
from pincodes import AE_SECRET_LEN, PA_IS_BLANK from pincodes import AE_SECRET_LEN, PA_IS_BLANK

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import backups, stash import backups, stash
with stash.SensitiveValues() as sv: with stash.SensitiveValues() as sv:

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import backups, stash import backups, stash
for ln in backups.generate_public_contents(): for ln in backups.generate_public_contents():

View File

@ -1,2 +1,4 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import backups import backups
RV.write(backups.render_backup_contents()) RV.write(backups.render_backup_contents())

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import main import main
from main import settings from main import settings
from ujson import dumps from ujson import dumps

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
from main import settings from main import settings
from ujson import dumps from ujson import dumps
RV.write(dumps(settings.current)) RV.write(dumps(settings.current))

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
import seed import seed
RV.write(seed.pp_sofar) RV.write(seed.pp_sofar)

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# Unit test for shared/nvstore.py # Unit test for shared/nvstore.py
# #
# this will run on the simulator # this will run on the simulator

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# this will run on the simulator # this will run on the simulator
# run manually with: # run manually with:
# execfile('../../testing/devtest/segwit_addr.py') # execfile('../../testing/devtest/segwit_addr.py')

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# load up the simulator w/ indicated encoded secret. could be xprv/words/etc. # load up the simulator w/ indicated encoded secret. could be xprv/words/etc.
import tcc, main import tcc, main
from sim_settings import sim_defaults from sim_settings import sim_defaults

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# load up the simulator w/ indicated test master key # load up the simulator w/ indicated test master key
import tcc, main import tcc, main
from sim_settings import sim_defaults from sim_settings import sim_defaults

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# load up the simulator w/ indicated list of seed words # load up the simulator w/ indicated list of seed words
import tcc, main import tcc, main
from sim_settings import sim_defaults from sim_settings import sim_defaults

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# load up the simulator w/ indicated test master key # load up the simulator w/ indicated test master key
import tcc, main import tcc, main
from sim_settings import sim_defaults from sim_settings import sim_defaults

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# unit test for address decoding from various types of CTxOuts # unit test for address decoding from various types of CTxOuts
from h import a2b_hex, b2a_hex from h import a2b_hex, b2a_hex
from psbt import psbtObject from psbt import psbtObject

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# work thru the first example given in BIP-143 # work thru the first example given in BIP-143
from h import a2b_hex, b2a_hex from h import a2b_hex, b2a_hex
from psbt import psbtObject, psbtInputProxy, psbtOutputProxy from psbt import psbtObject, psbtInputProxy, psbtOutputProxy

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
from utils import HexStreamer, Base64Streamer from utils import HexStreamer, Base64Streamer
from ubinascii import unhexlify as a2b_hex from ubinascii import unhexlify as a2b_hex
from ubinascii import hexlify as b2a_hex from ubinascii import hexlify as b2a_hex

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# unit test for address decoding for multisig # unit test for address decoding for multisig
from h import a2b_hex, b2a_hex from h import a2b_hex, b2a_hex
from chains import BitcoinMain, BitcoinTestnet from chains import BitcoinMain, BitcoinTestnet

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# unit test for code in shared/psbt.py # unit test for code in shared/psbt.py
# #
# this will run on the simulator # this will run on the simulator

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# work thru examples given in SLIP-132 # work thru examples given in SLIP-132
# in simulator # in simulator
# #

View File

@ -1,3 +1,5 @@
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
#
# quickly clear all multisig wallets installed # quickly clear all multisig wallets installed
from main import settings from main import settings
from ux import restore_menu from ux import restore_menu

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# stuff I need sometimes # stuff I need sometimes
from io import BytesIO from io import BytesIO

View File

@ -1,5 +1,4 @@
# (c) Copyright 2020 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# objstruct.py # objstruct.py
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# psbt.py - yet another PSBT parser/serializer but used only for test cases. # psbt.py - yet another PSBT parser/serializer but used only for test cases.
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# test "show address" feature # test "show address" feature
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2019 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
import pytest, time, os import pytest, time, os
from ckcc_protocol.constants import * from ckcc_protocol.constants import *

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Tests that need a person there... mostly when not run on simulator # Tests that need a person there... mostly when not run on simulator
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# BIP39 seed word encryption # BIP39 seed word encryption
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# UX and interactions related to Settings > Pin Options # UX and interactions related to Settings > Pin Options
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# test drv_entro.py features # test drv_entro.py features
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Exporting of wallet files and similar things. # Exporting of wallet files and similar things.
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2020 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Test HSM and its policy file. # Test HSM and its policy file.
# #

View File

@ -1,5 +1,4 @@
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com> # (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
# and is covered by GPLv3 license found in COPYING.
# #
# Message signing. # Message signing.
# #

Some files were not shown because too many files have changed in this diff Show More