Compare commits
No commits in common. "master" and "better-ax" have entirely different histories.
2
.gitignore
vendored
@ -24,5 +24,3 @@ __pycache__/
|
||||
|
||||
.tags
|
||||
pp
|
||||
|
||||
.idea/
|
||||
|
||||
20
.gitmodules
vendored
@ -1,22 +1,16 @@
|
||||
[submodule "external/micropython"]
|
||||
path = external/micropython
|
||||
url = https://github.com/Coldcard/micropython.git
|
||||
branch = mk4-base
|
||||
branch = master
|
||||
[submodule "external/modcryptocurrency"]
|
||||
path = external/modcryptocurrency
|
||||
url = https://github.com/Coldcard/modcryptocurrency.git
|
||||
[submodule "external/crypto"]
|
||||
path = external/crypto
|
||||
url = https://github.com/peter-conalgo/trezor-crypto.git
|
||||
[submodule "external/ckcc-protocol"]
|
||||
path = external/ckcc-protocol
|
||||
url = https://github.com/Coldcard/ckcc-protocol.git
|
||||
[submodule "external/mpy-qr"]
|
||||
path = external/mpy-qr
|
||||
url = https://github.com/coinkite/mpy-qr.git
|
||||
[submodule "external/libngu"]
|
||||
path = external/libngu
|
||||
url = https://github.com/switck/libngu.git
|
||||
[submodule "stm32/mk4-bootloader/hal"]
|
||||
path = stm32/mk4-bootloader/hal
|
||||
url = https://github.com/STMicroelectronics/STM32CubeL4.git
|
||||
[submodule "misc/gpu/external/stm32c0xx_hal_driver"]
|
||||
path = misc/gpu/external/stm32c0xx_hal_driver
|
||||
url = https://github.com/STMicroelectronics/stm32c0xx_hal_driver.git
|
||||
[submodule "misc/gpu/external/cmsis_device_c0"]
|
||||
path = misc/gpu/external/cmsis_device_c0
|
||||
url = https://github.com/STMicroelectronics/cmsis_device_c0.git
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
## Individual Contributor License Agreement (CLA)
|
||||
|
||||
**Thank you for submitting your contributions to this project.**
|
||||
|
||||
By signing this CLA, you agree that the following terms apply to all of your past, present and future contributions
|
||||
to the project.
|
||||
|
||||
### License.
|
||||
|
||||
You hereby represent that all present, past and future contributions are governed by the
|
||||
[COPYING-CC](https://raw.githubusercontent.com/Coldcard/firmware/master/COPYING-CC)
|
||||
copyright statement.
|
||||
|
||||
This entails that to the extent possible under law, you transfer all copyright and related or neighboring rights
|
||||
of the code or documents you contribute to the project itself or its maintainers.
|
||||
Furthermore you also represent that you have the authority to perform the above waiver
|
||||
with respect to the entirety of you contributions.
|
||||
|
||||
### Moral Rights.
|
||||
|
||||
To the fullest extent permitted under applicable law, you hereby waive, and agree not to
|
||||
assert, all of your “moral rights” in or relating to your contributions for the benefit of the project.
|
||||
|
||||
### Third Party Content.
|
||||
|
||||
If your Contribution includes or is based on any source code, object code, bug fixes, configuration changes, tools,
|
||||
specifications, documentation, data, materials, feedback, information or other works of authorship that were not
|
||||
authored by you (“Third Party Content”) or if you are aware of any third party intellectual property or proprietary
|
||||
rights associated with your Contribution (“Third Party Rights”),
|
||||
then you agree to include with the submission of your Contribution full details respecting such Third Party
|
||||
Content and Third Party Rights, including, without limitation, identification of which aspects of your
|
||||
Contribution contain Third Party Content or are associated with Third Party Rights, the owner/author of the
|
||||
Third Party Content and Third Party Rights, where you obtained the Third Party Content, and any applicable
|
||||
third party license terms or restrictions respecting the Third Party Content and Third Party Rights. For greater
|
||||
certainty, the foregoing obligations respecting the identification of Third Party Content and Third Party Rights
|
||||
do not apply to any portion of a Project that is incorporated into your Contribution to that same Project.
|
||||
|
||||
### Representations.
|
||||
|
||||
You represent that, other than the Third Party Content and Third Party Rights identified by
|
||||
you in accordance with this Agreement, you are the sole author of your Contributions and are legally entitled
|
||||
to grant the foregoing licenses and waivers in respect of your Contributions. If your Contributions were
|
||||
created in the course of your employment with your past or present employer(s), you represent that such
|
||||
employer(s) has authorized you to make your Contributions on behalf of such employer(s) or such employer
|
||||
(s) has waived all of their right, title or interest in or to your Contributions.
|
||||
|
||||
### Disclaimer.
|
||||
|
||||
To the fullest extent permitted under applicable law, your Contributions are provided on an "as is"
|
||||
basis, without any warranties or conditions, express or implied, including, without limitation, any implied
|
||||
warranties or conditions of non-infringement, merchantability or fitness for a particular purpose. You are not
|
||||
required to provide support for your Contributions, except to the extent you desire to provide support.
|
||||
|
||||
### No Obligation.
|
||||
|
||||
You acknowledge that the maintainers of this project are under no obligation to use or incorporate your contributions
|
||||
into the project. The decision to use or incorporate your contributions into the project will be made at the
|
||||
sole discretion of the maintainers or their authorized delegates.
|
||||
674
COPYING
Normal file
@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
16
LICENSE
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
(c) Copyright 2017-2020 by Coinkite Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
in the file COPYING. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
270
README.md
@ -1,156 +1,37 @@
|
||||
# COLDCARD Hardware Wallet
|
||||
# Coldcard Wallet
|
||||
|
||||
Coldcard is an Affordable, Ultra-secure & Verifiable Hardware Wallet for Bitcoin.
|
||||
Get yours at [Coldcard.com](http://coldcard.com)
|
||||
|
||||
Coldcard is a Cheap, Ultra-secure & Opensource Hardware Wallet for Bitcoin.
|
||||
Get yours at [ColdcardWallet.com](http://coldcardwallet.com)
|
||||
|
||||
[Follow @COLDCARDwallet on Twitter](https://twitter.com/coldcardwallet) to keep up
|
||||
with the latest updates and security alerts.
|
||||
with the latest updates and security alerts.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Latest firmware changes and updates](releases/ChangeLog.md)
|
||||
- [PGP signature file](releases/signatures.txt)
|
||||
- [Firmware binaries](https://coldcard.com/downloads)
|
||||
|
||||
## Reproducible Builds
|
||||
|
||||
To have confidence this source code tree is the same as the binary on your device,
|
||||
you can rebuild it from source and get **exactly the same bytes**. This process
|
||||
has been automated using Docker. Steps are as follows:
|
||||
|
||||
1. Install [Docker](https://www.docker.com) and start it.
|
||||
2. Install [make (GNUMake)](https://www.gnu.org/software/make/) if you don't already have it.
|
||||
3. Checkout a specific version of the code, and start the process.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/Coldcard/firmware.git
|
||||
cd firmware
|
||||
# DOWNLOAD https://coldcard.com/downloads
|
||||
# get a copy of binary into ./releases/2026-03-05T2052-v5.5.0-mk-coldcard.dfu
|
||||
git checkout 2026-03-05T2052-v5.5.0
|
||||
cd stm32
|
||||
make -f MK4-Makefile repro
|
||||
```
|
||||
|
||||
4. At the end of the process a clear confirmation message is shown, or the differences.
|
||||
5. Build products can be found `firmware/stm32/built`.
|
||||
6. If you do not trust the results of `make repro` refer to `docs/notes-on-repro.md`
|
||||
which breaks down the process.
|
||||
7. Process for Q firmware is the same, but change `MK4-Makefile` in last step to `Q1-Makefile`
|
||||
|
||||
## Long-Lived Branches
|
||||
|
||||
We are now maintaining two branches: `master` and `edge`.
|
||||
|
||||
"Edge" will contain features that may not be ready for prime time,
|
||||
such as Taproot or Miniscript. Our standards for releasing new Edge
|
||||
versions are lower, so we can iterate faster and get these advancements
|
||||
out to other developers.
|
||||
|
||||
Q and Mk series share the same code base. Individual files that are added,
|
||||
or removed, can be see in differences between `shared/manifest_mk4.py`
|
||||
and `shared/manifest_q1.py`. Common files are in `shared/manifest.py`.
|
||||
Firmware built for Mk5, supports the Mk4 without any functional differences.
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
## Check-out and Setup
|
||||
|
||||
**NOTE** This is the `master` branch and covers the latest hardware (Mk and Q).
|
||||
See branch `v4-legacy` for firmware which supports only Mk3/Mk2 and earlier.
|
||||
Do a checkout, recursively to get all the submodules:
|
||||
|
||||
Do a checkout, recursively, to get all the submodules:
|
||||
|
||||
```shell
|
||||
git clone --recursive https://github.com/Coldcard/firmware.git
|
||||
```
|
||||
|
||||
Already checked-out and getting git errors? Do this:
|
||||
|
||||
```shell
|
||||
git fetch
|
||||
git reset --hard origin/master
|
||||
```
|
||||
|
||||
Alternatively, to get the latest release, you checkout a tagged branch:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/Coldcard/firmware.git
|
||||
cd firmware
|
||||
git checkout $(git describe --match "20*" --abbrev=0)
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Do not use a path with any spaces in it. The Makefiles do not handle
|
||||
that well and we're not planning to fix it.
|
||||
|
||||
Keep in mind that python requirements may change between versions,
|
||||
so at the top level, do this command:
|
||||
|
||||
```shell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
[Python 3.5 or higher](https://www.python.org) and [Homebrew](https://brew.sh) is required.
|
||||
|
||||
If working on an ARM-based MacOS system, you may want to create a
|
||||
new shell with `arch -x86_64 bash` before starting, or continuing
|
||||
to work on this source tree.
|
||||
|
||||
#### Setup and run the desktop simulator
|
||||
|
||||
You'll probably need to install at least these packages:
|
||||
|
||||
```shell
|
||||
brew install sdl2 xterm swig
|
||||
brew install --cask xquartz gcc-arm-embedded
|
||||
```
|
||||
|
||||
Used to be these were needed as well:
|
||||
|
||||
```shell
|
||||
brew tap PX4/px4
|
||||
brew search px4/px4/gcc-arm-none-eabi
|
||||
```
|
||||
|
||||
Then install the newest version, currently 83:
|
||||
|
||||
```shell
|
||||
brew install px4/px4/gcc-arm-none-eabi-83
|
||||
```
|
||||
|
||||
You may need to `brew upgrade gcc-arm-embedded` because we need 10.2 or higher.
|
||||
git clone --recursive https://github.com/Coldcard/firmware.git
|
||||
|
||||
Then:
|
||||
|
||||
```shell
|
||||
brew install automake autogen virtualenv
|
||||
virtualenv -p python3 ENV
|
||||
source ENV/bin/activate (or source ENV/bin/activate.csh based on shell preference)
|
||||
pip install -U pip
|
||||
pip install -r requirements.txt
|
||||
# apply micropython patch
|
||||
pushd external/micropython
|
||||
git apply ../../macos-mpy.patch
|
||||
popd
|
||||
make -C external/micropython/mpy-cross
|
||||
cd unix; make setup && make ngu-setup && make && ./simulator.py
|
||||
```
|
||||
- `cd firmware`
|
||||
- `git submodule update --init` _(if needed?)_
|
||||
- `brew install autogen`
|
||||
- `virtualenv -p python3 ENV` (Python > 3.5 is required)
|
||||
- `source ENV/bin/activate` (or `source ENV/bin/activate.csh` based on shell preference)
|
||||
- `pip install -r requirements.txt`
|
||||
|
||||
You may need to reboot to avoid a `DISPLAY is not set` error.
|
||||
Setup and Run the Desktop-based Coldcard simulator:
|
||||
|
||||
The next time you want to run the simulator, you can simply do
|
||||
- `cd unix; make setup && make; ./simulator.py`
|
||||
|
||||
```shell
|
||||
source ENV/bin/activate && cd unix && ./simulator.py
|
||||
```
|
||||
|
||||
#### Building the firmware
|
||||
Building the firmware:
|
||||
|
||||
- `cd ../cli; pip install --editable .`
|
||||
- `cd ../stm32; make setup && make; make firmware-signed.dfu`
|
||||
@ -160,72 +41,42 @@ source ENV/bin/activate && cd unix && ./simulator.py
|
||||
|
||||
Which looks like this:
|
||||
|
||||
```shell
|
||||
[ENV] [firmware/stm32 42] ckcc upgrade firmware-signed.dfu
|
||||
675328 bytes (start @ 293) to send from 'firmware-signed.dfu'
|
||||
Uploading [##########--------------------------] 29% 0d 00:01:04
|
||||
```
|
||||
[ENV] [firmware/stm32 42] ckcc upgrade firmware-signed.dfu
|
||||
675328 bytes (start @ 293) to send from 'firmware-signed.dfu'
|
||||
Uploading [##########--------------------------] 29% 0d 00:01:04
|
||||
|
||||
#### Big Sur Issues
|
||||
|
||||
`defaults write org.python.python ApplePersistenceIgnoreState NO` will suppress a warning about `Python[22580:10101559] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to...`
|
||||
### MacOS
|
||||
|
||||
See <https://bugs.python.org/issue32909>
|
||||
You'll probably need to install at least these packages:
|
||||
|
||||
brew cask install xquartz
|
||||
brew install sdl2
|
||||
brew cask install gcc-arm-none-eabi
|
||||
|
||||
Used to be these were needed as well:
|
||||
|
||||
brew install sdl2
|
||||
brew tap PX4/px4
|
||||
brew search px4
|
||||
brew install px4/px4/gcc-arm-none-eabi-80 (latest gcc-arm-none-eabi-XX, currently 80)
|
||||
|
||||
You may need to reboot to avoid a `DISPLAY is not set` error.
|
||||
|
||||
### Linux
|
||||
|
||||
All steps you need to install and run the Coldcard simulator on Ubuntu 20.04:
|
||||
You'll probably need to install these (Ubuntu 16):
|
||||
|
||||
apt install libudev-dev python-sdl2 gcc-arm-none-eabi
|
||||
|
||||
```shell
|
||||
# Install (system) requirements, tools and libraries
|
||||
apt install build-essential git python3 python3-pip libudev-dev gcc-arm-none-eabi libffi-dev xterm swig libpcsclite-dev python-is-python3 autoconf libtool python3-venv
|
||||
|
||||
# Get sources, this takes a long time (because of external libraries), then open
|
||||
git clone --recursive https://github.com/Coldcard/firmware.git
|
||||
cd firmware
|
||||
|
||||
# Apply address patch
|
||||
# if unix/linux_addr.patch exists use below command
|
||||
# not needed in current revision
|
||||
# git apply unix/linux_addr.patch
|
||||
|
||||
# * below is needed for ubuntu 24.04
|
||||
pushd external/micropython
|
||||
git apply ../../ubuntu24_mpy.patch
|
||||
popd
|
||||
# *
|
||||
|
||||
|
||||
# Create Python virtual environment and activate it
|
||||
python3 -m venv ENV # or virtualenv -p python3 ENV
|
||||
source ENV/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -U pip setuptools
|
||||
pip install -r requirements.txt #general requirements
|
||||
pip install pysdl2-dll # Ubuntu needs this dependency
|
||||
|
||||
# Build the Coldcard simulator
|
||||
cd unix
|
||||
pushd ../external/micropython/mpy-cross/
|
||||
make # mpy-cross
|
||||
popd
|
||||
make setup
|
||||
make ngu-setup
|
||||
make
|
||||
|
||||
# Run the simulator in the active virtualenv
|
||||
./simulator.py
|
||||
|
||||
# Later, if you want to run it (after a reboot). This assumes you extracted the git repo in ~ (home)
|
||||
cd ~/firmware
|
||||
source ENV/bin/activate
|
||||
cd unix
|
||||
./simulator.py
|
||||
If you get stuck on the "Skip PIN" screen after the startup, edit the `pyb.py` file located under `/unix/frozen-modules/` and follow the instructions from line 27 to line 31:
|
||||
```
|
||||
# If on linux, try commenting the following line
|
||||
addr = bytes([len(fn)+2, socket.AF_UNIX] + list(fn))
|
||||
# If on linux, try uncommenting the following two lines
|
||||
#import struct
|
||||
#addr = struct.pack('H108s', socket.AF_UNIX, fn)
|
||||
```
|
||||
|
||||
Also make sure that you have your python3 symlinked to python.
|
||||
|
||||
## Code Organization
|
||||
|
||||
@ -235,55 +86,40 @@ Top-level dirs:
|
||||
|
||||
- shared code between desktop test version and real-deal
|
||||
- expected to be largely in python, and higher-level
|
||||
- code exclusive to the Mk4 or Mk5 will be listed in `manifest_mk4.py`, and
|
||||
to the Q will be listed in `manifest_q1.py`
|
||||
|
||||
`unix`
|
||||
|
||||
- unix (macOS) version for testing/rapid dev
|
||||
- unix (MacOS) version for testing/rapid dev
|
||||
- this is a simulator for the product
|
||||
|
||||
`testing`
|
||||
|
||||
- test cases and associated data
|
||||
|
||||
|
||||
`stm32`
|
||||
|
||||
- embedded binaries (and building), for actual product hardware
|
||||
- embedded micro version, for actual product
|
||||
- final target is a binary file for loading onto hardware
|
||||
|
||||
`external`
|
||||
|
||||
- code from other projects, ie. the dreaded submodules
|
||||
|
||||
`graphics`
|
||||
|
||||
- images which ship as part of the final product (icons)
|
||||
|
||||
`stm32/bootloader`
|
||||
|
||||
- 32k of factory-set code that you cannot change (Mk3)
|
||||
- however, you can inspect what code is on your coldcard and compare to this.
|
||||
|
||||
`stm32/mk4-bootloader`
|
||||
`stm32/q1-bootloader`
|
||||
|
||||
- 128k of factory-set code that you cannot change
|
||||
- 32k of factory-set code that you cannot change
|
||||
- however, you can inspect what code is on your coldcard and compare to this.
|
||||
|
||||
`hardware`
|
||||
|
||||
- schematic and bill of materials for the Coldcard, all versions.
|
||||
- schematic and bill of materials for the Coldcard
|
||||
|
||||
`unix/work/...`
|
||||
`unix/work/MicroSD`
|
||||
|
||||
- `/MicroSD/*` files on "simulated" microSD card
|
||||
- files on "simulated" microSD card
|
||||
|
||||
- `/VirtDisk/*` simulated emulated virtual Disk files.
|
||||
|
||||
- `/settings/*.aes` persistent settings for Simulator
|
||||
|
||||
## Support
|
||||
|
||||
Found a bug? Email: support@coinkite.com
|
||||
|
||||
|
||||
@ -1,4 +1,15 @@
|
||||
# only items needed for signit.py
|
||||
# NOTE: much of this is implied by ckcc-protocol/setup
|
||||
|
||||
# may need "brew install hidapi" before this?
|
||||
hidapi>=0.7.99.post21
|
||||
|
||||
click>=6.7
|
||||
|
||||
# required by signit.py and link-layer encryption in client.py
|
||||
ecdsa>=0.13
|
||||
|
||||
# so pydfu.py can run?
|
||||
pyusb
|
||||
|
||||
# for link-layer encryption
|
||||
pyaes
|
||||
|
||||
@ -11,7 +11,7 @@ from setuptools import setup
|
||||
setup(
|
||||
name='signit',
|
||||
version='1.0',
|
||||
py_modules=['signit', 'sigheader'],
|
||||
#py_modules=['ckcc'],
|
||||
install_requires=[
|
||||
'Click',
|
||||
],
|
||||
|
||||
@ -1 +1 @@
|
||||
../stm32/sigheader.py
|
||||
../stm32/bootloader/sigheader.py
|
||||
146
cli/signit.py
@ -12,6 +12,9 @@ from ecdsa import SigningKey, VerifyingKey
|
||||
from ecdsa.curves import SECP256k1
|
||||
from sigheader import *
|
||||
|
||||
# list of hardware we are presently supporting
|
||||
CURRENT_HARDWARE = MK_2_OK | MK_3_OK
|
||||
|
||||
# more details about header
|
||||
header = namedtuple('header', FWH_PY_VALUES)
|
||||
packed_len = struct.calcsize(FWH_PY_FORMAT)
|
||||
@ -91,10 +94,6 @@ def show_version(fname):
|
||||
# just dump the version number in a form that makes for good filenames
|
||||
data = open(fname, 'rb').read()
|
||||
|
||||
if data[0:5] == b'DfuSe':
|
||||
# Got DFU file, pulling out raw binary.
|
||||
(_, _, data),*_ = dfu_parse(open(fname, 'rb'))
|
||||
|
||||
hdr = data[FW_HEADER_OFFSET:FW_HEADER_OFFSET+FW_HEADER_SIZE ]
|
||||
|
||||
hdr = header(**dict(zip(FWH_PY_VALUES.split(), struct.unpack(FWH_PY_FORMAT, hdr))))
|
||||
@ -106,82 +105,13 @@ def show_version(fname):
|
||||
|
||||
print('{built}-v{ver}'.format(built=built, ver=ver))
|
||||
|
||||
def dfu_parse(fd):
|
||||
# do just a little parsing of DFU headers, to find start/length of main binary
|
||||
# - not trying to support anything but what ../stm32/Makefile will generate
|
||||
# - see external/micropython/tools/pydfu.py for details
|
||||
# - works sequentially only
|
||||
import struct
|
||||
from collections import namedtuple
|
||||
|
||||
fd.seek(0)
|
||||
|
||||
def consume(xfd, tname, fmt, names):
|
||||
# Parses the struct defined by `fmt` from `data`, stores the parsed fields
|
||||
# into a named tuple using `names`. Returns the named tuple.
|
||||
size = struct.calcsize(fmt)
|
||||
here = xfd.read(size)
|
||||
ty = namedtuple(tname, names.split())
|
||||
values = struct.unpack(fmt, here)
|
||||
return ty(*values)
|
||||
|
||||
dfu_prefix = consume(fd, 'DFU', '<5sBIB', 'signature version size targets')
|
||||
|
||||
#print('dfu: ' + repr(dfu_prefix))
|
||||
|
||||
assert dfu_prefix.signature == b'DfuSe', "Not a DFU file (bad magic)"
|
||||
|
||||
for idx in range(dfu_prefix.targets):
|
||||
|
||||
prefix = consume(fd, 'Target', '<6sBI255s2I',
|
||||
'signature altsetting named name size elements')
|
||||
|
||||
#print("target%d: %r" % (idx, prefix))
|
||||
|
||||
for ei in range(prefix.elements):
|
||||
# Decode target prefix
|
||||
# < little endian
|
||||
# I uint32_t element address
|
||||
# I uint32_t element size
|
||||
elem = consume(fd, 'Element', '<2I', 'addr size')
|
||||
|
||||
#print("target%d: %r" % (ei, elem))
|
||||
|
||||
yield fd.tell(), elem.size, fd.read(elem.size)
|
||||
|
||||
|
||||
@main.command('split')
|
||||
@click.argument('dfu', metavar='202....-coldcard.dfu')
|
||||
@click.argument('firmware', metavar='FIRMWARE.bin')
|
||||
@click.argument('bootrom', metavar='BOOTROM.bin')
|
||||
def split_dfu(dfu, firmware, bootrom):
|
||||
"Pull out sections from DFU file for verification purposes"
|
||||
|
||||
with open(dfu, 'rb') as fd:
|
||||
for n, (off, ln, data) in enumerate(dfu_parse(fd)):
|
||||
if n == 0:
|
||||
target = firmware
|
||||
name = 'Firmware'
|
||||
elif n == 1:
|
||||
target = bootrom
|
||||
name = 'Bootrom'
|
||||
else:
|
||||
raise ValueError(n)
|
||||
|
||||
# keep this printout so others can check our copy is faithful
|
||||
print(f'start {off} for {ln} bytes: {name} => {target}')
|
||||
|
||||
open(target, 'wb').write(data)
|
||||
|
||||
@main.command('check')
|
||||
@click.argument('fname', default='firmware-signed.bin')
|
||||
def readback(fname):
|
||||
"Verify pubkey and signature used in binary file"
|
||||
data = open(fname, 'rb').read()
|
||||
|
||||
if data[0:5] == b'DfuSe':
|
||||
click.secho("Got DFU file, pulling out raw binary.", fg='red')
|
||||
(_, _, data),*_ = dfu_parse(open(fname, 'rb'))
|
||||
assert data[0:5] != b'DfuSe', "got DFU, expect raw binary"
|
||||
|
||||
hdr = data[FW_HEADER_OFFSET:FW_HEADER_OFFSET+FW_HEADER_SIZE ]
|
||||
|
||||
@ -207,10 +137,7 @@ def readback(fname):
|
||||
if v & MK_1_OK: d.append('Mk1')
|
||||
if v & MK_2_OK: d.append('Mk2')
|
||||
if v & MK_3_OK: d.append('Mk3')
|
||||
if v & MK_4_OK: d.append('Mk4')
|
||||
if v & MK_5_OK: d.append('Mk5')
|
||||
if v & MK_Q1_OK: d.append('Q1')
|
||||
if v & ~(MK_1_OK | MK_2_OK | MK_3_OK | MK_4_OK | MK_5_OK | MK_Q1_OK):
|
||||
if v & ~(MK_1_OK | MK_2_OK | MK_3_OK):
|
||||
d.append('?other?')
|
||||
v = nv + '+'.join(d)
|
||||
elif fld == 'timestamp':
|
||||
@ -228,7 +155,7 @@ def readback(fname):
|
||||
a.update(data[FW_HEADER_OFFSET+FW_HEADER_SIZE:])
|
||||
chk = sha256(a.digest()).digest()
|
||||
|
||||
print("sha256^2: %s" % b2a_hex(chk).decode('ascii'))
|
||||
#print("sha256^2: %s" % b2a_hex(chk).decode('ascii'))
|
||||
|
||||
# from pubkey
|
||||
vk = VerifyingKey.from_pem(open("keys/%02d.pubkey.pem" % vals['pubkey_num']).read())
|
||||
@ -246,29 +173,20 @@ def readback(fname):
|
||||
@click.option('--pubkey-num', '-k', type=int, help='Which key # to use for signing', default=0)
|
||||
@click.option('--high_water', '-h', is_flag=True, help='Mark version as new highwater mark (no downgrades below this version)')
|
||||
@click.option('--verbose', '-v', default=False, is_flag=True, help='Show numbers related to signature')
|
||||
@click.option('--hw-compat', '-m', type=str, metavar='mk', help="Set HW compat field (hw_label value)")
|
||||
@click.option('--force-hw-compat', type=str, metavar='BITMASK', help="Override HW compat field (hex)")
|
||||
@click.option('--backdate', type=int, metavar='DAYS',
|
||||
help='Make downgrade attack test version', default=0)
|
||||
@click.option('--build_dir', '-b', default='l-port/build-COLDCARD')
|
||||
@click.option('--resign_file', '-r', type=click.File('rb'),
|
||||
help='Replace existing signature', default=None)
|
||||
@click.option('--outfn', '-o', type=click.Path(),
|
||||
help='Output filename', default='firmware-signed.bin')
|
||||
@click.option('--keydir', type=str, metavar='DIRPATH', help="Where to find priv keys for signing", default='keys')
|
||||
def doit(keydir, outfn=None, build_dir=None, high_water=False,
|
||||
current=False, hw_compat=None,
|
||||
def doit(keydir, outfn=None, build_dir='l-port/build-COLDCARD', high_water=False,
|
||||
current=False, force_hw_compat=None,
|
||||
version='0.1a', pubkey_num=0, backdate=0, verbose=False, resign_file=None):
|
||||
"Add signature into binary file before it becomes a DFU file."
|
||||
|
||||
assert len(version) < 8, "Version string limited to 8 bytes, got: %r" % version
|
||||
|
||||
# load key
|
||||
try:
|
||||
sk = SigningKey.from_pem(open(f"{keydir}/{pubkey_num:02d}.pem").read())
|
||||
except FileNotFoundError:
|
||||
click.secho(f"You don't have that key ({pubkey_num}), so using key zero instead!", fg='red')
|
||||
pubkey_num = 0
|
||||
sk = SigningKey.from_pem(open(f"{keydir}/{pubkey_num:02d}.pem").read())
|
||||
|
||||
if resign_file:
|
||||
whole = resign_file.read()
|
||||
@ -279,31 +197,17 @@ def doit(keydir, outfn=None, build_dir=None, high_water=False,
|
||||
vectors = open(build_dir + '/firmware0.bin', 'rb').read()
|
||||
body = open(build_dir + '/firmware1.bin', 'rb').read()
|
||||
|
||||
if hw_compat in { 'mk4', '4', 'mk5', '5', 'mk' }:
|
||||
# Mk4 and 5 can run the same firmware, once Mk5 support was added
|
||||
hw_compat = MK_4_OK | MK_5_OK
|
||||
elif hw_compat == 'q1':
|
||||
hw_compat = MK_Q1_OK
|
||||
elif hw_compat in { 'mk3', '3'}:
|
||||
hw_compat = MK_2_OK | MK_3_OK
|
||||
else:
|
||||
assert not "known"
|
||||
|
||||
assert len(vectors) <= FW_HEADER_OFFSET, "isr vectors area is too big!"
|
||||
assert len(body) >= FW_MIN_LENGTH, "main firmware is too small: %d" % len(body)
|
||||
|
||||
body_len = align_to(len(body), 512)
|
||||
|
||||
if hw_compat & (MK_1_OK | MK_2_OK | MK_3_OK):
|
||||
# bugfix: size must be non-page aligned, so extra bytes are erased past end
|
||||
if (body_len % 4096) == 0:
|
||||
body_len += 512
|
||||
assert body_len % 512 == 0, body_len
|
||||
if force_hw_compat is not None:
|
||||
hw_compat = int(force_hw_compat, 16)
|
||||
click.echo("Overriding hw_compat field: 0x%02x" % hw_compat)
|
||||
else:
|
||||
# bugfix: PSRAM-based products (Mk4, Q1) need to erase 4k blocks, so
|
||||
# trouble happens if final binary isn't aligned to that size.
|
||||
body_len = align_to(body_len, 4096)
|
||||
assert body_len % 4096 == 0, body_len
|
||||
hw_compat = CURRENT_HARDWARE
|
||||
|
||||
body_len = align_to(len(body), 512)
|
||||
assert body_len % 512 == 0, body_len
|
||||
|
||||
# pad out
|
||||
vectors = pad_to(vectors, FW_HEADER_OFFSET)
|
||||
@ -315,22 +219,15 @@ def doit(keydir, outfn=None, build_dir=None, high_water=False,
|
||||
firmware_length=FW_HEADER_OFFSET+FW_HEADER_SIZE+body_len,
|
||||
install_flags=(FWHIF_HIGH_WATER if high_water else 0x0),
|
||||
hw_compat=hw_compat,
|
||||
best_ts=bytes(8),
|
||||
future=b'\0'*(4*FWH_NUM_FUTURE),
|
||||
signature=b'\xff'*64,
|
||||
pubkey_num=pubkey_num,
|
||||
timestamp=timestamp(backdate) )
|
||||
|
||||
assert FW_MIN_LENGTH <= hdr.firmware_length <= FW_MAX_LENGTH, hdr.firmware_length
|
||||
|
||||
if hw_compat & MK_3_OK:
|
||||
# actual file length limited by size of SPI flash area reserved to txn data/uploads
|
||||
assert FW_MIN_LENGTH <= hdr.firmware_length <= FW_MAX_LENGTH, hdr.firmware_length
|
||||
USB_MAX_LEN = (786432-128)
|
||||
else:
|
||||
# new value for Mk4 and later: limited only by final binary size, not SPI flash
|
||||
assert FW_MIN_LENGTH <= hdr.firmware_length <= FW_MAX_LENGTH_MK4, hdr.firmware_length
|
||||
USB_MAX_LEN = 1472 * 1024
|
||||
|
||||
# actual file length limited by size of SPI flash area reserved to txn data/uploads
|
||||
USB_MAX_LEN = (786432-128)
|
||||
assert hdr.firmware_length <= USB_MAX_LEN, \
|
||||
"too big for our USB upgrades: %d = %d bytes too big" % (
|
||||
hdr.firmware_length, hdr.firmware_length-USB_MAX_LEN)
|
||||
@ -339,6 +236,8 @@ def doit(keydir, outfn=None, build_dir=None, high_water=False,
|
||||
|
||||
binhdr = struct.pack(FWH_PY_FORMAT, *hdr)
|
||||
assert len(binhdr) == FW_HEADER_SIZE
|
||||
|
||||
assert len(vectors + binhdr[:-64]) == 0x3fc0
|
||||
assert len(vectors + binhdr[:-64]) == 0x3fc0
|
||||
|
||||
hashable = vectors + binhdr[:-64] + body
|
||||
@ -350,6 +249,9 @@ def doit(keydir, outfn=None, build_dir=None, high_water=False,
|
||||
print("Hdr: %s" % repr(hdr))
|
||||
print('Hash: %s' % b2a_hex(fw_hash).decode('ascii'))
|
||||
|
||||
# load key
|
||||
sk = SigningKey.from_pem(open(f"{keydir}/{pubkey_num:02d}.pem").read())
|
||||
|
||||
from ecdsa.util import sigencode_string
|
||||
sig = sk.sign_digest(fw_hash, sigencode=sigencode_string)
|
||||
|
||||
|
||||
@ -3,32 +3,11 @@
|
||||
These docs are meant for you hackers out there... but also for anyone who
|
||||
wants to understand why it's safe to put your moneys into Coldcard.
|
||||
|
||||
- [`security-model.md`](security-model.md) The COLDCARD Mk4/Mk5/Q security model.
|
||||
- [`pin-entry.md`](pin-entry.md) Huge and detailed discussion of PIN codes and the security element that holds the secrets.
|
||||
- [`secure-elements.md`](secure-elements.md) How the dual secure elements work together.
|
||||
- [`dev-access.md`](dev-access.md) How developers can modify Coldcard to extend it.
|
||||
- [`memory-map.md`](memory-map.md) Memory map highlights
|
||||
- [`notes-on-repro.md`](notes-on-repro.md) Detailed breakdown of the reproducible build process.
|
||||
- [`upgrade-recovery.md`](upgrade-recovery.md) Firmware upgrade and recovery process.
|
||||
- [`backup-files.md`](backup-files.md) Some details of our encrypted backup files.
|
||||
- [`temporary-seeds.md`](temporary-seeds.md) Temporary (ephemeral) seeds and the Seed Vault.
|
||||
- [`seed-xor.md`](seed-xor.md) More about _Seed XOR_ feature, including fully worked Seed XOR example, and useful XOR lookup chart.
|
||||
- [`key-teleport.md`](key-teleport.md) Key Teleport: encrypted transfer of seeds and secrets between Q devices.
|
||||
- [`spending-policy.md`](spending-policy.md) Spending policy: autonomous signing with configurable limits.
|
||||
- [`microsd-2fa.md`](microsd-2fa.md) Using a MicroSD card as a second factor for login.
|
||||
- [`web2fa.md`](web2fa.md) Web 2FA authentication.
|
||||
- [`bip85-passwords.md`](bip85-passwords.md) Deriving deterministic passwords via BIP-85.
|
||||
- [`msg-signing.md`](msg-signing.md) COLDCARD message signing.
|
||||
- [`proof-of-reserves-bip-322.md`](proof-of-reserves-bip-322.md) BIP-322 generic signed message format and proof of reserves.
|
||||
- [`generic-wallet-export.md`](generic-wallet-export.md) Generic JSON wallet export file format.
|
||||
- [`bip-21-extensions.md`](bip-21-extensions.md) Coldcard's BIP-21 URI extensions, including multisig ownership address check.
|
||||
- [`nfc-coldcard.md`](nfc-coldcard.md) NFC support on Coldcard Mk4 and Q.
|
||||
- [`nfc-pushtx.md`](nfc-pushtx.md) NFC Push Transaction: broadcast a signed transaction via your phone.
|
||||
- [`usb-batteries.md`](usb-batteries.md) Using USB battery packs with Coldcard.
|
||||
- [`electrum-usage.md`](electrum-usage.md) Importing seed words into Electrum for funds usage (and other tips).
|
||||
- [`bitcoin-core-usage.md`](bitcoin-core-usage.md) How to use with Bitcoin Core.
|
||||
- [`bitcoin-core2of2desc.md`](bitcoin-core2of2desc.md) Airgapped 2-of-2 multisig with Bitcoin Core using descriptors.
|
||||
- [`limitations.md`](limitations.md) Documented limitations, policy choices, and TODO items.
|
||||
- [`paperwallet.pdf`](paperwallet.pdf) Example paper wallet template file.
|
||||
- [`menu-tree.txt`](menu-tree.txt) Dump of the menu system. Incomplete, may be out of date.
|
||||
|
||||
|
||||
@ -34,33 +34,20 @@ process and that the data is in fact encrypted. Any 7z tool that
|
||||
supports AES256-SHA256 encryption should be able to read the files
|
||||
we make. Take the 12 words and put them together with a single space
|
||||
between each word (all lowercase). The decoded archive will contain
|
||||
a single file, which is a simple text file and
|
||||
easy to read. Before version 4.0.0, this text file was always
|
||||
called `ckcc-backup.txt`, but the filename is now picked randomly.
|
||||
|
||||
## BIP39 Passphrase
|
||||
|
||||
If BIP39 passphrase is active the default behavior is to back-up
|
||||
main wallet - not BIP39 passphrase wallet. From version `5.2.0`
|
||||
users can choose to back-up also BIP39 passphrase wallet.
|
||||
|
||||
## Ephemeral Seeds
|
||||
|
||||
If ephemeral seed is active the default behavior is to always
|
||||
back-up ephemeral wallet instead of the main wallet.
|
||||
a single file, `ckcc-backup.txt`, which is a simple text file and
|
||||
easy to read.
|
||||
|
||||
## Limitations
|
||||
|
||||
- The archive file names are not encrypted. You can see there is a single
|
||||
text file `word(number).txt` in the encrypted file without decrypting it.
|
||||
- The archive file names are not encrypted. You can see `ckcc-backup.txt` in
|
||||
the hex dump of the encrypted file, encoded as `utf-16-le` bytes.
|
||||
- The device PIN code is not preserved during backup.
|
||||
- We produce standards-compliant files, but do not support reading any
|
||||
file except the ones produced by Coldcard.
|
||||
- Do not attempt to edit the file and restore it onto a Coldcard.
|
||||
- You cannot construct a file for the Coldcard to read because we implement only
|
||||
enough to support reading files that we know that we've produced.
|
||||
- There is limited plausible deniability here: if you are forced to decrypt
|
||||
the file, it is clearly a Coldcard backup file.
|
||||
- There is no plausible deniability here: the 7z file is clearly a Coldcard backup file.
|
||||
|
||||
## Example File
|
||||
|
||||
@ -122,14 +109,6 @@ If you are playing along at home, the passphrase for the above file is:
|
||||
You can grab the [example file here](backup.7z) and test it yourself, or use
|
||||
a real Coldcard to make your own.
|
||||
|
||||
|
||||
## Internal Filenames
|
||||
|
||||
The internal filename in these examples is `ckcc-backup.txt`, but
|
||||
starting in version 4.0.0 of the Coldcard firwmare, your backup
|
||||
files will each have a different random filename inside. Use
|
||||
`7z l backup.7z` to view the actual filename inside the 7z file.
|
||||
|
||||
### Archive Contents
|
||||
|
||||
```
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
## Multisig Ownership address check: "wallet"
|
||||
|
||||
If the name of the multisig wallet related to an address is provided, address search
|
||||
can be greatly accelerated. Just provide `wallet=name` parameter in a standard
|
||||
[BIP-21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki) URL
|
||||
shown in QR code or NFC record. If omitted, search will continue across
|
||||
all multisig wallets known by COLDCARD.
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
tb1q4d67p7stxml3kdudrgkg5mgaxsrgzcqzjrrj4gg62nxtvnsnvqjsxjkej0?wallet=goldmine
|
||||
|
||||
bitcoin:mtHSVByP9EYZmB26jASDdPVm19gvpecb5R?label=coldcard_purchase&amount=50&wallet=Haystack%20Four
|
||||
```
|
||||
@ -1,113 +0,0 @@
|
||||
# BIP-85 Passwords
|
||||
|
||||
This feature derives a deterministic password from your seed,
|
||||
according to [BIP-85 PWD BASE64](https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#pwd-base64).
|
||||
Generated passwords can be sent as keystrokes via USB to the host computer,
|
||||
effectively using Coldcard as specialized password manager.
|
||||
|
||||
In addition to deriving up to 10,000 distinct secure passwords, the Coldcard
|
||||
can also type them into a computer by emulating a USB keyboard, and simulating the
|
||||
keystrokes needed to type the password.
|
||||
|
||||
#### Requirements
|
||||
|
||||
* Coldcard Mk4 or Mk5 (firmware 5.0.5 or newer), or any Q
|
||||
* USB-C with data link (won't work with power only cable from Coinkite)
|
||||
|
||||
## Type Passwords over USB
|
||||
|
||||
1. To enable "Type Passwords" feature, connect your Coldcard to host PC with USB cable (check requirements) and go to Settings -> Keyboard EMU -> Enable.
|
||||
2. Go back to top menu and "Type Passwords" option will be shown below "Address Explorer".
|
||||
3. When it is time to enter a secret password, select "Type Passwords" from the main menu. After
|
||||
an information screen, the USB emulation will be switch to keyboard emulation
|
||||
and `Switching...` shown on the screen.
|
||||
4. Choose "Password index" (BIP-85 index) and press OK to generate that password.
|
||||
5. It takes a moment to generate the password, and then you can scroll down to check BIP-85 path used and double-check password to be typed.
|
||||
6. To send keystrokes, place mouse at required password prompt and press OK. This will send desired keystrokes plus hit enter at the end.
|
||||
6. You are back at step 4, and can continue to generate passwords or you can press X
|
||||
to exit. Exiting from "Type Passwords" will cause Coldcard to turn off keyboard emulation and enable normal USB mode if it was enabled before. Otherwise, USB stays disabled.
|
||||
|
||||
## View BIP-85 passwords
|
||||
|
||||
1. Go to Advanced/Tools -> Derive Seed B85 -> Passwords
|
||||
2. Choose "Password/Index number" (BIP-85 index) and press OK to generate password.
|
||||
3. Screen shows generated password, path, and entropy from which password was derived
|
||||
4. A few different options are available at this point (on Mk; on Q the NFC and
|
||||
QR buttons are used instead of (3)/(4)):
|
||||
1. press (1) to save password backup file on MicroSD card (cleartext!)
|
||||
2. press (2) to save to Virtual Disk (only when available)
|
||||
3. press (3) to send over NFC (only appears when NFC is enabled)
|
||||
4. press (4) to view password as QR code
|
||||
5. press (6) to send keystrokes over USB (this enables keyboard emulation, sends keystrokes + enter, then disables keyboard emulation)
|
||||
|
||||
## Keyboard language settings
|
||||
|
||||
Emulated Keystrokes are mapped to specific characters based on your host PC keyboard
|
||||
language settings. For Coldcard to be able to type the correct BIP-85
|
||||
password your host computer MUST use language settings that
|
||||
corresponds to a [QWERTY](https://simple.wikipedia.org/wiki/QWERTY) key layout,
|
||||
including number row directly above QWERTY:
|
||||
|
||||
```
|
||||
1 2 3 4 5 6 7 8 9 0 - =
|
||||
Q W E R T Y U I O P [ ] \
|
||||
A S D F G H J K L ; '
|
||||
Z X C V B N M , . /
|
||||
```
|
||||
|
||||
Passwords generated and shown on Coldcard will always be correct
|
||||
with respect to BIP-85. However, when sending keystrokes, for example
|
||||
on German keyboard, what was typed will not match the text that was
|
||||
generated and shown on Coldcard's screen.
|
||||
|
||||
For example, if the correct password is `zYLoepugzdVJvdL56ogNV` but when used
|
||||
with German keyboard language settings, what will be typed is
|
||||
`yZLoepugydVJvdL56ogNV`. You can see that German keyboard is not
|
||||
QWERTY, but it is QUERTZ (y and z are swapped).
|
||||
|
||||
Even with "non-standard" keyboard language settings, Coldcard always
|
||||
sends exact same keystrokes for specific password index and it is
|
||||
deterministic, as long the keyboard language setting do not change.
|
||||
However, BIP-85 won't be respected in this case.
|
||||
|
||||
## Coldcard Specifics
|
||||
|
||||
Check [BIP-85](https://github.com/scgbckbone/bips/blob/passwords/bip-0085.mediawiki)
|
||||
for complete specification of the new addition to BIP-85.
|
||||
|
||||
Coldcard does not allow you to specify password length - we always
|
||||
use length of **21**. Passwords of this length generated according
|
||||
to BIP will have approximately 126 bits of entropy. This is on par
|
||||
with bitcoin security model and therefore all passwords the Coldcard
|
||||
will generate are considered very strong.
|
||||
|
||||
## Examples
|
||||
|
||||
Using below seed, path and index, we generate passwords shown in the table:
|
||||
|
||||
```shell
|
||||
wife shiver author away frog air rough vanish fantasy frozen noodle athlete pioneer citizen symptom firm much faith extend rare axis garment kiwi clarify
|
||||
```
|
||||
|
||||
| Index | Path | Password |
|
||||
|-------|----------------------------|----------|
|
||||
| 0 | m/83696968'/707764'/21'/0' | BSdrypS+J4Wr1q8DWjbFE |
|
||||
| 1 | m/83696968'/707764'/21'/1' | TkDX7d9fnX9FZ9QEpjFDB |
|
||||
| 2 | m/83696968'/707764'/21'/2' | cvfdmoZL3BcIpJ7G+Rb8k |
|
||||
| 3 | m/83696968'/707764'/21'/3' | wsCALdN+GgbSOGyGE9aRN |
|
||||
| 4 | m/83696968'/707764'/21'/4' | HfYbWx7gVmUmb2Bw4o4QD |
|
||||
| 5 | m/83696968'/707764'/21'/5' | vLOf9WPO5QiPbOTEbz/yJ |
|
||||
| 6 | m/83696968'/707764'/21'/6' | 1oSUs7Cy3fnpdh/fAS7EK |
|
||||
| 7 | m/83696968'/707764'/21'/7' | seh9WN6mlvPPB5jdVz3xN |
|
||||
| 8 | m/83696968'/707764'/21'/8' | U4RD0R0A0RjpHOFtwnv9k |
|
||||
|
||||
|
||||
## Incompatible Applications
|
||||
|
||||
Although the Coldcard is emulating a keyboard at the lowest possible level,
|
||||
for some reason occasionally high-level applications have
|
||||
trouble with our high-speed typing.
|
||||
|
||||
- KeePass2 2.45 (under Ubuntu). Capital/lowercase letters may be incorrectly typed. Use KeePassXC instead.
|
||||
|
||||
|
||||
@ -5,40 +5,8 @@ needs a USB connection and additional software such as [HWI](https://github.com/
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### Bitcoin Core v0.21.0+
|
||||
|
||||
As of Coldcard firmware v4.1.3, we recommend using the "importdescriptors"
|
||||
command with a native descriptor wallet in Core, so Core can generate
|
||||
and receive PSBT files natively from the GUI. The resulting wallet is
|
||||
no longer just a watch wallet, but can be used for spending by creating
|
||||
PSBT files for signing offline at the Coldcard.
|
||||
|
||||
Step 1: Create a new descriptor-based wallet in Bitcoin Core
|
||||
|
||||
- File -> Create Wallet ...
|
||||
- give it a unique name
|
||||
- check "Disable Private Keys"
|
||||
- check "Make Blank Wallet"
|
||||
- check "Descriptor Wallet"
|
||||
|
||||
Step 2: Export descriptor from Coldcard to Core
|
||||
|
||||
- (singlesig) on Coldcard, go to Advanced -> MicroSD card -> Export Wallet -> Bitcoin Core
|
||||
- (multisig) on Coldcard, go to Settings -> Multisig Wallets -> Choose desired multisig wallet -> Descriptors -> Bitcoin Core
|
||||
- on your computer, open `bitcoin-core-XX.txt`, copy the `importdescriptor` command line
|
||||
- in Bitcoin Core, go to Windows -> Console
|
||||
- select your newly created descriptor wallet in the wallet pulldown (top left)
|
||||
- paste the `importdescriptor` command. It should respond with a success message
|
||||
- in Bitcoin Core v24.1, the console response will include `"message": "Ranged descriptors should not have a label"` and Bitcoin Core won't allow address generation. Removing the entry `"label": "Coldcard x0x0x0x0"` from the .txt file fixes this issue.
|
||||
|
||||
NOTE: If you are importing an existing wallet this way, with UTXO on the blockchain,
|
||||
you may need to rescan and/or delete "timestamp=now" from the command. If the
|
||||
balance is zero this is why.
|
||||
|
||||
### Bitcoin Core v0.19.0+
|
||||
|
||||
(no longer recommended)
|
||||
|
||||
For compatibility with other wallet software we use the BIP84 address derivation
|
||||
(m/84'/0'/{account}'/{change}/{index}) and native SegWit (bech32) addresses. It's
|
||||
recommended to set `addresstype=bech32` in [bitcoin.conf](https://github.com/bitcoin/bitcoin/blob/9546a785953b7f61a3a50e2175283cbf30bc2151/doc/bitcoin-conf.md).
|
||||
@ -51,8 +19,8 @@ The public keys can exported via an SD card, or via USB.
|
||||
|
||||
To export via SD card:
|
||||
|
||||
- go to Advanced -> MicroSD card -> Export Wallet -> Bitcoin Core
|
||||
- on your computer open `bitcoin-core-XX.txt`, copy the `importmulti` command line
|
||||
- go to Advanced -> MicroSD card -> Bitcoin Core
|
||||
- on your computer open public.txt, copy the `importmulti` command
|
||||
- in Bitcoin Core, go to Windows -> Console
|
||||
- select Coldcard in the wallet dropdown
|
||||
- paste the `importmulti` command. It should respond with a success message
|
||||
@ -78,11 +46,6 @@ createwallet Coldcard true
|
||||
|
||||
## Day-to-day Operation
|
||||
|
||||
### Bitcoin Core v0.21.0+
|
||||
|
||||
PSBT files can be directly created and loaded from the Bitcoin Core Qt GUI! HWI is not
|
||||
required, and air-gap via MicroSD is easy to use.
|
||||
|
||||
### Bitcoin Core v0.18.0+
|
||||
|
||||
See HWI [instructions for usage](https://github.com/bitcoin-core/HWI/blob/master/docs/bitcoin-core-usage.md#usage).
|
||||
|
||||
@ -1,134 +0,0 @@
|
||||
## Airgapped 2of2 Multisig with Bitcoin Core (and descriptors)
|
||||
|
||||
#### Prerequisites
|
||||
* Coldcard Mk4 signing device
|
||||
* SD card and SD card reader (or NFC reader)
|
||||
* bitcoind (version v23.0)
|
||||
* [jq](https://stedolan.github.io/jq/)
|
||||
|
||||
### Tutorial 1
|
||||
2of2 with two Mk4 signing devices (+ bitcoin-qt watch only wallet as a coordinator)
|
||||
* full tutorial now on [coldcard.com](https://coldcard.com/docs/bitcoin-core-2of2desc)
|
||||
|
||||
### Tutorial 2
|
||||
2of2 with one Mk4 signing device and bitcoind sww (+ bitcoind watch only wallet as a coordinator)
|
||||
|
||||
1. start bitcoind (here I will use regtest)
|
||||
```shell
|
||||
bitcoind -regtest
|
||||
```
|
||||
2. Create descriptor wallet with private keys enabled. This wallet will be used for signing (1of2)
|
||||
```shell
|
||||
bitcoin-cli -regtest createwallet "signer"
|
||||
# if using core older than v23.0 you need to specify descriptor wallet as below
|
||||
bitcoin-cli -regtest help createwallet "signer" false false "__strong-random_password&%4568479" false true
|
||||
```
|
||||
3. Fill keypool for signer as we will generate addresses with watch_only wallet (this is needed, otherwise signer will not be able to sign other than index 0). If you spend more than 100 adress, you will need to refill keypool again with same command.
|
||||
```shell
|
||||
$ bitcoin-cli -regtest -rpcwallet="signer" keypoolrefill 100
|
||||
```
|
||||
4. Create watch-only descriptor wallet. This wallet will contain watch-only multisig descriptor (coordinator)
|
||||
```shell
|
||||
bitcoin-cli -regtest createwallet "watch_only" true true
|
||||
# if older than v23.0
|
||||
bitcoin-cli -regtest createwallet "watch_only" true true "" false true
|
||||
```
|
||||
5. Get descriptor from Coldcard Mk4 (Settings->Multisig Wallets->Eport XPUB + choose account number -> in our case 0)
|
||||
6. Step 4. produced a text file on SD card or in your .... as we will be creating wsh multisig get `p2wsh_desc` key from produced file (should start with ccxp-<xfp>.json). Should look like this:
|
||||
```shell
|
||||
"wsh(sortedmulti(M,[0F056943/48'/1'/0'/2']tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP/0/*,...))"
|
||||
```
|
||||
7. Above descriptor template needs to be filled with information form other signers (in our case only bitcoind). Specifically one must add all extended keys with key origin info and substitute `M` with threshold value.
|
||||
8. Bitcoin (unfortunately) does not support deriving xpubs at will, so we will use legacy derivations path which we can get from `listdescriptors`
|
||||
```shell
|
||||
$ bitcoin-cli -regtest -rpcwallet="signer" listdescriptors
|
||||
{
|
||||
"wallet_name": "signer",
|
||||
"descriptors": [
|
||||
{
|
||||
"desc": "pkh([122b6f56/44'/1'/0']tpubDCmC5bJAfQxjCFrRCG9qzBmz4FDy6SVieb4KZaPD5D8AFx5vYjngiRfJSCds4LzKcpy9Mx3he4uSdfdkJHGZBG2gJqz63ndKj9miiVbYzfc/0/*)#ryqp0qxp",
|
||||
"timestamp": 1656067182,
|
||||
"active": true,
|
||||
"internal": false,
|
||||
"range": [
|
||||
0,
|
||||
999
|
||||
],
|
||||
"next": 0
|
||||
},
|
||||
{
|
||||
"desc": "wpkh([122b6f56/84'/1'/0']tpubDCSoM4w4NXN5sAorw6pnEcwho1dJRiyLTjgK9FTkgc25RguDZ2Vnk3dt9bCdFt9oU5YHNWkjEaYERXbLro8HMTbF9ze7Shn15xdKNa2umkG/0/*)#pg8w9ku3",
|
||||
"timestamp": 1656067182,
|
||||
"active": true,
|
||||
"internal": false,
|
||||
"range": [
|
||||
0,
|
||||
999
|
||||
],
|
||||
"next": 0
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
9. Copy extended key (with key origin and derivation information) from `pkh` descriptor (shown below)
|
||||
```shell
|
||||
[122b6f56/44'/1'/0']tpubDCmC5bJAfQxjCFrRCG9qzBmz4FDy6SVieb4KZaPD5D8AFx5vYjngiRfJSCds4LzKcpy9Mx3he4uSdfdkJHGZBG2gJqz63ndKj9miiVbYzfc/0/*
|
||||
```
|
||||
10. Insert above to the multisig descriptor from step 5. and replace M with 2
|
||||
11. Use bitcoind to calculate descriptor checksum
|
||||
```shell
|
||||
$ bitcoin-cli -regtest getdescriptorinfo "wsh(sortedmulti(2,[0f056943/48'/1'/0'/2']tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP/0/*,[122b6f56/44'/1'/0']tpubDCmC5bJAfQxjCFrRCG9qzBmz4FDy6SVieb4KZaPD5D8AFx5vYjngiRfJSCds4LzKcpy9Mx3he4uSdfdkJHGZBG2gJqz63ndKj9miiVbYzfc/0/*))"
|
||||
{
|
||||
"descriptor": "wsh(sortedmulti(2,[0f056943/48'/1'/0'/2']tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP/0/*,[122b6f56/44'/1'/0']tpubDCmC5bJAfQxjCFrRCG9qzBmz4FDy6SVieb4KZaPD5D8AFx5vYjngiRfJSCds4LzKcpy9Mx3he4uSdfdkJHGZBG2gJqz63ndKj9miiVbYzfc/0/*))#flsx3jj8",
|
||||
"checksum": "flsx3jj8",
|
||||
"isrange": true,
|
||||
"issolvable": true,
|
||||
"hasprivatekeys": false
|
||||
}
|
||||
```
|
||||
12. Copy descriptor with checksum to new file (2of2core.txt) on SD card (descriptor has to be in single line)
|
||||
13. Insert SD card to Coldcard and import multisig wallet.(Settings->Multisig Wallets->Import from file) select 2of2core.txt and approve.
|
||||
14. Export imported multisig wallet for bitcoin core (Settings->Multisig Wallets->2/2: 2of2core->Descriptors->Bitcoin Core)
|
||||
15. Step 14. will produce `bitcoin-core-2of2core.txt` file on SD card. Copy command from file.
|
||||
16. Import descriptor to watch_only wallet (signer would not allow us as he has private keys)
|
||||
```shell
|
||||
bitcoin-cli -regtest -rpcwallet=watch_only importdescriptors '[{"active": true, "timestamp": "now", "range": [0, 100], "internal": true, "desc": "wsh(sortedmulti(2,[0f056943/48h/1h/0h/2h]tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP/1/*,[122b6f56/44h/1h/0h]tpubDCmC5bJAfQxjCFrRCG9qzBmz4FDy6SVieb4KZaPD5D8AFx5vYjngiRfJSCds4LzKcpy9Mx3he4uSdfdkJHGZBG2gJqz63ndKj9miiVbYzfc/1/*))#tv5t5plj"}, {"active": true, "timestamp": "now", "range": [0, 100], "internal": false, "desc": "wsh(sortedmulti(2,[0f056943/48h/1h/0h/2h]tpubDF2rnouQaaYrXF4noGTv6rQYmx87cQ4GrUdhpvXkhtChwQPbdGTi8GA88NUaSrwZBwNsTkC9bFkkC8vDyGBVVAQTZ2AS6gs68RQXtXcCvkP/0/*,[122b6f56/44h/1h/0h]tpubDCmC5bJAfQxjCFrRCG9qzBmz4FDy6SVieb4KZaPD5D8AFx5vYjngiRfJSCds4LzKcpy9Mx3he4uSdfdkJHGZBG2gJqz63ndKj9miiVbYzfc/0/*))#zyueunr0"}]'
|
||||
```
|
||||
15. Get address and get some funds
|
||||
```shell
|
||||
$ bitcoin-cli -regtest -rpcwallet=watch_only getnewaddress
|
||||
bcrt1q6u8s97fnd6ggad4apqs8vvcgup0d085ttd9v0c6fmpf38tf6q0ysgfh4vm
|
||||
```
|
||||
16. If you use regtest as I do, make sure to set Coldcard to regtest (Advanced/Tools-> Danger Zone->Testnet Mode->Regtest)
|
||||
17. Check that core generated address matches with address generated by Coldcard (Address Explorer->multi_2of2_core)
|
||||
18. Fund address
|
||||
```shell
|
||||
# this is only useful for those who use regtest - if you're on testnet use faucet
|
||||
bitcoin-cli -regtest -rpcwallet=watch_only generatetoaddress 101 "bcrt1q6u8s97fnd6ggad4apqs8vvcgup0d085ttd9v0c6fmpf38tf6q0ysgfh4vm"
|
||||
```
|
||||
19. Generate destination address to send to (in our case another `getnewaddress` -> self spend)
|
||||
```shell
|
||||
`bitcoin-cli -regtest -rpcwallet=watch_only getnewaddress`
|
||||
```
|
||||
20. Create PSBT
|
||||
```shell
|
||||
# we need to use change address as we do not support change descriptor - yet
|
||||
psbt=$(bitcoin-cli -regtest -rpcwallet=watch_only walletcreatefundedpsbt '[]' '[{"bcrt1qagwmv5706xm8da8fh2dldk6rsjcxv06zae9w2nm8yex6rsvanf6s5re53w": 1.0}]' 0 '{"fee_rate": 20}' | jq -r '.psbt')
|
||||
```
|
||||
21. Sign with core (signer wallet)
|
||||
```shell
|
||||
psbt_core_signed=$(bitcoin-cli -regtest -rpcwallet=signer walletprocesspsbt $psbt true "ALL" | jq -r '.psbt')
|
||||
```
|
||||
22. Get half signed PSBT to Coldcard (via SD card or NFC) for signing
|
||||
```shell
|
||||
# move half signed PSBT to micro SD card (must end with .psbt)
|
||||
echo $psbt_core_signed > /media/MicroSD/core_signed.psbt
|
||||
```
|
||||
23. On Coldcard (Ready To Sign) verify transaction (check addresses, amounts...) and sign. Coldcard will produce `core_signed-part.psbt`. Copy this file back to PC.
|
||||
24. Finalize and send
|
||||
```shell
|
||||
$ tx_hex=$(bitcoin-cli -regtest finalizepsbt $(cat /media/MicroSD/core_signed-part.psbt | head -n 1) | jq -r ".hex")
|
||||
$ bitcoin-cli -regtest sendrawtransaction $tx_hex
|
||||
93fcd4e926978260406844ffb73a2e506cd427b0e0c3ee2882bffd6c2855d7a5 # tx id returned
|
||||
```
|
||||
@ -1,81 +1,93 @@
|
||||
# Developing on COLDCARD
|
||||
# Developers on Coldcard
|
||||
|
||||
Yes, external developers can modify COLDCARD and make their own versions!
|
||||
Yes, external developers can modify Coldcard... We've saved 128k of flash just for you!
|
||||
|
||||
## Approaches
|
||||
|
||||
### Hard Core
|
||||
|
||||
- build a new image, all the way to a DFU file (see `../stm32/Makefile`)
|
||||
- sign with non-production key, provided in github tree (key zero)
|
||||
- install your DFU file using existing upgrade methods (microSD, usb upload, VirtDisk)
|
||||
- sign with non-production key, provided in github tree
|
||||
- install your DFU file using existing upgrade methods (microSD, usb upload)
|
||||
- you can replace any part of the python code, and even the mpy interpreter itself
|
||||
- you cannot change the bootrom, and it still runs first
|
||||
- since your code is not signed by a factory key, a warning and forced delay always occurs:
|
||||
- your code will not be signed by the factory key, so warning and delay, is shown:
|
||||
|
||||

|
||||

|
||||
|
||||
- to get green light, the user (who knows the main PIN) must do the "bless" operation
|
||||
- you can distrubute your DFU file to the world
|
||||
- you can take factory-fresh Coldcards, destroy the tamper-evident bag, and load your
|
||||
firmware onto them before shipping to your customers.
|
||||
|
||||
- in versions before the Mk4, if you had the green light set, via blessing the custom firmware,
|
||||
this delay/warning could be avoided, but that is no longer the case.
|
||||
- you can distribute your DFU file to the world, but everyone who runs it will see above warning
|
||||
- remember the main PIN has to be set and provided correctly before new firmware can be installed
|
||||
- your COLDCARD will be bricked if your code crashes before it gets running "enough" that you
|
||||
can upload a corrected version. Bugs in the boot & login sequence are fatal in that sense.
|
||||
|
||||
### Medium Core
|
||||
|
||||
- develop your changes using the Simulator (see `../unix`)
|
||||
- submit a PR (pull request) explaining your new feature or fix.
|
||||
- Coinkite team will review for security and other code-quality issues
|
||||
- your PR could get merged into the next Coinkite firmware release for all to use.
|
||||
- install any published dev version (or build your own, see above)
|
||||
- enable the virtual disk feature:
|
||||
Advanced > Danger Zone > I Am Developer. > Enable USB Disk
|
||||
- REPL and mass-storage emulation will now be active
|
||||
- or you can just enable the REPL, and connect there and experiment first
|
||||
- expect to find helpful file: `/Volumes/COLDCARD/README.txt`
|
||||
- write code and save into `.../COLDCARD/lib`
|
||||
- any same-named file will replace built-in stock module
|
||||
- `lib/boot2.py` and `lib/main2.py` are loaded if they exist during boot sequence
|
||||
- the only python you cannot override is `main.py`
|
||||
- RAM limits complexity of the override you can do (but freezing your micropython helps)
|
||||
- you cannot change the bootrom, and it still runs first
|
||||
- changes to the virtual disk cause the light to go red, you'll need to bless after
|
||||
each change you make.
|
||||
- distribute your version by capturing an image of your working virtual disk drive,
|
||||
and then putting just that into a DFU file (128k)
|
||||
- users will have to have a similar dev version of main firmware, but
|
||||
you would probably give it to them, as separate DFU file.
|
||||
- both files could be put into a single DFU, but that's not supported by
|
||||
MicroSD upgrade method
|
||||
|
||||
### Soft Core
|
||||
|
||||
- send an email to support asking for your improvements to be implemented.
|
||||
- await reply patiently.
|
||||
|
||||
## Corrupt Flash
|
||||
|
||||
If the red/green light is red, this means some part of flash was
|
||||
changed without the secure checksum inside SE1 being first updated.
|
||||
The upgrade process does this correctly in Mk4, and there is no
|
||||
point in time the checksum is wrong, so there should be no way to see this
|
||||
screen:
|
||||
|
||||

|
||||
|
||||
But it will be shown if the COLDCARD finds its flash checksum does
|
||||
not match the checksum held in SE1, secured by the main PIN. This
|
||||
can be false positive, but in Mk4 we've worked hard to avoid those cases.
|
||||
|
||||
A checksum error on the firmware itself (the main code) will always
|
||||
fail with a "(lemon icon) Firmware?" screen. The broken firmware is not
|
||||
started, but it's possible to recover the COLDCARD using a firmware loaded
|
||||
from an SD Card.
|
||||
|
||||
You cannot load *new* code via the SD Card firmware recovery mode.
|
||||
It requires the new firmware (based on whatever is found on SD Card)
|
||||
to have a checksum that already matches the value found in SE1.
|
||||
This means only the signed firmware that was attempting to be
|
||||
installed during the power-fail can be loaded, and not new code you
|
||||
may have written.
|
||||
- Send an email to support asking for your altcoin to be supported. Await reply patiently.
|
||||
|
||||
|
||||
## Shortcuts and Accelerations
|
||||
## Shortcuts and Accerations
|
||||
|
||||
- You can access a micropython REPL if you are willing to break your case
|
||||
and attach to the test points along right edge of board, marked: G=Gnd, R=Rx, T=Tx.
|
||||
It's a serial port with 3.3v TTL signals running
|
||||
at 115,200 bps. Enter the REPL by pressing `^C` after enabling the REPL in
|
||||
Advanced > Danger Zone > I Am Developer. > Serial REPL
|
||||
|
||||
- To skip the prompts for the PIN, assuming correct PIN is '12-12'... run this code
|
||||
in the REPL:
|
||||
- You can enable USB, or USB disk emulation, automatically at the end of `boot2.py`.
|
||||
Mount the emulated disk, and create this file, as `/Volumes/COLDCARD/lib/boot2.py`
|
||||
|
||||
```python
|
||||
from nvstore import SettingsObject
|
||||
s=SettingsObject()
|
||||
s.set('_skip_pin', '12-12')
|
||||
s.save()
|
||||
# start the REPL very early
|
||||
import uasyncio.core as asyncio
|
||||
from usb import enable_usb
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
enable_usb(loop, True)
|
||||
```
|
||||
|
||||
- To skip the prompts for the PIN, assuming correct PIN is '12-12'... add this code
|
||||
to `boot2.py` or run once when the system hasn't yet logged in.
|
||||
|
||||
```python
|
||||
from main import settings
|
||||
settings.set('_skip_pin', '12-12')
|
||||
```
|
||||
|
||||
- For max crash-change-rerun speed, enable the mass storage all the
|
||||
time, and work directly in `/COLDCARD/lib` files. After making a
|
||||
change, you just need to do a warm reboot (^D in REPL, 'warm reset'
|
||||
on menus). At that point, `main.py` runs, and your code will be
|
||||
used again. The USB doesn't disconnect, and the drive will still
|
||||
be mounted, ready for more changes.
|
||||
|
||||
## Limitations
|
||||
|
||||
- You cannot enable mass storage, virtual comm port (VCP = REPL access) and also HID
|
||||
for the Coldcard protocol at the same time. Pick any two.
|
||||
|
||||
- `.py` files in `/lib` will be interpreted at runtime. This is slow, and bytecode
|
||||
takes large amounts of RAM. Some files in the normal code are too big to even
|
||||
fit in RAM. The solution is to freeze your code before copying onto Coldcard. See the
|
||||
`stm32/Makefile` target `up` which does a build (freezing all the files, using
|
||||
`mpy-cross`) and then rsyncs the changed `.mpy` files into place. You'll want to do
|
||||
this on a smaller scale, and probably only for the files you are working on.
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 4.5 KiB |
@ -5,12 +5,9 @@ wallet systems, but we also have a file format for general purpose
|
||||
exports, which we hope future wallet makers will leverage.
|
||||
|
||||
It contains master XPUB, XFP for that, and derived values for the top hardened
|
||||
position of the single-signature schemes BIP44, BIP49 and BIP84, plus the
|
||||
multisig schemes BIP48 (`bip48_1` = `.../1h` P2SH-P2WSH and `bip48_2` = `.../2h` P2WSH).
|
||||
When the account number is zero, a BIP45 (`m/45h`) multisig section is also included
|
||||
(it is omitted for non-zero accounts, as in the example below).
|
||||
position of BIP44, BIP84 and BIP49.
|
||||
|
||||
The feature can be found here: _Advanced/Tools > Export Wallet > Generic JSON_
|
||||
The feature can be found here: _Advanced > MicroSD > Export Wallet > Generic JSON_
|
||||
|
||||
Please contact us (or better yet, make a pull request), if you need something
|
||||
more in this file.
|
||||
@ -21,51 +18,32 @@ Here is an example, produced by the Simulator for account number 123.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"chain": "BTC",
|
||||
"chain": "XTN",
|
||||
"xfp": "0F056943",
|
||||
"xpub": "tpubD6NzVbkrYhZ4XzL5Dhayo67Gorv1YMS7j8pRUvVMd5odC2LBPLAygka9p7748JtSq82FNGPppFEz5xxZUdasBRCqJqXvUHq6xpnsMcYJzeh",
|
||||
"account": 123,
|
||||
"xpub": "xpub661MyMwAqRbcGC9DmWbtbAmuUjpMYxw4BWE88NSDHB3jSjfUK7KtYJuKa52GbowD3DVLkgsxH9QwPnTx5mjdHykYFEncnmAsNsCTbWzBhA7",
|
||||
"bip44": {
|
||||
"deriv": "m/44'/1'/123'",
|
||||
"first": "n44vs1Rv7T8SANrg2PFGQhzVkhr5Q6jMMD",
|
||||
"name": "p2pkh",
|
||||
"xfp": "5F898064",
|
||||
"deriv": "m/44h/0h/123h",
|
||||
"xpub": "xpub6DStQXfAgHuLbMpCf86ruVkF4yT9pSLyWsFiqQTWY9osuinq8Dyee4W5jCjMfyku5LNkRB9oFinrY5ufn9XXEn8Vvzc2jnifKMaQCNV7RBZ",
|
||||
"desc": "pkh([0f056943/44h/0h/123h]xpub6DStQXfAgHuLbMpCf86ruVkF4yT9pSLyWsFiqQTWY9osuinq8Dyee4W5jCjMfyku5LNkRB9oFinrY5ufn9XXEn8Vvzc2jnifKMaQCNV7RBZ/<0;1>/*)#4tl8jryn",
|
||||
"first": "1GTNtzG5xX2UhdD5e3Nu7i1WPxFdjxQMJt"
|
||||
"xfp": "B7908B26",
|
||||
"xpub": "tpubDCiHGUNYdRRGoSH22j8YnruUKgguCK1CC2NFQUf9PApeZh8ewAJJWGMUrhggDNK73iCTanWXv1RN5FYemUH8UrVUBjqDb8WF2VoKmDh9UTo"
|
||||
},
|
||||
"bip49": {
|
||||
"name": "p2sh-p2wpkh",
|
||||
"xfp": "A748B1FC",
|
||||
"deriv": "m/49h/0h/123h",
|
||||
"xpub": "xpub6DDm8WzH5a9qjKkttzqSB3uGofNohU9D3n3UG8WMxkUZzJEMPTYiQRf1dvTFCQR82MjGW4LUMVuTtnW4hF17RpzCqVwhf6Z2fnJPWtjG164",
|
||||
"desc": "sh(wpkh([0f056943/49h/0h/123h]xpub6DDm8WzH5a9qjKkttzqSB3uGofNohU9D3n3UG8WMxkUZzJEMPTYiQRf1dvTFCQR82MjGW4LUMVuTtnW4hF17RpzCqVwhf6Z2fnJPWtjG164/<0;1>/*))#5j7t2n2u",
|
||||
"_pub": "ypub6Y42SBfCEFhKacx1jMd4P8zmydXFe68hxtZh3XQFLkrT3Q3ae7iH2VK9f8QqCK53Rzr5FXw2pAG1n57dQwR8E4fohqe8F1NWwWN2uVRfBry",
|
||||
"first": "3CeBRbJKCpg7BpJME2vM8ZxhCjBnhG4toy"
|
||||
"_pub": "upub5DMRSsh6mNak9KbcVjJ7xAgHJvbE3Nx22CBTier5C35kv8j7g2q58ywxskBe6JCcAE2VH86CE2aL4MifJyKbRw8Gj9ay7SWvUBkp2DJ7y52",
|
||||
"deriv": "m/49'/1'/123'",
|
||||
"first": "2N87V39riUUCd4vmXfDjMWAu9gUCiBji5jB",
|
||||
"name": "p2wpkh-p2sh",
|
||||
"xfp": "CEE1D809",
|
||||
"xpub": "tpubDCDqt7XXvhAdy1MpSze5nMJA9x8DrdRaKALRRPasfxyHpiqWWEAr9cbDBQ9BcX7cB3up98Pk97U2QQ3xrvQsi5dNPmRYYhdcsKY9wwEY87T"
|
||||
},
|
||||
"bip84": {
|
||||
"_pub": "vpub5Y5a91QvDT45EnXQaKeuvJupVvX8f9BiywDcadSTtaeJ1VgJPPXMitnYsqd9k7GnEqh44FKJ5McJfu6KrihFXhAmvSWgm7BAVVK8Gupu4fL",
|
||||
"deriv": "m/84'/1'/123'",
|
||||
"first": "tb1qc58ys2dphtphg6yuugdf3d0kufmk0tye044g3l",
|
||||
"name": "p2wpkh",
|
||||
"xfp": "2C5207AA",
|
||||
"deriv": "m/84h/0h/123h",
|
||||
"xpub": "xpub6CaWStGvcXqSW9BzU2vpCoP7aWjz9VfR5DS2nuYWVvKV2nug2dESg3HdFsaWHeoZaxuAhNcPB3TH2gq8MugS3JX1yGuhB4QbC2BneaYqB16",
|
||||
"desc": "wpkh([0f056943/84h/0h/123h]xpub6CaWStGvcXqSW9BzU2vpCoP7aWjz9VfR5DS2nuYWVvKV2nug2dESg3HdFsaWHeoZaxuAhNcPB3TH2gq8MugS3JX1yGuhB4QbC2BneaYqB16/<0;1>/*)#yk84tprf",
|
||||
"_pub": "zpub6rF34DckutvQCjaE8kW4cya7vT2t2jeQuSUUMhLHFw5F8zY8XwZZvAbuJHVgHU7QQF8nCKoW6NANoG4FoJWTdmtDhxJYLt3ZjUK5RqUSMdF",
|
||||
"first": "bc1qhj6avwmp5lhpgqwm6dgxrf3v5lf67rjm99a8an"
|
||||
},
|
||||
"bip48_1": {
|
||||
"name": "p2sh-p2wsh",
|
||||
"xfp": "845A3542",
|
||||
"deriv": "m/48h/0h/123h/1h",
|
||||
"xpub": "xpub6EkcQSTygvxVnBP2X2fM6HY5D7wv46tWbBc54ADaypuCr47vQh1GPdPAZFdx81ou5Rp4vBnzeJT5MDWDZstzijxkHfrofXRycpt1ASfg1La",
|
||||
"desc": "sh(wsh(sortedmulti(M,[0f056943/48h/0h/123h/1h]xpub6EkcQSTygvxVnBP2X2fM6HY5D7wv46tWbBc54ADaypuCr47vQh1GPdPAZFdx81ou5Rp4vBnzeJT5MDWDZstzijxkHfrofXRycpt1ASfg1La/0/*,...)))",
|
||||
"_pub": "Ypub6kUxqLsLQa4M43jXJ3ux8SyP6t8dD5ZbpZmxkpP1jc7VXLW4RkZ76ouEPAZ1gMgiiXzrYFPfzBC8MfjYaoTxfTm1zUfdeqiTnHDX8raCfeg"
|
||||
},
|
||||
"bip48_2": {
|
||||
"name": "p2wsh",
|
||||
"xfp": "2A01C6B0",
|
||||
"deriv": "m/48h/0h/123h/2h",
|
||||
"xpub": "xpub6EkcQSTygvxVneXmk3ywiS2PFhBdiPxeMxYf6RFxHCHH36NxdcN7DjUpudCppAAxs58CG6DQLjtqZNmyC3MpgVob6wpdeATjpZZ1woX92EF",
|
||||
"desc": "wsh(sortedmulti(M,[0f056943/48h/0h/123h/2h]xpub6EkcQSTygvxVneXmk3ywiS2PFhBdiPxeMxYf6RFxHCHH36NxdcN7DjUpudCppAAxs58CG6DQLjtqZNmyC3MpgVob6wpdeATjpZZ1woX92EF/0/*,...))",
|
||||
"_pub": "Zpub75KE91YFZFbpup5PMS2AxgZCKRWnozdEWTEmaUKGQysSmUaKuL5WYyf2kk5UNQhhupRnddQe9GzST7crvfLoRTHTg6KtDPZiFjxBJobzcUz"
|
||||
"xfp": "78CF94E5",
|
||||
"xpub": "tpubDC7jGaaSE66VDB6VhEDFYQSCAyugXmfnMnrMVyHNzW9wryyTxvha7TmfAHd7GRXrr2TaAn2HXn9T8ep4gyNX1bzGiieqcTUNcu2poyntrET"
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -73,23 +51,16 @@ Here is an example, produced by the Simulator for account number 123.
|
||||
## Notes
|
||||
|
||||
1. The `first` address is formed by added `/0/0` onto the given derivation, and is assumed
|
||||
to be the first (non-change) receive address for the wallet. It is only present on the
|
||||
single-signature sections (`bip44`, `bip49`, `bip84`); multisig sections omit it.
|
||||
|
||||
1a. Each section includes a `desc` field: a ready-to-import Bitcoin output descriptor
|
||||
(with `#checksum`). Single-sig descriptors use the `<0;1>/*` multipath form. Multisig
|
||||
sections (`bip48_1`, `bip48_2`, and `bip45` when present) emit a `sortedmulti(...)`
|
||||
template with `M` and a trailing `...` as placeholders, to be completed with your
|
||||
threshold and the other co-signers' keys.
|
||||
to be the first (non-change) receive address for the wallet.
|
||||
|
||||
2. The user may specify any value (up to 9999) for the account number, and it's meant to
|
||||
segregate funds into sub-wallets. Don't assume it's zero.
|
||||
|
||||
3. When making your PSBT files to spend these amounts, remember that the XFP of the master
|
||||
(`0F056943` in this example) is the root of the subkey paths found in the file, and
|
||||
(`0F056943` in this example) is is the root of the subkey paths found in the file, and
|
||||
you must include the full derivation path from master. So based on this example,
|
||||
to spend a UTXO on `bc1qhj6avwmp5lhpgqwm6dgxrf3v5lf67rjm99a8an`, the input section
|
||||
of your PSBT would need to specify `(m=0F056943)/84'/0'/123'/0/0`.
|
||||
to spend a UTXO on `tb1qc58ys2dphtphg6yuugdf3d0kufmk0tye044g3l`, the input section
|
||||
of your PSBT would need to specify `(m=0F056943)/84'/1'/123'/0/0`.
|
||||
|
||||
4. The `_pub` value is the [SLIP-132](https://github.com/satoshilabs/slips/blob/master/slip-0132.md) style "ypub/zpub/etc" which some systems might want. It implies
|
||||
a specific address format.
|
||||
|
||||
23
docs/installing.md
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
# Check-out and build
|
||||
|
||||
- clone repo
|
||||
- do submodule magic in `external`
|
||||
- make top-level virtual env: `virtualenv -p python3 ENV`
|
||||
- activate it
|
||||
- then:
|
||||
|
||||
cd external/ckcc-protocol
|
||||
pip install -r requirements.txt
|
||||
pip install --editable .
|
||||
cd ../..
|
||||
pip install -r requirements.txt
|
||||
pip install -r unix/requirements.txt
|
||||
|
||||
- should give you a command-line program "ckcc" in your path
|
||||
- should be able to do:
|
||||
|
||||
cd unix
|
||||
make && ./simulator.py
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 27 KiB |
@ -1,224 +0,0 @@
|
||||
|
||||
# Key Teleport
|
||||
|
||||
Purpose: Send a small quantity of very secret data between two COLDCARD Q systems, with
|
||||
no risk of anything in the middle learning the secret.
|
||||
|
||||
Method: ECDH and AES-256-CTR plus an extra wrapping layer, transmitted over a mixture of
|
||||
NFC, passive websites, and QR/BBQr codes.
|
||||
|
||||
# Protocol Overview
|
||||
|
||||
## Steps
|
||||
|
||||
- Receiver picks an EC keypair, stores it in settings, and publishes the pubkey via a QR/NFC
|
||||
- The pubkey is encrypted by a short 8-digit numeric code, which should be
|
||||
sent by a different channel.
|
||||
- Sender gets QR and numeric code, picks own keypair, and does ECDH to arrive at a
|
||||
shared session key
|
||||
- Sender picks a human-readable secret which is independent of anything else (P key)
|
||||
- The secret data (perhaps a seed phrase, XPRV, secure note, full backup, etc) is
|
||||
AES-256-CTR encrypted with P key, then encrypted + MAC added with session key
|
||||
- Data packet is sent to receiver (via BBQr), who can reconstruct the session key via ECDH
|
||||
- Prompt user for the P key to finish decoding
|
||||
- Decoded secret value is saved to Seed Vault or secure notes as appropriate
|
||||
- Receiver destroys EC keypair used in transfer
|
||||
|
||||
### When used for PSBT Multisig
|
||||
|
||||
- No action required on receiver
|
||||
- Sender uses the pubkey derived from pre-shared XPUB involved in the multisig wallet.
|
||||
- Same steps, but drops immediately into signing process when decoded correctly
|
||||
|
||||
## Notes and Limitations
|
||||
|
||||
- max 4k (after encoding) of data is possible due to HTTP limitations
|
||||
- all transfers are "data typed" and decode only on COLDCARD
|
||||
- Q model is required due to the use of QR codes to ultimately get data into the COLDCARD
|
||||
|
||||
|
||||
# Details
|
||||
|
||||
## Data Type Codes
|
||||
|
||||
The first byte encodes what the package contents (under all the encryption).
|
||||
|
||||
- `s` - 12/18/24 words/raw master/xprv - 17-72 bytes follow, encoded in an internal format
|
||||
- `x` - XPRV mode, full details - 4 bytes (XPRV) + base58 *decoded* binary-XPRV follows
|
||||
- `n` - one or many notes export (JSON array)
|
||||
- `v` - seed vault export (JSON: one secret key but includes name, source of key)
|
||||
- `p` - binary PSBT to be signed, perhaps multisig but not required.
|
||||
- `b` - complete system backup file (text lines, internal format)
|
||||
|
||||
## QR details
|
||||
|
||||
BBQr is always used for the QR's involved in this process, even if
|
||||
they are short enough for a normal QR code. Because the BBQr is
|
||||
being generated by the COLDCARD embedded firmware, it will not be
|
||||
compressed and will always be Base32 encoded.
|
||||
|
||||
New type codes for BBQr are defined for the purposes of this application:
|
||||
|
||||
- `R` contains `(pubkey)` ... begins the process from receiver; compressed pubkey is 33 bytes
|
||||
- `S` contains `(pubkey)(data)` ... data from sender; first 33 bytes are sender's pubkey
|
||||
- `E` for Multisig PSBT: `(randint)(data)` ... randint (4 byte nonce) indicates which
|
||||
derived subkey from pre-shared xpub associated with receiver
|
||||
|
||||
All the data is encrypted with the exception randint. Keep in mind
|
||||
this is a nonce value picked uniquely for each transfer. The
|
||||
receiver's pubkey is only weakly encrypted by the 8-digit numeric
|
||||
password, but is also a nonce effectively.
|
||||
|
||||
### PSBT Key Selection
|
||||
|
||||
When sending PSBT data, a nonce is picked at random by the sender
|
||||
in range: `0..(2^28)`
|
||||
|
||||
This nonce is called `randint`. The receiver's pubkey will be
|
||||
|
||||
.../20250317/(randint)
|
||||
|
||||
where `...` is the derivation used in the multisig wallet for the co-signer who will
|
||||
receive the package. The sender's keypair has the same sub key path assuming all
|
||||
co-signers have same derivation path from root (not required).
|
||||
|
||||
Because both the sender and receiver already have each other's XPUB they can derive
|
||||
the appropriate pubkeys (and privkey for their side) without communicating
|
||||
more than `randint`. The sending COLDCARD will pick a new random value each time.
|
||||
|
||||
When receiving a multisig PSBT encrypted this way, the receiver does not need
|
||||
to do any setup (nor numeric password) and can receive a QR code at any time.
|
||||
This works because the shared multisig wallet is already setup. Receiver will
|
||||
take the nonce value (randint) and seach all pre-defined multisig wallets for
|
||||
any pubkey that can decrypt the package successfully (based on checksum inside
|
||||
first layer of ECDH encryption).
|
||||
|
||||
The next layer of encryption (paranoid password) is unchanged.
|
||||
|
||||
## Encryption Details
|
||||
|
||||
AES-256-CTR is used exclusively. Session key is picked via ECDH with final
|
||||
key value being the SHA256 over 64 bytes of coordinate X (concat) Y.
|
||||
|
||||
While ECDH is enough to assure privacy from men in the middle, we
|
||||
add an additional layer of encryption. We call this the "paranoid key" internally
|
||||
and in the UX it is called "Teleport Password".
|
||||
|
||||
The user sees a random 8-character password, generated as a random 40-bit value, but
|
||||
shown in Base32 (8 chars) for the human to enter. We apply PBKDF2-SHA512 with
|
||||
an iteration count of 5000 to stretch that to 512 bits, of which we use half.
|
||||
The session key is used as the key for the KDF, and the entered value as salt.
|
||||
|
||||
- ECDH arrives at session key
|
||||
- decrypt (AES-256-CTR) the binary body of message
|
||||
- verify checksum:
|
||||
- final 2 bytes should be `== SHA256(decrypted body[0:-2])[-2:]`
|
||||
- if not, corruption, truncation, or wrong keys
|
||||
- if that decryption is correct, then prompt user for the paranoid key (8 chars)
|
||||
- stretch that value using session key and 5000 iterations of PBKDF2-SHA512
|
||||
- use upper 256 bits and run AES-256-CTR again
|
||||
- same checksum of 2 bytes of SHA256 are found inside after decryption
|
||||
|
||||
Encryption adds 4 bytes of overhead because of these MAC values,
|
||||
but should catch truncation and bitrot. There are no other
|
||||
protections against truncation as length data is not transmitted.
|
||||
|
||||
# Receiver Password
|
||||
|
||||
When the teleport process is started, the receiver shares his pubkey
|
||||
as QR. However, we also show an 8-digit numeric password. The
|
||||
purpose of this is force the receiver to share this separately from
|
||||
the pubkey QR on another channel. The code is randomly picked, but
|
||||
only represents about 26 bits of entropy and is stretched with
|
||||
a single round of SHA256 before being used as a AES-256-CTR key
|
||||
to decrypt the pubkey. No checksum verifies correct
|
||||
decryption, so any code is accepted, and will with near-50% odds,
|
||||
decrypt to a valid pubkey.
|
||||
|
||||
When the sender is given the receiver's pubkey via QR code, it
|
||||
prompts for the numeric code and uses it to decrypt the pubkey.
|
||||
Thus a MiTM who injects their pubkey will be detected and blocked.
|
||||
|
||||
The "paranoid key" serves the same role in the other direction but
|
||||
it is Base32 character set, so it will not look similar or be
|
||||
confusing as to its purpose.
|
||||
|
||||
# Web Component
|
||||
|
||||
In order to "teleport" the contents of a QR code over NFC, we will
|
||||
publish a static website directly from an open Github repository.
|
||||
The single-page website contains javascript code which looks at the
|
||||
"hash" part of the incoming URL (`window.location.hash`) and if it
|
||||
meets the requirements, renders a large QR. The QR data must look like
|
||||
a correctly-encoded BBQr with one of the 3 type-codes above (`R` `S` or `E`).
|
||||
Otherwise the website could render any QR, which we don't want to
|
||||
support.
|
||||
|
||||
The page will offer "copy to clipboard" features for the data inside
|
||||
the QR as a URL (ie. same URL as shown) and as an image and of course,
|
||||
the COLDCARD Q can scan from the web browser screen itself.
|
||||
|
||||
When the BBQr data is larger than comfortable for a single QR, the
|
||||
website can split into a multi-frame BBQr. The website can
|
||||
do this without understanding the contents of the BBQr data (all
|
||||
of which is encrypted). Download options will be provided for
|
||||
single-frame QR, animated PNG, and "stacked BBQr" (a single tall
|
||||
PNG with each QR frame stacked).
|
||||
|
||||
On the COLDCARD side, when NFC is tapped, it will offer a long URL
|
||||
to this site with the data to be transferred "after the hash". This
|
||||
is optional since the QR can be shown on the Q itself, and would
|
||||
pass the same data.
|
||||
|
||||
Since the website is running on Github, Coinkite does not have
|
||||
access to IP addresses or other access log details. Because the data for
|
||||
teleport is "after the hash" it is never sent to Github's servers
|
||||
but remains in the browser only. All JS resources referenced by the
|
||||
webpage will have content hashes applied to prevent interference,
|
||||
and the site will be served over SSL.
|
||||
|
||||
Visit [keyteleport.com](https://keyteleport.com/), or an
|
||||
[example small QR](https://keyteleport.com/#B$2R0100VHT2AGUUH7KUZUUSTOWOIWHJX3XM7GA2N4BHQOXDFHXLVHVA7K6ZO)
|
||||
and [view source code](https://github.com/coinkite/keyteleport.com).
|
||||
|
||||
# UX Details
|
||||
|
||||
- When the receive process is started by the user, a pubkey is picked
|
||||
and stored, so that they can come back later (after a power cycle)
|
||||
and make use of the data encoded by the sender. However once a package
|
||||
is decoded successfully, that key is deleted.
|
||||
|
||||
- Sender must start by scanning the QR from a receiver. Then can pick what
|
||||
to send, from secure notes to seeds and so on.
|
||||
|
||||
- For PSBT multisig, user must pick a single co-signer (who hasn't already
|
||||
signed) and the QR is prepared for that receiver. Because we
|
||||
cannot do arbitary combining, it's best if the next signer continues
|
||||
to teleport the updated PSBT to further signers. In other words,
|
||||
a daisy-chain pattern is prefered to a star pattern. The signer
|
||||
who completes the Mth (of N) signature will be able to finalize
|
||||
the transaction, and ideally with PushTx feature, broadcast it.
|
||||
|
||||
# Security Comments
|
||||
|
||||
## Such short passwords?
|
||||
|
||||
We are using 8-character passwords because we want them to be
|
||||
practical to share over non-digital channels such as a voice phone
|
||||
call, or hand-written note.
|
||||
|
||||
It is very important to remind users that the passwords should be sent
|
||||
by a different channel from the QR itself. Best is to call up your
|
||||
other party and say the letters to them directly.
|
||||
|
||||
## Is it safe to save image of QR to cloud?
|
||||
|
||||
Yes, this seems safe. Of course, if you can control it, perhaps not
|
||||
a risk to accept... but the QR is encrypted via ECDH using a key
|
||||
that is forgotten after the transfer, so forward privacy is protected.
|
||||
Also your cloud service (or photo roll, chat app log, etc) will not
|
||||
have the 8-character password which is also required unpack the secrets.
|
||||
|
||||
The QR codes themselves are fully random and do not reveal the
|
||||
identity of your COLDCARD, your on chain funds or anything linked
|
||||
to you.
|
||||
@ -1,12 +1,12 @@
|
||||
# BIP-39 Import
|
||||
# BIP39 Import
|
||||
|
||||
- there must be 12, 18 or 24 words in your mnemonic
|
||||
- there must be 12, 16 or 24 words in your mnemonic
|
||||
- we have only the English word list
|
||||
- we support BIP-39 passwords during import
|
||||
- ~~we do not support BIP39 passwords during import~~ (full support added in version 2.0.0)
|
||||
|
||||
# XPRV Import
|
||||
|
||||
- we can import a BIP-32 HD-wallet root private key in XPRV format
|
||||
- we can import a BIP32 HD-wallet root private key in XPRV format
|
||||
- it's assumed to be top-level, and we don't store the parent fingerprint or depth value
|
||||
- SLIP-132 format HD-wallet keys are also supported (xprv/yprv/zprv), but we strip
|
||||
the implied address format
|
||||
@ -14,12 +14,11 @@
|
||||
# PIN Codes
|
||||
|
||||
- 2-2 through 6-6 in size, numeric digits only
|
||||
- pin code 999999-999999 was reserved (meaning 'clear pin'), but now available again
|
||||
- pin code 999999-999999 is reserved (means 'clear pin')
|
||||
|
||||
# Backup Files
|
||||
|
||||
- we don't know what day it is, so meta data on files will not have correct date/time
|
||||
- release date of the firmware version that made the file is used instead of true date
|
||||
- encrypted files produced cannot be changed, and we don't support other tools making them
|
||||
|
||||
# Micro SD
|
||||
@ -33,78 +32,51 @@
|
||||
- with Electrum, we support classic payment addresses (p2pkh), Bech32 Segwit and P2SH/Segwit
|
||||
- however, each wallet must be of a single address type; cannot be mixed (their limitation)
|
||||
- the same Coldcard could be used in each of the three modes (we don't care about address format)
|
||||
- with Bitcoin Core (version 0.17+), we can do PSBT transactions, which support all address types
|
||||
- we don't support signing coinbase transactions, so don't mine directly into a Coldcard wallet
|
||||
- with Bitcoin Core (version 0.17?), we can do PSBT transactions, which support all address types
|
||||
- we don't support coinbase transactions, so don't mine directly into a Coldcard wallet
|
||||
|
||||
# Max Transaction Size
|
||||
|
||||
- mk3:
|
||||
- we support transactions up to 384k-bytes in size when serialized into PSBT format
|
||||
- we can handle transactions with up to 20 inputs to be signed at one time.
|
||||
- a maximum of 250 outputs per transaction is supported (will attempt more if memory allows)
|
||||
- mk4/Q:
|
||||
- we support PSBT files up to 2M bytes in size.
|
||||
- any number of inputs and outputs are supported, limited only by final transaction size (100k)
|
||||
- tested with: 250 inputs, 2000 outputs
|
||||
- we support transactions up to 384k-bytes in size when serialized into PSBT format
|
||||
- bitcoin limits transactions to 100k, but there could be large input transactions
|
||||
inside the PSBT. Reduce this by using segwit signatures and provide only the
|
||||
individual UTXO ("out points").
|
||||
- every transaction needs to have at least one output, or we reject it
|
||||
- we can handle transactions with up to 20 inputs to be signed at one time.
|
||||
- a maximum of 250 outputs per transaction is supported (will attempt more if memory allows)
|
||||
|
||||
|
||||
# P2SH / Multisig
|
||||
|
||||
- only one signature will be added per input. However, if needed the partly-signed
|
||||
PSBT can be given again, and the "next" leg will be signed.
|
||||
- finalizing of multisig transactions involving P2SH signatures:
|
||||
* SD/Vdisk signing exports both signed PSBT and finalized txn ready for broadcast (if txn is complete)
|
||||
* QR/NFC outputs finalized txn ready for broadcast if txn is complete otherwise signed PSBT only
|
||||
* USB signing requires `--finalize` parameter (as for standard single signature wallets)
|
||||
|
||||
- we do not support PSBT combining or finalizing of transactions involving
|
||||
P2SH signatures (so the combine step must be off-device)
|
||||
- we can sign for P2SH and P2WSH addresses that represent multisig (M of N) but
|
||||
we cannot sign for non-standard scripts because we don't know how to present
|
||||
that to the user for approval.
|
||||
- during USB "show address" for multisig, we limit subkey paths to
|
||||
16 levels deep (including master fingerprint)
|
||||
- max of 15 co-signers due to 1650 byte `scriptSig` limitation in policy with classic P2SH (same limit applies to segwit even though consensus allows up to 20 co-signers).
|
||||
note: the consensus layer sets an upper bound of 520 bytes for the length of each stack element
|
||||
- (mk3) we have space for up to 8 M-of-3 wallets, or a single M-of-15 wallet. YMMV
|
||||
- max of 15 co-signers due to 520 byte script limitation in consensus layer with classic P2SH
|
||||
- we have space for up to 8 M-of-3 wallets, or a single M-of-15 wallet. YMMV
|
||||
- only a single multisig wallet can be involved in a PSBT; can't sign inputs from two different
|
||||
multisig wallets at the same time.
|
||||
- we always store xpubs in BIP-32 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
|
||||
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
|
||||
- XFP values (fingerprints) MUST be unique for each of the co-signers
|
||||
- multisig wallet `name` can only contain printable ASCII characters `range(32, 127)`
|
||||
|
||||
### BIP-67
|
||||
|
||||
- importing multisig from PSBT can ONLY create `sortedmulti(...)` multisig according to BIP-67, DO NOT use with `multi(...)`
|
||||
- creating airgapped multisig using COLDCARD as coordinator always produces `sortedmulti(...)` multisig according to BIP-67
|
||||
- COLDCARD import/export [format](https://coldcard.com/docs/multisig/#configuration-text-file-for-multisig) only supports `sortedmulti(...)` multisig according to BIP-67. To import multisig wallet with `multi(...)` use descriptor import [format](https://github.com/bitcoin/bips/blob/master/bip-0383.mediawiki)
|
||||
- encrypted COLDCARD backups that contains multisig wallets with `multi(...)` MUST only be restored on firmware versions with `multi(...)` support
|
||||
- all imported `multi(...)` must differ in keys (same as `sortedmulti(...)`). If `wsh(multi(2,A,B))` is already registered, `wsh(multi(2,B,A))` will be rejected upon import as duplicate, even thought they are actually different script/wallet.
|
||||
- just BIP67 difference is also treated as duplicate. If `wsh(multi(2,A,B)` is registered, `wsh(sortedmulti(2,A,B))` will be rejected as duplicate and vice-versa.
|
||||
- fixed: XFP values (fingerprints) for each of the co-signers must be unique (limitation removed)
|
||||
|
||||
# SIGHASH types
|
||||
|
||||
- all sighash flags are supported:
|
||||
- `ALL`
|
||||
- `NONE`
|
||||
- `SINGLE`
|
||||
- `ALL|ANYONECANPAY`
|
||||
- `NONE|ANYONECANPAY`
|
||||
- `SINGLE|ANYONECANPAY`
|
||||
- any value other than ALL will cause a warning to be shown to user
|
||||
- by default, we reject `NONE` and `NONE|ANYONECANPAY` but there is a setting to allow
|
||||
- only `SIGHASH_ALL` is supported at this time
|
||||
- in time, we will add support for others, especially to support Coinjoin usage
|
||||
|
||||
# U2F Protocol / Web Access to USB / WebUSB
|
||||
|
||||
- we do not support U2F protocol, WebUSB or any other means for random websites to talk to us
|
||||
- only native desktop/mobile apps, or helpers for those, will be able to talk USB to Coldcard
|
||||
|
||||
# Fee Limits / Warnings
|
||||
# Policy Stuff
|
||||
|
||||
- Coldcard will, by default, reject any txn that pays a fee of more than 10% of its total
|
||||
value to miners. This limit is a setting: 10% (default), 25%, 50% or 'no limit'.
|
||||
@ -117,9 +89,9 @@
|
||||
|
||||
# Change Outputs
|
||||
|
||||
We will summarize transaction outputs as "change" back into same wallet, however:
|
||||
We will hide transaction outputs if they are "change" back into same wallet, however:
|
||||
|
||||
- PSBT must specify BIP-32 path in corresponding output section for us to treat as change
|
||||
- PSBT must specify BIP32 path in corresponding output section for us to treat as change
|
||||
- for p2sh-wrapped segwit outputs, redeem script must be provided when needed
|
||||
- any incorrect values here are assumed to be fraud attempts, and are highlighted to user
|
||||
- the _redeemScript_ for `p2wsh-p2sh` is optional, but if provided must be
|
||||
@ -140,84 +112,5 @@ We will summarize transaction outputs as "change" back into same wallet, however
|
||||
|
||||
- key derivatation paths must be 12 or less in depth (`MAX_PATH_DEPTH`)
|
||||
|
||||
# Pay-to-Pubkey
|
||||
|
||||
- although we have some code for "pay to pubkey" (P2PK not P2PKH), it is untested
|
||||
and unused since this style of payment address is obsolete and largely unused today
|
||||
|
||||
# NFC Feature
|
||||
|
||||
- can share up to 8000 bytes of PSBT or signed transaction data.
|
||||
- NFC-V (ISO-15693) radio/modulation is common on mobile phones but very rare on desktops
|
||||
|
||||
# Fast Wipe
|
||||
|
||||
- each use of "fast wipe" feature consumes a MCU key slot, of which there are 256.
|
||||
- use _Advanced > Danger Zone > MCU Key Slots_ to view usage
|
||||
|
||||
# Trick Pins
|
||||
|
||||
- "deltamode" PIN must be same length as true pin, and differ only in final 4 positions.
|
||||
- there are 14 trick "slots", but on mk4, we avoid slot 10, so 13 available. (Q: 14)
|
||||
- duress wallets consume 2 slots (or 3 slots for legacy duress wallet) which must be contiguous
|
||||
- when restoring trick pins from backup files, "forgotten" pins are not restored,
|
||||
and any trick pin which matches the true PIN of the restored system will be dropped
|
||||
- deltamode PIN requirements are checked during wallet restore, and if the new true PIN
|
||||
is not compatible, the deltamode trick PIN is dropped and not restored
|
||||
- duress wallets are supported when derived from 24- or 12-word seed phrases
|
||||
|
||||
# Debug Serial Port
|
||||
|
||||
- virtual USB serial port disabled completely by default, and even if enabled
|
||||
in Danger Zone, only echos output, and does not accept any input
|
||||
- use hardware serial port for interactive REPL access (3.3v TTL levels)
|
||||
|
||||
# BBQr Scanning (Q)
|
||||
|
||||
- files up to 2MiB in size can be accepted
|
||||
- when 14 or less parts, we display status of each part, if more, just percent complete
|
||||
|
||||
# QR Scanning (Q)
|
||||
|
||||
- if not BBQr (or sent as unicode inside BBQr) we auto detect these data types:
|
||||
- PSBT in Base64 or hex
|
||||
- Wire Transaction in Base64 or hex
|
||||
- XPRV, XPUB, bare payment addresses, BIP-21 invoices
|
||||
- seed words (truncated, or full)
|
||||
- SeedQR (but not Compact SeedQR)
|
||||
- for Base58, Bech32 encoded values, if checksum is wrong then it is shown as text
|
||||
- binary QR codes are not supported
|
||||
- when pasting into a secure note, if the QR's data is > 60 chars long, we assume done
|
||||
|
||||
# Secure Notes & Passwords (Q)
|
||||
|
||||
- when Q picks a password for you (using F1 thru F5), the entropy is 126 bits or more,
|
||||
except F3 which makes an easier to type password of around 48 bits entropy.
|
||||
- Title, Sitename and Username fields are limited to 32 characters in length.
|
||||
- Passwords are limited to 128 characters.
|
||||
- Note text is unlimited, but storing very large notes may affect performance system-wide.
|
||||
- Q Backup files restored onto Mk4 will lose their notes, since notes feature is not supported.
|
||||
|
||||
# Address Ownership
|
||||
|
||||
- only the first 1528 addresses (764 each from internal and external (change/not) paths)
|
||||
are considered
|
||||
- if you have used an sub-account, without ever exploring it in the Address Explorer, nor
|
||||
signing a PSBT with those account numbers, we do not search it.
|
||||
- does not search Seed Vault, you'll need to load each of those and re-search
|
||||
- if you have an XFP collision between multiple wallets in SeedVault (ie. two wallets
|
||||
with same descriptors, but different seeds) you will get false negatives
|
||||
|
||||
# Spending Policy
|
||||
|
||||
- (Cosign mode) only 12 or 24 word seeds (not XPRV) are accepted for "key C"
|
||||
- velocity limit:
|
||||
- based on a max magnitude per txn, and a required minimum block height
|
||||
gap, based on previous `nLockTime` value in last-signed PSBT.
|
||||
- if you sign a transaction, but never broadcast it, you will still have to wait out
|
||||
the velocity policy.
|
||||
- PSBT creator must put in `nLockTime` block heights (most already do to avoid fee sniping)
|
||||
- maximum of 25 whitelisted addresses can be stored
|
||||
- Web2FA: any number of mobile devices can be enrolled, but all will have the same shared secret
|
||||
- any warning from the PSBT, such as huge fees, will cause the transaction to be rejected
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
## Background
|
||||
|
||||
The microprocess on the Coldcard is from the STM32L4 family. It comes with
|
||||
one or 2 megabytes of flash, and 128k to 512k of SRAM depending on Mk2/3/4.
|
||||
All types of memory share the same 32-bit address space.
|
||||
The microprocess on the Coldcard is the STM32L476R?. It comes with
|
||||
one megabyte of flash, and 128k of SRAM. All types of memory share
|
||||
the same 32-bit address space.
|
||||
|
||||
The bootloader code runs first, and enables specific hardware
|
||||
firewall features, which cover various parts of the address space.
|
||||
@ -13,15 +13,11 @@ so for example, you cannot see any of the flash used by the boot loader.
|
||||
|
||||
If you want to verify the contents of the boot loader, you can give
|
||||
it a 32-bit nonce and it will provide a SHA256 of itself with that
|
||||
nonce as a prefix. That hash covers `0x0800 0000` to `0x0800 7800`
|
||||
(to `0x0801 c000` for mk4).
|
||||
Flash above `0x0800 8000` (Mk4: `0x0802 0000`) can be examined
|
||||
directly from python programs.
|
||||
nonce as a prefix. That hash covers `0x0800 0000` to `0x0800 7800`.
|
||||
Flash above `0x0800 8000` can be examined directly from python programs.
|
||||
|
||||
## Memory Layout
|
||||
|
||||
(Mk3)
|
||||
|
||||
| Start | Size | Notes
|
||||
|---------------|-----------|--------------------------
|
||||
| 0x0800 0000 | 0x7800 | Mapped at zero briefly at boot time. Code. see `stm32/bootloader`
|
||||
@ -34,33 +30,18 @@ directly from python programs.
|
||||
| 0x1000 7c00 | 0x0400 | Read-only. "Root seed" (once per bootup nonce), copy of firmware sig
|
||||
| 0x2000 0000 | 96k | SRAM1: heap and working SRAM for micropython
|
||||
|
||||
(Mk4)
|
||||
|
||||
| Start | Size | Notes
|
||||
|---------------|-----------|--------------------------
|
||||
| 0x0800 0000 | 112k | Bootloader code, including reset vector. See `stm32/mk4-bootloader`
|
||||
| 0x0801 c000 | 8k | Sensitive "pairing secrets" for SE1 and SE2
|
||||
| 0x0801 e000 | 8k | MCU keys, consumable; 256 32-bit write-once slots.
|
||||
| 0x0802 0000 | 16k | Interrupt handlers, file header (Micropython and Coldcard code)
|
||||
| 0x0802 4000 | (~2m) | Main flash area for Micropython / Coldcard C code.
|
||||
| 0x0818 0000 | 512k | Internal LFS filesystem (holds settings)
|
||||
| 0x2000 0000 | 640k | SRAM1,2,3: used by micropython code: disk caches, byte arrays, stack
|
||||
| 0x2009 e000 | 8k | Top of SRAM3 reserved for bootloader
|
||||
|
||||
|
||||
## Security Measures
|
||||
|
||||
- (Mk1-3) On entry the bootloader always wipes its entire working SRAM2 area. You may change
|
||||
- On entry the bootloader always wipes its entire working SRAM2 area. You may change
|
||||
it, or even use it for very temporary storage, but it will be destroyed once the callgate
|
||||
into the bootloader is accessed.
|
||||
- (Mk4) On entry the bootloader wipes the SRAM it's allocated before and after use.
|
||||
- All of SRAM is cleared on boot up, and when the "secure logout" feature is used.
|
||||
- DFU firmware updates can only affect areas at and above the bootrom. Upgrade process will
|
||||
have not effect if you give a DFU file which changes another area. Built-in DFU is disabled
|
||||
- All of SRAM1 and SRAM2 is cleared on boot up, and when the "secure logout" feature is used.
|
||||
- DFU firmware updates can only affect areas at and above 0x08008000. Upgrade process will
|
||||
crash (harmlessly) if you give a DFU file which changes another area. DFU is disabled
|
||||
once the system leaves the factory.
|
||||
- If you manage to erase the entire chip's flash (not clear that's possible), then you will
|
||||
lose the pairing secret (0x0800 7800 / 0x0800 c000) and be unable to communicate with the
|
||||
secure element(s).
|
||||
lose the pairing secret (0x0800 7800) and be unable to communicate with the secure element.
|
||||
- Boot up verification process does a double-SHA256 over all of flash (including the pairing
|
||||
secret area) and also a few registers that are loaded from flash cells.
|
||||
See `verify.c` in `stm32/bootloader`.
|
||||
|
||||
@ -1,724 +0,0 @@
|
||||
[IF NO PIN SET]
|
||||
Choose PIN Code
|
||||
Advanced/Tools
|
||||
View Identity
|
||||
Paper Wallets
|
||||
Perform Selftest
|
||||
Secure Logout
|
||||
Bag Number
|
||||
Help
|
||||
---
|
||||
|
||||
[IF BLANK WALLET]
|
||||
New Seed Words
|
||||
12 Words
|
||||
24 Words
|
||||
Advanced
|
||||
12 Word Dice Roll
|
||||
24 Word Dice Roll
|
||||
Import Existing
|
||||
12 Words
|
||||
[SEED WORD ENTRY]
|
||||
18 Words
|
||||
[SEED WORD ENTRY]
|
||||
24 Words
|
||||
[SEED WORD ENTRY]
|
||||
Scan QR Code [IF QR SCANNER]
|
||||
Restore Backup
|
||||
Clone Coldcard
|
||||
Import XPRV
|
||||
Tapsigner Backup
|
||||
Seed XOR
|
||||
Migrate Coldcard
|
||||
Key Teleport (start)
|
||||
Help
|
||||
Advanced/Tools
|
||||
View Identity
|
||||
Temporary Seed
|
||||
Generate Words
|
||||
12 Words
|
||||
24 Words
|
||||
12 Word Dice Roll
|
||||
24 Word Dice Roll
|
||||
Import from QR Scan [IF QR SCANNER]
|
||||
Import Words
|
||||
12 Words
|
||||
18 Words
|
||||
24 Words
|
||||
Import via NFC [IF NFC ENABLED]
|
||||
Import XPRV
|
||||
Tapsigner Backup
|
||||
Coldcard Backup
|
||||
Restore Seed XOR
|
||||
Upgrade Firmware [IF NOT TMP SEED]
|
||||
Show Version
|
||||
From MicroSD
|
||||
From VirtDisk [IF VIRTDISK ENABLED]
|
||||
File Management
|
||||
Verify Backup
|
||||
List Files
|
||||
Verify Sig File
|
||||
NFC File Share [IF NFC ENABLED]
|
||||
BBQr File Share [IF QR SCANNER]
|
||||
QR File Share [IF QR SCANNER]
|
||||
Format SD Card
|
||||
Format RAM Disk [IF VIRTDISK ENABLED]
|
||||
Key Teleport (start)
|
||||
Paper Wallets
|
||||
Perform Selftest
|
||||
I Am Developer.
|
||||
Serial REPL
|
||||
Warm Reset
|
||||
Restore Bkup
|
||||
Reflash GPU [IF QWERTY KEYBOARD]
|
||||
Secure Logout
|
||||
Settings
|
||||
Login Settings
|
||||
Change Main PIN
|
||||
Set Nickname
|
||||
Scramble Keys
|
||||
Kill Key
|
||||
Login Countdown
|
||||
Disabled
|
||||
5 minutes
|
||||
15 minutes
|
||||
30 minutes
|
||||
1 hour
|
||||
2 hours
|
||||
4 hours
|
||||
8 hours
|
||||
12 hours
|
||||
24 hours
|
||||
48 hours
|
||||
3 days
|
||||
1 week
|
||||
28 days later
|
||||
MicroSD 2FA [IF SECRET AND NOT TMP SEED]
|
||||
Add Card
|
||||
Check Card
|
||||
Remove Card #1
|
||||
Calculator Login [IF QWERTY KEYBOARD]
|
||||
Default Off
|
||||
Calculator Login
|
||||
Test Login Now
|
||||
Hardware On/Off
|
||||
USB Port
|
||||
Default On
|
||||
Disable USB
|
||||
Virtual Disk
|
||||
Default Off
|
||||
Enable
|
||||
Enable & Auto
|
||||
NFC Sharing
|
||||
Default Off
|
||||
Enable NFC
|
||||
NFC Push Tx
|
||||
coldcard.com
|
||||
mempool.space
|
||||
Custom URL...
|
||||
Disable
|
||||
Display Units
|
||||
BTC
|
||||
mBTC
|
||||
bits
|
||||
sats
|
||||
Max Network Fee
|
||||
10% (default)
|
||||
25%
|
||||
50%
|
||||
no limit
|
||||
Idle Timeout
|
||||
2 minutes
|
||||
5 minutes
|
||||
15 minutes
|
||||
1 hour
|
||||
4 hours
|
||||
8 hours
|
||||
Never
|
||||
Idle Timeout (on battery) [IF BATTERIES]
|
||||
30 seconds
|
||||
60 seconds
|
||||
2 minutes
|
||||
5 minutes
|
||||
10 minutes
|
||||
15 minutes
|
||||
30 minutes
|
||||
1 hour
|
||||
4 hours
|
||||
Never
|
||||
LCD Brightness (on battery) [IF BATTERIES]
|
||||
25%
|
||||
50%
|
||||
60%
|
||||
70%
|
||||
80%
|
||||
90%
|
||||
95% (default)
|
||||
100%
|
||||
Delete PSBTs
|
||||
Default Keep
|
||||
Delete PSBTs
|
||||
Buried Settings
|
||||
Home Menu XFP [IF SECRET AND NOT TMP SEED]
|
||||
Only Tmp
|
||||
Always Show
|
||||
Menu Wrapping
|
||||
Default
|
||||
Always Wrap
|
||||
[QR key shortcut] [IF QR SCANNER]
|
||||
---
|
||||
|
||||
[NORMAL OPERATION]
|
||||
Ready To Sign
|
||||
Passphrase [IF WORD BASED SEED]
|
||||
Restore Saved
|
||||
c*******
|
||||
[3A14F788]
|
||||
Restore
|
||||
Delete
|
||||
Edit Phrase
|
||||
Scan Any QR Code [IF QR SCANNER]
|
||||
Start HSM Mode [IF HSM POLICY]
|
||||
Address Explorer
|
||||
Classic P2PKH
|
||||
↳ mtHSVByP9EYZ⋯Vm19gvpecb5R
|
||||
P2SH-Segwit
|
||||
↳ 2NCAJ5wD4Gvm⋯NphNU8UYoEJv
|
||||
Segwit P2WPKH
|
||||
↳ tb1qupyd58nd⋯vu9jtdyws9n9
|
||||
Applications
|
||||
Samourai
|
||||
Post-mix
|
||||
Pre-mix
|
||||
Wasabi
|
||||
Account Number
|
||||
Custom Path
|
||||
CC-2-of-4
|
||||
Secure Notes & Passwords [IF ENBALED] [MAYBE]
|
||||
1: note0
|
||||
"note0"
|
||||
View Note
|
||||
Edit
|
||||
Delete
|
||||
Export
|
||||
Sign Note Text
|
||||
2: secret-PWD
|
||||
"secret-PWD"
|
||||
↳ satoshi
|
||||
↳ abc.org
|
||||
View Password
|
||||
Send Password [MAYBE]
|
||||
Export
|
||||
Edit Metadata
|
||||
Delete
|
||||
Change Password
|
||||
Sign Note Text
|
||||
New Note
|
||||
New Password
|
||||
Export All
|
||||
Sort By Title
|
||||
Import
|
||||
Type Passwords [MAYBE]
|
||||
Seed Vault [MAYBE]
|
||||
1: [7126EB3C]
|
||||
[7126EB3C]
|
||||
Use This Seed
|
||||
Rename
|
||||
Delete
|
||||
2: [CCEE13B9]
|
||||
[CCEE13B9]
|
||||
Use This Seed
|
||||
Rename
|
||||
Delete
|
||||
3: [03EE9989]
|
||||
[03EE9989]
|
||||
Use This Seed
|
||||
Rename
|
||||
Delete
|
||||
Advanced/Tools
|
||||
Backup
|
||||
Backup System
|
||||
Verify Backup
|
||||
Restore Backup
|
||||
Clone Coldcard
|
||||
Export Wallet
|
||||
Sparrow
|
||||
Cove
|
||||
Bitcoin Core
|
||||
Nunchuk
|
||||
Bull Bitcoin
|
||||
Blue Wallet
|
||||
Electrum Wallet
|
||||
Wasabi Wallet
|
||||
Fully Noded
|
||||
Unchained
|
||||
Theya
|
||||
Bitcoin Safe
|
||||
Zeus
|
||||
Samourai Postmix
|
||||
Samourai Premix
|
||||
Descriptor
|
||||
Generic JSON
|
||||
Export XPUB
|
||||
Segwit (BIP-84)
|
||||
Classic (BIP-44)
|
||||
P2WPKH/P2SH (BIP-49)
|
||||
Master XPUB
|
||||
Current XFP
|
||||
Key Expression
|
||||
Dump Summary
|
||||
Upgrade Firmware [IF NOT TMP SEED]
|
||||
Show Version
|
||||
From MicroSD
|
||||
From VirtDisk [IF VIRTDISK ENABLED]
|
||||
File Management
|
||||
Verify Backup
|
||||
Backup System
|
||||
Export Wallet
|
||||
Sparrow
|
||||
Cove
|
||||
Bitcoin Core
|
||||
Nunchuk
|
||||
Bull Bitcoin
|
||||
Blue Wallet
|
||||
Electrum Wallet
|
||||
Wasabi Wallet
|
||||
Fully Noded
|
||||
Unchained
|
||||
Theya
|
||||
Bitcoin Safe
|
||||
Zeus
|
||||
Samourai Postmix
|
||||
Samourai Premix
|
||||
Descriptor
|
||||
Generic JSON
|
||||
Export XPUB
|
||||
Segwit (BIP-84)
|
||||
Classic (BIP-44)
|
||||
P2WPKH/P2SH (BIP-49)
|
||||
Master XPUB
|
||||
Current XFP
|
||||
Key Expression
|
||||
Dump Summary
|
||||
Sign Text File
|
||||
Batch Sign PSBT
|
||||
Teleport Multisig PSBT
|
||||
List Files
|
||||
Verify Sig File
|
||||
NFC File Share [IF NFC ENABLED]
|
||||
BBQr File Share [IF QR SCANNER]
|
||||
QR File Share [IF QR SCANNER]
|
||||
Clone Coldcard
|
||||
Format SD Card
|
||||
Format RAM Disk [IF VIRTDISK ENABLED]
|
||||
Secure Notes & Passwords [IF QWERTY KEYBOARD]
|
||||
1: note0
|
||||
"note0"
|
||||
View Note
|
||||
Edit
|
||||
Delete
|
||||
Export
|
||||
Sign Note Text
|
||||
2: secret-PWD
|
||||
"secret-PWD"
|
||||
↳ satoshi
|
||||
↳ abc.org
|
||||
View Password
|
||||
Send Password [MAYBE]
|
||||
Export
|
||||
Edit Metadata
|
||||
Delete
|
||||
Change Password
|
||||
Sign Note Text
|
||||
New Note
|
||||
New Password
|
||||
Export All
|
||||
Sort By Title
|
||||
Import
|
||||
Derive Seeds (BIP-85)
|
||||
View Identity
|
||||
Temporary Seed
|
||||
Generate Words
|
||||
12 Words
|
||||
24 Words
|
||||
12 Word Dice Roll
|
||||
24 Word Dice Roll
|
||||
Import from QR Scan [IF QR SCANNER]
|
||||
Import Words
|
||||
12 Words
|
||||
18 Words
|
||||
24 Words
|
||||
Import via NFC [IF NFC ENABLED]
|
||||
Import XPRV
|
||||
Tapsigner Backup
|
||||
Coldcard Backup
|
||||
Restore Seed XOR
|
||||
Key Teleport (start)
|
||||
Spending Policy [IF SECRET AND NOT TMP SEED]
|
||||
Single-Signer [IF SECRET AND NOT TMP SEED]
|
||||
Co-Sign Multisig (CCC) [IF NOT TMP SEED]
|
||||
HSM Mode [IF HSM AND SECRET]
|
||||
Default Off
|
||||
Enable
|
||||
User Management [MAYBE]
|
||||
Paper Wallets
|
||||
WIF Store
|
||||
NFC Tools [IF NFC ENABLED]
|
||||
Sign PSBT
|
||||
Show Address
|
||||
Sign Message
|
||||
Verify Sig File
|
||||
Verify Address
|
||||
File Share
|
||||
Import Multisig
|
||||
Push Transaction [IF PUSHTX ENABLED]
|
||||
Danger Zone
|
||||
Debug Functions
|
||||
Seed Functions
|
||||
View Seed Words
|
||||
Seed XOR
|
||||
Split Existing [IF WORD BASED SEED]
|
||||
Restore Seed XOR
|
||||
Destroy Seed [IF SECRET AND NOT TMP SEED]
|
||||
Lock Down Seed [MAYBE]
|
||||
Export SeedQR [IF WORD BASED SEED]
|
||||
I Am Developer.
|
||||
Serial REPL
|
||||
Warm Reset
|
||||
Restore Bkup
|
||||
BKPW Override
|
||||
Reflash GPU [IF QWERTY KEYBOARD]
|
||||
Seed Vault [IF SECRET AND NOT TMP SEED]
|
||||
Default Off
|
||||
Enable
|
||||
Perform Selftest
|
||||
Set High-Water
|
||||
Wipe HSM Policy [IF HSM POLICY]
|
||||
Clear OV cache
|
||||
Clear Address cache
|
||||
Sighash Checks
|
||||
Default: Block
|
||||
Warn
|
||||
Testnet Mode
|
||||
Bitcoin
|
||||
Testnet4
|
||||
Regtest
|
||||
AE Start Index
|
||||
Default Off
|
||||
Enable
|
||||
B85 Idx Values
|
||||
Default Off
|
||||
Unlimited
|
||||
Settings Space
|
||||
MCU Key Slots
|
||||
Bless Firmware
|
||||
Wipe LFS
|
||||
Nuke Device
|
||||
Settings
|
||||
Login Settings
|
||||
Change Main PIN
|
||||
Trick PINs [IF SECRET AND NOT TMP SEED]
|
||||
Trick PINs:
|
||||
↳11-11
|
||||
PIN 11-11
|
||||
↳Bricks CC
|
||||
Hide Trick
|
||||
Delete Trick
|
||||
Change PIN
|
||||
↳333-3334
|
||||
PIN 333-3334
|
||||
↳Duress Wallet
|
||||
Activate Wallet
|
||||
Hide Trick
|
||||
Delete Trick
|
||||
Change PIN
|
||||
↳WRONG PIN
|
||||
After 3 wrong:
|
||||
↳Wipes seed
|
||||
↳Reboots
|
||||
Hide Trick
|
||||
Delete Trick
|
||||
Add New Trick
|
||||
Delete All
|
||||
Set Nickname
|
||||
Scramble Keys
|
||||
Kill Key
|
||||
Login Countdown
|
||||
Disabled
|
||||
5 minutes
|
||||
15 minutes
|
||||
30 minutes
|
||||
1 hour
|
||||
2 hours
|
||||
4 hours
|
||||
8 hours
|
||||
12 hours
|
||||
24 hours
|
||||
48 hours
|
||||
3 days
|
||||
1 week
|
||||
28 days later
|
||||
MicroSD 2FA [IF SECRET AND NOT TMP SEED]
|
||||
Add Card
|
||||
Check Card
|
||||
Remove Card #1
|
||||
Calculator Login [IF QWERTY KEYBOARD]
|
||||
Default Off
|
||||
Calculator Login
|
||||
Test Login Now
|
||||
Hardware On/Off
|
||||
USB Port
|
||||
Default On
|
||||
Disable USB
|
||||
Virtual Disk
|
||||
Default Off
|
||||
Enable
|
||||
Enable & Auto
|
||||
NFC Sharing
|
||||
Default Off
|
||||
Enable NFC
|
||||
Multisig Wallets
|
||||
2/4: CC-2-of-4
|
||||
"CC-2-of-4"
|
||||
View Details
|
||||
Delete
|
||||
Coldcard Export
|
||||
Electrum Wallet
|
||||
Descriptors
|
||||
View Descriptor
|
||||
Export
|
||||
Bitcoin Core
|
||||
Import
|
||||
Export XPUB
|
||||
Create Airgapped
|
||||
Trust PSBT?
|
||||
Skip Checks?
|
||||
Full Address View?
|
||||
Partly Censor
|
||||
Show Full
|
||||
Unsorted Multisig?
|
||||
NFC Push Tx
|
||||
coldcard.com
|
||||
mempool.space
|
||||
Custom URL...
|
||||
Disable
|
||||
Display Units
|
||||
BTC
|
||||
mBTC
|
||||
bits
|
||||
sats
|
||||
Max Network Fee
|
||||
10% (default)
|
||||
25%
|
||||
50%
|
||||
no limit
|
||||
Idle Timeout
|
||||
2 minutes
|
||||
5 minutes
|
||||
15 minutes
|
||||
1 hour
|
||||
4 hours
|
||||
8 hours
|
||||
Never
|
||||
Idle Timeout (on battery) [IF BATTERIES]
|
||||
30 seconds
|
||||
60 seconds
|
||||
2 minutes
|
||||
5 minutes
|
||||
10 minutes
|
||||
15 minutes
|
||||
30 minutes
|
||||
1 hour
|
||||
4 hours
|
||||
Never
|
||||
LCD Brightness (on battery) [IF BATTERIES]
|
||||
25%
|
||||
50%
|
||||
60%
|
||||
70%
|
||||
80%
|
||||
90%
|
||||
95% (default)
|
||||
100%
|
||||
Delete PSBTs
|
||||
Default Keep
|
||||
Delete PSBTs
|
||||
Keyboard EMU
|
||||
Default Off
|
||||
Enable
|
||||
Buried Settings
|
||||
Home Menu XFP [IF SECRET AND NOT TMP SEED]
|
||||
Only Tmp
|
||||
Always Show
|
||||
Menu Wrapping
|
||||
Default
|
||||
Always Wrap
|
||||
Secure Logout
|
||||
[NFC key shortcut] [IF NFC ENABLED]
|
||||
Sign PSBT
|
||||
Show Address
|
||||
Sign Message
|
||||
Verify Sig File
|
||||
Verify Address
|
||||
File Share
|
||||
Import Multisig
|
||||
Push Transaction [IF PUSHTX ENABLED]
|
||||
---
|
||||
|
||||
[FACTORY MODE]
|
||||
Bag Me Now
|
||||
Version: 5.x.x
|
||||
DFU Upgrade
|
||||
Ship W/O Bag
|
||||
Debug Functions
|
||||
Perform Selftest
|
||||
---
|
||||
|
||||
[SSSP]
|
||||
Ready To Sign
|
||||
Passphrase [IF WORD BASED SEED & SSSP RELATED KEYS ENABLED]
|
||||
Restore Saved
|
||||
c*******
|
||||
[3A14F788]
|
||||
Restore
|
||||
Delete
|
||||
Edit Phrase
|
||||
Scan Any QR Code [IF QR SCANNER]
|
||||
Address Explorer
|
||||
Classic P2PKH
|
||||
↳ mtHSVByP9EYZ⋯Vm19gvpecb5R
|
||||
P2SH-Segwit
|
||||
↳ 2NCAJ5wD4Gvm⋯NphNU8UYoEJv
|
||||
Segwit P2WPKH
|
||||
↳ tb1qupyd58nd⋯vu9jtdyws9n9
|
||||
Applications
|
||||
Samourai
|
||||
Post-mix
|
||||
Pre-mix
|
||||
Wasabi
|
||||
Account Number
|
||||
Custom Path
|
||||
CC-2-of-4
|
||||
Secure Notes & Passwords[IF ENABLED & SSSP ALLOW NOTES]
|
||||
1: note0
|
||||
"note0"
|
||||
View Note
|
||||
Sign Note Text
|
||||
2: secret-PWD
|
||||
"secret-PWD"
|
||||
↳ satoshi
|
||||
↳ abc.org
|
||||
View Password
|
||||
Send Password [MAYBE]
|
||||
Sign Note Text
|
||||
Type Passwords [MAYBE]
|
||||
Seed Vault[IF ENABLED & SSSP RELATED KEYS ENABLED]
|
||||
1: [7126EB3C]
|
||||
[7126EB3C]
|
||||
Use This Seed
|
||||
2: [CCEE13B9]
|
||||
[CCEE13B9]
|
||||
Use This Seed
|
||||
3: [03EE9989]
|
||||
[03EE9989]
|
||||
Use This Seed
|
||||
Advanced/Tools
|
||||
File Management
|
||||
Sign Text File
|
||||
Batch Sign PSBT
|
||||
List Files
|
||||
Export Wallet
|
||||
Sparrow
|
||||
Cove
|
||||
Bitcoin Core
|
||||
Nunchuk
|
||||
Bull Bitcoin
|
||||
Blue Wallet
|
||||
Electrum Wallet
|
||||
Wasabi Wallet
|
||||
Fully Noded
|
||||
Unchained
|
||||
Theya
|
||||
Bitcoin Safe
|
||||
Zeus
|
||||
Samourai Postmix
|
||||
Samourai Premix
|
||||
Descriptor
|
||||
Generic JSON
|
||||
Export XPUB
|
||||
Segwit (BIP-84)
|
||||
Classic (BIP-44)
|
||||
P2WPKH/P2SH (BIP-49)
|
||||
Master XPUB
|
||||
Current XFP
|
||||
Key Expression
|
||||
Dump Summary
|
||||
Verify Sig File
|
||||
NFC File Share [IF NFC ENABLED]
|
||||
BBQr File Share [IF QR SCANNER]
|
||||
QR File Share [IF QR SCANNER]
|
||||
Format SD Card
|
||||
Format RAM Disk [IF VIRTDISK ENABLED]
|
||||
Export Wallet
|
||||
Sparrow
|
||||
Cove
|
||||
Bitcoin Core
|
||||
Nunchuk
|
||||
Bull Bitcoin
|
||||
Blue Wallet
|
||||
Electrum Wallet
|
||||
Wasabi Wallet
|
||||
Fully Noded
|
||||
Unchained
|
||||
Theya
|
||||
Bitcoin Safe
|
||||
Zeus
|
||||
Samourai Postmix
|
||||
Samourai Premix
|
||||
Descriptor
|
||||
Generic JSON
|
||||
Export XPUB
|
||||
Segwit (BIP-84)
|
||||
Classic (BIP-44)
|
||||
P2WPKH/P2SH (BIP-49)
|
||||
Master XPUB
|
||||
Current XFP
|
||||
Key Expression
|
||||
Dump Summary
|
||||
Teleport Multisig PSBT [MAYBE]
|
||||
View Identity
|
||||
Temporary Seed [IF SSSP RELATED KEYS ENABLED]
|
||||
Import from QR Scan [IF QR SCANNER]
|
||||
Import Words
|
||||
12 Words
|
||||
18 Words
|
||||
24 Words
|
||||
Import via NFC [IF NFC ENABLED]
|
||||
Import XPRV
|
||||
Tapsigner Backup
|
||||
Coldcard Backup
|
||||
Restore Seed XOR
|
||||
Paper Wallets
|
||||
WIF Store
|
||||
NFC Tools [IF NFC ENABLED]
|
||||
Sign PSBT
|
||||
Show Address
|
||||
Sign Message
|
||||
Verify Sig File
|
||||
Verify Address
|
||||
File Share
|
||||
Push Transaction [IF PUSHTX ENABLED]
|
||||
Show Firmware Version
|
||||
Destroy Seed [IF SECRET AND NOT TMP SEED]
|
||||
Secure Logout
|
||||
EXIT TEST DRIVE [MAYBE]
|
||||
[NFC key shortcut] [IF NFC ENABLED]
|
||||
Sign PSBT
|
||||
Show Address
|
||||
Sign Message
|
||||
Verify Sig File
|
||||
Verify Address
|
||||
File Share
|
||||
Push Transaction [IF PUSHTX ENABLED]
|
||||
---
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
|
||||
# MicroSD as a Second Factor for Login
|
||||
|
||||
When enabled, this feature requires a specially prepared MicroSD
|
||||
card to be inserted during login process. After correct PIN is
|
||||
provided, if card slot is empty or unknown card present, the seed
|
||||
is wiped.
|
||||
|
||||
## How it Works
|
||||
|
||||
To "enroll" a card, a small encrypted file is written to the card.
|
||||
|
||||
During login, after the correct (true) PIN is entered, we use
|
||||
the master secret to construct an AES key which is used to decrypt
|
||||
the file found on the card. If the file is JSON and contains a nonce,
|
||||
we check that in our list of acceptable cards.
|
||||
|
||||
The AES key includes the master secret and also a hash of the
|
||||
unique serial number of the card, retrieved using low-level
|
||||
protocols. This prevents moving the file to another card.
|
||||
|
||||
To allow the same card to unlock multiple Coldcards, we write the
|
||||
file using a filename derived from the serial number of the Coldcard
|
||||
(hashed). Thus there could be a number of 2FA-enabling files on a
|
||||
single card.
|
||||
|
||||
The file name starts with a dot, and has extension `.2fa`. Your
|
||||
tools may or may not hide it from you based on Unix filename
|
||||
conventions. Reformating the card will certainly remove this file,
|
||||
so keep that in mind when managing your "special" cards.
|
||||
|
||||
If using COLDCARD Q and both card slot are populated during login
|
||||
make sure that enrolled card is in top slot (slot A).
|
||||
|
||||
## Menu Settings
|
||||
|
||||
See menu in: `Settings -> Login Settings -> MicroSD 2FA`
|
||||
|
||||
The option is enabled only once the main secret is picked. It cannot
|
||||
be used with ephemeral seeds, as that secret will not be in effect
|
||||
during boot time.
|
||||
|
||||
The menu initially contains only "Add Card". Once one or more
|
||||
cards are enabled (and the feature is activated), additional
|
||||
options appear: "Check Card" and "Remove Card #N" (for each
|
||||
enrolled card).
|
||||
|
||||
"Check Card" validates the card inserted and indicates if it would
|
||||
be accepted or not.
|
||||
|
||||
Use "Remove Card #N" is remove cards from the approved list. When
|
||||
the last card is removed, the feature is disabled and no card will
|
||||
be required for login. Access to the card in question is not required
|
||||
to remove it.
|
||||
|
||||
## During Login
|
||||
|
||||
After the PIN is entered, and if it is the true PIN (or the main
|
||||
code thinks it is, in Delta Mode or Duress Wallet cases) the main
|
||||
settings are read. After this point, if there are one or more card
|
||||
enrolled, then the check is performed. If the slot is empty or
|
||||
the card fails the check, a fast wipe of the seed is done and shown
|
||||
on screen. The memory is wipe and system stops. You must power cycle
|
||||
to continue.
|
||||
|
||||
## Tricky Thinking
|
||||
|
||||
Because settings are encrypted by the master seed, if you have a
|
||||
duress wallet, it could have required cards set as well. Generally,
|
||||
we do not see a good use for this, and assume that typically only
|
||||
the "true" PIN will have required cards associated with it. Remember
|
||||
any Trick PIN can wipe the seed directly.
|
||||
|
||||
In Delta Mode, the usual card policy is in effect. However, if you
|
||||
are relying on this 2FA feature to wipe the seed in a case of duress,
|
||||
there doesn't seem to be any need for Delta Mode.
|
||||
|
||||
## Duress Defenses
|
||||
|
||||
We recommend simply keeping no card in your Coldcard once activating
|
||||
this feature. Your attacker, or yourself under duress, will login
|
||||
normally and trigger this defense without you taking any explicit
|
||||
action.
|
||||
|
||||
If you were being forced to prepare a PSBT under duress, you can
|
||||
choose which SD card to use (so pick a normal one, which isn't
|
||||
enrolled) and you may also have a chance to clear your card of the
|
||||
special file. Either way would be an opportunity to ensure the
|
||||
automatic wipe occurs, even as you comply and provide the PIN code.
|
||||
|
||||
Your enrolled SD cards can also be stored at another location away
|
||||
from your Coldcard. This could be a bank safety deposit box, since
|
||||
it contains no sensitive data.
|
||||
|
||||
If you are closely surveilled when logging and using your Coldcard,
|
||||
the PIN code might already be known to your attacker. However, there
|
||||
is no indication on the screen during a normal (successful) login
|
||||
that this feature is in effect, so they would not know if the SD
|
||||
card was inserted by chance or necessity.
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
# COLDCARD Message Signing
|
||||
|
||||
COLDCARD can sign messages send to it via USB with the help of `ckcc` utility,
|
||||
sign messages provided via specially crafted file on SD card or Vdisk,
|
||||
and NFC-equipped models (Mk4, Mk5, and Q) can also sign messages sent to COLDCARD via NFC.
|
||||
The resulting signature can be returned over SD card/Vdisk, NFC, or — on Q — as a QR code.
|
||||
|
||||
Signature format follows [BIP-0137](https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki) specification.
|
||||
COLDCARD Mk3 and COLDCARD Mk4 up to version `5.1.0` used compressed P2PKH header byte for all script types.
|
||||
From version `5.1.0` correct header byte is used for corresponding script type.
|
||||
|
||||
### Verification
|
||||
|
||||
From version `5.1.0` users can verify signed messages directly on the device.
|
||||
If signature file is on SD card or Virtual disk `Advanced/Tools -> File Management -> Verify Sig File`. In case
|
||||
signature file is detached signature of signed export (or any other file), COLDCARD can check if digest of file
|
||||
specified in the message matches contents of file. This requires file signed to be available on SD card or Vdisk.
|
||||
File size limit for signature files is approximately 10KB.
|
||||
If signature file is imported via NFC `Advance/Tools -> NFC Tools -> Verify Sig File`.
|
||||
To cross-verify COLDCARD verification use https://www.verifybitcoinmessage.com/ as it supports multiple script types.
|
||||
Bitcoin core can only verify P2PKH.
|
||||
|
||||
## Signed Exports
|
||||
|
||||
From version `5.1.0` most of SD card and Virtual disk exports are accompanied by detached signature file.
|
||||
If exported file name is `addresses.csv` signature file name will be `addresses.sig`.
|
||||
|
||||
### Message construction and signature file format
|
||||
|
||||
1. contents of the exported file are hashed with single SHA256 hash
|
||||
2. `msg = hash from step 1. + two spaces + exported filename (basename)`
|
||||
3. msg from step 2. is hashed again with Bitcoin msg hash `"Bitcoin Signed Message:" + ser_compact_size(len(msg)) + msg`
|
||||
4. detached signature file format:
|
||||
```text
|
||||
-----BEGIN BITCOIN SIGNED MESSAGE-----
|
||||
f1591bfb04a89f723e1f14eb01a6b2f6f507eb0967d0a5d7822b329b98018ae4 coldcard-export.json
|
||||
-----BEGIN BITCOIN SIGNATURE-----
|
||||
mtHSVByP9EYZmB26jASDdPVm19gvpecb5R
|
||||
IFOvGVJrm31S0j+F4dVfQ5kbRKWKcmhmXIn/Lw8iIgaCG5QNZswjrN4X673R7jTZo1kvLmiD4hlIrbuLh/HqDuk=
|
||||
-----END BITCOIN SIGNATURE-----
|
||||
```
|
||||
|
||||
### What Is Signed
|
||||
|
||||
1. **Single sig address explorer exports:** Signed by the key corresponding to the first (0th) address on the exported list.
|
||||
2. **Specific single sig exports:** Signed by the key corresponding to the external address at index zero of chosen application specific derivation `m/<app_deriv>h/<coin_type>'h/<account>h/0/0`.
|
||||
* Bitcoin Core
|
||||
* Electrum Wallet
|
||||
* Wasabi Wallet
|
||||
* Samourai Postmix
|
||||
* Samourai Premix
|
||||
* Descriptor
|
||||
3. **Generic single sig exports:** Signed by key that corresponds to first (0th) external address at derivation `m/44h/<coin_type>h/<account>h/0/0`.
|
||||
* Lily Wallet
|
||||
* Generic JSON
|
||||
* Dump Summary
|
||||
4. **BIP85 derived entropy exports:** Signed by path that corresponds to specific BIP85 application.
|
||||
5. **Paper wallet exports:** Signed by key and address exported as paper wallet itself.
|
||||
6. **Multisig exports:** public keys are encoded as P2PKH address for all multisg signature exports
|
||||
* Multisig wallet descriptor: signed by the key corresponding to the first external address of own enrolled extended key `my_key/0/0`
|
||||
* Generic XPUBs export: signed by the key corresponding to the first external address of own standard P2WSH derivation `m/48h/<coin_type>h/<account>h/2h/0/0`
|
||||
* Multisig address explorer export: Signed by own key at the same derivation as first (0th) row on exported list. `my_key/<change>/<start_index>`
|
||||
@ -1,228 +0,0 @@
|
||||
# NFC and Coldcard
|
||||
|
||||
(Applies to NFC-equipped models: Mk4, Mk5, and Q)
|
||||
|
||||
## Usage
|
||||
|
||||
The NFC antenna location depends on the hardware:
|
||||
|
||||
- **Mk4**: a PCB trace loop, centered under number `8` on the keypad.
|
||||
- **Mk5**: a discrete coil (`L6`) in the **top-right corner** of the device
|
||||
- **Q1**: a flexible "sticker" antenna behind the display. The green LED below the
|
||||
bottom-right of the display (`D12`) lights up while an NFC transfer is active —
|
||||
it is the activity indicator, not the antenna.
|
||||
|
||||

|
||||
|
||||
Before using NFC,
|
||||
it is important to locate the position of NFC antenna on your device and point it
|
||||
correctly towards the Coldcard NFC antenna. Picture below shows an example with iPhone
|
||||
that has NFC antenna located at the top right edge. The NFC smartphone antenna
|
||||
can be positioned almost anywhere on the device, but mostly on the top, middle,
|
||||
or bottom of the back side of the phone, and it is rarely indicated on the phone case.
|
||||
It is always best to look up your specific phone model on the internet to find
|
||||
the exact antenna location.
|
||||
|
||||

|
||||
|
||||
## Standards Background
|
||||
|
||||
NFC is a layer of protocols on top of ISO standards for short-range
|
||||
radio communications.
|
||||
|
||||
Unfortunately, both ISO and NFC Forum bodies are so poor they must
|
||||
sell their standards. Membership starts at a few thousand dollars,
|
||||
or you must buy each PDF for a few hundred dollars. Every single
|
||||
thing is behind a paywall.
|
||||
|
||||
This policy does not allow us to link to reference standards. Instead
|
||||
we have to hand-wave about our interpretation of their standards
|
||||
documents.
|
||||
|
||||
In our opinion, this policy is not in the public interest and is
|
||||
hindering adoption of their standards and even technological progress
|
||||
in general. Good interoperability is critical with radio standards.
|
||||
|
||||
|
||||
## Lower Layers
|
||||
|
||||
The Coldcard has a chip that acts as a Type 5 NFC tag. The
|
||||
radio standard is called "NFC-V" or ISO-15693, and operates on a
|
||||
13.56 Mhz carrier wave.
|
||||
|
||||
The tag chip implements NFC standards to support reading and writing
|
||||
commands appropriate to a typical Type 5 tag.
|
||||
|
||||
Effectively it exposes a flash memory chip, of up to 8k Bytes in
|
||||
size. NDEF standards describes the organization of the data in that
|
||||
memory. This document will describe what bytes are needed in those
|
||||
records.
|
||||
|
||||
## Security
|
||||
|
||||
All NFC features of the Coldcard can be disabled from the settings
|
||||
menu, and when that is done, the tag chip is completely disabled,
|
||||
and there is no way to probe, detect or access the Coldcard over
|
||||
RF. Even when NFC features are enabled, we keep the tag chip disabled
|
||||
unless we are actively sharing something. We disable the "energy
|
||||
harvesting" features of the chip, so it will not do anything when
|
||||
the Coldcard is powered-down, regardless of the NFC setting.
|
||||
|
||||
If the above is not enough for you, the antenna can be destroyed:
|
||||
|
||||
- **Mk4**: cut the trace labeled "NFC" inside the hole for the MicroSD card,
|
||||
using the point of a sharp knife to cut and peel up the trace.
|
||||
- **Mk5**: has no such trace — its antenna is the discrete coil `L6` in the
|
||||
top-right corner, which would have to be physically removed instead.
|
||||
- **Q1**: cut the trace labeled "NFC DATA" under the batteries.
|
||||
|
||||
The NFC traffic is not encrypted and is subject to eavesdropping.
|
||||
While the NFC feature is active, your Coldcard can be uniquely
|
||||
identified because the NFC protocol requires a unique ID (64 bits)
|
||||
that is defined by the NFC tag chip and shared automatically as
|
||||
part of the anti-collision protocol. Again, that happens only during
|
||||
active transfers, not when idle.
|
||||
|
||||
## Desktop Testing
|
||||
|
||||
Most USB-powered desktop contactless card readers will not work
|
||||
with the Coldcard because they do not implement NFC-V (ISO-15693).
|
||||
Instead they are doing ISO-14443A or B.
|
||||
|
||||
Smartphones, on the other hand, all support NFC-V and they are the
|
||||
intended targets. Generic NFC tag reading apps can view the data
|
||||
we share, and that may be enough to be useful. Our long-term goal
|
||||
is integration with mobile wallets.
|
||||
|
||||
# Types of Records
|
||||
|
||||
## Background
|
||||
|
||||
The "NDEF message" is a list of values ("NDEF records"). In most
|
||||
cases we share only a a single value, but for more complex object
|
||||
data we will use multiple records. The order is not defined and may
|
||||
change. Each NDEF record has data-type information and a payload
|
||||
of bytes.
|
||||
|
||||
If we can use "text" or "URI" records, we will, but we generally
|
||||
need our own Bitcoin-specific types.
|
||||
|
||||
We are using "NFC Forum Local Types" for new stuff. Other Bitcoin
|
||||
developers are welcome to use the same types as long as it doesn't
|
||||
create interoperation problems.
|
||||
|
||||
Types are shown in full URN format (RFC 2141) but only the final
|
||||
two parts are sent as part of the NDEF record (ie. `bitcoin.org:psbt`).
|
||||
We are using TNF=4 (NFC Forum external type) to communicate the
|
||||
prefix of `urn:nfc:ext:`
|
||||
|
||||
# Simple Data
|
||||
|
||||
## General QR Replacement
|
||||
|
||||
Anytime there is a QR displayed on the Coldcard screen, you can
|
||||
press (3) and the same data will be shared over NFC. In these cases,
|
||||
it will be shared as a simple text record, regardless of the content.
|
||||
|
||||
Type: `urn:nfc:wkt:T` (text)
|
||||
|
||||
Body: varies, but always ascii text.
|
||||
|
||||
Many values can be exported this way, include xpub and even seed
|
||||
words after enough warning screens.
|
||||
|
||||
## Payment Address
|
||||
|
||||
This is typically a deposit address, generated on the Coldcard via
|
||||
the address explorer. We share these by themselves as simple text
|
||||
records for max compatibility.
|
||||
|
||||
Type: `urn:nfc:wkt:T` (text)
|
||||
|
||||
Body: bech32 or base58 encoded Bitcoin payment address
|
||||
|
||||
If there are multiple addresses (10 shown for address explorer case)
|
||||
then they are separated by a single unix new line (`0x0a`).
|
||||
|
||||
# Complex Data
|
||||
|
||||
For Bitcoin-specific data we provide a few records together. The
|
||||
first is a label, then various binary data related to what's going
|
||||
on (such as a PSBT file after signing).
|
||||
|
||||
## Text Label
|
||||
|
||||
Coldcard's first record will be a simple text record (English, UTF-8) that
|
||||
describes what is being shared.
|
||||
|
||||
Type: urn:nfc:wkt:T (standard text)
|
||||
|
||||
Body: "Partly signed PSBT", "Deposit Address", "Signed Transaction" and similar.
|
||||
|
||||
Consider this a title for what's being offered for sharing purposes.
|
||||
|
||||
## SHA256 Checksum
|
||||
|
||||
When the Coldcard is sharing a larger object, such as a PSBT file,
|
||||
we know the SHA256 of that object, so we share that as well. This value can
|
||||
be ignored or used for end-to-end error detection. It does not
|
||||
protect against tampering.
|
||||
|
||||
Type: `urn:nfc:ext:bitcoin.org:sha256`
|
||||
|
||||
Body: Exactly 32 bytes of binary. It's the SHA256 over the main
|
||||
payload (PSBT file, for example).
|
||||
|
||||
If present, this value will always directly preceed the object (txn
|
||||
or PSBT) that it covers. NFC-V has CRC16 over each low-level message,
|
||||
but that's all.
|
||||
|
||||
## TXID Value
|
||||
|
||||
When sharing a fully-signed transaction, the TXID, if known, will be
|
||||
shared in hex.
|
||||
|
||||
Type: `urn:nfc:ext:bitcoin.org:txid`
|
||||
|
||||
Body: Exactly 32 bytes of binary.
|
||||
|
||||
The transaction ID is calculated as a hash over the transaction.
|
||||
Without signature witness data, it is simply SHA256 over the bytes
|
||||
of the transaction. For segwit transactions, it's a bit more complex
|
||||
to calculate.
|
||||
|
||||
## PSBT File
|
||||
|
||||
The payload is a binary PSBT file, per BIP-174. The PSBT may be unsigned,
|
||||
partly signed, fully signed or otherwise incomplete.
|
||||
|
||||
Type: `urn:nfc:ext:bitcoin.org:psbt`
|
||||
|
||||
Body: Binary PSBT file, variable length. First five bytes will be `psbt\xff`.
|
||||
|
||||
|
||||
## Bitcoin Transaction
|
||||
|
||||
A fully-signed, wire-ready Bitcoin transaction.
|
||||
|
||||
Type: `urn:nfc:ext:bitcoin.org:txn`
|
||||
|
||||
Body: Binary, variable length. First four bytes will typically be
|
||||
`0x02 0x00 0x00 0x00` (version number two, in LE32).
|
||||
|
||||
When the Coldcard has signed and finalized a transaction, it can
|
||||
share it in this format. Typically the user will want to broadcast
|
||||
this new transaction on the Bitcoin P2P network.
|
||||
|
||||
## JSON Files
|
||||
|
||||
When exporting wallet details, we need to share a JSON file in most
|
||||
cases. These are marked as "application/json".
|
||||
|
||||
# Examples
|
||||
|
||||
This section will include a number of examples, with analysis of the content.
|
||||
|
||||
- __comming soon__
|
||||
|
||||
|
||||
@ -1,103 +0,0 @@
|
||||
# NFC Push Tx
|
||||
|
||||
This feature allows single-tap broadcast of the freshly-signed transaction.
|
||||
|
||||
`PSBT ==[SD|QR|NFC]==> COLDCARD signed TXN ==[NFC tap]==> Phone Browser ==> Server TXN Broadcast`
|
||||
|
||||
Once enabled with a URL, the COLDCARD will show the NFC animation
|
||||
after signing the transaction. When the user taps their phone, the
|
||||
phone will see an NFC tag with URL inside. That URL contains the
|
||||
signed transaction ready to go, and once opening in the mobile
|
||||
browser, that URL will load. The landing page will connect to a
|
||||
Bitcoin node (or similar) and send the transaction on the public
|
||||
Bitcoin network.
|
||||
|
||||
This feature is available on Q and Mk4 and requires NFC to be enabled.
|
||||
See `Settings > NFC Push Tx`.
|
||||
|
||||
See the latest on our feature minisite: [PushTx.org](https://pushtx.org)
|
||||
|
||||
## Protocol Spec
|
||||
|
||||
The COLDCARD needs a URL prefix. To that it appends some values:
|
||||
|
||||
- `t=...`
|
||||
- this is the transaction, in binary encoded with
|
||||
[base64url](https://datatracker.ietf.org/doc/html/rfc4648#section-5)
|
||||
|
||||
- `&c=...`
|
||||
- the rightmost 8 bytes of SHA256 over the transaction. Also `base64url` encoded.
|
||||
|
||||
- `&n=XTN`
|
||||
- if, and only if, the COLDCARD is set for Testnet, this value is appended to
|
||||
indicate that the transaction is for Testnet3 and not MainNet.
|
||||
- when RegTest is enabled, the value will be `XRT`
|
||||
|
||||
We provide a few default URL values to our customers, including one backend we
|
||||
will operate on `coldcard.com`. The URL can also be directly entered by the
|
||||
customer. On the Q, it can be scanned from a QR code.
|
||||
|
||||
For COLDCARD backend, the url used is:
|
||||
|
||||
https://coldcard.com/pushtx#
|
||||
|
||||
The complete URL with a typical transaction might look like this (but longer):
|
||||
|
||||
https://coldcard.com/pushtx#t=AgAAAAMNCxXtp2GVYVhkRXHLMmdZFs4p3kbFK ⋯ ABf&c=uiSVRda-1tw
|
||||
|
||||
We are using hash symbol here so that our server logs do not get
|
||||
contaminated with the arguments. The landing page uses javascript
|
||||
to read the hash part of the URL and decodes from there. If you
|
||||
prefer, your URL can end with `?` and then the arguments will be
|
||||
sent by the phone's browser to your server. Your processing can be
|
||||
entirely done in the backend in this case.
|
||||
|
||||
## Expectations for the Backend
|
||||
|
||||
Your code should decode the transaction and check the SHA-256 hash
|
||||
matches. If it does not match, or if `c` value is missing, assume
|
||||
the URL has been truncated and report that to the user.
|
||||
|
||||
Once decoded, your code should immediately broadcast the transaction.
|
||||
A confirmation step is not required in our opinion. Once it is
|
||||
submitted to Bitcoin Core (or other API), any status response should
|
||||
be decoded and shown to the user so they know it is on it's way.
|
||||
If it was not accepted, please report the error to the user as
|
||||
clearly as possible.
|
||||
|
||||
Next, it would make sense to either link to the TXID on a block
|
||||
explorer to provide further proof that it has been sent and that
|
||||
it is now waiting in the mempool.
|
||||
|
||||
## Backend Implementations
|
||||
|
||||
- Mempool.space's [implementation of this feature](https://github.com/mempool/mempool/pull/5132).
|
||||
|
||||
- A single-file (html and javascript) file is available
|
||||
at [coldcard.com/static/coldcard-pushtx.html](https://coldcard.com/static/coldcard-pushtx.html).
|
||||
You can host this file anywhere your phone can reach, and then use that URL in your
|
||||
COLDCARD settings. It uses your phone's browser to submit directly
|
||||
to `mempool.space` and `blockstream.info` sites (both at same time). It is equivalent
|
||||
to the page hosted at `https://coldcard.com/pushtx#`. Full source code is published here:
|
||||
[github.com/Coldcard/push-tx](https://github.com/Coldcard/push-tx)
|
||||
|
||||
### Notes
|
||||
|
||||
- Complete URL might be as large as 8,000 bytes. Some web servers will not support beyond
|
||||
4k bytes and the NFC implementation of the phone may also have limits.
|
||||
- The service URL provided must end in `?` or `#` or `&`.
|
||||
- `base64url` values from COLDCARD will not have padding (`=` bytes) at end.
|
||||
- POST cannot be used directly because the expect the phone to do a GET on the URL provided.
|
||||
- Honest backends will not log the IP address of incoming transactions, but there is
|
||||
no way to enforce that, and CloudFlare sees all.
|
||||
|
||||
## Example URL
|
||||
|
||||
```
|
||||
https://mempool.space/pushtx#t=AgAAAAOHqK3w3hC6PSC0buthnJA5R9Y88WAlEvm9cifNVUPhIwAAAABqRzBEAiB-M9YprNYoohqHdQHg4wY_qcEMwDmyIQH8prykk8-0KwIgARxcojKrtixicouiUxhk4jQq_MAl11ptIgHDlRjgk5ABIQM4bgMAVDbDSr_9CvLjbg5nxrWnDGI-kVmkfL81GXZtCf____8OaH0RxW7DjZKdIF6rvbHvvyFGCBQ0PTgpx20nA_wbLgAAAABqRzBEAiBwUFigORJDPK8ptnYPAntjV-RUn1jAuzphicQstwVv-QIgEbMC8FWXQ5Jve5DaAqKJsqoj3peK83iub_oOkmbiYg4BIQO5Ehn2t0oUG3hnK4cBnwCwMc33DcdJ8aSMWzRQ_wjZL_____-UG6M-eBeAun-EZp6EbVypvVJ3mXCQrN_fUDn-kwoEnQAAAABqRzBEAiAgFAtVTpQYTKplc9NuV7Ws7ZFYeNO8BCS4ozgWrgd2ogIgGTTcw98xQdcGWeWQhVfVm_vZorBIOYovQPQeK0Lg9t8BIQLPWPioVWvj1z4NMHBCkeirYOUalCa83wbSH0CREnGZvv____8CjM_wCAAAAAAZdqkUIJA8_yqzaj0NzhvYVEIBno5gETGIrIzP8AgAAAAAGXapFEaV7xTyleuEX9OejdlUlsz7RTr0iKwAAAAA&c=hre47vyMC78&n=XTN
|
||||
```
|
||||
|
||||
- this transaction doesn't have valid inputs, and will cause an error
|
||||
- mempool.space will redirect this to a testnet endpoint (because ends with `n=XTN`)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 258 KiB |
@ -1,196 +0,0 @@
|
||||
# Notes on Reproducible Builds
|
||||
|
||||
The following document aims to breakdown how reproducibility is verified in the `make repro` build step.
|
||||
|
||||
## stm32/shared.mk
|
||||
|
||||
The entrypoint makefile for repro builds.
|
||||
|
||||
### repro
|
||||
|
||||
The `repro` command in `shared.mk` is the first step in the repro build process, which triggers a docker build and run process.
|
||||
|
||||
```makefile
|
||||
repro: submods-match code-committed
|
||||
repro:
|
||||
docker build -t coldcard-build - < dockerfile.build
|
||||
(cd ..; docker run $(DOCK_RUN_ARGS) sh src/stm32/repro-build.sh $(VERSION_STRING) $(HW_MODEL) $(PARENT_MKFILE))
|
||||
```
|
||||
|
||||
`$(HW_MODEL)` is the model string (e.g. `mk4`, `q1`) and `$(PARENT_MKFILE)` is the
|
||||
top-level makefile being used (`MK-Makefile` or `Q1-Makefile`). The `submods-match`
|
||||
and `code-committed` prerequisites ensure the submodules and working tree are clean
|
||||
before a repro build.
|
||||
|
||||
Below are interesting sections from the docker logs that give an idea as to what is going on in build process:
|
||||
|
||||
```stdout
|
||||
+ mkdir /tmp/checkout
|
||||
+ mount -t tmpfs tmpfs /tmp/checkout
|
||||
|
||||
...
|
||||
```
|
||||
We will pull the release from coldcard.com into the `/tmp/checkout` directory.
|
||||
|
||||
```
|
||||
+ git clone /work/src/.git firmware
|
||||
|
||||
...
|
||||
|
||||
+ cd firmware/external
|
||||
+ git submodule update --init
|
||||
|
||||
...
|
||||
|
||||
Successfully installed signit-1.0
|
||||
|
||||
...
|
||||
|
||||
+ cd ../stm32
|
||||
+ cd ../releases
|
||||
+ '[' -f '*-v5.0.7-mk4-coldcard.dfu' ]
|
||||
+ dd 'bs=66' 'skip=1'
|
||||
+ grep -F v5.0.7-mk4-coldcard.dfu signatures.txt
|
||||
0+1 records in
|
||||
0+1 records out
|
||||
+ PUBLISHED_BIN=2022-10-05T1724-v5.0.7-mk4-coldcard.dfu
|
||||
+ '[' -z 2022-10-05T1724-v5.0.7-mk4-coldcard.dfu ]
|
||||
+ wget -S https://coldcard.com/downloads/2022-10-05T1724-v5.0.7-mk4-coldcard.dfu
|
||||
|
||||
...
|
||||
|
||||
'2022-10-05T1724-v5.0.7-mk4-coldcard.dfu' saved
|
||||
|
||||
...
|
||||
|
||||
+ PUBLISHED_BIN=/tmp/checkout/firmware/releases/2022-10-05T1724-v5.0.7-mk4-coldcard.dfu
|
||||
|
||||
...
|
||||
|
||||
+ make -f MK-Makefile setup
|
||||
|
||||
...
|
||||
|
||||
+ make -f MK-Makefile firmware-signed.bin firmware-signed.dfu production.bin dev.dfu firmware.lss firmware.elf
|
||||
|
||||
...
|
||||
|
||||
signit sign -b l-port/build-COLDCARD_MK4 -m mk4 5.0.7 -o firmware-signed.bin
|
||||
|
||||
...
|
||||
|
||||
signit sign -m mk4 5.0.7 -r firmware-signed.bin -k 1 -o production.bin
|
||||
You don't have that key (1), so using key zero instead!
|
||||
...
|
||||
|
||||
cd ../external/micropython/ports/stm32 && make BOARD=COLDCARD_MK4 -j 4 EXCLUDE_NGU_TESTS=1 DEBUG_BUILD=0
|
||||
|
||||
...
|
||||
|
||||
../external/micropython/tools/dfu.py -b 0x08020000:dev.bin dev.dfu
|
||||
arm-none-eabi-objdump -h -S l-port/build-COLDCARD_MK4/firmware.elf > firmware.lss
|
||||
cp l-port/build-COLDCARD_MK4/firmware.elf .
|
||||
+ '[' /tmp/checkout/firmware/stm32 '!=' /work/src/stm32 ]
|
||||
+ rsync -av --ignore-missing-args firmware-signed.bin firmware-signed.dfu production.bin dev.dfu firmware.lss firmware.elf /work/built
|
||||
sending incremental file list
|
||||
dev.dfu
|
||||
firmware-signed.bin
|
||||
firmware-signed.dfu
|
||||
firmware.elf
|
||||
firmware.lss
|
||||
production.bin
|
||||
|
||||
...
|
||||
|
||||
+ make -f MK-Makefile 'PUBLISHED_BIN=/tmp/checkout/firmware/releases/2022-10-05T1724-v5.0.7-mk4-coldcard.dfu' check-repro
|
||||
|
||||
...
|
||||
|
||||
Comparing against: /tmp/checkout/firmware/releases/2022-10-05T1724-v5.0.7-mk4-coldcard.dfu
|
||||
test -n "/tmp/checkout/firmware/releases/2022-10-05T1724-v5.0.7-mk4-coldcard.dfu" -a -f /tmp/checkout/firmware/releases/2022-10-05T1724-v5.0.7-mk4-coldcard.dfu
|
||||
rm -f -f check-fw.bin check-bootrom.bin
|
||||
signit split /tmp/checkout/firmware/releases/2022-10-05T1724-v5.0.7-mk4-coldcard.dfu check-fw.bin check-bootrom.bin
|
||||
start 293 for 870400 bytes: Firmware => check-fw.bin
|
||||
start 870701 for 114688 bytes: Bootrom => check-bootrom.bin
|
||||
signit check check-fw.bin
|
||||
magic_value: 0xcc001234
|
||||
timestamp: 2022-10-05 17:24:55 UTC
|
||||
version_string: 5.0.7
|
||||
pubkey_num: 1
|
||||
firmware_length: 870400
|
||||
install_flags: 0x0 =>
|
||||
hw_compat: 0x8 => Mk4
|
||||
best_ts: b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
future: 0000000000000000 ... 0000000000000000
|
||||
signature: 293948e7ce4a3555 ... 766437aa65d3e88a
|
||||
sha256^2: 7f3a7c5f794ce72f68280447cddc837fa62245fdf4b795822127624f8775dca2
|
||||
ECDSA Signature: CORRECT
|
||||
signit check firmware-signed.bin
|
||||
magic_value: 0xcc001234
|
||||
timestamp: 2022-10-24 13:33:16 UTC
|
||||
version_string: 5.0.7
|
||||
pubkey_num: 0
|
||||
firmware_length: 870400
|
||||
install_flags: 0x0 =>
|
||||
hw_compat: 0x8 => Mk4
|
||||
best_ts: b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
future: 0000000000000000 ... 0000000000000000
|
||||
signature: deb643d0a140d89e ... c544f09cd80fa65c
|
||||
sha256^2: a46ddd6e599a49a573bf76054f438c9efe1ee031bfae74a00b0e7bbe76f516c3
|
||||
ECDSA Signature: CORRECT
|
||||
hexdump -C firmware-signed.bin | sed -e 's/^00003f[89abcdef]0 .*/(firmware signature here)/' > repro-got.txt
|
||||
hexdump -C check-fw.bin | sed -e 's/^00003f[89abcdef]0 .*/(firmware signature here)/' > repro-want.txt
|
||||
diff repro-got.txt repro-want.txt
|
||||
|
||||
SUCCESS.
|
||||
|
||||
You have built a bit-for-bit identical copy of Coldcard firmware for v5.0.7
|
||||
```
|
||||
|
||||
## check-repro
|
||||
|
||||
The `check-repro` section of the makefile contains the steps required to verify that the build artifacts are infact a bit-for-bit match to the release candidates.
|
||||
|
||||
```makefile
|
||||
check-repro: TRIM_SIG = sed -e 's/^00003f[89abcdef]0 .*/(firmware signature here)/'
|
||||
check-repro: firmware-signed.bin
|
||||
ifeq ($(PUBLISHED_BIN),)
|
||||
@echo ""
|
||||
@echo "Need published binary for: $(VERSION_STRING)"
|
||||
@echo ""
|
||||
@echo "Copy it into ../releases"
|
||||
@echo ""
|
||||
else
|
||||
@echo Comparing against: $(PUBLISHED_BIN)
|
||||
test -n "$(PUBLISHED_BIN)" -a -f $(PUBLISHED_BIN)
|
||||
$(RM) -f check-fw.bin check-bootrom.bin
|
||||
$(SIGNIT) split $(PUBLISHED_BIN) check-fw.bin check-bootrom.bin
|
||||
$(SIGNIT) check check-fw.bin
|
||||
$(SIGNIT) check firmware-signed.bin
|
||||
hexdump -C firmware-signed.bin | $(TRIM_SIG) > repro-got.txt
|
||||
hexdump -C check-fw.bin | $(TRIM_SIG) > repro-want.txt
|
||||
diff repro-got.txt repro-want.txt
|
||||
@echo ""
|
||||
@echo "SUCCESS. "
|
||||
@echo ""
|
||||
@echo "You have built a bit-for-bit identical copy of Coldcard firmware for v$(VERSION_STRING)"
|
||||
endif
|
||||
```
|
||||
|
||||
To summarize `check-repro`:
|
||||
|
||||
- At the final `check-repro` step, we have a locally built `firmware-signed.bin` and we want to check that it matches the binary release provided by Coinkite.
|
||||
|
||||
- This step verifies the signature of the binary is valid, using either the Coinkite key factory key or the "debug" key zero which is public.
|
||||
|
||||
- An identical checksum match will not be possible as is, since there is signature data embedded into into the binary, which must be removed.
|
||||
|
||||
- The specific release of the version that is being built is fetched, and placed it under /tmp/checkout/firmware/releases/*.dfu
|
||||
|
||||
- `split` (cli/signit.py: Line 153-175) is run against the release `*.dfu` resulting in a `check-fw.bin` and `check-bootrom.bin`. "This splits the DFU file into the two parts it contains: the main firmware (COLDCARD application) and the boot loader code."
|
||||
|
||||
- `check` (cli/signit.py: Line 176-243) is run against each the release `check-fw.bin` and our built `firmware-signed.bin`.
|
||||
|
||||
- a hexdump is taken of each the release `check-fw.bin` and our built `firmware-signed.bin` piped through $TRIM_SIG which removes 64 bytes of signature data and subsitutes it with a common string.
|
||||
|
||||
- Finally the diff of the two hexdumps are compared to prove reproducibility.
|
||||
@ -81,7 +81,7 @@ of your duress PIN.
|
||||
The attackers could tell when the brick-me PIN has worked, but when
|
||||
the brick-me PIN works, the Coldcard will immediately use it to
|
||||
destroy the main pairing secret. This renders the security element
|
||||
useless. This happens in about 50 milliseconds and is done long
|
||||
useless. This happens in about 50 milliseconds and is done long before
|
||||
before anyone gets an on-screen confirmation that it worked.
|
||||
|
||||
There is little time to interrupt this or jam the bus to stop it.
|
||||
@ -183,12 +183,12 @@ This double-hashed value is what's stored inside the secure element
|
||||
as it travels on the bus. Because of the inclusion of the pairing
|
||||
secret, the hashes generated by each Coldcard will be different.
|
||||
|
||||
With Mark3 hardware, we've added a key-stretching step, which starts
|
||||
With Mark3 hardware, we've added a key-streching step, which starts
|
||||
with the above value, and does HMAC-SHA256 using a secret key known
|
||||
only to the 608a (repeatedly). That value is used directly to check the
|
||||
duress PIN, and if that doesn't match, it is HMAC-SHA256'ed again,
|
||||
using a key that is usage limited. This limits actual PIN login attempts
|
||||
to a set value and is enforced by the 608a internally.
|
||||
to a set value and is enfoced by the 608a internally.
|
||||
|
||||
## Genuine vs. Caution Lights
|
||||
|
||||
@ -273,15 +273,13 @@ Here's what the warning screen looks like:
|
||||
- Dev signs binary release with private "zero key" published in our Github
|
||||
- Give firmware binary file to users (via web download probably)
|
||||
- They upgrade via normal process (copy to MicroSD, or USB upgrade)
|
||||
- On first reboot, big "unauthorized firmware" warning is shown, with delay.
|
||||
- On first reboot, big "unauthorized firmware" warning is shown, with delay
|
||||
- If they know the main PIN (since they are the owner), they follow process to set green light
|
||||
- Next reboot and following, as long as "genuine" mode is maintained, they boot without
|
||||
warnings (Mk3 and earlier)
|
||||
- Mk4 will always alert on boot-up when running code not approved by Coinkite.
|
||||
- Next reboot and following, as long as "genuine" mode is maintained, they boot without warnings
|
||||
|
||||
### Benefits
|
||||
|
||||
- no warnings, but still trustable thanks to ATECC608
|
||||
- no warnings, but still trustable thanks to ATECC608A
|
||||
- random devs can replace 99% of firmware at Micropython layer (everything but bootloader)
|
||||
- but they need to retain our code for talking to bootloader and secure element,
|
||||
so that PIN can be entered and verified.
|
||||
|
||||
@ -1,112 +0,0 @@
|
||||
# BIP-322 Generic Signed Message Format
|
||||
|
||||
BIP-322 specification: <https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki>
|
||||
|
||||
## Proof of Reserves (POR)
|
||||
|
||||
### PoR PSBT
|
||||
|
||||
COLDCARD accepts a specially crafted PSBT file to sign as BIP-322 Proof of Reserves. The PSBT
|
||||
must meet all these requirements:
|
||||
|
||||
* COLDCARD acts as a BIP-322 PSBT signer. It validates the BIP-322 `to_sign`
|
||||
transaction, shows the message from `PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE`, and
|
||||
adds signatures to the PSBT. Finalizing and encoding the final BIP-322
|
||||
signature string is the responsibility of the finalizer.
|
||||
* PSBT MUST include `PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE = 0x09`; the value is
|
||||
the exact message shown to the user and signed by BIP-322.
|
||||
* PSBT requires `PSBT_IN_BIP32_DERIVATION` for each input
|
||||
* P2SH wrapped segwit addresses MUST have proper redeem script in PSBT: `PSBT_IN_REDEEM_SCRIPT`
|
||||
* P2WSH segwit addresses MUST have proper witness script in PSBT: `PSBT_IN_WITNESS_SCRIPT`
|
||||
* PSBT (`to_sign`) MUST have at least one input.
|
||||
* First (0th) input of `to_sign` MUST spend the BIP-322 `to_spend` output.
|
||||
* Input 0 MUST include one of `PSBT_IN_NON_WITNESS_UTXO` or `PSBT_IN_WITNESS_UTXO`.
|
||||
* When input 0 provides `PSBT_IN_WITNESS_UTXO`, COLDCARD reconstructs the
|
||||
expected `to_spend` txid from `PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE` and the
|
||||
witness UTXO scriptPubKey.
|
||||
* When input 0 provides `PSBT_IN_NON_WITNESS_UTXO`, it MUST be the BIP-322
|
||||
`to_spend` transaction as defined in
|
||||
[BIP-322](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki#full):
|
||||
* 1 input, 1 output
|
||||
* output nValue is 0
|
||||
* input prevout hash is 0
|
||||
* input prevout n is 0xffffffff
|
||||
* input scriptSig is `OP_0 PUSH32 message_hash`
|
||||
* PSBT (`to_sign`) MUST only have one output with null-data `OP_RETURN`
|
||||
* `to_sign` transaction version MUST be 0 or 2.
|
||||
* Optionally inputs can be added to `to_sign` for Proof of Reserve signing.
|
||||
* PSBT MUST be version 0 or 2.
|
||||
* Foreign inputs not allowed in POR PSBT.
|
||||
|
||||
The signatures created by the BIP-322 process will never be suitable
|
||||
for a on-chain Bitcoin transaction that could move funds, because
|
||||
of these restrictions imposed by BIP-322.
|
||||
|
||||
### Output
|
||||
|
||||
COLDCARD always returns a signed PSBT for BIP-322 message signing and Proof of
|
||||
Reserves. It never returns an extracted/finalized transaction for these PSBTs.
|
||||
This is true even when finalization is requested over USB, such as with
|
||||
`ckcc unsigned.psbt --finalize`.
|
||||
|
||||
The signed PSBT is the handoff artifact for the external finalizer/verifier. It
|
||||
keeps the PSBT metadata needed to verify or finalize the BIP-322 signature,
|
||||
including public keys, scripts, partial signatures, and UTXO data. This matters
|
||||
because the address being proven normally commits only to a hash of the public
|
||||
key or script, not the public key or script itself.
|
||||
|
||||
### Proof of Reserves Signing Experience
|
||||
|
||||
After Coldcard recognizes a BIP-322 PSBT it reads the message from
|
||||
`PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE` and shows it to the user for approval.
|
||||
COLDCARD verifies that the message hash matches the input 0 `to_spend`
|
||||
commitment before offering to sign.
|
||||
|
||||
When the PSBT contains only input 0, COLDCARD labels the request as
|
||||
`BIP-322 Message`, because it is message signing and does not prove ownership
|
||||
of any additional reserve UTXOs. In that case it does not show transaction
|
||||
input/output counts. When the PSBT contains additional inputs, COLDCARD labels
|
||||
the request as `Proof of Reserves` and shows the reserve amount.
|
||||
|
||||
If the message contains non-ASCII characters, COLDCARD warns that some
|
||||
characters may not be readable on screen.
|
||||
|
||||
Legacy PoR PSBTs without `PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE` are rejected by
|
||||
this flow.
|
||||
|
||||
Read more [here.](https://gist.github.com/orangesurf/0c1d0a31d3ebe7e48335a34d56788d4c)
|
||||
|
||||
Example screen text for a one-input BIP-322 message signing PSBT:
|
||||
|
||||
```text
|
||||
BIP-322 Message
|
||||
|
||||
Message:
|
||||
This is the signed message
|
||||
|
||||
Challenge Address:
|
||||
bc1qzvjnhf7k70uxv6xvneaqxql7k09dd6nsr5wheq
|
||||
|
||||
Press ENTER to approve and sign message. Press (2) to explore transaction.
|
||||
CANCEL to abort.
|
||||
```
|
||||
|
||||
Example screen text for a Proof of Reserves PSBT:
|
||||
|
||||
```text
|
||||
Proof of Reserves
|
||||
|
||||
Message:
|
||||
POR
|
||||
|
||||
Amount 0.20000000 BTC
|
||||
|
||||
Challenge Address:
|
||||
bc1qzvjnhf7k70uxv6xvneaqxql7k09dd6nsr5wheq
|
||||
|
||||
21 inputs
|
||||
1 output
|
||||
|
||||
Press ENTER to approve and sign proof of reserves. Press (2) to explore transaction.
|
||||
CANCEL to abort.
|
||||
```
|
||||
@ -8,6 +8,36 @@
|
||||
#
|
||||
from hashlib import sha256
|
||||
|
||||
# Read input, remove whitespace around it
|
||||
r = input().strip()
|
||||
|
||||
# Calc sha256
|
||||
h = sha256(r.encode()).digest()
|
||||
|
||||
# Show the hash
|
||||
print(h.hex())
|
||||
print()
|
||||
|
||||
# Sanity check for empty input
|
||||
empty = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
if h.hex() == empty:
|
||||
print('WARNING: Input is empty. This is a known wallet\n')
|
||||
|
||||
# Warnings for short length
|
||||
if len(r) < 99:
|
||||
ae = 2.585 * len(r)
|
||||
print('WARNING: Input is only %d bits of entropy\n' % ae)
|
||||
|
||||
# Apply BIP39 to convert into seed words
|
||||
v = int.from_bytes(h, 'big') << 8
|
||||
w = []
|
||||
for i in range(24):
|
||||
v, m = divmod(v, 2048)
|
||||
w.insert(0, m)
|
||||
assert not v
|
||||
|
||||
# final 8 bits are a checksum
|
||||
w[-1] |= sha256(h).digest()[0]
|
||||
|
||||
# English wordlist
|
||||
wl = '''\
|
||||
@ -182,42 +212,5 @@ window wine wing wink winner winter wire wisdom wise wish witness wolf woman
|
||||
wonder wood wool word work world worry worth wrap wreck wrestle wrist write
|
||||
wrong yard year yellow you young youth zebra zero zone zoo'''.split()
|
||||
|
||||
|
||||
def entropy_to_mnemonic24(entropy):
|
||||
# Apply BIP39 to convert entropy into seed words
|
||||
assert len(entropy) == 32
|
||||
v = int.from_bytes(entropy, 'big') << 8
|
||||
indexes = []
|
||||
for i in range(24):
|
||||
v, m = divmod(v, 2048)
|
||||
indexes.insert(0, m)
|
||||
assert not v
|
||||
# final 8 bits are a checksum
|
||||
indexes[-1] |= sha256(entropy).digest()[0]
|
||||
return [wl[i] for i in indexes]
|
||||
|
||||
|
||||
def main():
|
||||
# Read input, remove whitespace around it
|
||||
r = input().strip()
|
||||
# Calc sha256
|
||||
h = sha256(r.encode()).digest()
|
||||
# Show the hash
|
||||
print(h.hex())
|
||||
print()
|
||||
# Sanity check for empty input
|
||||
empty = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
if h.hex() == empty:
|
||||
print('WARNING: Input is empty. This is a known wallet\n')
|
||||
# Warnings for short length
|
||||
if len(r) < 99:
|
||||
ae = 2.585 * len(r)
|
||||
print('WARNING: Input is only %d bits of entropy\n' % ae)
|
||||
|
||||
mnemonic = entropy_to_mnemonic24(h)
|
||||
# Print index number and each word (24)
|
||||
print('\n'.join('%4d: %s' % (n + 1, word) for n, word in enumerate(mnemonic)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# Print index number and each word (24)
|
||||
print('\n'.join('%4d: %s' % (n+1, wl[i]) for n, i in enumerate(w)))
|
||||
|
||||
222
docs/rolls12.py
@ -1,222 +0,0 @@
|
||||
# Usage:
|
||||
#
|
||||
# echo 123456123456 | python3 rolls12.py
|
||||
#
|
||||
# - Requires python3 and nothing else!
|
||||
# - This file is <https://coldcardwallet.com/docs/rolls12.py>
|
||||
# - Public domain.
|
||||
#
|
||||
from hashlib import sha256
|
||||
|
||||
|
||||
# English wordlist
|
||||
wl = '''\
|
||||
abandon ability able about above absent absorb abstract absurd abuse access
|
||||
accident account accuse achieve acid acoustic acquire across act action actor
|
||||
actress actual adapt add addict address adjust admit adult advance advice
|
||||
aerobic affair afford afraid again age agent agree ahead aim air airport aisle
|
||||
alarm album alcohol alert alien all alley allow almost alone alpha already
|
||||
also alter always amateur amazing among amount amused analyst anchor ancient
|
||||
anger angle angry animal ankle announce annual another answer antenna antique
|
||||
anxiety any apart apology appear apple approve april arch arctic area arena
|
||||
argue arm armed armor army around arrange arrest arrive arrow art artefact
|
||||
artist artwork ask aspect assault asset assist assume asthma athlete atom
|
||||
attack attend attitude attract auction audit august aunt author auto autumn
|
||||
average avocado avoid awake aware away awesome awful awkward axis baby bachelor
|
||||
bacon badge bag balance balcony ball bamboo banana banner bar barely bargain
|
||||
barrel base basic basket battle beach bean beauty because become beef before
|
||||
begin behave behind believe below belt bench benefit best betray better between
|
||||
beyond bicycle bid bike bind biology bird birth bitter black blade blame blanket
|
||||
blast bleak bless blind blood blossom blouse blue blur blush board boat body
|
||||
boil bomb bone bonus book boost border boring borrow boss bottom bounce box
|
||||
boy bracket brain brand brass brave bread breeze brick bridge brief bright
|
||||
bring brisk broccoli broken bronze broom brother brown brush bubble buddy
|
||||
budget buffalo build bulb bulk bullet bundle bunker burden burger burst bus
|
||||
business busy butter buyer buzz cabbage cabin cable cactus cage cake call calm
|
||||
camera camp can canal cancel candy cannon canoe canvas canyon capable capital
|
||||
captain car carbon card cargo carpet carry cart case cash casino castle casual
|
||||
cat catalog catch category cattle caught cause caution cave ceiling celery
|
||||
cement census century cereal certain chair chalk champion change chaos chapter
|
||||
charge chase chat cheap check cheese chef cherry chest chicken chief child
|
||||
chimney choice choose chronic chuckle chunk churn cigar cinnamon circle citizen
|
||||
city civil claim clap clarify claw clay clean clerk clever click client cliff
|
||||
climb clinic clip clock clog close cloth cloud clown club clump cluster clutch
|
||||
coach coast coconut code coffee coil coin collect color column combine come
|
||||
comfort comic common company concert conduct confirm congress connect consider
|
||||
control convince cook cool copper copy coral core corn correct cost cotton
|
||||
couch country couple course cousin cover coyote crack cradle craft cram crane
|
||||
crash crater crawl crazy cream credit creek crew cricket crime crisp critic
|
||||
crop cross crouch crowd crucial cruel cruise crumble crunch crush cry crystal
|
||||
cube culture cup cupboard curious current curtain curve cushion custom cute
|
||||
cycle dad damage damp dance danger daring dash daughter dawn day deal debate
|
||||
debris decade december decide decline decorate decrease deer defense define
|
||||
defy degree delay deliver demand demise denial dentist deny depart depend
|
||||
deposit depth deputy derive describe desert design desk despair destroy detail
|
||||
detect develop device devote diagram dial diamond diary dice diesel diet differ
|
||||
digital dignity dilemma dinner dinosaur direct dirt disagree discover disease
|
||||
dish dismiss disorder display distance divert divide divorce dizzy doctor
|
||||
document dog doll dolphin domain donate donkey donor door dose double dove
|
||||
draft dragon drama drastic draw dream dress drift drill drink drip drive drop
|
||||
drum dry duck dumb dune during dust dutch duty dwarf dynamic eager eagle early
|
||||
earn earth easily east easy echo ecology economy edge edit educate effort egg
|
||||
eight either elbow elder electric elegant element elephant elevator elite else
|
||||
embark embody embrace emerge emotion employ empower empty enable enact end
|
||||
endless endorse enemy energy enforce engage engine enhance enjoy enlist enough
|
||||
enrich enroll ensure enter entire entry envelope episode equal equip era erase
|
||||
erode erosion error erupt escape essay essence estate eternal ethics evidence
|
||||
evil evoke evolve exact example excess exchange excite exclude excuse execute
|
||||
exercise exhaust exhibit exile exist exit exotic expand expect expire explain
|
||||
expose express extend extra eye eyebrow fabric face faculty fade faint faith
|
||||
fall false fame family famous fan fancy fantasy farm fashion fat fatal father
|
||||
fatigue fault favorite feature february federal fee feed feel female fence
|
||||
festival fetch fever few fiber fiction field figure file film filter final
|
||||
find fine finger finish fire firm first fiscal fish fit fitness fix flag flame
|
||||
flash flat flavor flee flight flip float flock floor flower fluid flush fly
|
||||
foam focus fog foil fold follow food foot force forest forget fork fortune
|
||||
forum forward fossil foster found fox fragile frame frequent fresh friend
|
||||
fringe frog front frost frown frozen fruit fuel fun funny furnace fury future
|
||||
gadget gain galaxy gallery game gap garage garbage garden garlic garment gas
|
||||
gasp gate gather gauge gaze general genius genre gentle genuine gesture ghost
|
||||
giant gift giggle ginger giraffe girl give glad glance glare glass glide glimpse
|
||||
globe gloom glory glove glow glue goat goddess gold good goose gorilla gospel
|
||||
gossip govern gown grab grace grain grant grape grass gravity great green grid
|
||||
grief grit grocery group grow grunt guard guess guide guilt guitar gun gym
|
||||
habit hair half hammer hamster hand happy harbor hard harsh harvest hat have
|
||||
hawk hazard head health heart heavy hedgehog height hello helmet help hen hero
|
||||
hidden high hill hint hip hire history hobby hockey hold hole holiday hollow
|
||||
home honey hood hope horn horror horse hospital host hotel hour hover hub huge
|
||||
human humble humor hundred hungry hunt hurdle hurry hurt husband hybrid ice
|
||||
icon idea identify idle ignore ill illegal illness image imitate immense immune
|
||||
impact impose improve impulse inch include income increase index indicate
|
||||
indoor industry infant inflict inform inhale inherit initial inject injury
|
||||
inmate inner innocent input inquiry insane insect inside inspire install intact
|
||||
interest into invest invite involve iron island isolate issue item ivory jacket
|
||||
jaguar jar jazz jealous jeans jelly jewel job join joke journey joy judge juice
|
||||
jump jungle junior junk just kangaroo keen keep ketchup key kick kid kidney
|
||||
kind kingdom kiss kit kitchen kite kitten kiwi knee knife knock know lab label
|
||||
labor ladder lady lake lamp language laptop large later latin laugh laundry
|
||||
lava law lawn lawsuit layer lazy leader leaf learn leave lecture left leg legal
|
||||
legend leisure lemon lend length lens leopard lesson letter level liar liberty
|
||||
library license life lift light like limb limit link lion liquid list little
|
||||
live lizard load loan lobster local lock logic lonely long loop lottery loud
|
||||
lounge love loyal lucky luggage lumber lunar lunch luxury lyrics machine mad
|
||||
magic magnet maid mail main major make mammal man manage mandate mango mansion
|
||||
manual maple marble march margin marine market marriage mask mass master match
|
||||
material math matrix matter maximum maze meadow mean measure meat mechanic
|
||||
medal media melody melt member memory mention menu mercy merge merit merry
|
||||
mesh message metal method middle midnight milk million mimic mind minimum minor
|
||||
minute miracle mirror misery miss mistake mix mixed mixture mobile model modify
|
||||
mom moment monitor monkey monster month moon moral more morning mosquito mother
|
||||
motion motor mountain mouse move movie much muffin mule multiply muscle museum
|
||||
mushroom music must mutual myself mystery myth naive name napkin narrow nasty
|
||||
nation nature near neck need negative neglect neither nephew nerve nest net
|
||||
network neutral never news next nice night noble noise nominee noodle normal
|
||||
north nose notable note nothing notice novel now nuclear number nurse nut oak
|
||||
obey object oblige obscure observe obtain obvious occur ocean october odor off
|
||||
offer office often oil okay old olive olympic omit once one onion online only
|
||||
open opera opinion oppose option orange orbit orchard order ordinary organ
|
||||
orient original orphan ostrich other outdoor outer output outside oval oven
|
||||
over own owner oxygen oyster ozone pact paddle page pair palace palm panda
|
||||
panel panic panther paper parade parent park parrot party pass patch path
|
||||
patient patrol pattern pause pave payment peace peanut pear peasant pelican
|
||||
pen penalty pencil people pepper perfect permit person pet phone photo phrase
|
||||
physical piano picnic picture piece pig pigeon pill pilot pink pioneer pipe
|
||||
pistol pitch pizza place planet plastic plate play please pledge pluck plug
|
||||
plunge poem poet point polar pole police pond pony pool popular portion position
|
||||
possible post potato pottery poverty powder power practice praise predict
|
||||
prefer prepare present pretty prevent price pride primary print priority prison
|
||||
private prize problem process produce profit program project promote proof
|
||||
property prosper protect proud provide public pudding pull pulp pulse pumpkin
|
||||
punch pupil puppy purchase purity purpose purse push put puzzle pyramid quality
|
||||
quantum quarter question quick quit quiz quote rabbit raccoon race rack radar
|
||||
radio rail rain raise rally ramp ranch random range rapid rare rate rather
|
||||
raven raw razor ready real reason rebel rebuild recall receive recipe record
|
||||
recycle reduce reflect reform refuse region regret regular reject relax release
|
||||
relief rely remain remember remind remove render renew rent reopen repair
|
||||
repeat replace report require rescue resemble resist resource response result
|
||||
retire retreat return reunion reveal review reward rhythm rib ribbon rice rich
|
||||
ride ridge rifle right rigid ring riot ripple risk ritual rival river road
|
||||
roast robot robust rocket romance roof rookie room rose rotate rough round
|
||||
route royal rubber rude rug rule run runway rural sad saddle sadness safe sail
|
||||
salad salmon salon salt salute same sample sand satisfy satoshi sauce sausage
|
||||
save say scale scan scare scatter scene scheme school science scissors scorpion
|
||||
scout scrap screen script scrub sea search season seat second secret section
|
||||
security seed seek segment select sell seminar senior sense sentence series
|
||||
service session settle setup seven shadow shaft shallow share shed shell sheriff
|
||||
shield shift shine ship shiver shock shoe shoot shop short shoulder shove
|
||||
shrimp shrug shuffle shy sibling sick side siege sight sign silent silk silly
|
||||
silver similar simple since sing siren sister situate six size skate sketch
|
||||
ski skill skin skirt skull slab slam sleep slender slice slide slight slim
|
||||
slogan slot slow slush small smart smile smoke smooth snack snake snap sniff
|
||||
snow soap soccer social sock soda soft solar soldier solid solution solve
|
||||
someone song soon sorry sort soul sound soup source south space spare spatial
|
||||
spawn speak special speed spell spend sphere spice spider spike spin spirit
|
||||
split spoil sponsor spoon sport spot spray spread spring spy square squeeze
|
||||
squirrel stable stadium staff stage stairs stamp stand start state stay steak
|
||||
steel stem step stereo stick still sting stock stomach stone stool story stove
|
||||
strategy street strike strong struggle student stuff stumble style subject
|
||||
submit subway success such sudden suffer sugar suggest suit summer sun sunny
|
||||
sunset super supply supreme sure surface surge surprise surround survey suspect
|
||||
sustain swallow swamp swap swarm swear sweet swift swim swing switch sword
|
||||
symbol symptom syrup system table tackle tag tail talent talk tank tape target
|
||||
task taste tattoo taxi teach team tell ten tenant tennis tent term test text
|
||||
thank that theme then theory there they thing this thought three thrive throw
|
||||
thumb thunder ticket tide tiger tilt timber time tiny tip tired tissue title
|
||||
toast tobacco today toddler toe together toilet token tomato tomorrow tone
|
||||
tongue tonight tool tooth top topic topple torch tornado tortoise toss total
|
||||
tourist toward tower town toy track trade traffic tragic train transfer trap
|
||||
trash travel tray treat tree trend trial tribe trick trigger trim trip trophy
|
||||
trouble truck true truly trumpet trust truth try tube tuition tumble tuna
|
||||
tunnel turkey turn turtle twelve twenty twice twin twist two type typical ugly
|
||||
umbrella unable unaware uncle uncover under undo unfair unfold unhappy uniform
|
||||
unique unit universe unknown unlock until unusual unveil update upgrade uphold
|
||||
upon upper upset urban urge usage use used useful useless usual utility vacant
|
||||
vacuum vague valid valley valve van vanish vapor various vast vault vehicle
|
||||
velvet vendor venture venue verb verify version very vessel veteran viable
|
||||
vibrant vicious victory video view village vintage violin virtual virus visa
|
||||
visit visual vital vivid vocal voice void volcano volume vote voyage wage wagon
|
||||
wait walk wall walnut want warfare warm warrior wash wasp waste water wave way
|
||||
wealth weapon wear weasel weather web wedding weekend weird welcome west wet
|
||||
whale what wheat wheel when where whip whisper wide width wife wild will win
|
||||
window wine wing wink winner winter wire wisdom wise wish witness wolf woman
|
||||
wonder wood wool word work world worry worth wrap wreck wrestle wrist write
|
||||
wrong yard year yellow you young youth zebra zero zone zoo'''.split()
|
||||
|
||||
|
||||
def entropy_to_mnemonic12(entropy):
|
||||
# Apply BIP39 to convert entropy into seed words
|
||||
assert len(entropy) == 16
|
||||
v = int.from_bytes(entropy, 'big') << 4
|
||||
indexes = []
|
||||
for i in range(12):
|
||||
v, m = divmod(v, 2048)
|
||||
indexes.insert(0, m)
|
||||
# final 4 bits are a checksum
|
||||
indexes[-1] += sha256(entropy).digest()[0] >> 4
|
||||
return [wl[i] for i in indexes]
|
||||
|
||||
|
||||
def main():
|
||||
# Read input, remove whitespace around it
|
||||
r = input().strip()
|
||||
# Calc sha256
|
||||
h = sha256(r.encode()).digest()[:16]
|
||||
# Show the hash
|
||||
print(h.hex())
|
||||
print()
|
||||
# Sanity check for empty input
|
||||
empty = "e3b0c44298fc1c149afbf4c8996fb924"
|
||||
if h.hex() == empty:
|
||||
print('WARNING: Input is empty. This is a known wallet\n')
|
||||
# Warnings for short length
|
||||
if len(r) < 50:
|
||||
ae = 2.585 * len(r)
|
||||
print('WARNING: Input is only %d bits of entropy\n' % ae)
|
||||
|
||||
mnemonic = entropy_to_mnemonic12(h)
|
||||
# Print index number and each word (12)
|
||||
print('\n'.join('%4d: %s' % (n + 1, word) for n, word in enumerate(mnemonic)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,299 +0,0 @@
|
||||
# Dual Secure Elements
|
||||
|
||||
## Background
|
||||
|
||||
The **COLDCARD<sup>®</sup>** Mk4 and Q have two secure elements:
|
||||
|
||||
- SE1 (Secure Element 1): Microchip ATECC608
|
||||
- SE2 (Secure Element 2): Maxim DS28C36B
|
||||
|
||||
Because different vendors make them, they do not share bugs and weaknesses.
|
||||
|
||||
Although Mk3 uses the SE1 chip, Mk4 uses it a little differently.
|
||||
SE2 is an entirely new chip, never previously deployed in a COLDCARD.
|
||||
Both chips use secp256r1 for Elliptic-curve Cryptography (ECC) and
|
||||
HMAC-SHA256.
|
||||
|
||||
## Design Goals
|
||||
|
||||
### The Assumption
|
||||
|
||||
Assume attackers have physical access to a COLDCARD, have opened
|
||||
the case, and can probe the bus connections between the MCU and SE1
|
||||
or SE2. They may even de-solder SE1 and SE2 from the board, and put
|
||||
active circuits between them and the MCU — an active MiTM attack.
|
||||
|
||||
|
||||
### The Solutions
|
||||
|
||||
Three parties hold secrets in the COLDCARD: the main MCU (microcontroller)
|
||||
and the two secure elements. Our goal is that **all three** must
|
||||
be fully compromised to access the seed words. Thus, if one part
|
||||
has a vulnerability, the COLDCARD as a whole is still secure.
|
||||
Additionally, knowledge of the correct PIN code is required, even
|
||||
if all three devices are cracked wide open. (This is a last line
|
||||
of defence, a brute-force attack on all PIN combinations will breach
|
||||
it.)
|
||||
|
||||
COLDCARD also supports new Trick PIN codes with side effects such
|
||||
as wiping or bricking the COLDCARD, or providing access to a decoy
|
||||
or duress wallet. Ideally, attackers will not detect using a false
|
||||
PIN, even while probing the signals on the board.
|
||||
|
||||
|
||||
## MCU vs. SE1 and SE2
|
||||
|
||||
As in Mk1 through Mk3 of the COLDCARD, the MCU has a Pairing Secret.
|
||||
The MCU uses the Pairing Secret to authenticate itself to SE1 and
|
||||
vice versa. Flash memory holds the 32-byte Pairing Secret in a
|
||||
protected area. Only the boot loader code can access this memory,
|
||||
and the MicroPython code cannot read this area of the chip. Using
|
||||
an internal firewall feature and PCROP (proprietary code readout
|
||||
protection) achieves this result.
|
||||
|
||||
COLDCARD also shares a secret between SE2 and the MCU. Just like SE1,
|
||||
this authenticates SE2 to the MCU and encrypts their mutual
|
||||
communications. The Pairing Secret for SE2 is not stored in SE1 and
|
||||
is unique from the other Pairing Secret used for SE1.
|
||||
|
||||
These Pairing Secrets secure the electrical connections between
|
||||
SE1, SE2, and the MCU probing attempts. In practice, this means
|
||||
most commands and responses are XOR'ed with an HMAC-SHA256 value
|
||||
where the HMAC key is the Pairing Secret and the message is a hash
|
||||
of the command arguments.
|
||||
|
||||
There are also cases where an ECC signature and ECDH establish a
|
||||
shared secret between devices.
|
||||
|
||||
|
||||
## Seed Decryption
|
||||
|
||||
SE1 still holds the seed words, but they are AES-encrypted, and SE1
|
||||
does not contain a key to decrypt it. The MCU and SE2 store the
|
||||
seed word decryption key. To access the AES key held in SE2, SE1
|
||||
has to perform a public key signature and ECDH setup, which requires
|
||||
the main PIN code. The MCU will only provide its key if both SE1
|
||||
and SE2 are satisfied.
|
||||
|
||||
Earlier COLDCARD versions XOR-ed the stored value with a secret in
|
||||
the MCU (one-time pad), but this new approach is more powerful
|
||||
because it mixes in values from the SE2 and MCU using AES; this
|
||||
increases flexibility and resistance to known plain text attacks.
|
||||
|
||||
|
||||
## All the Keys
|
||||
|
||||
| Symbol | Chip's Name | Type | Holder | Purpose
|
||||
|-----------------------|---------------------|--------------|----------|----------
|
||||
| `SE1 pairing` | slot 1 | HMAC | SE1, MCU | Protects communications between SE1 and MCU
|
||||
| `SE2 pairing` | secret A | HMAC | SE2, MCU | Pairing for SE2
|
||||
| `SE2 comms` | keypair A | ECC | SE2 | MCU captures pubkey half, used in ECDH comms
|
||||
| `SE joiner` | slot 7, pubkey C | ECC | SE1/SE2 | SE2 knows only public part, SE1 has privkey
|
||||
| `pin stretch` | slot 2 | HMAC | SE1 | Key stretching for PIN entry and anti-phish words
|
||||
| `firmware` | slot 14 | SHA256d | SE1 | Firmware checksum, controls green/red LEDs
|
||||
| `nonce/chksum` | slot 10 | data | SE1 | AES nonce and GMAC tag, protected by PIN
|
||||
| `SE2 easy key` | page 14 | AES via HMAC | SE2 | Another SE2 part of AES seed key
|
||||
| `SE2 hard key` | page 15 | AES via ECC | SE2 | SE2's part of AES seed key; ECC used to unlock
|
||||
| `tpin key` | `tpin_key` | HMAC(key) | MCU | Key for HMAC used to encrypt trick PINs
|
||||
| `trick PIN slots` | pages 0-12 | HMAC | SE2 | Protect duress wallet seeds and pins (6 spots)
|
||||
| `SE2 trash` | secret B | HMAC | SE2 | Used to destroy values (only SE2 knows the value)
|
||||
| `hash cache secret` | `hash_cache_secret` | XOR/AES | MCU | In-memory encryption of actual PIN when unlocked
|
||||
| `mcu hmac key` | `mcu_hmac_key` | HMAC | MCU | Used as HMAC key to compress other keys
|
||||
| `replaceable mcu key` | `MCU_KEYS` | AES | MCU | Replaceable MCU key (up to 256 times)
|
||||
|
||||
All keys listed are 32 bytes long and picked randomly using the hardware RNG.
|
||||
|
||||
An entered PIN code goes through the same hashing process as with
|
||||
previous COLDCARD versions. This involved process uses a value in
|
||||
SE1 for key stretching purposes. Before the final hashed PIN value
|
||||
unlocks a few slots in SE1, `MCU pin check` HMACs the hashed PIN
|
||||
value and uses the result to check for Trick PINs in SE2. If no
|
||||
Trick PINs decode in SE2's memory, the PIN is tested against SE1.
|
||||
|
||||
If the PIN is correct:
|
||||
|
||||
- The the PIN-attempt counter resets to 13 attempts remaining.
|
||||
- The `SE joiner` slot on SE1 establishes ECDH communication between the MCU and SE1.
|
||||
- `SE joiner` authorizes the page on SE2 holding `SE2 seed key`.
|
||||
|
||||
The `SE2 seed key` value and the `MCU seed key` are now available
|
||||
to decrypt the seed words unlocked by the PIN.
|
||||
|
||||
These sources combine to create the final seed decryption key:
|
||||
|
||||
k = HMAC-SHA256 (key = (`mcu hmac key`), msg = (`SE2 easy key` + `SE2 hard key` + current `replaceable mcu key`))
|
||||
|
||||
### AES Details
|
||||
|
||||
COLDCARD uses AES-256 in
|
||||
[CTR mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)).
|
||||
Message authentication involves adding 32 bytes of zeros to the
|
||||
end of the message and checking they decode correctly.
|
||||
|
||||
A new keyslot, 10, stores the MAC data (encrypted zeros).
|
||||
|
||||
The starting nonce for CTR mode is fixed random value composed of
|
||||
the first 15 bytes of the `mcu hmac key`, followed by a zero (this
|
||||
increments if more than 16 bytes are encrypted or decrypted).
|
||||
|
||||
|
||||
### MCU Keys
|
||||
|
||||
There are two keys in the MCU that affect the seed, the `mcu hmac
|
||||
key`, and the `replaceable mcu key`.
|
||||
|
||||
The `mcu hmac key` is fixed at factory setup time. It acts as the key for
|
||||
an HMAC operation that compresses the seed key.
|
||||
|
||||
The `replaceable mcu keys` are picked at runtime and saved to flash.
|
||||
There is only one key active at a time, but there are a few slots
|
||||
in flash for new ones. When the COLDCARD performs a wipe, it clears
|
||||
the current `mcu replaceable key`. That process is very fast and
|
||||
does not have any external signals to betray it's occurring. There
|
||||
is no need to clear the keys in SE2 or SE1 since without the `mcu
|
||||
replaceable key`, the actual AES key is still unknown.
|
||||
|
||||
New COLDCARDs will ship from the factory with no key picked yet.
|
||||
Once the main PIN is set and a wallet imported or created, the
|
||||
first `mcu replaceable key` is picked.
|
||||
|
||||
Each wipe operation consumes a replaceable key, and there are a limited
|
||||
number of them (256) available during the life of the COLDCARD.
|
||||
|
||||
|
||||
### Captured values
|
||||
|
||||
The MCU captures public and semi-public values and prohibits them
|
||||
from changing. These include:
|
||||
|
||||
- SE1 and SE2 chip serial numbers (used in most HMAC responses, fully public)
|
||||
- The public key for `SE joiner` and `SE2 comms` key pairs
|
||||
|
||||
Logically, these values are potentially readable at runtime. Since
|
||||
they are not expected to change, storing them assures they will not
|
||||
change when under attack (perhaps by substituting a different part).
|
||||
|
||||
Blocking the values from read-back out of the secure element is
|
||||
done where possible.
|
||||
|
||||
|
||||
## Trick PINs
|
||||
|
||||
Supporting certain COLDCARD features requires several distinct PIN
|
||||
codes with various side effects, such as:
|
||||
|
||||
- Unlocking a duress wallet
|
||||
- Triggering a long login delay
|
||||
- Bricking the COLDCARD
|
||||
- Blanking the COLDCARD
|
||||
|
||||
SE2's even-numbered pages store these PINs with the adjacent odd
|
||||
slot holding the corresponding secret. The MCU tries each PIN a
|
||||
user enters against all the slots and works silently to support the
|
||||
Trick features.
|
||||
|
||||
The type of support depends on the type of Trick. Duress wallets
|
||||
require storing 32 or 64 bytes of seed words (generated from the true
|
||||
seed via BIP-85). Other cases dictate encoding a short numeric code
|
||||
provided to higher layers for implementation. For example, a flag
|
||||
in that code can trigger the boot ROM to wipe the `mcu seed key`.
|
||||
External actors cannot interrupt or monitor this change because it
|
||||
is internal to the MCU. The `mcu seed key` is as critical as the
|
||||
other parts of the AES key to access the seed words. The `mcu seed
|
||||
key` being zero makes the seed permanently inaccessible.
|
||||
|
||||
The MCU code may continue speaking to SE1 to complete the fraud,
|
||||
but in general, SE1 will no longer store the duress wallet or Brick
|
||||
Me PINs as in previous generations (Mk1-3). Mk4 and Q implement
|
||||
those feature in SE2.
|
||||
|
||||
|
||||
### Trick PIN Operation
|
||||
|
||||
When a PIN is entered, it is hashed through a series of operations
|
||||
that take a round trip to SE1. This is the same as the Mk3 using
|
||||
its secure element for key stretching. A 32-byte deterministic hash,
|
||||
dependent on secrets from the main MCU and SE1 and unique to each
|
||||
COLDCARD, is returned. The hash is compared against all 14 of the
|
||||
slots in SE2.
|
||||
|
||||
A few least significant bits (LSB) are masked out of the hash. If
|
||||
the PIN entered matches one of the slots, the masked-out bits are
|
||||
checked. Based on the check, two values are taken: `tc_arg` and
|
||||
`tc_flags`. These two values implement flags and arguments
|
||||
required to implement the Trick PIN features.
|
||||
|
||||
|
||||
### Trick PIN slot data
|
||||
|
||||
The flags are checked along with their corresponding arguments if
|
||||
a match is found while iterating through all the slots. Some flags
|
||||
are implemented directly in the boot loader before anything is sent
|
||||
to Micropython. Other flags are passed to Micropython for implementation
|
||||
or are implemented in both the boot ROM and Micropython.
|
||||
|
||||
**Example 1** A flag is set for Fast Wipe and an attacker has control
|
||||
of the I2C-bus going to SE2 and the one-wire bus going to SE1. They
|
||||
can attempt to block the wipe in hopes of trying different PINs
|
||||
without damaging anything, but they will fail. If `tc_flags`, which
|
||||
is a bitmask, indicates a Fast Wipe, the seed wiping happens inside
|
||||
the main MCU. It clears data inside the main MCU extremely quickly
|
||||
and with no external signals — either before or after —
|
||||
to indicate it happened. This prevents blocking Fast Wipe.
|
||||
|
||||
**Example 2** The login countdown feature allows specifying a Trick
|
||||
PIN. When the Trick PIN is entered, the COLDCARD will show a countdown
|
||||
timer and the time that must elapse before logging in is allowed.
|
||||
`tc_arg` encodes the number of minutes on the delay.
|
||||
|
||||
**Example 3** Although SE2 holds the Trick PINs, it does not know
|
||||
the True PIN and Delta Mode will not reveal the full True PIN. The
|
||||
"Delta Mode" Trick PIN has its unique hash result, and an entry from SE1 is used
|
||||
to calculate the True PIN. This is accomplished by masking out the
|
||||
last four digits of the hash and substituting four digits from
|
||||
`tc_arg`. Limiting it to four digits enables a difference between
|
||||
the Delta PIN and the True PIN.
|
||||
|
||||
This makes the True PIN unknowable without knowing the Delta Mode
|
||||
Trick PIN; SE2 cannot be searched for the True PIN and generating
|
||||
the PIN hashes for SE2 would only be possible after cracking SE1
|
||||
and the main MCU.
|
||||
|
||||
|
||||
## Spare Slots
|
||||
|
||||
Moving the duress wallet and Brick Me PINs to SE2 left free storage
|
||||
inside SE1. This storage is called Spare Secrets. Spare Secrets has
|
||||
3 × 72 bytes of space, protected by the same measures as the
|
||||
seed words.
|
||||
|
||||
Mk4/Q still supports the Long Secret (416 bytes), but its API is
|
||||
changed. The slow speed of fetching the Long Secret in 32-byte
|
||||
blocks due to the reconstructing the primary AES Seed Key for each
|
||||
call necessitated the change.
|
||||
|
||||
|
||||
## Observations
|
||||
|
||||
It's essential that SE2 cannot validate a PIN code. Brute-forcing
|
||||
SE2 would be easy because it lacks SE1's rate limiting (or usage
|
||||
counter), so SE2 only stores Trick PINs. Testing against the Trick
|
||||
PINs would require compromising the MCU. Due to SE2's performance,
|
||||
the maximum attempt rate would be 6 ms. However, controlling the
|
||||
MCU makes that testing pointless to such an attacker; they could
|
||||
just NOP-out the Trick PIN checking.
|
||||
|
||||
A user expecting attackers smart enough to crack open the case and
|
||||
monitor the buses should provide a Trick PIN that wipes the COLDCARD's
|
||||
secrets and bricks it. Sophisticated attackers could detect Trick
|
||||
PINs that continue operation (duress PINs), unlike the average thug.
|
||||
|
||||
|
||||
## Fast Brick
|
||||
|
||||
Quickly bricking the system is done by rotating the SE1
|
||||
pairing secret by mixing in a random nonce via the chip's key
|
||||
rotation process. Only a knowledge of the old pairing secret is
|
||||
needed for this change. This is similar the to `brick_me` PIN
|
||||
and how that worked on previous products.
|
||||
|
||||
@ -1,293 +0,0 @@
|
||||
# COLDCARD Mk4/Mk5/Q Security Model
|
||||
|
||||
## Abstract
|
||||
|
||||
**COLDCARD<sup>®</sup>** Marks 1 through 3 used a single secure
|
||||
element to store a hardware wallet’s most important secret: the 24
|
||||
seed words used to generate a deterministic wallet. This secure
|
||||
element is in a limited and read-only state until authorized by PIN entry.
|
||||
|
||||
Clearing the secure element is impossible without first entering
|
||||
the correct PIN. The Mk4 COLDCARD introduced several new security
|
||||
features, including a second secure element and Trick PINs which
|
||||
can render stored data unrecoverable, or brick the COLDCARD entirely
|
||||
if necessary, without entering the true authorization PIN (True
|
||||
PIN).
|
||||
|
||||
The COLDCARD Q continues with the same security model introduced
|
||||
in Mk4.
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
Previous versions of the COLDCARD had a single secure element, first
|
||||
the Microchip ATECC508A and later the ATECC608B, to store its
|
||||
secrets. This secure element has 72 bytes of storage protected by
|
||||
a 4- to 12-digit PIN code.
|
||||
|
||||
Mk4 adds a second secure element to the COLDCARD. The ATECC608 is
|
||||
still used, now called SE1 (Secure Element 1), along with a new
|
||||
chip, the Maxim DS28C36B, called SE2 (Secure Element 2). The
|
||||
DS28C36B (SE2) has more memory with fifteen 32-byte slots of secure
|
||||
storage. The two chips have a cryptographic link requiring signed
|
||||
challenges between them.
|
||||
|
||||
The design goal for Mk4 is that both secure elements 1 and 2, and
|
||||
the main MCU need to be fully compromised before seed words are
|
||||
leaked. It's no longer the case that an unanticipated issue with a
|
||||
secure element's chip design can allow seed words to escape the
|
||||
COLDCARD.
|
||||
|
||||
Encrypting values inside the secure element began with Mk2. Mk4
|
||||
distributes the encryption key among three components: the main
|
||||
MCU, SE1 and SE2. The main MCU now can clear its part of the
|
||||
HMAC-SHA256 key, making it impossible to decrypt the data held inside
|
||||
SE1. The Fast Wipe feature performs a simple write to the main MCU without
|
||||
authentication data. This also allows Trick PIN configurations to
|
||||
include functions like wiping the seed.
|
||||
|
||||
Learn more about the [Mk4's dual secure elements.](mk4-secure-elements.md)
|
||||
|
||||
## Trick PINs Implementation
|
||||
|
||||
Mk4 introduces a new concept called Trick PINs. These PIN codes
|
||||
are any PIN other than the True PIN, and the user configures them
|
||||
to perform different functions in many different ways.
|
||||
|
||||
When a user sets a Trick PIN, SE2 records it with a few bytes of
|
||||
flags and arguments. This may also include up to 64 bytes of seed
|
||||
data. When a PIN is entered on a COLDCARD, the boot ROM checks all
|
||||
the Trick PINs first. If the PIN entered matches a set Trick PIN,
|
||||
the COLDCARD performs the trick.
|
||||
|
||||
Find the Trick PIN settings under:
|
||||
`Settings > Login Settings > Trick PINS`
|
||||
|
||||
|
||||
### Trick PIN Options
|
||||
|
||||
Using a Trick PIN can initiate one or more of the following options:
|
||||
|
||||
- Wipe the seed
|
||||
- Mimic a blank device (appears to have wiped the seed)
|
||||
- Load a duress wallet (two types supported)
|
||||
- Brick the COLDCARD immediately
|
||||
- Start a login countdown timer (may include wipe/brick)
|
||||
- Pretend PIN is incorrect and perform additional tricks (e.g. wipe)
|
||||
- Display wiped message on the screen (nothing is actually wiped)
|
||||
- Reboot the COLDCARD (no change to state)
|
||||
- Delta Mode: advanced duress option with real seed
|
||||
|
||||
ANY wrong PIN can trigger most types of tricks, as can specific values.
|
||||
|
||||
|
||||
#### Hidden Tricks
|
||||
|
||||
Once defined, you may hide a Trick PIN from the menu. The Trick PIN
|
||||
won't appear in the menu despite being set. To change it later, select
|
||||
`Add New Trick` and re-enter the Trick PIN. The COLDCARD will
|
||||
restore the Trick PIN to the menu.
|
||||
|
||||
|
||||
#### Duress Wallets
|
||||
|
||||
This Trick PIN leads to a duress wallet that operates as if the
|
||||
user entered the True PIN. An attacker will only have access to the
|
||||
duress wallet. They won't have access to steal the main stash.
|
||||
|
||||
The private key can be automatically derived using BIP-85 methods,
|
||||
based on account numbers 1001, 1002, or 1003 for a 24-word duress wallet
|
||||
(or 2001, 2002, 2003 for a 12-word one). Because this is BIP-85
|
||||
based, it behaves exactly like a normal wallet. Defining a passphrase
|
||||
for the wallet is also possible.
|
||||
|
||||
The Mk4 also supports older COLDCARD duress wallets and their UTXOs
|
||||
on the blockchain. There is an option to create compatible wallets
|
||||
easily.
|
||||
|
||||
|
||||
#### Brick Self
|
||||
|
||||
A Trick PIN that bricks (destroys) the COLDCARD.
|
||||
|
||||
The brick effect happens immediately, and the screen displays
|
||||
"Bricked." Subsequent reboots will also show "Bricked." The COLDCARD
|
||||
is now e-waste.
|
||||
|
||||
|
||||
#### Wipe Seed
|
||||
|
||||
Use a Trick PIN to wipe (forget) the COLDCARD's seed. After the
|
||||
seed is wiped, a user can set the device to:
|
||||
|
||||
- Reboot with no message
|
||||
- Be silent and pretend the PIN code was wrong
|
||||
- Proceed to a duress wallet
|
||||
- Show a message saying, "Seed is wiped, Stop"
|
||||
|
||||
|
||||
#### Look Blank
|
||||
|
||||
The COLDCARD pretends it is blank, but it is not. All data remain intact.
|
||||
|
||||
|
||||
#### Login Countdown
|
||||
|
||||
The COLDCARD wipes the seed and displays a login countdown. This
|
||||
function has two options for the end of the countdown: the COLDCARD
|
||||
will brick itself, or the COLDCARD will just reset.
|
||||
|
||||
|
||||
#### Delta Mode
|
||||
|
||||
Delta Mode is the most advanced option, and it is not recommended
|
||||
novices use it. This function is inspired by safes that allow adding
|
||||
one digit to the final number to act as a duress code. The safe
|
||||
will open, but a silent alarm is triggered (or poison gas is
|
||||
released). A Delta Mode Trick PIN must differ from the True PIN,
|
||||
but only by **the last four digits**.
|
||||
|
||||
Delta Mode logs into the secrets in SE1 using the True PIN code.
|
||||
The COLDCARD calculates the True PIN using the Trick PIN and contents
|
||||
of SE2. Nothing unusual can be detected externally; the COLDCARD
|
||||
behaves normally and it has access to the actual seed words.
|
||||
Internally, however, the COLDCARD operates in a special mode.
|
||||
|
||||
In Delta Mode, attempting to view the seed words wipes the seed.
|
||||
Anything that could reveal the seed words, like accessing the Trick
|
||||
PIN menu to determine if a trick is in effect will wipe the seed.
|
||||
An attacker avoiding these menu functions could sign transactions
|
||||
in Delta Mode.
|
||||
|
||||
Transactions signed in Delta Mode do not have correct signatures.
|
||||
If the signed transaction is broadcast, the network will reject it
|
||||
because the signatures do not verify.
|
||||
|
||||
The value of Delta Mode is against a well-researched attacker who
|
||||
knows the XPUB or XFP of the true wallet which is their target. An
|
||||
Electrum wallet file on a user's personal computer can provide these
|
||||
identifiers to an attacker. The computer's owner obviously controls
|
||||
that wallet and its temptingly massive balance. Since the user's
|
||||
UTXOs are known; a duress wallet won't suffice. Delta Mode lets the
|
||||
attacker believe they control the correct XPUB/XPRV and UTXOs.
|
||||
|
||||
|
||||
## Other Mk4 Security-Related Improvements
|
||||
|
||||
In addition to adding a second secure element in Mk4, COLDCARD gains
|
||||
several other security improvements.
|
||||
|
||||
|
||||
### Countdown to Login Feature
|
||||
|
||||
The Mk4 adds a configuration option to incorporate Fast Wipe.
|
||||
Implementing this feature as a Trick PIN keeps it protected inside SE2.
|
||||
|
||||
Also new in Mk4: the login delay starts over after a power cycle.
|
||||
Up to 28 days long, the delay restarts from zero after losing power.
|
||||
Increasing the waiting time is a prudent policy change that our
|
||||
customers suggested.
|
||||
|
||||
|
||||
### Kill Key Feature
|
||||
|
||||
On the Mk4, this feature allows the user to execute a Fast Wipe
|
||||
when the anti-phishing words are displayed on the screen. This
|
||||
feature is turned off by default.
|
||||
|
||||
The user sets a particular key number to trigger Fast Wipe. If that
|
||||
key is pressed while viewing the anti-phishing words, the seed is
|
||||
wiped immediately, and the login process continues. Nothing is shown
|
||||
to indicate the seed has been wiped.
|
||||
|
||||
It is strongly recommended that the first digit for the second half
|
||||
of the True PIN is **not** used as the Kill Key. Missing a step
|
||||
would unintentionally wipe the seed.
|
||||
|
||||
For the COLDCARD Q, the same feature exists: any letter may be
|
||||
specified but numbers are not supported. This change allows the
|
||||
"kill button" to be active through-out the entire login process.
|
||||
It can be even be pressed while the nickname is shown, and at any
|
||||
point during the PIN entry.
|
||||
|
||||
|
||||
### SPI Serial Flash Removed
|
||||
|
||||
The Mk3 and earlier had a dedicated, external chip to hold settings
|
||||
and the PSBT during operation. Mk4 and later, do not have that
|
||||
chip. The settings now reside inside the main MCU, increasing
|
||||
security. Settings are still AES-encrypted as before.
|
||||
|
||||
The separate settings chip could be blanked externally or even
|
||||
removed/replaced. This possibility might enable getting around
|
||||
security features that were not part of the secure element. Although
|
||||
it is not significant risk, Mk4 eliminates that risk entirely.
|
||||
|
||||
In addition, the PSBT file now is held in an 8 MB pseudo-SRAM (PSRAM)
|
||||
chip during operation. It is word-addressed, not page-based, and
|
||||
there is nothing to erase. This change makes signing transactions
|
||||
much faster and permits transaction sizes up to 1 MB (the network
|
||||
only accepts transactions up to 100 KB). Previous transaction size
|
||||
limits no longer apply.
|
||||
|
||||
|
||||
### Virtual Debug Serial Port Removed
|
||||
|
||||
Mk3 and earlier made a virtual serial port available over USB. As
|
||||
it was only useful to developers, it was disabled by default. Mk4
|
||||
uses a real universal asynchronous receiver-transmitter (UART)
|
||||
leading to physical pins. It is not only disabled by default, but
|
||||
it also cannot be accessed without breaking the case. A developer
|
||||
wanting to interact with the pins must be willing to damage the
|
||||
COLDCARD's case to do so, but the option is there if needed.
|
||||
|
||||
|
||||
## SD Card Recovery Mode
|
||||
|
||||
Mk4/Mk5/Q bootloader is smart enough to be able to read an SD card. You
|
||||
will only be able to trigger the SD card loading code, if the
|
||||
COLDCARD was powered down during the upgrade process. At that point,
|
||||
the intended firmware image has been lost because it it held in
|
||||
PSRAM only, during the flash writing process. The bootloader knows
|
||||
main flash (ie. Micropython code) is corrupt because it fails the
|
||||
checksum check (and/or signature check).
|
||||
|
||||
The bootloader will only install an image of exactly the same version
|
||||
as was being installed when interrupted. This is done by verifying
|
||||
the checksum of the proposed firmware vs. a value held in SE1. The
|
||||
new firmware's expected checksum is recorded before any flash is
|
||||
erased.
|
||||
|
||||
The SD card will be searched for all DFU files, and each is
|
||||
checked for valid factory signature, and that its checksum matches
|
||||
the anticipated version the user was attempting to install.
|
||||
|
||||
If any other parts of flash---beyond the normal upgradable firmware
|
||||
area---have also been corrupted, this process will not work and the
|
||||
unit will be a brick.
|
||||
|
||||
On the COLDCARD Q, only the top slot (A) is supported for this
|
||||
operation.
|
||||
|
||||
|
||||
## Flash ECC (Error Detection/Correction Codes)
|
||||
|
||||
Flash memory cells in this MCU are protected by ECC bits. An
|
||||
additional 8 bits are calculated and stored alongside each 64-bit
|
||||
value. This allows detecting any 2-bits changing and correction of
|
||||
up to 1-bit error per 64 bits.
|
||||
|
||||
When a corrupted flash memory word is detected, an NMI (non maskable
|
||||
interrupt) is caused which will crash the microprocessor. This
|
||||
typically happens during boot-up when the checksum over flash memory
|
||||
is performed.
|
||||
|
||||
We know of no legitimate way for this to occur, so we will assume
|
||||
that it's an attack, such as exposing the bare die to targeted UV-C
|
||||
radiation. If the attacker is able to flip 2 or more bits, then
|
||||
this will effectively brick the COLDCARD once the ECC error is detected.
|
||||
|
||||
Critical flash cells, such as those that prevent JTAG access, are
|
||||
not a single bit (it's a special bit pattern), and regardless are
|
||||
protected via ECC the same as other flash cells.
|
||||
|
||||
273
docs/seed-xor.md
@ -1,273 +0,0 @@
|
||||
# Seed XOR
|
||||
|
||||
## The Problem
|
||||
|
||||
What do I do to secure my [SEEDPLATE](https://bitcoinmetalbackup.com/)?
|
||||
|
||||
Most people now understand that metal seed backups are paramount,
|
||||
as paper burns. The challenge is how to store clear-text secrets?
|
||||
Anyone with physical access could use it and gain complete
|
||||
control of your funds! Encrypted digital backups are great, but they
|
||||
are not discrete and you could be compelled to produce the passphrase.
|
||||
|
||||
Enter [_Seed XOR_](https://seedxor.com), a plausibly deniable means
|
||||
of storing secrets in two or more parts that look and behave just
|
||||
like the original secret. One 12-, 18-, or 24-word seed phrase becomes two or more parts
|
||||
that are also BIP-39 compatible seeds phrases. These should be backed up in your
|
||||
preferred method, metal or otherwise. These parts can be individually loaded
|
||||
with honeypot funds as each one has same word length, with the last being
|
||||
the checksum and will work as such in any normal BIP-39 compatible wallet.
|
||||
|
||||
This one more solution for your game-theory arsenal.
|
||||
|
||||
- *Q*: I'm lazy, can I do this to my Existing Seed?
|
||||
- *A*: Yes. You can split the words you have already in your Coldcard, making
|
||||
2, 3 or 4 new SEEDPLATES. You could also use any number of existing SEEDPLATES
|
||||
you have, and combine them to make a new random wallet that is the XOR of
|
||||
their values. Effectively that makes a new random wallet.
|
||||
|
||||
## Background
|
||||
|
||||
[_Seed XOR_](https://seedxor.com) works by taking any number of
|
||||
seed phrases in BIP-39 style, and simply XOR-ing them together,
|
||||
bit-by-bit into a new phrase. All seed phrases have to be of the same length.
|
||||
|
||||
The last word contains checksum.
|
||||
For the "parts" (sometimes called "shares") this checksum
|
||||
is calculated as normal for BIP-39, but those final bits are not used in
|
||||
the XOR process. But the checksums still protects the integrity of the
|
||||
individual parts. In 24-words XOR last 8 bits are checksum and in 12-words
|
||||
XOR last 4 bits are checksum.
|
||||
|
||||
Useful properties of this approach:
|
||||
|
||||
- Every "part" looks and operates as a valid BIP-39 wallet.
|
||||
- All the parts can be combined in any order and you arrive at the same result.
|
||||
- You must have all parts, because any combination of less than all parts is a
|
||||
valid Seed XOR wallet too.
|
||||
- Each "part" can be recorded on a SEEDPLATE like normal and no new recording tools
|
||||
are needed. No information about you original seed is leaked by finding up
|
||||
to N-1 of the parts.
|
||||
- You can store funds on the seeds of any part, and any subset of parts, which
|
||||
opens even more duress options.
|
||||
|
||||
We recommend storing the checksum word of the original
|
||||
wallet along with your N parts. This allows you to be sure you've
|
||||
gotten all the parts and assembled them correctly. This does reveal
|
||||
3 bits of your real wallet however, and also reveals that a
|
||||
working and correct subset of parts has been assembled.
|
||||
|
||||
It is not hard to calculate a Seed XOR on paper (or to verify or
|
||||
reconstruct a seed split by Coldcard). Below is a complete example,
|
||||
and a lookup table that allows you to XOR together hex digits. You
|
||||
can do the XOR at the bit level, but we recommend looking up each
|
||||
word and finding it's 3-digit hex value (0x000 to 0x7FF), and going
|
||||
hex-digit by hex-digit (4 bits).
|
||||
|
||||
## How Parts are Generated
|
||||
|
||||
Create new parts on your Coldcard:
|
||||
|
||||
Advanced > Danger Zone > Seed Functions > Seed XOR > Split Existing
|
||||
|
||||
You can choose between 2, 3 or 4 parts. You can also choose (next
|
||||
screen) to generate them deterministically or using the TRNG. The
|
||||
advantage of the deterministic approach is you'll always get the
|
||||
same answers, so you can check that you've recorded the correct
|
||||
words right the next day.
|
||||
|
||||
When the parts are made deterministically, we take a double-SHA256 over
|
||||
a fixed string (`Batshitoshi`), your master secret, and the text
|
||||
`0 of 4 parts` which changes for each part (the index is 0-based).
|
||||
|
||||
In random mode, we simply pick random bytes (and then double-SHA256
|
||||
them) from the Coldcard's True Random Number Generator (TRNG). The number
|
||||
of bytes matches your secret length: 16, 24, or 32 bytes for a 12-, 18-,
|
||||
or 24-word seed respectively.
|
||||
|
||||
This is done to make all but the one part. The final part is the
|
||||
value needed to get back to your secret, so it's the XOR of the
|
||||
other N-1 parts. It contains just as much entropy as the other
|
||||
parts.
|
||||
|
||||
### Other Notes
|
||||
|
||||
- So many possible duress games are possible once you've split your
|
||||
seed up, and you are able to "give up" all of the seed phrases,
|
||||
except one, and the attackers will still get nothing. You can load
|
||||
various possible combinations of your Seed XOR's with various amounts,
|
||||
so none are obviously empty and so on.
|
||||
|
||||
- Any two or more SEEDPLATES you have already encoded can be used
|
||||
together to make a new wallet based on their XOR. No changes to
|
||||
their existing values are needed... just import the set into a new
|
||||
Coldcard and effectively a new random seed is in play at that point.
|
||||
|
||||
- One downside of the deterministic approach is that it allows
|
||||
attackers to verify they have a seed that was split by Coldcard.
|
||||
They can import the N parts into a Coldcard, and then split them
|
||||
again on that Coldcard, and should arrive at the same values. If
|
||||
they don't then either you used the TRNG, or they have some subset
|
||||
of all the parts.
|
||||
|
||||
- You can pick your XOR parts randomly, and the result when XOR'ed
|
||||
together, is a random wallet. However, it would be best to get the
|
||||
last word checksum recorded correctly, so please use a tool such
|
||||
as the Coldcard to lookup the 24th word and save that (for each
|
||||
part). For example, you might take a fresh Coldcard (no secret)
|
||||
and draw 23 words from a hat. After providing the 23rd word, the
|
||||
Coldcard will show 8 possible final words. You can pick randomly
|
||||
from that list, or simply use the first one, and then cancel the seed
|
||||
import process on the Coldcard. Record that final word along
|
||||
with the others on a SEEDPLATE.
|
||||
|
||||
|
||||
## XOR Lookup Table
|
||||
|
||||
|
||||
| XOR | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
||||
|----:|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---
|
||||
|**0**| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
|
||||
|**1**| 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6 | 9 | 8 | B | A | D | C | F | E
|
||||
|**2**| 2 | 3 | 0 | 1 | 6 | 7 | 4 | 5 | A | B | 8 | 9 | E | F | C | D
|
||||
|**3**| 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | B | A | 9 | 8 | F | E | D | C
|
||||
|**4**| 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | C | D | E | F | 8 | 9 | A | B
|
||||
|**5**| 5 | 4 | 7 | 6 | 1 | 0 | 3 | 2 | D | C | F | E | 9 | 8 | B | A
|
||||
|**6**| 6 | 7 | 4 | 5 | 2 | 3 | 0 | 1 | E | F | C | D | A | B | 8 | 9
|
||||
|**7**| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | F | E | D | C | B | A | 9 | 8
|
||||
|**8**| 8 | 9 | A | B | C | D | E | F | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
||||
|**9**| 9 | 8 | B | A | D | C | F | E | 1 | 0 | 3 | 2 | 5 | 4 | 7 | 6
|
||||
|**A**| A | B | 8 | 9 | E | F | C | D | 2 | 3 | 0 | 1 | 6 | 7 | 4 | 5
|
||||
|**B**| B | A | 9 | 8 | F | E | D | C | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4
|
||||
|**C**| C | D | E | F | 8 | 9 | A | B | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3
|
||||
|**D**| D | C | F | E | 9 | 8 | B | A | 5 | 4 | 7 | 6 | 1 | 0 | 3 | 2
|
||||
|**E**| E | F | C | D | A | B | 8 | 9 | 6 | 7 | 4 | 5 | 2 | 3 | 0 | 1
|
||||
|**F**| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
|
||||
|
||||
|
||||
- XOR = EOR = ⊕ = Exclusive OR [Wikipedia](https://en.wikipedia.org/wiki/Exclusive_or)
|
||||
- values in table are: x ⊕ y in hex
|
||||
- go sideways for first digit, then look down for second digit
|
||||
- in fact, doesn't matter if you do row or column first
|
||||
- example: 2 XOR 6 => 4 same as 6 XOR 2 => 4
|
||||
- any values XOR itself is zero (diagonal on this table)
|
||||
- alternative view: (x) XOR (y) = flip bits of (x) that are set in (y)
|
||||
- XOR with zero does nothing (flips no bits)
|
||||
- XOR with 0xF flips all four bits
|
||||
- XOR with self flips all set bits, so gives zero
|
||||
- to XOR three values together, do (a⊕b)=X then (X⊕c)=answer
|
||||
- right to A, down to B ... take that number, and go to that column
|
||||
- down to C, that is answer: a ⊕ b ⊕ c
|
||||
|
||||
## Open Standard
|
||||
Seed XOR is an open standard. Other software and hardware wallets are encouraged to
|
||||
implement support. No license or permission is required, including usage of the term
|
||||
"Seed XOR" when referring to implementations of this feature. Such implementations
|
||||
should match the process described in this documentation and be fully interoperable.
|
||||
|
||||
---
|
||||
|
||||
# 24 Words XOR Seed Example Using 3 Parts
|
||||
|
||||
## Seed A (1 of 3)
|
||||
|
||||
1=romance [5DC], 2=wink [7DE], 3=lottery [420], 4=autumn [07D], 5=shop [635], 6=bring [0E1],
|
||||
7=dawn [1BF], 8=tongue [723], 9=range [58E], 10=crater [194], 11=truth [74E], 12=ability [001],
|
||||
13=miss [46E], 14=spice [68C], 15=fitness [2BF], 16=easy [22E], 17=legal [3FB], 18=release [5A9],
|
||||
19=recall [59B], 20=obey [4BF], 21=exchange [275], 22=recycle [59F], 23=dragon [210], 24=room [5DF]
|
||||
|
||||
A = 5DC 7DE 420 07D 635 0E1 1BF 723 58E 194 74E 001 46E 68C 2BF 22E 3FB 5A9 59B 4BF 275 59F 210 5DF
|
||||
|
||||
|
||||
## Seed B (2 of 3)
|
||||
|
||||
1=lion [411], 2=misery [46D], 3=divide [1FF], 4=hurry [37D], 5=latin [3EB], 6=fluid [2CD], 7=camp [106],
|
||||
8=advance [01F], 9=illegal [388], 10=lab [3E0], 11=pyramid [578], 12=unaware [763], 13=eager [227],
|
||||
14=fringe [2E8], 15=sick [63E], 16=camera [105], 17=series [620], 18=noodle [4B0], 19=toy [733],
|
||||
20=crowd [1A2], 21=jeans [3BD], 22=select [61A], 23=depth [1D9], 24=lounge [422]
|
||||
|
||||
B = 411 46D 1FF 37D 3EB 2CD 106 01F 388 3E0 578 763 227 2E8 63E 105 620 4B0 733 1A2 3BD 61A 1D9 422
|
||||
|
||||
|
||||
## Seed C (3 of 3)
|
||||
|
||||
1=vault [78E], 2=nominee [4AF], 3=cradle [18F], 4=silk [644], 5=own [4F0], 6=frown [2EC], 7=throw [70A],
|
||||
8=leg [3FA], 9=cactus [100], 10=recall [59B], 11=talent [6EB], 12=worry [7EE], 13=gadget [2F5],
|
||||
14=surface [6D1], 15=shy [63C], 16=planet [52F], 17=purpose [573], 18=coffee [169], 19=drip [219],
|
||||
20=few [2AC], 21=seven [625], 22=term [6FB], 23=squeeze [69C], 24=educate [234]
|
||||
|
||||
C = 78E 4AF 18F 644 4F0 2EC 70A 3FA 100 59B 6EB 7EE 2F5 6D1 63C 52F 573 169 219 2AC 625 6FB 69C 234
|
||||
|
||||
|
||||
## Calculation (XOR each hex digit)
|
||||
|
||||
A = 5DC 7DE 420 07D 635 0E1 1BF 723 58E 194 74E 001 46E 68C 2BF 22E 3FB 5A9 59B 4BF 275 59F 210 5DF
|
||||
B = 411 46D 1FF 37D 3EB 2CD 106 01F 388 3E0 578 763 227 2E8 63E 105 620 4B0 733 1A2 3BD 61A 1D9 422
|
||||
C = 78E 4AF 18F 644 4F0 2EC 70A 3FA 100 59B 6EB 7EE 2F5 6D1 63C 52F 573 169 219 2AC 625 6FB 69C 234
|
||||
| | | | | | |
|
||||
XOR = 643 71C 450 544 12E 0C0 7B3 4C6 706 7EF 4DD 08C 4BC 2B5 2BD 604 0A8 070 0B1 7B1 7ED 57E 555 3xx
|
||||
|
||||
|
||||
## Resulting Seed Phrase
|
||||
|
||||
1=silent [643], 2=toe [71C], 3=meat [450], 4=possible [544], 5=chair [12E], 6=blossom [0C0],
|
||||
7=wait [7B3], 8=occur [4C6], 9=this [706], 10=worth [7EF], 11=option [4DD], 12=bag [08C],
|
||||
13=nurse [4BC], 14=find [2B5], 15=fish [2BD], 16=scene [604], 17=bench [0A8], 18=asthma [070],
|
||||
19=bike [0B1], 20=wage [7B1], 21=world [7ED], 22=quit [57E], 23=primary [555]
|
||||
|
||||
final word between: gas [300] - lend [3FF]
|
||||
correct final word: indoor [398]
|
||||
|
||||
|
||||
|
||||
# 12 Words XOR Seed Example Using 3 Parts
|
||||
|
||||
## Seed A (1 of 3)
|
||||
|
||||
1=romance [5DC], 2=wink [7DE], 3=lottery [420], 4=autumn [07D], 5=shop [635], 6=bring [0E1],
|
||||
7=dawn [1BF], 8=tongue [723], 9=range [58E], 10=crater [194], 11=truth [74E], 12=ability [001]
|
||||
|
||||
A = 5DC 7DE 420 07D 635 0E1 1BF 723 58E 194 74E 001
|
||||
|
||||
|
||||
## Seed B (2 of 3)
|
||||
|
||||
1=boat [0C6], 2=unfair [768], 3=shell [62B], 4=violin [7A2], 5=tree [73F], 6=robust [5DA], 7=open [4D9],
|
||||
8=ride [5CB], 9=visual [7A7], 10=forest [2D9], 11=vintage [7A1], 12=approve [056]
|
||||
|
||||
B = 0C6 768 62B 7A2 73F 5DA 4D9 5CB 7A7 2D9 7A1 056
|
||||
|
||||
|
||||
## Seed C (3 of 3)
|
||||
|
||||
1=lion [411], 2=misery [46D], 3=divide [1FF], 4=hurry [37D], 5=latin [3EB], 6=fluid [2CD], 7=camp [106],
|
||||
8=advance [01F], 9=illegal [388], 10=lab [3E0], 11=pyramid [578], 12=unhappy [76A]
|
||||
|
||||
C = 411 46D 1FF 37D 3EB 2CD 106 01F 388 3E0 578 76A
|
||||
|
||||
|
||||
## Calculation (XOR each hex digit)
|
||||
|
||||
A = 5DC 7DE 420 07D 635 0E1 1BF 723 58E 194 74E 001
|
||||
B = 0C6 768 62B 7A2 73F 5DA 4D9 5CB 7A7 2D9 7A1 056
|
||||
C = 411 46D 1FF 37D 3EB 2CD 106 01F 388 3E0 578 76A
|
||||
| | |
|
||||
XOR = 10B 4DB 3F4 4A2 2E1 7F6 460 2F7 1A1 0AD 597 73x
|
||||
|
||||
|
||||
## Resulting Seed Phrase
|
||||
|
||||
1=cannon [10B], 2=opinion [4DB], 3=leader [3F4], 4=nephew [4A2], 5=found [2E1], 6=yard [7F6],
|
||||
7=metal [460], 8=galaxy [2F7], 9=crouch [1A1], 10=between [0AD], 11=real [597]
|
||||
|
||||
final word between: toward [730] - tree [73F]
|
||||
correct final word: trade [735]
|
||||
|
||||
|
||||
|
||||
- It's not possible to calculate the checksum of the final seed phrase on paper (needs SHA256).
|
||||
- But it must start with the indicated digit(s). If using 24 words XOR, there will be only one
|
||||
suitable choice offered by the Coldcard in that range (x00 to xFF),
|
||||
once you have entered the other 23 words.
|
||||
- The checksum of each of the XOR-parts protects the final result, assuming your XOR
|
||||
math is correct.
|
||||
@ -1,209 +0,0 @@
|
||||
# Spending Policy
|
||||
|
||||
This special mode will stop you from signing transactions if they
|
||||
exceed a spending policy you define beforehand. Once enabled, many
|
||||
features of the COLDCARD are disabled or inaccessible.
|
||||
|
||||
You might want to use this feature when traveling with your COLDCARD.
|
||||
|
||||
## Spending Policy: Multisig (formerly CCC)
|
||||
|
||||
We also support a mode where the COLDCARD is a multisig co-signer
|
||||
and only performs its signature when a spending policy is met. The
|
||||
other multisig signers are free to sign or not sign as appropriate.
|
||||
|
||||
Multisig mode is more advanced and requires use of multisig addresses,
|
||||
new UTXO, and cooperating multisig on-chain wallets.
|
||||
|
||||
This document will only discuss the "Single signer" version of
|
||||
Spending Policy. Both modes can be active at the same time, but if
|
||||
a transaction would be signed by Multisig policy, then we assume
|
||||
it's also okay to sign your main key as well.
|
||||
|
||||
# Before You Start
|
||||
|
||||
When a Spending Policy is in effect, there are limitations
|
||||
in effect:
|
||||
|
||||
- Firmware updates are blocked.
|
||||
- There is no way to backup the COLDCARD.
|
||||
- Seed vault and Secure Notes are read-only (and can also be hidden).
|
||||
- Settings menu is inaccessible.
|
||||
- BIP-39 passphrases may be blocked (optional).
|
||||
|
||||
We recommend getting the COLDCARD fully configured and setup
|
||||
for typical transactions before enabling the Spending Policy.
|
||||
|
||||
# Setup Spending Policy
|
||||
|
||||
Visit `Advanced / Tools > Spending Policy` menu and choose
|
||||
"Single-Signer". First some background information is shown,
|
||||
then you are prompted to define the "Bypass PIN". This PIN code
|
||||
is only used when you need to disable the spending policy, but is
|
||||
also the only way to do so once enabled... so don't loose it.
|
||||
|
||||
Once the "Bypass PIN" is confirmed, you will arrive at menu for
|
||||
related settings. Use "Edit Policy..." to change the spending policy
|
||||
and define a Max Magnitude (limit number of BTC per transaction),
|
||||
Velocity (minimum time gaps between signed transactions). You can
|
||||
define a whitelist of up to 25 destination addresses (leave empty
|
||||
for any). Finally you can enroll your phone in 2FA (second factor)
|
||||
so that you must open an Authenticator app on your phone before
|
||||
transactions are signed.
|
||||
|
||||
## Other Security Settings
|
||||
|
||||
In addition to policy itself, there are a number of on/off
|
||||
switches which affect operation of the COLDCARD while the Spending
|
||||
Policy is in effect:
|
||||
|
||||
### Word Check
|
||||
|
||||
If enabled, you will have to enter the first and last seed word
|
||||
after the Bypass PIN as an additional security check.
|
||||
|
||||
### Allow Notes
|
||||
|
||||
On the Q, secure notes and passwords may be visible or hidden
|
||||
using this setting. In either case they are strictly readonly.
|
||||
|
||||
### Related Keys
|
||||
|
||||
BIP-39 passphrase entry, Seed Vault usage will be blocked unless this
|
||||
setting is enabled. Even when enabled, the Seed Vault is always readonly
|
||||
and cannot be changed.
|
||||
|
||||
# Other Menu Items
|
||||
|
||||
## Last Violation
|
||||
|
||||
If you have recently tried and failed to sign a transaction, the
|
||||
reason for the transaction being rejected can be viewed and cleared,
|
||||
using menu item "Last Violation". It is shown only if a Spending
|
||||
Policy violation (attempt) has occurred since the last valid signing.
|
||||
|
||||
This is meant as a debugging tool, and the information stored is
|
||||
terse.
|
||||
|
||||
## Remove Policy
|
||||
|
||||
This will remove your spending policy completely and remove
|
||||
the Bypass PIN. Your COLDCARD will be back to normal.
|
||||
|
||||
## Test Drive
|
||||
|
||||
Experiment with how the COLDCARD will function if the Spending
|
||||
Policy was enabled. You can try to sign transactions that should
|
||||
be rejected and view the menus in the new mode without rebooting.
|
||||
|
||||
Choose "EXIT TEST DRIVE" on top menu to return to the Spending
|
||||
Policy menu. Reboot will also restore normal operation without
|
||||
any special challenges.
|
||||
|
||||
## ACTIVATE
|
||||
|
||||
This step will enable the Spending Policy and return to the
|
||||
main menu with it in effect. When you reboot the COLDCARD,
|
||||
the policy will still be in effect. You must use the
|
||||
Bypass PIN, followed by the normal main PIN, possibly
|
||||
followed by entering the first and last words of your seed
|
||||
phrase, before you can disable and change the policy.
|
||||
|
||||
We recommend test-driving the feature before doing that.
|
||||
|
||||
|
||||
# Tips and Tricks
|
||||
|
||||
## Money Manager Mode
|
||||
|
||||
You could setup a Coldcard for another person, perhaps a family member,
|
||||
and enable web 2FA authentication. There does not need to be any
|
||||
other spending policy limits (velocity could be unlimited).
|
||||
|
||||
Then enroll your own phone with the required 2FA values, and
|
||||
keep both that and the spending policy bypass PIN confidential.
|
||||
|
||||
The holder the the Coldcard will need a 2FA code from your phone
|
||||
when they want to spend. They can call you for the 6-digit code
|
||||
from the 2FA app on your phone. This is not hard to provide over a
|
||||
voice call.
|
||||
|
||||
Because a spending policy is in effect, they will not be able to
|
||||
see the seed words, other private key material, so regardless of
|
||||
any spoofing or phishing, they cannot move funds without your help.
|
||||
|
||||
You should record the bypass PIN, so it can be revealed somehow,
|
||||
should you die. You do not need to share the risks associated with
|
||||
holding a copy of the seed words.
|
||||
|
||||
## Passphrase Considerations
|
||||
|
||||
If you are using the same BIP-39 passphrase for everything, you should
|
||||
probably do a "Lock Down Seed" (Advanced/Tools > Danger Zone > Seed
|
||||
Functions) first. This takes your master seed and BIP-39 passphrase
|
||||
and cooks them together into an XPRV which then is stored as your
|
||||
master secret. (Replacing the master seed phrase.) This process
|
||||
cannot be reversed, so other funds you may have on the same seed
|
||||
words are protected. Once you are operating in XPRV mode, you can
|
||||
define a spending policy, and know that it is restricted to only
|
||||
that wallet.
|
||||
|
||||
When operating in XPRV mode, the "Passphrase" menu item is not shown
|
||||
because BIP-39 passwords cannot be applied to XPRV secrets.
|
||||
|
||||
## Trick PIN Thoughts
|
||||
|
||||
When doing your game theory w.r.t to bypass mode and this feature,
|
||||
remember that you should assume the attacker already has your main
|
||||
PIN. That's how they know they cannot spend all your coin, because
|
||||
they either tried to, or noticed the menus are very limited. They also
|
||||
have all your UTXO locations and total wallet balance (because they
|
||||
can export your xpubs to any wallet and load balance from there).
|
||||
|
||||
Therefore, a trick pin that leads to a duress wallet after giving up
|
||||
the bypass unlock PIN, will not fool them. Best would be to provide
|
||||
a false bypass PIN that is in fact a brick/wipe PIN.
|
||||
|
||||
|
||||
## Lock Out Changes to Policy
|
||||
|
||||
In the Trick Pin menu once Spending Policy has been enabled, you will
|
||||
find the Bypass PIN listed. You could delete or "hide" it. Hiding
|
||||
it is pointless since you cannot get to the trick PIN menu while
|
||||
the policy is in effect. Deleting the PIN however, is useful because
|
||||
it assures changes to spending policy are impossible. To recover
|
||||
the COLDCARD when this move is later regretted, under Advanced,
|
||||
there is "Destroy Seed" option which will clear the seed words and
|
||||
all settings, including the spending policy.
|
||||
|
||||
### Unlock Policy & Wipe
|
||||
|
||||
We've provided a new trick PIN that pretends to be the unlock
|
||||
spending policy pin, so the login sequence is correct... but it
|
||||
will wipe the seed in the process. It will be obvious to your
|
||||
attackers that you've wiped the seed because the main PIN will lead
|
||||
to blank wallet now (no seed loaded).
|
||||
|
||||
### Delta Mode and Spending Policy
|
||||
|
||||
If, from the start, you gave your "delta mode PIN" to the attackers,
|
||||
then when they bypass the policy (after also getting the bypass PIN
|
||||
from you), they will still be in Delta Mode.
|
||||
|
||||
They could attempt unlimited spending, but transactions signed will
|
||||
not be valid. If they try to view the seed words or generally export
|
||||
private key material, they will hit many of the "wipe seed if delta
|
||||
mode" cases.
|
||||
|
||||
## Forgotten Bypass PIN Code
|
||||
|
||||
If you've enabled a spending policy and still remember the main PIN,
|
||||
but cannot disable the feature because you've forgotten the Bypass
|
||||
PIN, your only option is to use `Advanced > Destroy Seed`. After
|
||||
some confirmations, this erases the master seed, all settings, seed
|
||||
vault items, secure notes, and trick pins. It's basically a factory
|
||||
reset except for the main PIN code which is unchanged. Once you've
|
||||
done that, you can enter your seed words from backup (or restore a
|
||||
backup file) and continue to use the COLDCARD again.
|
||||
|
||||
|
||||
@ -1,122 +0,0 @@
|
||||
# Temporary Seeds
|
||||
|
||||
|
||||
[_(new in v5.0.7, requires Mk4, Mk5, or Q)_](upgrade.md)
|
||||
|
||||
|
||||
Temporary seed (renamed in `5.2.0` from Ephemeral seed) is a temporary secret completely separate
|
||||
from the master seed, typically held in **COLDCARD<sup>®</sup>** RAM and
|
||||
not persisted between reboots in the Secure Element.
|
||||
Temporary seeds *completely* defeat the design
|
||||
of Coldcard's security model, based on secure elements.
|
||||
Enable the `Seed Vault` feature to store these secrets longer-term.
|
||||
Read more about `Seed Vault` feature below.
|
||||
|
||||
|
||||
!!! warning "Make sure you know what you're doing!"
|
||||
|
||||
This feature is intended for those one-off signings, like recovering
|
||||
a lost seed from some other system or importing some seed as a
|
||||
balance check. We do not recommend handing unencrypted seed material
|
||||
on a regular basis!
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
* if temporary seed is already in use, first home menu option `[<xfp>]` is visible with fingerprint of temporary master secret
|
||||
* go to `Advanced/Tools > Temporary Seed`
|
||||
|
||||
* temporary seed words can be Generated with TRNG
|
||||
- `Advanced/Tools > Temporary Seed > Generate Words`
|
||||
|
||||
* temporary seed words can be imported
|
||||
- `Advanced/Tools > Temporary Seed > Import Words`
|
||||
|
||||
* importing extended private keys
|
||||
- `Advanced/Tools > Temporary Seed > Import XPRV`
|
||||
- `Advanced/Tools > Temporary Seed > Tapsigner Backup`
|
||||
|
||||
* temporary seed can be activated from BIP-85 derived secrets - go to `Advanced/Tools > Derive Seed B85` and pick types of secret. Keep in mind that only word based and xprv based secrets can be used as temporary seed.
|
||||
- `12 words`
|
||||
- `18 words`
|
||||
- `24 words`
|
||||
- `XPRV (BIP-32)`
|
||||
- pick derivation `Index` in next prompt, or just press OK for index 0
|
||||
- Press (0) in next prompt to activate derived secret as a temporary seed
|
||||
|
||||
* temporary seed can be activated from Duress Wallet
|
||||
- go to `Settings -> Login Settings -> Trick Pins`
|
||||
- add new Duress Wallet trick pin and save it
|
||||
- choose newly created trick pin in trick pins menu and use `Activate Wallet` option
|
||||
|
||||
* temporary seed can be obtained from `SeedXOR`
|
||||
- go to `Advanced/Tools -> Danger Zone -> Seed Functions -> SeedXOR`
|
||||
- pick `Restore Seed XOR` option and provide all XOR parts
|
||||
- Press (2) to activate restored seed as temporary seed
|
||||
|
||||
* BIP-39 passphrase is from version `5.2.0` handled internally as temporary seed
|
||||
|
||||
|
||||
Ability to generate and use **Temporary seed** is available on Coldcard when:
|
||||
|
||||
1. no PIN chosen and no secret chosen (newly unpacked Coldcard)
|
||||
2. PIN set up but no secret chosen yet
|
||||
3. with both PIN and secret already picked
|
||||
|
||||
|
||||
# Restore Master
|
||||
|
||||
[_(new in v5.2.0, requires Mk4, Mk5, or Q)_](upgrade.md)
|
||||
|
||||
From version `5.2.0` users no longer need to reboot COLDCARD to return
|
||||
to their "master seed" (one stored in SE2). Once COLDCARD has temporary
|
||||
seed active, first item in home menu is `[xfp]` and is a clone of `Ready To Sign`.
|
||||
Last item in home menu is `Restore Master`.
|
||||
|
||||
`Restore Master` offers two options. First, if user presses OK, COLDCARD wipes temporary seed settings
|
||||
and switches back to master seed and its settings.
|
||||
If user presses (1) temporary seed settings are preserved for later use and COLDCARD only switches
|
||||
back to master seed and its settings.
|
||||
|
||||
If current temporary seed is also saved in Seed Vault, option to wipe settings is not available.
|
||||
Seed Vault entries can only be deleted in Seed Vault menu.
|
||||
|
||||
|
||||
# Seed Vault
|
||||
|
||||
[_(new in v5.2.0, requires Mk4, Mk5, or Q)_](upgrade.md)
|
||||
|
||||
Seed Vault adds the ability to store multiple temporary secrets into encrypted settings for simple
|
||||
recall and later use (AES-256-CTR encrypted with your master seed's key).
|
||||
Users can capture and hold master secret from any temporary seed source, including: TRNG, Dice Rolls,
|
||||
SeedXOR, TAPSIGNER backups, BIP-85 derived values, BIP-39 passphrase wallets.
|
||||
|
||||
## Enable Seed Vault
|
||||
|
||||
Enable this functionality in `Advanced/Tools -> Danger Zone -> Seed Vault -> Enable`.
|
||||
Once seed vault is enabled new menu item is visible in home menu `Seed Vault`.
|
||||
To disable Seed Vault user needs to remove all entries from Seed Vault first.
|
||||
|
||||
|
||||
## Add Seed to Vault
|
||||
|
||||
After `Seed Vault` is enabled, users will see a new prompt, after
|
||||
creation of temporary seed, asking whether to save this temporary
|
||||
seed to Seed Vault. Press (1) to save or any other key to ignore.
|
||||
|
||||
If option to save was chosen, confirmation prompt is shown - `Saved to seed vault.`
|
||||
|
||||
|
||||
## Seed Vault menu
|
||||
|
||||
* if Seed Vault is empty `(none saved yet)` is the first menu item followed by shortcut to `Temporary Seed` menu.
|
||||
* if not empty, saved seeds are listed in menu as `[xfp]`
|
||||
* if current active temporary seed is stored in Seed Vault - it has checkmark next to it
|
||||
* if temporary seed is active - last menu item of Seed Vault menu is `Restore Master`
|
||||
|
||||
## Seed Vault entry submenu
|
||||
|
||||
1. by default `[xfp]` but can be renamed to allow user labeling and leads to additional information about the seed
|
||||
2. `Use This Seed` allows to switch to the saved temporary seed. If it is already active `In Use` is shown instead.
|
||||
3. `Rename` allows to change 1. menu item to something personalized to user (limited to 40 characters)
|
||||
4. `Delete` allows to remove temporary seed from Seed Vault and optionally to completely wipe its settings.
|
||||
@ -1,118 +0,0 @@
|
||||
|
||||
# Firmware Upgrade and Recovery Process
|
||||
|
||||
_This document applies to the Mk4, Mk5, and Q. Earlier COLDCARDs did not use this approach._
|
||||
|
||||
On the COLDCARD, we have done away with the slow external SPI flash
|
||||
(serial flash) chip entirely (used in Mk1-Mk3). In it's place we
|
||||
use a much faster and huge 64 Mbit PSRAM chip (quad SPI RAM chip:
|
||||
ESP-PSRAM64H).
|
||||
|
||||
This chip is volatile and forgets its contents at power down.
|
||||
|
||||
For working space of PSBT files during signing, that's okay but it
|
||||
can be a problem during firmware upgrades. This document explains
|
||||
how we've solved the risks of firmware upgrades and possible bricking
|
||||
that can happen with power fails at just the wrong time.
|
||||
|
||||
## Firmware Upgrade Process
|
||||
|
||||
Steps:
|
||||
|
||||
- firmware image (DFU file) is copied onto the COLDCARD, either by USB or SDCard
|
||||
- the proposed firmware image (up to about 1.5Mbytes) is stored in PSRAM
|
||||
- the user approves the upgrade, and they must process the main PIN code to do that.
|
||||
- firmware image is checked for correct signature from factory (nothing proceeds if
|
||||
not signed by a legit key)
|
||||
- a checksum is calculated over the new firmware, and the current contents of
|
||||
flash, including the bootloader code, its secrets, unique identity bits
|
||||
(for the main chip). We call this the "world checksum".
|
||||
- before anything else happens, we update the main secure element (608C) with
|
||||
the world checksum, and during boot, knowledge of the world checksum is required
|
||||
to light the green genuine light.
|
||||
- the light stays green at this point, and the system could still boot the old firmware
|
||||
- flash erasing and writing of the new firmware starts
|
||||
- this takes about 15 seconds because flash is relatively slow
|
||||
- once that is done, the system resets, and the normal bootup sequence will
|
||||
re-verify the flash for its signature
|
||||
- the green light will be active because the world checksum was already written earlier
|
||||
|
||||
## Recovery Cases
|
||||
|
||||
When the system boots up, it always checks the firmware's signature. If it's
|
||||
corrupt or missing, then we attempt a few different recovery stpes.
|
||||
|
||||
### PSRAM Holds New Firmware
|
||||
|
||||
If the system resets before the flash is erased and programmed completely, we
|
||||
still have enough information in the PSRAM to start over. If main firmware
|
||||
is corrupt, then we look in PSRAM for an image that we might have been
|
||||
burning when we got interrupted. A full signature check is done (so any bitrot
|
||||
will be detected).
|
||||
|
||||
Importantly, the firmware we find in PSRAM at this point must also reconstruct
|
||||
the right "world checksum" as is already stored in the 608. If it does not,
|
||||
then we do not use the contents of the PSRAM and continue with other options.
|
||||
|
||||
We need this policy because the PSRAM is an external chip, and an attacker
|
||||
might try this:
|
||||
|
||||
1) Corrupt the main firmware slightly. Perhaps by shining a UV-C light source
|
||||
at the bare die. Only one bit flip is required. This is done only to trigger
|
||||
recovery mode.
|
||||
|
||||
2) Replace the PSRAM contents with a special firmware image. (It would need
|
||||
to factory signed, but perhaps it has some feature they want to abuse or
|
||||
something.)
|
||||
|
||||
3) Power up the COLDCARD, and it would try to restore the firmware image in PSRAM.
|
||||
|
||||
Because of the world checksum, only the intended firmware can be
|
||||
restored, not any other version. There is no way to for the attacker
|
||||
to change the other parts of the firmware based just corrupting a few
|
||||
bits using UV-C.
|
||||
|
||||
### Recovering from Power Fails
|
||||
|
||||
The most likely way to make the upgrade fail is a power failure
|
||||
during the 15-second period while the firmware is copied from PSRAM
|
||||
to main flash. The PSRAM will forget it's contents, and the COLDCARD
|
||||
no longer has a complete copy of firmware anywhere.
|
||||
|
||||
Most products would be a "brick" at this point, and the docs would
|
||||
warn against power fails during upgrade. However, the COLCARD can read
|
||||
SD Cards to load replacement firmware. The card does not need to
|
||||
be specially prepared, but we recommend erasing it, formating with
|
||||
FAT32 and then copying just the firmware onto the card.
|
||||
|
||||
If the main firmware is corrupt or missing, and the PSRAM does not
|
||||
hold a suitable firmware upgrade, then the screen will show "Insert Card".
|
||||
Once a card is inserted, a search is made for a suitable firmware file.
|
||||
|
||||
All DFU files will be considered, but you must provide the firmware
|
||||
file that you were attempting to upgrade to during the power failure,
|
||||
because the "world checksum" is calculated for each image found on
|
||||
the card. You will not be able to substitute a newer version of firmware.
|
||||
Of course, firmware factory signatures are checked as well.
|
||||
|
||||
|
||||
## Key Entry Sequence
|
||||
|
||||
We do **not** provide a key sequence to enter recovery mode. This
|
||||
would be a nice to have to recover from major bugs in the main firmware,
|
||||
but our security model does not allow it: Since the recovery methods
|
||||
will only replace the bits you used to have with the exact same
|
||||
bits you had previously, there is no need to "recover" if the
|
||||
firmware is already there.
|
||||
|
||||
Attackers would not need a sequence, since they can gitch the clock
|
||||
or use UV-C light on the bare die to change bits.
|
||||
|
||||
|
||||
## Reality of Flipping Bits in Flash
|
||||
|
||||
It's not actually possible to flip a few flash bits because this
|
||||
chip has ECC for all flash cells. It will auto-correct single-bit
|
||||
errors, and for double-bit errors it stops execution completely.
|
||||
So good luck attackers, have a nice day!
|
||||
|
||||
@ -1,97 +0,0 @@
|
||||
# Web 2FA Authentication
|
||||
|
||||
How to support [RFC 6238](https://www.rfc-editor.org/rfc/rfc6238)
|
||||
TOTP (Time based One Time Password) 2FA check, on our little embedded
|
||||
device without a real-time clock?
|
||||
|
||||
Solution: Store the pre-shared secret in the COLDCARD, and send that
|
||||
securely to a trusted webserver which knows the time and can do a
|
||||
fancy UX. That webserver accepts the time-based-one-time 2FA numeric
|
||||
code from the user, and if correct, reveals a secret
|
||||
that can be used back on the COLDCARD to authorize an action.
|
||||
|
||||
For the Mk4, the secret is 8 digit numeric code to be entered,
|
||||
for the COLDCARD Q, it is a QR code to be scanned.
|
||||
|
||||
### History / Background
|
||||
|
||||
The HSM feature uses HOTP tokens, which do not require a backend,
|
||||
but are not as robust as time-based tokens.
|
||||
|
||||
Web2FA is available to be enabled as part of a Spending Policy,
|
||||
both in Multisig and Single Signer modes. When enabled, you will be
|
||||
prompted complete 2FA authentication after viewing the details of
|
||||
the transaction to be signed. You will not be able to sign without
|
||||
the correct code.
|
||||
|
||||
## How It Works
|
||||
|
||||
- Web backend has a ECC keypair, with pubkey known to CC firmware releases.
|
||||
- Usual 2fa base32 secret is picked by CC and stored in CC (so that server is stateless)
|
||||
- CC creates URL encrypted to the pubkey of server, containing args:
|
||||
- shared secret for TOTP (same value as held in user's phone)
|
||||
- the response nonce (32 bytes, shown as 64 hex chars, on Q; or 8 digits on Mk4)
|
||||
to be revealed to the user on successful auth
|
||||
- flag if Q model, so can provide a QR to be scanned in that case (rather than digits)
|
||||
- some text label for what's being approved, which is presented to user so they can pick
|
||||
correct 2fa shared secret.
|
||||
- above is all encrypted in transit, and only the server can decrypt
|
||||
- user is sent to that encrypted URL using NFC tap on the COLDCARD
|
||||
- user arrives at server:
|
||||
- shown label [which also indicates the server can be trusted, since only it could decrypt it]
|
||||
- prompt for 6 digits from authenticator app
|
||||
- does [RFC 6238](https://www.rfc-editor.org/rfc/rfc6238) 2FA check using current time
|
||||
- checks using current time and the shared secret provided by CC, fails if wrong.
|
||||
- time based failure: offer retry (they typed too slow / minor clock drift)
|
||||
- can offer to retry, but also do some rate limiting (only one attempt per 30-sec period)
|
||||
- server will store very recent responses so attacker cannot get two codes
|
||||
in any 30sec period (ie. blocks immediate reuse of same URL)
|
||||
- until a valid code is given, user is stuck here
|
||||
- when valid token received:
|
||||
- if Q, show a QR code to be scanned, with the full nonce
|
||||
- for non-Q system, a 8-digit decimal value is given: user has to enter that into the COLDCARD
|
||||
- web site shows instructions about what to do next on product.
|
||||
|
||||
## From COLDCARD PoV
|
||||
|
||||
- makes complex encrypted URL, which contains a nonce it wants, waits for that nonce back (or QR)
|
||||
- it's either the nonce from the URL, or fail
|
||||
- if the right nonce, then we know the server knows the decryption key, and we
|
||||
are trusting it actually verify the 2FA token properly.
|
||||
|
||||
## Encryption - Simple ECDH
|
||||
|
||||
- CC picks a secp256k1 keypair, generates compressed pubkey
|
||||
- multiplies that private key by server's known public key
|
||||
- apply sha256(resulting coordinate) => the session key
|
||||
- apply AES-256-CTR over URL contents (ascii text)
|
||||
- prepend 33 bytes of pubkey, and then base64url encode all of it
|
||||
- full url is: `https://coldcard.com/2fa?{base64 encoded binary}`
|
||||
|
||||
## Trust Issues
|
||||
|
||||
- 2FA enrol happens on the CC, which picks the shared secret and shows QR for mobile
|
||||
app setup. Same TRNG process as picking a seed.
|
||||
- Server knows the shared secret, but only during operation, and we won't store it [sorry,
|
||||
gotta trust us on that, but no help to us to store it].
|
||||
- Only we can run the server, because the private key is company-secret.
|
||||
- MiTM and network snoopers get nothing because HTTPS is used and only your browser
|
||||
can see the nonce, and only after you've given the right digits.
|
||||
- Coinkite server could skip the 2FA checks and just give you the answer
|
||||
you want to type into the COLDCARD. Again, you have to trust us on that.
|
||||
|
||||
## URL Format
|
||||
|
||||
https://coldcard.com/2fa?g={nonce}&ss={shared_secret}&nm={label_text}&q={is_q}
|
||||
|
||||
(the query string is then encrypted to the server's pubkey, so the args above
|
||||
are what is inside the encrypted payload.)
|
||||
|
||||
- `nonce`: text string that is either 8 digits on Mk4, or 64 hex chars on Q
|
||||
- `shared_secret`: 16 chars of Base32-encoded pre-shared secret
|
||||
- `nm`: human readable label for the transaction/purpose
|
||||
- `is_q`: flag indicating use of QR to provide nonce back to user
|
||||
|
||||
Server will accept plaintext arguments as above, but normally everything
|
||||
after the question mark is encrypted.
|
||||
|
||||
@ -3,12 +3,10 @@
|
||||
#
|
||||
# paper.py - generate paper wallets, based on random values (not linked to wallet)
|
||||
#
|
||||
import ujson
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
from utils import imported
|
||||
from public_constants import AF_CLASSIC, AF_P2WPKH
|
||||
from actions import needs_microsd
|
||||
from ux import ux_show_story, ux_dramatic_pause
|
||||
from files import CardSlot, CardMissingError, needs_microsd
|
||||
from files import CardSlot, CardMissingError
|
||||
from actions import file_picker
|
||||
from menu import MenuSystem, MenuItem
|
||||
|
||||
@ -53,8 +51,9 @@ class PaperWalletMaker:
|
||||
self.is_segwit = False
|
||||
|
||||
async def pick_template(self, *a):
|
||||
fn = await file_picker(suffix='.pdf', min_size=20000, taster=template_taster,
|
||||
none_msg=no_templates_msg)
|
||||
fn = await file_picker('Pick PDF template to use, or X for none.',
|
||||
suffix='.pdf', min_size=20000,
|
||||
taster=template_taster, none_msg=no_templates_msg)
|
||||
self.template_fn = fn
|
||||
|
||||
self.update_menu()
|
||||
@ -64,42 +63,44 @@ class PaperWalletMaker:
|
||||
def set(idx, text):
|
||||
self.is_segwit = bool(idx)
|
||||
self.update_menu()
|
||||
return int(self.is_segwit), ['Classic P2PKH', 'Segwit P2WPKH'], set
|
||||
return int(self.is_segwit), ['Classic', 'Segwit/Bech32'], set
|
||||
|
||||
@staticmethod
|
||||
def can_do_qr():
|
||||
import version
|
||||
return version.has_fatram
|
||||
|
||||
def update_menu(self):
|
||||
# Reconstruct the menu contents based on our state.
|
||||
self.my_menu.replace_items([
|
||||
MenuItem("Don't make PDF" if not self.template_fn else 'Making PDF',
|
||||
f=self.pick_template),
|
||||
MenuItem('Classic P2PKH' if not self.is_segwit else 'Segwit P2WPKH',
|
||||
chooser=self.addr_format_chooser),
|
||||
f=self.pick_template, predicate=self.can_do_qr),
|
||||
MenuItem('Classic Address' if not self.is_segwit else 'Segwit Address',
|
||||
chooser=self.addr_format_chooser),
|
||||
MenuItem('Use Dice', f=self.use_dice),
|
||||
MenuItem('GENERATE WALLET', f=self.doit),
|
||||
], keep_position=True)
|
||||
|
||||
async def doit(self, *a, have_key=None):
|
||||
# make the wallet.
|
||||
from glob import dis, VD
|
||||
from main import dis
|
||||
|
||||
try:
|
||||
import ngu
|
||||
from msgsign import write_sig_file
|
||||
from chains import current_chain
|
||||
import tcc
|
||||
from serializations import hash160
|
||||
from stash import blank_object
|
||||
|
||||
if not have_key:
|
||||
# get some random bytes
|
||||
await ux_dramatic_pause("Picking key...", 2)
|
||||
pair = ngu.secp256k1.keypair()
|
||||
privkey = tcc.secp256k1.generate_secret()
|
||||
else:
|
||||
# caller must range check this already: 0 < privkey < order
|
||||
# - actually libsecp256k1 will check it again anyway
|
||||
pair = ngu.secp256k1.keypair(have_key)
|
||||
privkey = have_key
|
||||
|
||||
# pull out binary versions (serialized) as we need
|
||||
privkey = pair.privkey()
|
||||
pubkey = pair.pubkey().to_bytes(False) # always compressed style
|
||||
# calculate corresponding public key value
|
||||
pubkey = tcc.secp256k1.publickey(privkey, True) # always compressed style
|
||||
|
||||
dis.fullscreen("Rendering...")
|
||||
|
||||
@ -107,66 +108,45 @@ class PaperWalletMaker:
|
||||
digest = hash160(pubkey)
|
||||
ch = current_chain()
|
||||
if self.is_segwit:
|
||||
addr = ngu.codecs.segwit_encode(ch.bech32_hrp, 0, digest)
|
||||
addr = tcc.codecs.bech32_encode(ch.bech32_hrp, 0, digest)
|
||||
else:
|
||||
addr = ngu.codecs.b58_encode(ch.b58_addr + digest)
|
||||
addr = tcc.codecs.b58_encode(ch.b58_addr + digest)
|
||||
|
||||
wif = ngu.codecs.b58_encode(ch.b58_privkey + privkey + b'\x01')
|
||||
wif = tcc.codecs.b58_encode(ch.b58_privkey + privkey + b'\x01')
|
||||
|
||||
with imported('uqr') as uqr:
|
||||
# make the QR's now, since it's slow
|
||||
is_alnum = self.is_segwit
|
||||
qr_addr = uqr.make(addr if not is_alnum else addr.upper(),
|
||||
min_version=4, max_version=4,
|
||||
encoding=(uqr.Mode_ALPHANUMERIC if is_alnum else 0))
|
||||
if self.can_do_qr():
|
||||
with imported('uqr') as uqr:
|
||||
# make the QR's now, since it's slow
|
||||
is_alnum = self.is_segwit
|
||||
qr_addr = uqr.make(addr if not is_alnum else addr.upper(),
|
||||
min_version=4, max_version=4,
|
||||
encoding=(uqr.Mode_ALPHANUMERIC if is_alnum else 0))
|
||||
|
||||
qr_wif = uqr.make(wif, min_version=4, max_version=4, encoding=uqr.Mode_BYTE)
|
||||
qr_wif = uqr.make(wif, min_version=4, max_version=4, encoding=uqr.Mode_BYTE)
|
||||
else:
|
||||
qr_addr = None
|
||||
qr_wif = None
|
||||
|
||||
# Use address as filename. clearly will be unique, but perhaps a bit
|
||||
# awkward to work with.
|
||||
basename = addr
|
||||
force_vdisk = False
|
||||
if VD:
|
||||
prompt = "Press (1) to save paper wallet file to SD Card"
|
||||
escape = "1"
|
||||
if VD is not None:
|
||||
prompt += ", press (2) to save to VDisk"
|
||||
escape += "2"
|
||||
prompt += "."
|
||||
ch = await ux_show_story(prompt, escape=escape)
|
||||
if ch == "2":
|
||||
force_vdisk = True
|
||||
elif ch == '1':
|
||||
force_vdisk = False
|
||||
else:
|
||||
return
|
||||
|
||||
dis.fullscreen("Saving...")
|
||||
with CardSlot(force_vdisk=force_vdisk) as card:
|
||||
with CardSlot() as card:
|
||||
fname, nice_txt = card.pick_filename(basename +
|
||||
('-note.txt' if self.template_fn else '.txt'))
|
||||
sig_cont = []
|
||||
with card.open(fname, 'wt+') as fp:
|
||||
self.make_txt(fp, addr, wif, privkey, qr_addr, qr_wif)
|
||||
fp.seek(0)
|
||||
contents0 = fp.read()
|
||||
|
||||
h = ngu.hash.sha256s(contents0.encode())
|
||||
sig_cont.append((h, fname))
|
||||
with open(fname, 'wt') as fp:
|
||||
self.make_txt(fp, addr, wif, privkey, qr_addr, qr_wif)
|
||||
|
||||
if self.template_fn:
|
||||
fname, nice_pdf = card.pick_filename(basename + '.pdf')
|
||||
|
||||
with open(fname, 'wb+') as fp:
|
||||
with open(fname, 'wb') as fp:
|
||||
self.make_pdf(fp, addr, wif, qr_addr, qr_wif)
|
||||
fp.seek(0)
|
||||
contents1 = fp.read()
|
||||
h = ngu.hash.sha256s(contents1)
|
||||
sig_cont.append((h, fname))
|
||||
else:
|
||||
nice_pdf = ''
|
||||
|
||||
nice_sig = write_sig_file(sig_cont, pk=privkey, sig_name=basename,
|
||||
addr_fmt=AF_P2WPKH if self.is_segwit else AF_CLASSIC)
|
||||
|
||||
# Half-hearted attempt to cleanup secrets-contaminated memory
|
||||
# - better would be force user to reboot
|
||||
# - and yet, we just output the WIF to SDCard anyway
|
||||
@ -178,21 +158,16 @@ class PaperWalletMaker:
|
||||
await needs_microsd()
|
||||
return
|
||||
except Exception as e:
|
||||
from utils import problem_file_line
|
||||
await ux_show_story('Failed to write!\n\n'+problem_file_line(e))
|
||||
await ux_show_story('Failed to write!\n\n\n'+str(e))
|
||||
return
|
||||
|
||||
story = "Done! Created file(s):\n\n%s" % nice_txt
|
||||
if nice_pdf:
|
||||
story += "\n\n%s" % nice_pdf
|
||||
story += "\n\n%s" % nice_sig
|
||||
await ux_show_story(story)
|
||||
await ux_show_story('Done! Created file(s):\n\n%s\n\n%s' % (nice_txt, nice_pdf))
|
||||
|
||||
async def use_dice(self, *a):
|
||||
# Use lots of (D6) dice rolls to create privkey entropy.
|
||||
privkey = b''
|
||||
with imported('seed') as seed:
|
||||
count, privkey = await seed.add_dice_rolls(0, privkey, True, enforce=True)
|
||||
count, privkey = await seed.add_dice_rolls(0, privkey, True)
|
||||
if count == 0: return
|
||||
|
||||
if privkey >= SECP256K1_ORDER or privkey == bytes(32):
|
||||
@ -204,7 +179,9 @@ class PaperWalletMaker:
|
||||
|
||||
def make_txt(self, fp, addr, wif, privkey, qr_addr=None, qr_wif=None):
|
||||
# Generate the "simple" text file version, includes private key.
|
||||
from ubinascii import hexlify as b2a_hex
|
||||
from descriptor import append_checksum
|
||||
import ujson
|
||||
|
||||
fp.write('Coldcard Generated Paper Wallet\n\n')
|
||||
|
||||
@ -228,7 +205,7 @@ class PaperWalletMaker:
|
||||
w = qr.width()
|
||||
for y in range(w):
|
||||
fp.write(' ')
|
||||
ln = ''.join('\u2588\u2588' if qr.get(x,y) else ' ' for x in range(w))
|
||||
ln = ''.join('\u2588\u2588' if qr.get(x,y) else '' for x in range(w))
|
||||
fp.write(ln)
|
||||
fp.write('\n')
|
||||
|
||||
@ -285,8 +262,7 @@ class PaperWalletMaker:
|
||||
|
||||
async def make_paper_wallet(*a):
|
||||
|
||||
msg = background_msg.format(can_qr='\nIf you have a special PDF template file, '
|
||||
'it can also make a pretty version of the same data.')
|
||||
msg = background_msg.format(can_qr=('\nIf you have a special PDF template file, it can also make a pretty version of the same data.' if PaperWalletMaker.can_do_qr() else ''))
|
||||
|
||||
if await ux_show_story(msg) != 'y':
|
||||
return
|
||||
@ -296,6 +272,11 @@ async def make_paper_wallet(*a):
|
||||
menu = MenuSystem([])
|
||||
rv = PaperWalletMaker(menu)
|
||||
|
||||
# annoying?
|
||||
# always have them pick the template, because that's mostly required
|
||||
#if rv.can_do_qr():
|
||||
# await rv.pick_template()
|
||||
|
||||
rv.update_menu()
|
||||
|
||||
return menu
|
||||
2
external/README.md
vendored
@ -1,7 +1,7 @@
|
||||
|
||||
## Background on Submodules
|
||||
|
||||
This project uses many submodules, and to build the final products, you will
|
||||
This project uses many submodules, and to build the final produts, you will
|
||||
have to get all the submodules into place and build them in appropriate orders.
|
||||
|
||||
A good resource, from an unrelated project, is:
|
||||
|
||||
2
external/c-modules/README.md
vendored
@ -1,2 +0,0 @@
|
||||
This link forest allows micropython's makefile to find the C-language modules
|
||||
we are using today.
|
||||
7
external/c-modules/aes256ctr/README.md
vendored
@ -1,7 +0,0 @@
|
||||
|
||||
# AES-256 for CTR mode (only)
|
||||
|
||||
- from <https://github.com/Ko-/aes-armcortexm> under CC0-1.0
|
||||
- ASM for ARM (only)
|
||||
- very thin interface
|
||||
|
||||
12
external/c-modules/aes256ctr/aes_256_ctr.h
vendored
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct param {
|
||||
uint8_t nonce[12];
|
||||
uint8_t ctr[4];
|
||||
uint8_t rk[15*16];
|
||||
} param;
|
||||
|
||||
extern void AES_256_keyschedule(const uint8_t *key, uint8_t *);
|
||||
extern void AES_256_encrypt_ctr(param const *, const uint8_t *, uint8_t *, uint32_t);
|
||||
|
||||
1308
external/c-modules/aes256ctr/aes_256_ctr.s
vendored
110
external/c-modules/aes256ctr/example.c
vendored
@ -1,110 +0,0 @@
|
||||
// from https://raw.githubusercontent.com/Ko-/aes-armcortexm/public/aes256ctr/aes_256_ctr.c
|
||||
// taken Feb 25/2021
|
||||
// REFERENCE ONLY -- not used.
|
||||
#include "../common/stm32wrapper.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct param {
|
||||
uint32_t ctr;
|
||||
uint8_t nonce[12];
|
||||
uint8_t rk[15*16];
|
||||
} param;
|
||||
|
||||
extern void AES_256_keyschedule(const uint8_t *, uint8_t *);
|
||||
extern void AES_256_encrypt_ctr(param const *, const uint8_t *, uint8_t *, uint32_t);
|
||||
#define AES_256_decrypt_ctr AES_256_encrypt_ctr
|
||||
|
||||
int main(void)
|
||||
{
|
||||
clock_setup();
|
||||
gpio_setup();
|
||||
usart_setup(115200);
|
||||
|
||||
// plainly reading from CYCCNT is more efficient than using the
|
||||
// dwt_read_cycle_counter() interface offered by libopencm3,
|
||||
// as this adds extra overhead because of the function call
|
||||
|
||||
SCS_DEMCR |= SCS_DEMCR_TRCENA;
|
||||
DWT_CYCCNT = 0;
|
||||
DWT_CTRL |= DWT_CTRL_CYCCNTENA;
|
||||
|
||||
const uint32_t LEN = 256*16;
|
||||
const uint32_t LEN_ROUNDED = ((LEN+15)/16)*16;
|
||||
|
||||
const uint8_t nonce[12] = {1,2,3,1,2,4,1,2,5,1,2,6};
|
||||
const uint8_t key[32] = {4,5,6,7,4,5,6,8,4,5,6,9,4,5,6,10,4,5,6,11,4,5,6,12,4,5,6,13,4,5,6,14};
|
||||
uint8_t in[LEN];
|
||||
uint8_t out[LEN_ROUNDED];
|
||||
|
||||
unsigned int i;
|
||||
for(i=0;i<LEN;++i)
|
||||
in[i] = i%256;
|
||||
|
||||
char buffer[36];
|
||||
param p;
|
||||
p.ctr = 0;
|
||||
memcpy(p.nonce, nonce, 12);
|
||||
memcpy(p.rk, key, 32);
|
||||
|
||||
unsigned int oldcount = DWT_CYCCNT;
|
||||
AES_256_keyschedule(key, p.rk+32);
|
||||
unsigned int cyclecount = DWT_CYCCNT-oldcount;
|
||||
|
||||
/*
|
||||
// Print all round keys
|
||||
unsigned int j;
|
||||
for(i=0;i<15*4;++i) {
|
||||
sprintf(buffer, "rk[%2d]: ", i);
|
||||
for(j=0;j<4;++j)
|
||||
sprintf(buffer+2*j+8, "%02x", p.rk[i*4+j]);
|
||||
send_USART_str(buffer);
|
||||
}
|
||||
*/
|
||||
|
||||
sprintf(buffer, "cyc: %d", cyclecount);
|
||||
send_USART_str(buffer);
|
||||
|
||||
oldcount = DWT_CYCCNT;
|
||||
AES_256_encrypt_ctr(&p, in, out, LEN);
|
||||
cyclecount = DWT_CYCCNT-oldcount;
|
||||
|
||||
sprintf(buffer, "cyc: %d", cyclecount);
|
||||
send_USART_str(buffer);
|
||||
|
||||
/*
|
||||
// Print ciphertext
|
||||
sprintf(buffer, "out: ");
|
||||
send_USART_str(buffer);
|
||||
for(i=0;i<LEN;++i) {
|
||||
sprintf(buffer+((2*i)%32), "%02x", out[i]);
|
||||
if(i%16 == 15)
|
||||
send_USART_str(buffer);
|
||||
}
|
||||
if(LEN%16 > 0)
|
||||
send_USART_str(buffer);
|
||||
*/
|
||||
|
||||
/*
|
||||
// Perform decryption
|
||||
p.ctr = 0;
|
||||
|
||||
AES_256_decrypt_ctr(&p, out, in, LEN);
|
||||
|
||||
// Print plaintext
|
||||
sprintf(buffer, "in: ");
|
||||
send_USART_str(buffer);
|
||||
for(i=0;i<LEN;++i) {
|
||||
sprintf(buffer+((2*i)%32), "%02x", in[i]);
|
||||
if(i%16 == 15)
|
||||
send_USART_str(buffer);
|
||||
}
|
||||
if(LEN%16 > 0)
|
||||
send_USART_str(buffer);
|
||||
*/
|
||||
|
||||
while (1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
11
external/c-modules/aes256ctr/micropython.mk
vendored
@ -1,11 +0,0 @@
|
||||
#
|
||||
# Pure ASM version of AES-256 for CTR mode only
|
||||
#
|
||||
|
||||
ifdef BOARD
|
||||
|
||||
SRC_USERMOD += $(USERMOD_DIR)/aes_256_ctr.o
|
||||
SRC_USERMOD += $(USERMOD_DIR)/module.c
|
||||
|
||||
endif
|
||||
|
||||
187
external/c-modules/aes256ctr/module.c
vendored
@ -1,187 +0,0 @@
|
||||
//
|
||||
// module top-level
|
||||
//
|
||||
#include "py/obj.h"
|
||||
#include "py/runtime.h"
|
||||
#include "py/builtin.h"
|
||||
#include "aes_256_ctr.h"
|
||||
|
||||
#if MICROPY_ENABLE_DYNRUNTIME
|
||||
#error "Static Only"
|
||||
#endif
|
||||
|
||||
// AES block size
|
||||
#define BLKSIZE 16
|
||||
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
uint8_t runt[BLKSIZE];
|
||||
int runt_len;
|
||||
param p;
|
||||
} mp_obj_AES256CTR_t;
|
||||
|
||||
STATIC const mp_obj_type_t s_AES256CTR_type;
|
||||
|
||||
STATIC mp_obj_t s_AES256CTR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
// args: key, nonce
|
||||
mp_arg_check_num(n_args, n_kw, 1, 2, false);
|
||||
|
||||
mp_buffer_info_t key;
|
||||
mp_get_buffer_raise(args[0], &key, MP_BUFFER_READ);
|
||||
|
||||
if(key.len != 32) {
|
||||
// only AES-256 here
|
||||
mp_raise_ValueError(NULL);
|
||||
}
|
||||
|
||||
mp_obj_AES256CTR_t *o = m_new_obj_with_finaliser(mp_obj_AES256CTR_t);
|
||||
o->base.type = type;
|
||||
|
||||
// state setup, and key schedule
|
||||
memset(&o->p, 0, sizeof(param));
|
||||
o->runt_len = 0;
|
||||
|
||||
if(n_args == 2) {
|
||||
mp_buffer_info_t n_in;
|
||||
mp_get_buffer_raise(args[1], &n_in, MP_BUFFER_READ);
|
||||
if(n_in.len > 16) {
|
||||
mp_raise_ValueError(NULL);
|
||||
}
|
||||
// can be 12 bytes of nonce + 4 bytes ctr, or just all 16 bytes of state
|
||||
memcpy(o->p.nonce, n_in.buf, n_in.len);
|
||||
}
|
||||
|
||||
// key setup
|
||||
memcpy(o->p.rk, key.buf, 32);
|
||||
AES_256_keyschedule(key.buf, o->p.rk+32);
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
static bool inline
|
||||
is_unaligned(const void *p)
|
||||
{
|
||||
return !!(((uint32_t)p) & 0x3);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t s_AES256CTR_cipher(mp_obj_t self_in, mp_obj_t buf_in)
|
||||
{
|
||||
mp_obj_AES256CTR_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
mp_buffer_info_t buf;
|
||||
mp_get_buffer_raise(buf_in, &buf, MP_BUFFER_READ);
|
||||
|
||||
int len = buf.len, in_len = buf.len;
|
||||
const uint8_t *inp = buf.buf;
|
||||
uint8_t *rv = m_malloc(in_len);
|
||||
uint8_t *outp = rv;
|
||||
|
||||
if(self->runt_len) {
|
||||
// we've already encrypted (w/ zero bytes) for this part
|
||||
uint8_t *ch = &self->runt[BLKSIZE - self->runt_len];
|
||||
while(self->runt_len && in_len) {
|
||||
*(outp++) = *(inp++) ^ *(ch++);
|
||||
self->runt_len --;
|
||||
in_len --;
|
||||
}
|
||||
}
|
||||
|
||||
bool nogood = (is_unaligned(inp) || is_unaligned(outp));
|
||||
|
||||
while(in_len) {
|
||||
if(in_len >= BLKSIZE) {
|
||||
if(nogood) {
|
||||
uint8_t blk[BLKSIZE];
|
||||
|
||||
// align data so ASM can work
|
||||
memcpy(blk, inp, BLKSIZE);
|
||||
AES_256_encrypt_ctr(&self->p, blk, blk, BLKSIZE);
|
||||
memcpy(outp, blk, BLKSIZE);
|
||||
} else {
|
||||
// rare? but faster case
|
||||
AES_256_encrypt_ctr(&self->p, inp, outp, BLKSIZE);
|
||||
}
|
||||
|
||||
outp += BLKSIZE;
|
||||
inp += BLKSIZE;
|
||||
in_len -= BLKSIZE;
|
||||
} else {
|
||||
uint8_t blk[BLKSIZE] = {};
|
||||
memcpy(blk, inp, in_len);
|
||||
|
||||
AES_256_encrypt_ctr(&self->p, blk, self->runt, BLKSIZE);
|
||||
memcpy(outp, self->runt, in_len);
|
||||
self->runt_len = BLKSIZE - in_len;
|
||||
in_len = 0;
|
||||
}
|
||||
|
||||
// inc counter (big endian, assumes nonce != ~0)
|
||||
uint8_t *ctr = &self->p.ctr[3];
|
||||
while(1) {
|
||||
ctr[0] += 1;
|
||||
if(ctr[0]) break;
|
||||
ctr--;
|
||||
}
|
||||
}
|
||||
|
||||
return mp_obj_new_bytearray_by_ref(len, rv);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(s_AES256CTR_cipher_obj, s_AES256CTR_cipher);
|
||||
|
||||
STATIC mp_obj_t s_AES256CTR_copy(mp_obj_t self_in) {
|
||||
mp_obj_AES256CTR_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
mp_obj_AES256CTR_t *rv = m_new_obj_with_finaliser(mp_obj_AES256CTR_t);
|
||||
*rv = *self;
|
||||
rv->base.type = &s_AES256CTR_type;
|
||||
|
||||
return rv;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(s_AES256CTR_copy_obj, s_AES256CTR_copy);
|
||||
|
||||
|
||||
STATIC mp_obj_t s_AES256CTR_blank(mp_obj_t self_in) {
|
||||
mp_obj_AES256CTR_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
// cf_aes_finish is just this anyway
|
||||
memset(self, 0, sizeof(mp_obj_AES256CTR_t));
|
||||
self->base.type = &s_AES256CTR_type;
|
||||
|
||||
return self_in;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(s_AES256CTR_blank_obj, s_AES256CTR_blank);
|
||||
|
||||
|
||||
STATIC const mp_rom_map_elem_t s_AES256CTR_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_cipher), MP_ROM_PTR(&s_AES256CTR_cipher_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_blank), MP_ROM_PTR(&s_AES256CTR_blank_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_blank), MP_ROM_PTR(&s_AES256CTR_blank_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&s_AES256CTR_copy_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&s_AES256CTR_blank_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(s_AES256CTR_locals_dict, s_AES256CTR_locals_dict_table);
|
||||
|
||||
STATIC const mp_obj_type_t s_AES256CTR_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_AES256CTR,
|
||||
.make_new = s_AES256CTR_make_new,
|
||||
.locals_dict = (void *)&s_AES256CTR_locals_dict,
|
||||
};
|
||||
|
||||
|
||||
STATIC const mp_rom_map_elem_t mp_module_aes256ctr_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_aes256ctr) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_new), MP_ROM_PTR(&s_AES256CTR_type) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_aes256ctr_globals, mp_module_aes256ctr_globals_table);
|
||||
|
||||
const mp_obj_module_t mp_module_aes256ctr = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t *)&mp_module_aes256ctr_globals,
|
||||
};
|
||||
|
||||
MP_REGISTER_MODULE(MP_QSTR_aes256ctr, mp_module_aes256ctr, 1);
|
||||
|
||||
|
||||
1
external/c-modules/libngu
vendored
@ -1 +0,0 @@
|
||||
../libngu/ngu
|
||||
1
external/c-modules/mpy-qr
vendored
@ -1 +0,0 @@
|
||||
../mpy-qr
|
||||
2
external/ckcc-protocol
vendored
@ -1 +1 @@
|
||||
Subproject commit 3d1dfa858beb58b8dac37d8c66d7aed2909812f2
|
||||
Subproject commit f3033b9dce8619c3ad89bdd840f5c91156d9598c
|
||||
1
external/crypto
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit f83020c73dbb3d033b344345c99792d430d644c6
|
||||
1
external/libngu
vendored
@ -1 +0,0 @@
|
||||
Subproject commit 537519a829259622ea6b0334fbafd6cae852852f
|
||||
2
external/micropython
vendored
@ -1 +1 @@
|
||||
Subproject commit 4107246f8a080807b62c3b4838e71e812ea68b6f
|
||||
Subproject commit db4e297ea1f40d2105cea1e2592e46fbda918dbc
|
||||
1
external/modcryptocurrency
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit aba6ba441b0d51b255e680e1988a4d240155f849
|
||||
2
external/mpy-qr
vendored
@ -1 +1 @@
|
||||
Subproject commit 11347d83f4eb325b10676a4eb8e17deccfe0df44
|
||||
Subproject commit 198042652fa5720efeed09d20aee436ddbf35ece
|
||||
@ -1,16 +1,11 @@
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
|
||||
all: graphics_mk4.py graphics_q1.py
|
||||
all: graphics.py
|
||||
|
||||
MK4_SOURCES = $(wildcard mono/*.txt) $(wildcard mono/*.png)
|
||||
Q1_SOURCES = colour/*.???
|
||||
SOURCES = $(wildcard *.txt) $(wildcard *.png)
|
||||
|
||||
graphics_mk4.py: Makefile $(MK4_SOURCES) build.py
|
||||
./build.py graphics_mk4.py $(MK4_SOURCES)
|
||||
|
||||
graphics_q1.py: Makefile $(Q1_SOURCES) compress.py
|
||||
./compress.py graphics_q1.py $(Q1_SOURCES)
|
||||
graphics.py: Makefile $(SOURCES) build.py
|
||||
./build.py $(SOURCES)
|
||||
|
||||
up: all
|
||||
(cd ../shared; make up)
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
||||
# and is covered by GPLv3 license found in COPYING.
|
||||
#
|
||||
import os, sys, pdb
|
||||
from PIL import Image, ImageOps
|
||||
@ -33,7 +34,7 @@ def read_text(fname):
|
||||
def read_img(fn):
|
||||
img = Image.open(fn)
|
||||
w,h = img.size
|
||||
assert 1 <= w < 128, (w, fn)
|
||||
assert 1 <= w < 128, w
|
||||
|
||||
img = img.convert('L')
|
||||
# fix colour issues: assume minority colour is white (1)
|
||||
@ -65,8 +66,6 @@ def crunch(n):
|
||||
|
||||
def doit(outfname, fnames):
|
||||
|
||||
assert outfname.endswith('.py')
|
||||
assert outfname != 'build.py'
|
||||
assert fnames, "need some files"
|
||||
|
||||
fp = open(outfname, 'wt')
|
||||
@ -88,7 +87,7 @@ class Graphics:
|
||||
assert img.mode == '1'
|
||||
#img.show()
|
||||
|
||||
varname = fn.split('/')[-1].split('.')[0].replace('-', '_')
|
||||
varname = fn.split('.')[0].replace('-', '_')
|
||||
|
||||
w,h = img.size
|
||||
raw = img.tobytes()
|
||||
@ -109,4 +108,4 @@ class Graphics:
|
||||
fp.write("\n# EOF\n")
|
||||
|
||||
if 1:
|
||||
doit(sys.argv[1], sys.argv[2:])
|
||||
doit('graphics.py', sys.argv[1:])
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
@ -1,212 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# (c) Copyright 2023 by Coinkite Inc. This file is covered by license found in COPYING-CC.
|
||||
#
|
||||
# Read in PNG (or even JPG) and output heavily compressed RGB565 data suited to Q1's LCD panel.
|
||||
#
|
||||
# - also renders status bar icons/indicators
|
||||
#
|
||||
import os, sys, pdb
|
||||
from PIL import Image, ImageOps, ImageFont, ImageDraw
|
||||
import zlib
|
||||
from struct import pack
|
||||
|
||||
WBITS = -10
|
||||
|
||||
FONT_PATH = './fonts/'
|
||||
|
||||
def read_img(fn):
|
||||
img = Image.open(fn)
|
||||
w,h = img.size
|
||||
assert 1 <= w <= 320, f'too wide; {w}'
|
||||
assert 1 <= h <= 240, f'too tall: {h}'
|
||||
|
||||
img = img.convert('RGB')
|
||||
|
||||
# maybe: quantitize to a reasonable num colours, so compression
|
||||
# can work better?
|
||||
|
||||
return img
|
||||
|
||||
def compress(n, wbits=WBITS):
|
||||
# NOTE: neg wbits implies no zlib header, and receiver may need to know it?
|
||||
z = zlib.compressobj(wbits=wbits, level=zlib.Z_BEST_COMPRESSION)
|
||||
rv = z.compress(n)
|
||||
rv += z.flush(zlib.Z_FINISH)
|
||||
return rv
|
||||
|
||||
def crunch(n):
|
||||
# try them all... not finding any difference tho.
|
||||
a = [(wb,compress(n, wb)) for wb in range(-9, -15, -1)]
|
||||
|
||||
a.sort(key=lambda i: (-len(i[1]), -i[0]))
|
||||
|
||||
print("Wbit values:")
|
||||
print('\n'.join("%3d => %d" % (wb,len(d)) for wb,d in a))
|
||||
|
||||
return a[0]
|
||||
|
||||
# LCD Display wants RGB565 values, but big endian, so green gets split weird.
|
||||
def swizzle(r,g,b):
|
||||
# from 0-255 per component => two bytes
|
||||
b = (b >> 3)
|
||||
g = (g >> 3) # should be >> 2 for 6 bits; but looks trash?
|
||||
r = (r >> 3)
|
||||
|
||||
return pack('>H', ((r<<11) | (g<<6) | b))
|
||||
|
||||
# these values tested on real hardware
|
||||
assert swizzle(255, 0, 0) == b'\xf8\x00' # red
|
||||
##assert swizzle(0, 255, 0) == b'\xc0\x0f' # green (6 bits)
|
||||
assert swizzle(0, 255, 0) == b'\x07\xc0' # green (5 bits)
|
||||
assert swizzle(0, 0, 255) == b'\x00\x1f' # blue
|
||||
|
||||
|
||||
def into_bgr565(img):
|
||||
# get the raw bytes needed for this specific display
|
||||
rv = bytearray()
|
||||
for y in range(img.height):
|
||||
for x in range(img.width):
|
||||
px = img.getpixel((x, y))
|
||||
assert len(px) == 3
|
||||
r,g,b = px
|
||||
rv.extend(swizzle(r,g,b))
|
||||
|
||||
return rv
|
||||
|
||||
def make_icons():
|
||||
# return list of (varname, img) for each image
|
||||
|
||||
# - see shared/lcd_display.py TOP_MARGIN for this
|
||||
ICON_SIZE = 14
|
||||
MAX_HEIGHT = 14
|
||||
|
||||
# PROBLEM: this file costs money... altho free version looks okay too
|
||||
try:
|
||||
awesome = ImageFont.truetype(FONT_PATH + 'Font Awesome 6 Sharp-Regular-400.otf', ICON_SIZE)
|
||||
except:
|
||||
raise
|
||||
|
||||
# use a bitmap font for best readability
|
||||
sm_font = ImageFont.load('ter-powerline-x12b.pil')
|
||||
|
||||
targets = [
|
||||
#( 'brand', True, 'Q', dict(col='#ffb000') ),
|
||||
( 'shift', True, 'SHIFT', {} ),
|
||||
( 'symbol', True, 'SYM', {} ),
|
||||
( 'caps', True, 'CAPS', {} ),
|
||||
( 'bip39', True, 'PASSPHRASE', dict(col_1='yellow') ),
|
||||
( 'tmp', True, 'TMP.SEED', dict(col_0='black', col_1='red') ),
|
||||
( 'devmode', True, 'DEV', dict(col='#66E6FF') ),
|
||||
( 'edge', True, 'EDGE', dict(col='#66E6FF') ),
|
||||
( 'bat_0', False, '\uf244', dict(col='red', y=-1, pad=1)),
|
||||
( 'bat_1', False, '\uf243', dict(col='yellow', y=-1, pad=1)),
|
||||
( 'bat_2', False, '\uf242', dict(col='amber', y=-1, pad=1)),
|
||||
( 'bat_3', False, '\uf240', dict(col='amber', y=-1, pad=1)),
|
||||
( 'plugged', False, '\uf1e6', dict(col='amber', x=3, w=16, y=-2)), # to match width of bat_*
|
||||
#( 'locked', False, '\uf023', dict(col='green')),
|
||||
#( 'unlocked', False, '\uf3c1', dict(col='green')), # why tho?
|
||||
]
|
||||
|
||||
targets += [ ( 'ch_'+c, True, c.upper(), dict(col='white') ) for c in
|
||||
'0123456789abcdef']
|
||||
|
||||
samples = Image.new('RGB', (320*3, ICON_SIZE+1))
|
||||
s_x = 5
|
||||
|
||||
for basename, is_text, body, opts in targets:
|
||||
for state in [0, 1]:
|
||||
col = opts.get('col', '#fff' if state else '#444')
|
||||
vn = f'{basename}_{state}'
|
||||
|
||||
if 'col' in opts:
|
||||
if state == 0: continue
|
||||
vn = basename
|
||||
|
||||
if state == 0 and 'col_0' in opts:
|
||||
col = opts['col_0']
|
||||
if state == 1 and 'col_1' in opts:
|
||||
col = opts['col_1']
|
||||
|
||||
img = Image.new('RGB', (100,100))
|
||||
d = ImageDraw.Draw(img)
|
||||
f = sm_font if is_text else awesome
|
||||
|
||||
|
||||
x, y = (0, 1 if is_text else 0)
|
||||
y += opts.get('y', 0)
|
||||
x += opts.get('x', 0)
|
||||
|
||||
tl = (x, y)
|
||||
_,_, w,h = d.textbbox(tl, body, font=f)
|
||||
|
||||
w = opts.get('w', w)
|
||||
|
||||
if h > MAX_HEIGHT:
|
||||
h = MAX_HEIGHT
|
||||
print(f'"{vn}" too tall, cropped')
|
||||
elif opts.get('pad'):
|
||||
h = MAX_HEIGHT
|
||||
|
||||
if col == 'amber':
|
||||
# brand colour
|
||||
col = '#ffb000'
|
||||
|
||||
d.text(tl, body, font=f, fill=col)
|
||||
rv = img.crop( (0, 0, w,h) )
|
||||
|
||||
samples.paste(rv, (s_x, 0))
|
||||
s_x += w + 10
|
||||
|
||||
yield (vn, rv)
|
||||
|
||||
samples = samples.crop( (0,0, s_x, samples.height ))
|
||||
samples.save('icon-samples.png')
|
||||
|
||||
|
||||
|
||||
def doit(outfname, fnames):
|
||||
|
||||
assert outfname.endswith('.py')
|
||||
assert outfname != 'compress.py'
|
||||
assert fnames, "need some files"
|
||||
|
||||
fp = open(outfname, 'wt')
|
||||
|
||||
fp.write("""\
|
||||
# autogenerated; don't edit
|
||||
#
|
||||
# BGR565 pixel data
|
||||
#
|
||||
class Graphics:
|
||||
# (w,h, data)
|
||||
|
||||
""")
|
||||
|
||||
fnames += make_icons()
|
||||
|
||||
for fn in fnames:
|
||||
if isinstance(fn, str):
|
||||
img = read_img(fn)
|
||||
varname = fn.split('/')[-1].split('.')[0].replace('-', '_')
|
||||
else:
|
||||
varname, img = fn
|
||||
|
||||
assert img.mode == 'RGB'
|
||||
|
||||
w,h = img.size
|
||||
raw = into_bgr565(img)
|
||||
comp = compress(raw)
|
||||
#crunch(raw)
|
||||
|
||||
print(" %s = (%d, %d,\n %r\n )\n" % (varname, w, h, comp), file=fp)
|
||||
|
||||
print("done: '%s' (%d x %d) => %d raw => %d compressed bytes" % (
|
||||
varname, w, h, len(raw), len(comp)))
|
||||
|
||||
fp.write("\n# EOF\n")
|
||||
|
||||
if 1:
|
||||
doit(sys.argv[1], sys.argv[2:])
|
||||
|
||||
# EOF
|
||||
@ -1,7 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Generate some data for hsm_ux.py animation
|
||||
#
|
||||
from math import sin, pi
|
||||
from collections import Counter
|
||||
|
||||
|
||||
3
graphics/fonts/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
Font Awesome 6*.otf
|
||||
iosevka-*.ttf
|
||||
!iosevka-heavy.ttf
|
||||
@ -1,11 +0,0 @@
|
||||
|
||||
# Fonts for Q1
|
||||
|
||||
This directory may contain font files from Font Awesome.
|
||||
|
||||
We cannot re-distribute the OTF files themselves due to their license.
|
||||
|
||||
However, once we render and build the compressed graphics file that we need,
|
||||
the font is not required anymore.
|
||||
|
||||
Iosveka is open and can be re-distributed here.
|
||||
@ -1,18 +0,0 @@
|
||||
Font Awesome Pro License
|
||||
------------------------
|
||||
|
||||
Font Awesome Pro is commercial software that requires a paid license. Full
|
||||
Font Awesome Pro license: https://fontawesome.com/license.
|
||||
|
||||
# Commercial License
|
||||
The Font Awesome Pro commercial license allows you to pay for FA Pro once, own
|
||||
it, and use it just about everywhere you'd like.
|
||||
|
||||
# Attribution
|
||||
Attribution is not required by the Font Awesome Pro commercial license.
|
||||
|
||||
# Brand Icons
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
27
graphics/graphics.py
Normal file
@ -0,0 +1,27 @@
|
||||
# autogenerated; don't edit
|
||||
#
|
||||
class Graphics:
|
||||
# (w,h, w_bytes, wbits, data)
|
||||
|
||||
arrow_down = (7, 11, 1, 0, b'\x10\x10\x10\x10\x10\x10\x10\xfe|8\x10')
|
||||
|
||||
arrow_up = (7, 11, 1, 0, b'\x108|\xfe\x10\x10\x10\x10\x10\x10\x10')
|
||||
|
||||
box = (13, 21, 2, 0, b'?\xe0@\x10\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08@\x10?\xe0')
|
||||
|
||||
scroll = (3, 61, 1, 0, b'@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@@\xe0@')
|
||||
|
||||
selected = (15, 12, 2, 0, b'\x00\x00\x00\x00\x00\x06\x00\x0c\x00\x18\x0000`\x18\xc0\r\x80\x07\x00\x02\x00\x00\x00')
|
||||
|
||||
sm_box = (11, 17, 2, 0, b'\xe4\xe0\x80 \x80 \x80 \x00\x00\x00\x00\x80 \x00\x00\x00\x00\x00\x00\x80 \x00\x00\x00\x00\x80 \x80 \x80 \xe4\xe0')
|
||||
|
||||
space = (9, 2, 2, 0, b'\x80\x80\xff\x80')
|
||||
|
||||
spin = (13, 36, 2, 0, b'\x02\x00\x07\x00\x0f\x80\x1f\xc0\x00\x00\x00\x00\x00\x00\xf2x\x80\x08\x80\x08\x80\x08\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x80\x08\x80\x08\x80\x08\xf2x\x00\x00\x00\x00\x00\x00\x1f\xc0\x0f\x80\x07\x00\x02\x00\x00\x00')
|
||||
|
||||
wedge = (6, 11, 1, 0, b'\x00\x00\xc0\xe0p8\x1c8p\xe0\xc0')
|
||||
|
||||
xbox = (13, 21, 2, 0, b'?\xe0b0\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88b0?\xe0')
|
||||
|
||||
|
||||
# EOF
|
||||
@ -1,43 +0,0 @@
|
||||
# autogenerated; don't edit
|
||||
#
|
||||
class Graphics:
|
||||
# (w,h, w_bytes, wbits, data)
|
||||
|
||||
arrow_down = (7, 11, 1, 0, b'\x10\x10\x10\x10\x10\x10\x10\xfe|8\x10')
|
||||
|
||||
arrow_up = (7, 11, 1, 0, b'\x108|\xfe\x10\x10\x10\x10\x10\x10\x10')
|
||||
|
||||
box = (13, 21, 2, 0, b'?\xe0@\x10\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08\x80\x08@\x10?\xe0')
|
||||
|
||||
mk4_nfc_1 = (126, 49, 16, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\xfe\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\xfe\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
mk4_nfc_2 = (118, 49, 15, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
mk4_nfc_3 = (110, 49, 14, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
mk4_nfc_4 = (102, 49, 13, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xcf\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x03\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\x9f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\x8f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
mk5_nfc_1 = (126, 49, 16, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff\xfe0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff\xfe0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\xfe\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\xfe\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
mk5_nfc_2 = (118, 49, 15, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\xe0\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\xe0\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\xe0\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe00\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00\xe0?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
mk5_nfc_3 = (110, 49, 14, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x00\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x00\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x00\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xff0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xff0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x000\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e\x00?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\xff\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\xff\x0f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
mk5_nfc_4 = (102, 49, 13, 0, b'\x00\x7f\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xf0\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xfe\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x0e\x03\xff\xff\xff\xff\xff\xff\xff\xff\x80\x00\xe0\x0e\x07\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xe0\x0e\x1f\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 G\xe3\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xe0\x0e0\x000D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00(D\x04\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xff\xff\xb0\x00$G\xe4\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00"D\x04\x00\x00\x00xp\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00 \xc4\x04\x00\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x04\x10\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xff\xff\xb0\x00 D\x03\xe0\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e0\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x00\xe0\x0e?\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x00\xff\xff\x9f\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\xff\xff\x8f\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x7f\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xff\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
scroll = (3, 61, 1, 0, b'@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@\x00\x00@@\xe0@')
|
||||
|
||||
selected = (9, 12, 2, 0, b'\x00\x00\x00\x00\x00\x80\x01\x80\x01\x00\x03\x00\x82\x00\xc6\x00d\x00<\x00\x18\x00\x00\x00')
|
||||
|
||||
sm_box = (11, 17, 2, 0, b'\xe4\xe0\x80 \x80 \x80 \x00\x00\x00\x00\x80 \x00\x00\x00\x00\x00\x00\x80 \x00\x00\x00\x00\x80 \x80 \x80 \xe4\xe0')
|
||||
|
||||
space = (9, 2, 2, 0, b'\x80\x80\xff\x80')
|
||||
|
||||
spin = (13, 36, 2, 0, b'\x02\x00\x07\x00\x0f\x80\x1f\xc0\x00\x00\x00\x00\x00\x00\xf2x\x80\x08\x80\x08\x80\x08\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x80\x08\x80\x08\x80\x08\xf2x\x00\x00\x00\x00\x00\x00\x1f\xc0\x0f\x80\x07\x00\x02\x00\x00\x00')
|
||||
|
||||
wedge = (6, 11, 1, 0, b'\x00\x00\xc0\xe0p8\x1c8p\xe0\xc0')
|
||||
|
||||
xbox = (13, 21, 2, 0, b'?\xe0b0\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88\xa2(\x88\x88b0?\xe0')
|
||||
|
||||
|
||||
# EOF
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxx xxxxxxxxxxx
|
||||
xxx xxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,49 +0,0 @@
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxx xxxx xxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxx xxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxx
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy n n ffffff ccccc yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxx xxx yy nn n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n f c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxxxxxxxxxxxxxxxx yy n n n ffffff c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n n n f c yyyy yyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxx xxx yy n nn f c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f c c yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yy n n f ccccc yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yy yyyyyyyyyyy
|
||||
xxx xxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
xxxxxxxxxxxxxxxxx
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@ -1,12 +0,0 @@
|
||||
|
||||
|
||||
X
|
||||
XX
|
||||
X
|
||||
XX
|
||||
X X
|
||||
XX XX
|
||||
XX X
|
||||
XXXX
|
||||
XX
|
||||
|
||||
12
graphics/selected.txt
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
xx
|
||||
xx
|
||||
xx
|
||||
xx
|
||||
xx xx
|
||||
xx xx
|
||||
xx xx
|
||||
xxx
|
||||
x
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |