dev squashed
This commit is contained in:
parent
d88f14ea92
commit
b6b9191145
44
COPYING-CC
Normal file
44
COPYING-CC
Normal 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.
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# based on <http://click.pocoo.org/5/setuptools/#setuptools-integration>
|
||||
#
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Repackage and sign the firmware image.
|
||||
#
|
||||
|
||||
@ -63,6 +63,8 @@
|
||||
- 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
|
||||
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
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
|
||||
all: graphics.py
|
||||
|
||||
|
||||
@ -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
|
||||
[BIP-78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki).
|
||||
- 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
|
||||
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.
|
||||
- License changed from GPL to MIT+CC on files for which the GPL doesn't apply.
|
||||
|
||||
|
||||
## 3.1.9 - Aug 6, 2020
|
||||
|
||||
@ -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.
|
||||
|
||||
all:
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
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_enter_number
|
||||
from utils import imported, pretty_short_delay
|
||||
from utils import imported, pretty_short_delay, problem_file_line
|
||||
from main import settings
|
||||
from uasyncio import sleep_ms
|
||||
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',
|
||||
min_size=100, max_size=20*200, taster=possible)
|
||||
|
||||
if not fn: return
|
||||
|
||||
try:
|
||||
@ -1540,7 +1539,9 @@ async def import_multisig(*a):
|
||||
possible_name = (fn.split('/')[-1].split('.'))[0]
|
||||
maybe_enroll_xpub(config=data, name=possible_name)
|
||||
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):
|
||||
from hsm_ux import start_hsm_approval
|
||||
|
||||
@ -1043,17 +1043,14 @@ def start_show_p2sh_address(M, N, addr_format, xfp_paths, witdeem_script):
|
||||
raise AssertionError('Unknown/unsupported addr format')
|
||||
|
||||
# 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)
|
||||
assert idx >= 0, 'Multisig wallet with those fingerprints not found'
|
||||
|
||||
ms = MultisigWallet.get_by_idx(idx)
|
||||
assert ms
|
||||
ms = MultisigWallet.find_match(M, N, xs)
|
||||
assert ms, 'Multisig wallet with those fingerprints not found'
|
||||
assert ms.M == M
|
||||
assert ms.N == N
|
||||
|
||||
|
||||
UserAuthorizedAction.check_busy(ShowAddressBase)
|
||||
UserAuthorizedAction.active_request = ShowP2SHAddress(ms, addr_format, xfp_paths, witdeem_script)
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ import uasyncio.core as asyncio
|
||||
|
||||
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.
|
||||
from display import Display
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#
|
||||
import stash, chains, ustruct, ure, uio, sys
|
||||
#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 files import CardSlot, CardMissingError
|
||||
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_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):
|
||||
pass
|
||||
|
||||
@ -101,19 +105,22 @@ class MultisigWallet:
|
||||
(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.name = name
|
||||
assert len(m_of_n) == 2
|
||||
self.M, self.N = m_of_n
|
||||
self.chain_type = chain_type or 'BTC'
|
||||
self.xpubs = xpubs # list of (xfp(int), xpub(str))
|
||||
self.common_prefix = common_prefix # example: "45'" for BIP45 .. no m/ prefix
|
||||
assert len(xpubs[0]) == 3
|
||||
self.xpubs = xpubs # list of (xfp(int), deriv, xpub(str))
|
||||
self.addr_fmt = addr_fmt # not clear how useful that is.
|
||||
|
||||
# useful cache value
|
||||
self.xfps = sorted(k for k,v in self.xpubs)
|
||||
# calc useful cache value: numeric xfp+subpath, with lookup
|
||||
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
|
||||
def render_addr_fmt(cls, addr_fmt):
|
||||
@ -122,19 +129,6 @@ class MultisigWallet:
|
||||
return v.upper()
|
||||
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
|
||||
def chain(self):
|
||||
return chains.get_chain(self.chain_type)
|
||||
@ -150,64 +144,130 @@ class MultisigWallet:
|
||||
|
||||
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
|
||||
def deserialize(cls, vals, idx=-1):
|
||||
# take json object, make instance.
|
||||
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),
|
||||
common_prefix=opts.get('pp', None),
|
||||
chain_type=opts.get('ch', 'BTC'))
|
||||
rv.storage_idx = idx
|
||||
|
||||
return rv
|
||||
|
||||
@classmethod
|
||||
def find_match(cls, M, N, fingerprints):
|
||||
# Find index of matching wallet. Don't de-serialize everything.
|
||||
# - returns index, or -1 if not found
|
||||
# - fingerprints are iterable of uint32's: may not be unique!
|
||||
# - M, N must be known.
|
||||
def find_match(cls, M, N, xfp_paths):
|
||||
# Find index of matching wallet. Don't de-serialize more than needed.
|
||||
# - xfp_paths is list of lists: [xfp, *path] like in psbt files
|
||||
# - M and N must be known
|
||||
# - returns instance, or None if not found
|
||||
from main import settings
|
||||
lst = settings.get('multisig', [])
|
||||
|
||||
fingerprints = sorted(fingerprints)
|
||||
assert N == len(fingerprints)
|
||||
fingerprints = set(f[0] for f in xfp_paths)
|
||||
|
||||
for idx, rec in enumerate(lst):
|
||||
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
|
||||
def find_candidates(cls, fingerprints):
|
||||
# Find index of matching wallet and M value. Don't de-serialize everything.
|
||||
# - returns set of matches, each with M value
|
||||
# - fingerprints are iterable of uint32's
|
||||
def find_candidates(cls, xfp_paths, addr_fmt=None):
|
||||
# Return a list of matching wallets for various M values.
|
||||
# - xpfs_paths hsould already be sorted
|
||||
# - returns set of matches, of any M value
|
||||
from main import settings
|
||||
lst = settings.get('multisig', [])
|
||||
|
||||
fingerprints = sorted(fingerprints)
|
||||
N = len(fingerprints)
|
||||
rv = []
|
||||
# we know N, but not M at this point.
|
||||
N = len(xfp_paths)
|
||||
|
||||
rv = []
|
||||
for idx, rec in enumerate(lst):
|
||||
name, m_of_n, xpubs, opts = rec
|
||||
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
|
||||
|
||||
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
|
||||
# - xfp_paths must be sorted already
|
||||
assert (self.M, self.N) == (M, N), "M/N mismatch"
|
||||
assert len(fingerprints) == N, "XFP count"
|
||||
assert sorted(fingerprints) == self.xfps, "wrong XFPs"
|
||||
assert len(xfp_paths) == N, "XFP count"
|
||||
assert self.matching_subpaths(xfp_paths), "wrong XFP/derivs"
|
||||
|
||||
@classmethod
|
||||
def quick_check(cls, M, N, xfp_xor):
|
||||
@ -222,7 +282,7 @@ class MultisigWallet:
|
||||
if m_of_n[1] != N: continue
|
||||
|
||||
x = 0
|
||||
for xfp, _ in xpubs:
|
||||
for xfp, _, _ in xpubs:
|
||||
x ^= xfp
|
||||
if x != xfp_xor: continue
|
||||
|
||||
@ -291,30 +351,34 @@ class MultisigWallet:
|
||||
|
||||
raise MultisigOutOfSpace
|
||||
|
||||
|
||||
def has_dup(self):
|
||||
def has_similar(self):
|
||||
# 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)
|
||||
if idx == -1:
|
||||
similar = MultisigWallet.find_candidates(list(self.xfp_paths.values()))
|
||||
if not similar:
|
||||
# no matches
|
||||
return False, 0
|
||||
return None, []
|
||||
|
||||
# See if the xpubs are changing, which is risky... other differences like
|
||||
# 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.
|
||||
diffs = 0
|
||||
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
|
||||
if name_diff and len(diffs) == 1:
|
||||
return name_diff, []
|
||||
|
||||
return o, diffs
|
||||
return None, diffs
|
||||
|
||||
def delete(self):
|
||||
# remove saved entry
|
||||
@ -324,8 +388,9 @@ class MultisigWallet:
|
||||
assert self.storage_idx >= 0
|
||||
|
||||
# safety check
|
||||
expect_idx = self.find_match(self.M, self.N, self.xfps)
|
||||
assert expect_idx == self.storage_idx
|
||||
existing = self.find_match(self.M, self.N, list(self.xfp_paths.values()))
|
||||
assert existing
|
||||
assert existing.storage_idx == self.storage_idx
|
||||
|
||||
lst = settings.get('multisig', [])
|
||||
del lst[self.storage_idx]
|
||||
@ -336,7 +401,7 @@ class MultisigWallet:
|
||||
|
||||
def xpubs_with_xfp(self, 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)
|
||||
|
||||
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
|
||||
# subpaths: pubkey => (xfp, *path)
|
||||
# xfp_paths: (xfp, *path) in same order as pubkeys in redeem script
|
||||
from psbt import path_to_str
|
||||
|
||||
subpath_help = []
|
||||
used = set()
|
||||
@ -361,10 +425,11 @@ class MultisigWallet:
|
||||
if subpaths:
|
||||
# in PSBT, we are given a map from pubkey to xfp/path, use it
|
||||
# while remembering it's potentially one-2-many
|
||||
# TODO: this could be simpler now
|
||||
assert pubkey in subpaths, "unexpected 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 xp_idx in used: continue # only allow once
|
||||
check_these.append((xp_idx, path))
|
||||
@ -383,7 +448,7 @@ class MultisigWallet:
|
||||
too_shallow = False
|
||||
for xp_idx, path in check_these:
|
||||
# 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
|
||||
dp = node.depth()
|
||||
@ -405,7 +470,7 @@ class MultisigWallet:
|
||||
# part of the path from fingerprint to here.
|
||||
here = '(m=%s)\n' % xfp2str(xfp)
|
||||
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:
|
||||
# Not a match but not an error by itself, since might be
|
||||
@ -444,6 +509,8 @@ class MultisigWallet:
|
||||
# where label is:
|
||||
# name: nameforwallet
|
||||
# policy: M of N
|
||||
# format: p2sh (+etc)
|
||||
# derivation: m/45'/0 (common prefix)
|
||||
# (8digithex): xpub of cosigner
|
||||
#
|
||||
# quick checks:
|
||||
@ -454,11 +521,10 @@ class MultisigWallet:
|
||||
from main import settings
|
||||
|
||||
my_xfp = settings.get('xfp')
|
||||
common_prefix = None
|
||||
deriv = None
|
||||
xpubs = []
|
||||
path_tops = set()
|
||||
M, N = -1, -1
|
||||
has_mine = False
|
||||
has_mine = 0
|
||||
addr_fmt = AF_P2SH
|
||||
expect_chain = chains.current_chain().ctype
|
||||
|
||||
@ -479,7 +545,7 @@ class MultisigWallet:
|
||||
value = ln
|
||||
else:
|
||||
# complain?
|
||||
if ln: print("no colon: " + ln)
|
||||
#if ln: print("no colon: " + ln)
|
||||
continue
|
||||
else:
|
||||
label, value = ln.split(':')
|
||||
@ -501,11 +567,9 @@ class MultisigWallet:
|
||||
raise AssertionError('bad policy line')
|
||||
|
||||
elif label == 'derivation':
|
||||
# reveal the **common** path derivation for all keys
|
||||
# reveal the path derivation for following key(s)
|
||||
try:
|
||||
cp = 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:]
|
||||
deriv = cleanup_deriv_path(value)
|
||||
except BaseException as exc:
|
||||
raise AssertionError('bad derivation line: ' + str(exc))
|
||||
|
||||
@ -527,11 +591,9 @@ class MultisigWallet:
|
||||
continue
|
||||
|
||||
# deserialize, update list and lots of checks
|
||||
xfp = cls.check_xpub(xfp, value, expect_chain, xpubs, path_tops)
|
||||
|
||||
if xfp == my_xfp:
|
||||
# not conclusive, but enough for error catching.
|
||||
has_mine = True
|
||||
is_mine = cls.check_xpub(xfp, value, deriv, expect_chain, my_xfp, xpubs)
|
||||
if is_mine:
|
||||
has_mine += 1
|
||||
|
||||
assert len(xpubs), 'need xpubs'
|
||||
|
||||
@ -555,30 +617,27 @@ class MultisigWallet:
|
||||
|
||||
# check we're included... do not insert ourselves, even tho we
|
||||
# have enough info, simply because other signers need to know my xpubkey anyway
|
||||
assert has_mine, 'my key not included'
|
||||
|
||||
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()
|
||||
assert has_mine != 0, 'my key not included'
|
||||
assert has_mine == 1 # 'my key included more than once'
|
||||
|
||||
# done. have all the parts
|
||||
return cls(name, (M, N), xpubs, addr_fmt=addr_fmt,
|
||||
chain_type=expect_chain, common_prefix=common_prefix)
|
||||
return cls(name, (M, N), xpubs, addr_fmt=addr_fmt, chain_type=expect_chain)
|
||||
|
||||
@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
|
||||
# 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:
|
||||
# Note: addr fmt detected here via SLIP-132 isn't useful
|
||||
node, chain, _ = import_xpub(xpub)
|
||||
except:
|
||||
print(xpub)
|
||||
raise AssertionError('unable to parse xpub')
|
||||
|
||||
assert node.private_key() == None, 'no privkeys plz'
|
||||
assert chain.ctype == expect_chain, 'wrong chain'
|
||||
assert node.private_key() == None # 'no privkeys plz'
|
||||
assert chain.ctype == expect_chain # 'wrong chain'
|
||||
|
||||
# NOTE: could enforce all same depth, and/or all depth >= 1, but
|
||||
# seems like more restrictive than needed.
|
||||
@ -590,23 +649,36 @@ class MultisigWallet:
|
||||
# generally cannot check fingerprint values, but if we can, do.
|
||||
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
|
||||
path_top = None
|
||||
# In most cases, we cannot verify the derivation path because it's hardened
|
||||
# and we know none of the private keys involved.
|
||||
if node.depth() == 1:
|
||||
# but derivation is implied at depth==1
|
||||
cn = node.child_num()
|
||||
path_top = str(cn & 0x7fffffff)
|
||||
guess = 'm/%d' % (cn & 0x7fffffff)
|
||||
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.
|
||||
# - 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'):
|
||||
rv = '%s-%s.%s' % (prefix, self.name, suffix)
|
||||
@ -623,7 +695,7 @@ class MultisigWallet:
|
||||
ch = self.chain
|
||||
|
||||
# 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:
|
||||
# CHALLENGE: we must do slip-132 format [yz]pubs here when not p2sh mode.
|
||||
@ -632,11 +704,13 @@ class MultisigWallet:
|
||||
else:
|
||||
xp = xpub
|
||||
|
||||
assert deriv != UNSURE_DERIV # also checked above
|
||||
|
||||
rv['x%d/' % (idx+1)] = dict(
|
||||
hw_type='coldcard', type='hardware',
|
||||
ckcc_xfp=xfp,
|
||||
label='Coldcard %s' % xfp2str(xfp),
|
||||
derivation='m/'+self.common_prefix, xpub=xp)
|
||||
derivation=deriv, xpub=xp)
|
||||
|
||||
return rv
|
||||
|
||||
@ -675,15 +749,15 @@ class MultisigWallet:
|
||||
def render_export(self, 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:
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
@ -702,6 +776,7 @@ class MultisigWallet:
|
||||
raise FatalPSBTIssue("XPUBs in PSBT do not match any existing wallet")
|
||||
|
||||
# build up an in-memory version of the wallet.
|
||||
# TODO: capture address format from an input?
|
||||
|
||||
assert N == len(xpubs_list)
|
||||
assert 1 <= M <= N <= MAX_SIGNERS, 'M/N range'
|
||||
@ -709,28 +784,37 @@ class MultisigWallet:
|
||||
|
||||
expect_chain = chains.current_chain().ctype
|
||||
xpubs = []
|
||||
has_mine = False
|
||||
path_tops = set()
|
||||
has_mine = 0
|
||||
|
||||
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)
|
||||
xfp = cls.check_xpub(xfp, xpub, expect_chain, xpubs, path_tops)
|
||||
if xfp == my_xfp:
|
||||
has_mine = True
|
||||
is_mine = cls.check_xpub(xfp, xpub, keypath_to_str(path, skip=0),
|
||||
expect_chain, my_xfp, xpubs)
|
||||
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)
|
||||
|
||||
prefix = path_tops.pop() if len(path_tops) == 1 else None
|
||||
|
||||
ms = cls(name, (M, N), xpubs, chain_type=expect_chain, common_prefix=prefix)
|
||||
ms = cls(name, (M, N), xpubs, chain_type=expect_chain)
|
||||
|
||||
# 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
|
||||
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):
|
||||
# prompt them about a new wallet, let them see details and then commit change.
|
||||
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)
|
||||
|
||||
# Look for duplicate case.
|
||||
is_dup, diff_count = self.has_dup()
|
||||
name_change, diff_items = self.has_similar()
|
||||
|
||||
if not is_dup:
|
||||
story = 'Create new multisig wallet?'
|
||||
elif diff_count:
|
||||
if name_change:
|
||||
story = 'Update NAME only of existing multisig wallet?'
|
||||
elif diff_items:
|
||||
# Concern here is overwrite when similar, but we don't overwrite anymore, so
|
||||
# more of a warning about funny business.
|
||||
story = '''\
|
||||
CAUTION: This updated wallet has %d different XPUB values, but matching fingerprints \
|
||||
and same M of N. Perhaps the derivation path has changed legitimately, otherwise, much \
|
||||
DANGER!''' % diff_count
|
||||
WARNING: This new wallet is very similar to an existing wallet, but will NOT replace it. Consider deleting previous wallet first. Differences: \
|
||||
''' + ', '.join(diff_items)
|
||||
else:
|
||||
story = 'Update existing multisig wallet?'
|
||||
story = 'Create new multisig wallet?'
|
||||
|
||||
_, ds = self.format_deriv_paths()
|
||||
|
||||
story += '''\n
|
||||
Wallet Name:
|
||||
{name}
|
||||
@ -764,43 +852,63 @@ Policy: {M} of {N}
|
||||
|
||||
{exp}
|
||||
|
||||
Addresses:
|
||||
{at}
|
||||
|
||||
Derivation:
|
||||
m/{deriv}
|
||||
{deriv}
|
||||
|
||||
Press (1) to see extended public keys, \
|
||||
OK to approve, X to cancel.'''.format(M=M, N=N, name=self.name, exp=exp,
|
||||
deriv=self.common_prefix or 'unknown')
|
||||
OK to approve, X to cancel.'''.format(M=M, N=N, name=self.name, exp=exp, deriv=ds,
|
||||
at=self.render_addr_fmt(self.addr_fmt))
|
||||
|
||||
ux_clear_keys(True)
|
||||
while 1:
|
||||
ch = await ux_show_story(story, escape='1')
|
||||
|
||||
if ch == '1':
|
||||
# Show the xpubs; might be 2k or more rendered.
|
||||
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))
|
||||
|
||||
await self.show_detail(verbose=False)
|
||||
continue
|
||||
|
||||
if ch == 'y':
|
||||
# save to nvram, may raise MultisigOutOfSpace
|
||||
if is_dup:
|
||||
is_dup.delete()
|
||||
if name_change:
|
||||
name_change.delete()
|
||||
self.commit()
|
||||
await ux_dramatic_pause("Saved.", 2)
|
||||
break
|
||||
|
||||
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):
|
||||
# action for 'no wallets yet' menu item
|
||||
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
|
||||
from actions import electrum_export_story
|
||||
|
||||
prefix = ms.common_prefix
|
||||
if not prefix :
|
||||
return await ux_show_story("We don't know the common derivation path for "
|
||||
unsure, derivs = ms.format_deriv_paths()
|
||||
if unsure:
|
||||
return await ux_show_story("We don't know all the derivation paths for "
|
||||
"these keys, so cannot create Electrum wallet.")
|
||||
|
||||
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':
|
||||
return
|
||||
@ -955,35 +1063,11 @@ async def ms_wallet_electrum_export(menu, label, item):
|
||||
|
||||
|
||||
async def ms_wallet_detail(menu, label, item):
|
||||
# show details of single multisig wallet, offer to delete
|
||||
import chains
|
||||
# show details of single multisig wallet
|
||||
|
||||
ms = item.arg
|
||||
msg = uio.StringIO()
|
||||
|
||||
msg.write('''
|
||||
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)
|
||||
return await ms.show_detail()
|
||||
|
||||
|
||||
async def export_multisig_xpubs(*a):
|
||||
@ -1095,7 +1179,7 @@ async def ondevice_multisig_create(mode='p2wsh', addr_fmt=AF_P2WSH):
|
||||
|
||||
xpubs = []
|
||||
files = []
|
||||
has_mine = False
|
||||
has_mine = 0
|
||||
deriv = None
|
||||
try:
|
||||
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
|
||||
xfp = str2xfp(vals['xfp'])
|
||||
if not deriv:
|
||||
deriv = vals[mode+'_deriv']
|
||||
deriv = cleanup_deriv_path(vals[mode+'_deriv'])
|
||||
else:
|
||||
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)
|
||||
|
||||
except CardMissingError:
|
||||
@ -1171,7 +1254,9 @@ async def ondevice_multisig_create(mode='p2wsh', addr_fmt=AF_P2WSH):
|
||||
if not has_mine:
|
||||
with stash.SensitiveValues() as sv:
|
||||
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)
|
||||
|
||||
@ -1213,8 +1298,7 @@ Coldcard multisig setup file and an Electrum wallet file will be created automat
|
||||
assert 1 <= M <= N <= MAX_SIGNERS
|
||||
|
||||
name = 'CC-%d-of-%d' % (M, N)
|
||||
ms = MultisigWallet(name, (M, N), xpubs, chain_type=chain.ctype,
|
||||
common_prefix=deriv[2:], addr_fmt=addr_fmt)
|
||||
ms = MultisigWallet(name, (M, N), xpubs, chain_type=chain.ctype, addr_fmt=addr_fmt)
|
||||
|
||||
from auth import NewEnrollRequest, UserAuthorizedAction
|
||||
|
||||
|
||||
128
shared/psbt.py
128
shared/psbt.py
@ -3,19 +3,19 @@
|
||||
#
|
||||
# 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 ubinascii import hexlify as b2a_hex
|
||||
from utils import xfp2str, B2A
|
||||
import tcc, stash, gc, history
|
||||
from utils import xfp2str, B2A, keypath_to_str, problem_file_line
|
||||
import tcc, stash, gc, history, sys
|
||||
from uio import BytesIO
|
||||
from sffile import SizerFile
|
||||
from sram2 import psbt_tmp256
|
||||
from multisig import MultisigWallet, MAX_SIGNERS, disassemble_multisig, disassemble_multisig_mn
|
||||
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 (
|
||||
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 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):
|
||||
# take a set or list of numbers and show a tidy list in order.
|
||||
return ', '.join(str(i) for i in sorted(seq))
|
||||
@ -169,7 +165,7 @@ class psbtProxy:
|
||||
def __getattr__(self, nm):
|
||||
if nm in self.blank_flds:
|
||||
return None
|
||||
raise AttributeError
|
||||
raise AttributeError(nm)
|
||||
|
||||
def parse(self, fd):
|
||||
self.fd = fd
|
||||
@ -260,8 +256,7 @@ class psbtProxy:
|
||||
|
||||
# promote to a list of ints
|
||||
v = self.get(self.subpaths[pk])
|
||||
here = list(unpack_from('<I', v, off)[0] for off in range(0, vl, 4))
|
||||
assert len(here) == vl // 4
|
||||
here = list(unpack_from('<%dI' % (vl//4), v))
|
||||
|
||||
# update in place
|
||||
self.subpaths[pk] = here
|
||||
@ -269,8 +264,8 @@ class psbtProxy:
|
||||
if here[0] == my_xfp:
|
||||
num_ours += 1
|
||||
else:
|
||||
# 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
|
||||
# Address that isn't based on my seed; might be another leg in a p2sh,
|
||||
# or an input we're not supposed to be able to sign... and that's okay.
|
||||
pass
|
||||
|
||||
self.num_our_keys = num_ours
|
||||
@ -283,7 +278,7 @@ class psbtProxy:
|
||||
class psbtOutputProxy(psbtProxy):
|
||||
no_keys = { PSBT_OUT_REDEEM_SCRIPT, PSBT_OUT_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):
|
||||
super().__init__()
|
||||
@ -296,9 +291,6 @@ class psbtOutputProxy(psbtProxy):
|
||||
# this flag is set when we are assuming output will be change (same wallet)
|
||||
#self.is_change = False
|
||||
|
||||
# output is change into same multisig wallet
|
||||
#self.is_p2sh_change = False
|
||||
|
||||
self.parse(fd)
|
||||
|
||||
|
||||
@ -406,8 +398,8 @@ class psbtOutputProxy(psbtProxy):
|
||||
|
||||
# it cannot be change if it doesn't precisely match our multisig setup
|
||||
if not active_multisig:
|
||||
# might be a p2sh output for another wallet that isn't us
|
||||
# not fraud, just an output with more details than we need.
|
||||
# - might be a p2sh output for another wallet that isn't us
|
||||
# - not fraud, just an output with more details than we need.
|
||||
self.is_change = False
|
||||
return
|
||||
|
||||
@ -423,9 +415,6 @@ class psbtOutputProxy(psbtProxy):
|
||||
raise FraudulentChangeOutput(out_idx,
|
||||
"P2WSH or P2SH change output script: %s" % exc)
|
||||
|
||||
# mark pubkeys as already checked.
|
||||
self.is_p2sh_change = True
|
||||
|
||||
if is_segwit:
|
||||
# p2wsh case
|
||||
# - 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))
|
||||
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:
|
||||
# search for multisig wallet
|
||||
wal = MultisigWallet.find_match(M, N, xfps)
|
||||
if wal < 0:
|
||||
wal = MultisigWallet.find_match(M, N, xfp_paths)
|
||||
if not wal:
|
||||
raise FatalPSBTIssue('Unknown multisig wallet')
|
||||
|
||||
psbt.active_multisig = MultisigWallet.get_by_idx(wal)
|
||||
psbt.active_multisig = wal
|
||||
else:
|
||||
# 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
|
||||
try:
|
||||
psbt.active_multisig.validate_script(redeem_script, subpaths=self.subpaths)
|
||||
except BaseException as exc:
|
||||
sys.print_exception(exc)
|
||||
raise FatalPSBTIssue('Input #%d: %s' % (my_idx, exc))
|
||||
|
||||
if not which_key and DEBUG:
|
||||
@ -1019,18 +1010,27 @@ class psbtObject(psbtProxy):
|
||||
async def handle_xpubs(self):
|
||||
# Lookup correct wallet based on xpubs in globals
|
||||
# - only happens if they volunteered this 'extra' data
|
||||
# - do not assume multisig
|
||||
assert not self.active_multisig
|
||||
|
||||
xfps = [unpack_from('<I', k)[0] for k,_ in self.xpubs]
|
||||
if self.my_xfp not in xfps:
|
||||
xfp_paths = []
|
||||
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')
|
||||
|
||||
candidates = MultisigWallet.find_candidates(xfps)
|
||||
candidates = MultisigWallet.find_candidates(xfp_paths)
|
||||
|
||||
match_idx = -1
|
||||
if len(candidates) == 1:
|
||||
# exact match (by xfp set) .. normal case
|
||||
self.active_multisig = MultisigWallet.get_by_idx(candidates[0])
|
||||
# exact match (by xfp+deriv set) .. normal case
|
||||
self.active_multisig = candidates[0]
|
||||
else:
|
||||
# don't want to guess M if not needed, but we need it
|
||||
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
|
||||
return
|
||||
|
||||
assert N == len(xfps)
|
||||
assert N == len(xfp_paths)
|
||||
|
||||
if candidates:
|
||||
# maybe narrowed down to single match now
|
||||
match_idx = MultisigWallet.find_match(M, N, xfps)
|
||||
if match_idx != -1:
|
||||
self.active_multisig = MultisigWallet.get_by_idx(match_idx)
|
||||
for c in candidates:
|
||||
if c.M == M:
|
||||
assert c.N == N
|
||||
self.active_multisig = c
|
||||
break
|
||||
|
||||
del candidates
|
||||
|
||||
if not self.active_multisig:
|
||||
# Maybe create wallet, for today, forever, or fail, etc.
|
||||
@ -1063,6 +1065,16 @@ class psbtObject(psbtProxy):
|
||||
raise FatalPSBTIssue("Refused to import new wallet")
|
||||
|
||||
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:
|
||||
# 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)
|
||||
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):
|
||||
# Do a first pass over the txn. Raise assertions, be terse tho because
|
||||
@ -1194,8 +1203,8 @@ class psbtObject(psbtProxy):
|
||||
continue
|
||||
|
||||
probs.append("Output#%d: %s: %s not %s/{0~1}%s/{0~%d}%s expected"
|
||||
% (nout, iss, path_to_str(path, skip=0),
|
||||
path_to_str(path_prefix, skip=0),
|
||||
% (nout, iss, keypath_to_str(path, skip=0),
|
||||
keypath_to_str(path_prefix, skip=0),
|
||||
"'" if hard_pattern[-2] 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
|
||||
# it detects bad actors, not bugs or mistakes.
|
||||
# - equivilent check already done for p2sh outputs when we re-built the redeem script
|
||||
change_outs = [n for n,o in enumerate(self.outputs)
|
||||
if o.is_change and not o.is_p2sh_change]
|
||||
change_outs = [n for n,o in enumerate(self.outputs) if o.is_change]
|
||||
if change_outs:
|
||||
dis.fullscreen('Change Check...')
|
||||
|
||||
@ -1379,14 +1387,24 @@ class psbtObject(psbtProxy):
|
||||
# only expecting single case, but be general
|
||||
dis.progress_bar_show(count / len(change_outs))
|
||||
|
||||
for pubkey, subpath in self.outputs[out_idx].subpaths.items():
|
||||
# derive it
|
||||
skp = path_to_str(subpath)
|
||||
oup = self.outputs[out_idx]
|
||||
|
||||
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)
|
||||
|
||||
# check the pubkey of this BIP32 node
|
||||
pu = node.public_key()
|
||||
if pu != pubkey:
|
||||
if pubkey == node.public_key():
|
||||
good += 1
|
||||
|
||||
if not good:
|
||||
raise FraudulentChangeOutput(out_idx,
|
||||
"Deception regarding change output. "
|
||||
"BIP32 path doesn't match actual address.")
|
||||
@ -1430,7 +1448,7 @@ class psbtObject(psbtProxy):
|
||||
# need to consider a set of possible keys, since xfp may not be unique
|
||||
for which_key in inp.required_key:
|
||||
# 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)
|
||||
|
||||
# expensive test, but works... and important
|
||||
@ -1453,7 +1471,7 @@ class psbtObject(psbtProxy):
|
||||
continue
|
||||
|
||||
# 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)
|
||||
|
||||
# expensive test, but works... and important
|
||||
|
||||
@ -247,6 +247,30 @@ def cleanup_deriv_path(bin_path, allow_star=False):
|
||||
|
||||
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):
|
||||
# check for exact string match, or wildcard match (star in last position)
|
||||
# - both args must be cleaned by cleanup_deriv_path() already
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Build micropython for stm32 (an ARM processor). Also handles signing of resulting firmware images.
|
||||
#
|
||||
@ -29,7 +28,7 @@ BOOTLOADER_BASE = 0x08000000
|
||||
FILESYSTEM_BASE = 0x080e0000
|
||||
|
||||
# Our version for this release.
|
||||
VERSION_STRING = 3.1.10
|
||||
VERSION_STRING = 3.2.1
|
||||
|
||||
#
|
||||
# Sign and merge various parts
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# "Bootloader" Makefile
|
||||
#
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#include "basics.h"
|
||||
#include "ae.h"
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
//
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Plan:
|
||||
# - take a fixed background image and compose a number of info screens ontop
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#include "constant_time.h"
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* delay.c -- Software delay loops (we have no interrupts)
|
||||
*
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* dispatch.c
|
||||
*
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
//
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
// and is covered by GPLv3 license found in COPYING.
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
//
|
||||
//
|
||||
// enable.c
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
|
||||
#define NUM_KNOWN_PUBKEYS 6
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* gpio.c -- setup and control GPIO pins (and one button)
|
||||
*
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#include "oled.h"
|
||||
#include "delay.h"
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#include "basics.h"
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* pins.c -- PIN codes and security issues
|
||||
*
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* pins.h -- everything to do with PIN's and their policies
|
||||
*
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "rng.h"
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
#include "basics.h"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Secure Element Config Area.
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Secure Element Config debugging tools.
|
||||
#
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* sflash.c -- talk to the serial flash
|
||||
*
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#include "basics.h"
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
// and is covered by GPLv3 license found in COPYING.
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
//
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
# 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>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
|
||||
# Our simple firmware header.
|
||||
# Although called a header, this data is placed into the middle of the binary.
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* NOTE: heavily trimmed!
|
||||
*
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
// and is covered by GPLv3 license found in COPYING.
|
||||
//
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
//
|
||||
// storage.c -- manage flash and its sensitive contents.
|
||||
//
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*
|
||||
* verify.c -- Check signatures on firmware images in flash.
|
||||
*
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
#include "basics.h"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
// and is covered by GPLv3 license found in COPYING.
|
||||
// (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
//
|
||||
// Version string. Careful with changes because parsed by python code and probably others.
|
||||
//
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
* and is covered by GPLv3 license found in COPYING.
|
||||
* (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
|
||||
all:
|
||||
pytest .
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Access a local bitcoin-Qt/bitcoind on testnet
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import pytest, glob, time, sys, random, re
|
||||
from pprint import pprint
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
|
||||
SIM_PATH = '/tmp/ckcc-simulator.sock'
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#
|
||||
Name: CC-2-of-4
|
||||
Policy: 2 of 4
|
||||
|
||||
Derivation: m/45'
|
||||
|
||||
0F056943: tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
#
|
||||
Name: CC-2-of-4
|
||||
Policy: 2 of 4
|
||||
Derivation: m/48'/1'/0'/2'
|
||||
Format: P2WSH
|
||||
|
||||
Derivation: m/48'/1'/0'/2'
|
||||
|
||||
0F056943: tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP
|
||||
6BA6CFD0: tpubDFcrvj5n7gyaxWQkoX69k2Zij4vthiAwvN2uhYjDrE6wktKoQaE7gKVZRiTbYdrAYH1UFPGdzdtWJc6WfR2gFMq6XpxA12gCdQmoQNU9mgm
|
||||
747B698E: tpubDExj5FnaUnPAn7sHGUeBqD3buoNH5dqmjAT6884vbDpH1iDYWigb7kFo2cA97dc8EHb54u13TRcZxC4kgRS9gc3Ey2xc8c5urytEzTcp3ac
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
#
|
||||
Name: CC-2-of-4
|
||||
Policy: 2 of 4
|
||||
Derivation: m/48'/1'/0'/1'
|
||||
Format: P2WSH-P2SH
|
||||
|
||||
Derivation: m/48'/1'/0'/1'
|
||||
|
||||
0F056943: tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP
|
||||
6BA6CFD0: tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax
|
||||
747B698E: tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM
|
||||
|
||||
@ -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"}]
|
||||
@ -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}]
|
||||
@ -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}]
|
||||
@ -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
|
||||
from main import pa, numpad
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. All rights reserved.
|
||||
#
|
||||
# Unit test for shared/backups.py
|
||||
#
|
||||
# this will run on the simulator only
|
||||
|
||||
@ -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 ubinascii import hexlify as b2a_hex
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import sim_display
|
||||
|
||||
RV.write('\n'.join(sim_display.read_menu()))
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import sim_display
|
||||
|
||||
RV.write(sim_display.full_contents)
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import sim_display
|
||||
|
||||
if sim_display.story:
|
||||
|
||||
@ -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.
|
||||
import main
|
||||
expect = main.EXPECT
|
||||
|
||||
@ -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
|
||||
from main import pa, settings, numpad, dis
|
||||
from pincodes import AE_SECRET_LEN, PA_IS_BLANK
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import backups, stash
|
||||
|
||||
with stash.SensitiveValues() as sv:
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import backups, stash
|
||||
|
||||
for ln in backups.generate_public_contents():
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import backups
|
||||
RV.write(backups.render_backup_contents())
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import main
|
||||
from main import settings
|
||||
from ujson import dumps
|
||||
|
||||
@ -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 ujson import dumps
|
||||
RV.write(dumps(settings.current))
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import seed
|
||||
RV.write(seed.pp_sofar)
|
||||
|
||||
|
||||
@ -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
|
||||
#
|
||||
# this will run on the simulator
|
||||
|
||||
@ -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
|
||||
# run manually with:
|
||||
# execfile('../../testing/devtest/segwit_addr.py')
|
||||
|
||||
@ -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.
|
||||
import tcc, main
|
||||
from sim_settings import sim_defaults
|
||||
|
||||
@ -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
|
||||
import tcc, main
|
||||
from sim_settings import sim_defaults
|
||||
|
||||
@ -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
|
||||
import tcc, main
|
||||
from sim_settings import sim_defaults
|
||||
|
||||
@ -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
|
||||
import tcc, main
|
||||
from sim_settings import sim_defaults
|
||||
|
||||
@ -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
|
||||
from h import a2b_hex, b2a_hex
|
||||
from psbt import psbtObject
|
||||
|
||||
@ -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
|
||||
from h import a2b_hex, b2a_hex
|
||||
from psbt import psbtObject, psbtInputProxy, psbtOutputProxy
|
||||
|
||||
@ -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 ubinascii import unhexlify as a2b_hex
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
|
||||
@ -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
|
||||
from h import a2b_hex, b2a_hex
|
||||
from chains import BitcoinMain, BitcoinTestnet
|
||||
|
||||
@ -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
|
||||
#
|
||||
# this will run on the simulator
|
||||
|
||||
@ -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
|
||||
# in simulator
|
||||
#
|
||||
|
||||
@ -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
|
||||
from main import settings
|
||||
from ux import restore_menu
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# stuff I need sometimes
|
||||
from io import BytesIO
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# objstruct.py
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# psbt.py - yet another PSBT parser/serializer but used only for test cases.
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# test "show address" feature
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2019 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
import pytest, time, os
|
||||
from ckcc_protocol.constants import *
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Tests that need a person there... mostly when not run on simulator
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# BIP39 seed word encryption
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# UX and interactions related to Settings > Pin Options
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# test drv_entro.py features
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Exporting of wallet files and similar things.
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Test HSM and its policy file.
|
||||
#
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Message signing.
|
||||
#
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user