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>
|
# (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>
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
134
shared/psbt.py
134
shared/psbt.py
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||||
#
|
#
|
||||||
# "Bootloader" Makefile
|
# "Bootloader" Makefile
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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/
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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)
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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!
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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.
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
|
# (c) Copyright 2020 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||||
|
#
|
||||||
|
|
||||||
all:
|
all:
|
||||||
pytest .
|
pytest .
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
# abort menu system and return to top menu
|
||||||
from main import pa, numpad
|
from main import pa, numpad
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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()))
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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():
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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')
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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 *
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
Loading…
Reference in New Issue
Block a user