Skip to content
Switch branches/tags
Go to file
Latest commit acbc89b Jun 27, 2020 History
was was => was
4 contributors

Users who have contributed to this file

@doc-hex @Furunodo @nvk @clarkmoody

Coldcard PIN Design and Operation

What happens when I enter a PIN?

  1. Enter between 2 and 6 digits of your PIN code prefix and press OK (checkmark).

  2. The Coldcard will use those digits to find two specific words from the BIP39 word list. This two word response is unique to your Coldcard and pin combination.

  3. Look at the words. Are they right and what you expected? If not, press X and try again, and/or talk to your "evil maid" about her activities. Do not continue to enter more digits of your real PIN!

  4. If you want to proceed, and you have a secondary wallet enabled, press (2) to select the secondary wallet, otherwise, just press OK to continue for primary wallet (1).

  5. Then enter the remaining digits of the PIN (between 2 and 6 digits). Press OK.

  6. Is it the user-defined "brick me" PIN? If so, it's used to wipe the secure element and irreversibly brick the Coldcard immediately. There is no delay, even if failed PIN attempts are recorded. The pairing secret is rolled with a forgotten value, so it's impossible to ever talk to the secure element again. (Before enabling this feature, please triple-check your backups are correct!)

  7. Calculate the required delay to punish previous incorrect PIN attempts. The current "attempt counter" for the target wallet (1) or (2) is observed, and the difference between that counter's current value, and the recorded "last successful attempt level" is used. If there has been an incorrect attempt previously, then make the user wait some time here.

  8. After the delay, check the PIN. The PIN is checked in this order:

    • Is it the PIN for the duress wallet? If so, open that wallet and continue as if it was the normal (1) or (2) wallet that was picked in step 4.

    • Otherwise, we increment the PIN attempt counter for indicated wallet at this point.

    • Check if the PIN is right. If it is, use that PIN to record the PIN attempt counter's new value (typically +1), and read out the encrypted secret. System is open.

    • If the PIN is wrong, tell them and start over.


  • it's possible to use the same PIN for both main/secondary wallets
  • wrong pin attempts on secondary will not count against primary wallet, etc
  • we can only support two seeds (full wallets) because the chip has only two counters for PIN attempt tracking
  • we could wipe the device when too many PIN attempt happen, but we can't recover it as a blank/reusable device due to the security design.
  • there is a separate independent duress wallet available for both wallet (1) and (2)
  • there is no attempt limit/rate limiting for duress PIN's, but since the same (incorrect) pin is attempted on the real wallet, that attempt counter is effectively used to protect the duress PIN as well.

Dark Duress Thoughts

If you are relying on the duress PIN, you should probably have a brick-me PIN as well.

Very smart attackers could monitor the data bus between main micro and the secure element.

When the duress password succeeds, activity on the bus would be clearly different from the normal PIN. There is nothing we can do about that because traffic analysis of the bus is not hard even though the all sensitive data is encrypted.

However, on the screen, we will keep everything looking normal, and in fact, we try hard not to reveal to the main firmware that the duress PIN was used successfully.

So if there are wires connected to the Coldcard while you are being forced to enter a PIN, then you probably use the brick-me PIN instead 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 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. The system firmware always attempts the brick-me PIN before checking the PIN is correct for duress or normal usage.

But really, you should rethink your life choices before getting into these situations!

Secret Keystrokes vs. Special PIN Codes

We considered various "secret" key-presses to trigger the duress or brick features.

But this approach doesn't work because:

  • There is an Internet, and we have to document these features on it.
  • You might not be able to directly touch the subject Coldcard at the critical moment.
  • You might be forced to give a PIN code via phone or text message
  • You can write down a duress or brickme PIN in places where it might be found by bad actors, but you can't trick someone into pressing an undocumented key at a specific time.
  • Special PIN codes are fully configurable for the same reason: we can't make 666-666 do something special if a quick Google tells anyone that's a trap.

Where is the PIN stored?

All PINs are stored inside the secure element. The firmware does not know the PIN until you enter it, and then it checks it is right using the secure element, and may load the wallet seed if it works.

To be able to read the secrets (ie. wallet seed) out of the secure element, the PIN is required. In fact, you have to hash the PIN with a random number (nonce) generated by the secure element. This is the main protection against replay attacks, and monitoring the bus passively.


  • you cannot separate the secure element from main micro, and then brute-force the PIN.
  • monitoring the connection to the secure element does not reveal the PIN (or any protected secrets)
  • there is no way to read (or change) the wallet seed without the PIN
  • there is no way to change the PIN without knowing the old PIN
  • there is no way to directly read the PIN
  • PIN attempt counters cannot be reversed or reset, ever
  • only actual, correct knowledge of the PIN can update the "last successful" record (ie. mark PIN attempt as successful)
  • the main wallet PIN is required to update the flash contents and keep the genuine light green

All of the above claims are enforced by the secure element itself. The only policy that is enforced by our firmware is the PIN attempt timeout periods. The code that does that is in a specially restricted area we call "the bootloader". It cannot be changed in the field.

All interactions with the secure element are authenticated themselves with something we call the pairing secret. Thus, to test a PIN value, you need that pairing secret. Only the boot loader knows the pairing secret, so it is able to enforce rate limiting and other policy choices.

But I forgot my PIN...

Sorry, there is nothing we can do. Without knowledge of the PIN, it's impossible to read out any of the secrets of the secure element. That's true even if you knew the pairing secret and could talk directly to the chip. Even if you knew every byte of the main flash (and you do, it's open source software). Even if you desoldered the secure element and attacked it with a supercomputer.

The only downside to this design is the legitimate customers who have forgotten their PIN, and tragically have also lost their seed backup. The firmware will force them into ever-longer delay cycles between attempts, but they are free to spend the rest of their lives attempting to find the right PIN.

It's not possible for us to wipe the device and start again as fresh device. We cannot write the wallet seed stored in the secure element without the previous PIN.

PIN to Secret Value (hashing)

The low-level bootloader firmware supports PIN's of any length up to 32 characters. We recommend splitting that evenly between the prefix and final parts, and we will force you provide a minimum of two digits as prefix for the anti-phishing feature to work well. Due to the numeric keypad, we have to limit PINs to numeric digits.

Internally, the characters in the PIN are hashed as follows:

SHA256( SHA256( pairing_secret + purpose_salt + pin_digits ))


  • pairing_secret is 32-byte secret kept by the boot loader
  • purpose_salt is 4-byte value linked to the usage of the PIN (ie. for prefix vs. real pin)
  • pin_digits ascii digits, or could be any octets for custom solutions.

This double-hashed value is what's stored inside the secure element (32 bytes). It cannot be read back out, and when it's written (which requires proving knowledge of previous PIN), it is encrypted 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-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 enfoced by the 608a internally.

Genuine vs. Caution Lights

The green light will be lit only if the entire flash memory contents are unchanged since the last time you logged in with the main PIN. This is enforced cryptographically by the secure element which is the only way to change the light because the signals for the light connect exclusively to the secure element and are not connected to the main microprocessor.

Every time when the system starts, the entire flash is hashed using double-SHA256. The pairing secret is part of that, as is every single byte of flash in the chip so it will be unique per-device. The expected value of that hash is not stored except in the secure element. The bootloader writes whatever it calculates into the secure element and the secure element will only turn on the light if it matches the expected value.

There is no way to read out what the hash is supposed to be, and no way to change the state of the green/red lights without that hash (and the pairing secret). The red and green lights are directly connected to the secure element chip, and do not interact with the main micro.

When the user upgrades the firmware, they can use the main PIN code to capture the updated hash value into the secure element.

Code Signatures

The bootloader will not run the main firmware if it is unsigned or signed incorrectly. This means only Coinkite Inc. can produce firmware that will run on this platform. We are open to suggestions on how we could safely allow third parties to write software that can be run on the device. Here are some of the approached we considered:

  • a "red PCB" version of the hardware that looks different (and therefore cannot be used as a doppelgänger) but does not enforce the firmware signing policy.

  • the user can opt into a particular key (from a another vendor or their own key if they are a developer). The main PIN would be required to do the opt-in, and it would probably be a one-way trip.

  • some sort of centralized service that signs binaries if we trust your team---a walled garden. (Considered a hopeless approach, but in our list regardless.)

Regardless of the code signing policies, we want the genuine/caution feature to work and be a useful defense against maids.

We want everyone to be able to experiment on our platform, and their coins are just as precious to them, as Bitcoin are to us.

Production vs. Development Key

What we've done to satisfy these needs is as follows:

  • the bootloader knows and trusts five public keys
  • main firmware code is always checksumed and needs a correct signature
  • (the private part of) key zero has been published on Github as part of the source tree.
  • keys 1 through 4 are factory keys we will keep secret.
  • official releases are using key 1 for now, but all keys other than zero are considered "official"

Experimental code, written by anyone, can be signed with key zero, and the bootloader will accept it. However, if the genuine light is red, it will show a scary notice to the user during boot time, and enforce a delay of 25 seconds. If the main PIN is used to "bless the firmware", meaning the light is green during boot, the warning message is not shown, and bootup proceeds as normal.

Here's what the warning screen looks like:

dev-warning screen

How To Develop Professional Code on Coldcard

  • Hire and pay a dev to write the changes
  • 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
  • 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


  • 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.
  • all PIN related policy is enforced by unchangeable bootloader code, per this document


  • if new device is intercepted from our factory (ie. without a main pin set), new code cannot be loaded until the PIN is set, and there is no way to clear main PIN, using the normal firmware. However, as the proud new owner of the device, you could load custom firmware that does it.
  • the serialized, tamper-evident bag may indicate it's been touched
  • more discussion about supply-chain attacks is published here

Obvious Hack-Attack

Idea: Find or steal a Coldcard. Load your trojan firmware onto it. Profit.

  • but you don't know the main PIN (or else you'd have already stolen the funds)
  • so changing the firmware is not easy since it does little without the main PIN: you will have to crack the welded case, and do some difficult soldering.
  • regardless, once you change the firmware, red caution light will show
  • since you can't set the genuine light without the PIN, and your trojan is signed with key zero, when the victim gets back, they will see the big "unauthorized firmware" warning, plus the red light and probably some scratches on the case, etc.
  • weak solution: "helpfully" power up the Coldcard for them, and say... "Here it is ready for your pin, sir. No idea why the light is red today."
  • so we need to warn users to power up the Coldcard themselves every time they enter the PIN.

Four Types of wallets, one Coldcard

The Coldcard before Mark 3, effectively supported four independent wallets: primary (main), secondary, main duress, secondary duress.

The secondary wallet is a little less capable than the main one, since the main PIN is needed for changing certain configuration values, and enabling the Genuine light. However, the secondary wallet has the same secret protection, PIN retry counters and so on.

The intention is that main and secondary wallets are completely independent of each other, in terms of funds and wallet seeds.

In Mark 3, we removed the secondary wallet because we could not support PIN retry limits like the main wallet. We now recommend BIP39 passphrases to introduce an unlimited number of secondary wallets.

Duress Wallet

Duress wallet could store any 72-byte secret, and it's as well protected as the main/secondary secrets, but we only enable use of the duress wallet when attempting access to the corresponding main/secondary wallet. The PIN failure and retry delays are linked together. We also hide the fact that the duress wallet PIN was used successfully. These lies are promoted by the boot rom code---to higher levels of software, operation proceeds normally.

When creating the duress wallet, Coldcard derives the wallet root from the real wallet by a one-way process. This means when you backup the main wallet, you are also backing up the corresponding duress wallet and its give-away funds. Writing down the main wallet's seed words will include the duress wallet and its funds.

The duress wallet operates like any other wallet on the Coldcard. Load value and sign transactions as normal. By design, it acts just like a normal wallet (either main or secondary). As part of your preparations for a bad day when you need it, you should load it with some funds after setup.

Recovery of Funds from Duress Wallet

To recover funds from the "duress wallet", import your original seed words into a new Coldcard, and assign a duress PIN again. Then login to the duress wallet and re-import that into your desktop wallet.

Alternatively, if you have the 7Z encrypted backup file, decrypt that and import the xprv shown inside for the duress wallet. You could also calculate the extended private key based on the seed or xprv of the real wallet. We use BIP32 subkey paths to derive the duress wallet, if m was your real wallet, the duress wallet will be found at:



2147431408 = 0x80000000 - 0xCC10

Problems with changing PIN codes on Duress Wallets

We want users (and thugs) who are logged-in with the duress PIN to have a complete wallet experience. However, there are some problems when they start to change PIN codes like a normal user would. If they try to change the main PIN code, we can detect that and change the "duress" PIN code instead. To them, it looks like the main PIN was changed successfully. However, if they try to change the duress PIN code, then we cannot allow that to work. For one thing, we have nowhere to store the new PIN code, nor a fake wallet to give them if they tried to use it. For this reason, if they login with the duress PIN, we always pretend like there is a duress wallet enabled already.

To change the duress PIN, requires the previous duress PIN, and we show the same error as if they entered the wrong old PIN.

If you are somehow facing an attacker who is willing to verify he has the real main PIN, it's possible that careful analysis of system responses will imply he's working with the duress PIN. (If you discover any sequences that reveal this easily, please tell us and we'll see if we can cover them up better.)

If this is a scenario that concerns you, you may be asked what the actual duress PIN is, while under duress. We suggest providing the "brickme" PIN in that case. Alternatively, you can say you set the duress PIN once, but have since forgotten it.

A related problem: if the duress PIN counts as a failure on the real PIN, it would be obvious when it's used. Therefore, we cannot bump the counters unless we know the PIN isn't the duress PIN.

Known limitation:

  • When you login with the duress PIN, the real PIN failure counter cannot (and should not be) reset. We suppress display of that count if we know the duress PIN was recently used.

Anti-Phishing Feature

We ask the user for a number of prefix digits from their PIN. In response, two words are shown. How do we get those words?

The string of prefix digits is hashed (using SHA256) and that digest is used as the message to be authenticated by a standard HMAC/SHA256 operation as defined by FIPS-198-1. The HMAC/SHA256 key is contained in the secure element and it performs the HMAC operation.

The 256-bit key for this HMAC is known only to that specific chip and doesn't exist anywhere else.

The 22 bits of that HMAC result are converted into two words from the BIP39 English wordlist. Because there are just two words to remember, we hope our users can memorize those words and use this a simple test that they are talking to their Coldcard, and not a doppelgänger.

There are about 4 million (2^22) possible word combinations, and so as long as your PIN prefix is kept secret, it should not be possible to display the correct words.

If an attacker did know your PIN prefix, and had access to your Coldcard wallet, they could enter the prefix and note the words displayed. From there, they could make a replacement Coldcard that captures the remaining digits, after displaying the correct two words. However, if the victim tries a few different PIN prefixes, they can protect themselves (limited only by their mental storage capacity for random words). You don't even have to use your real PIN prefix for testing against phishing... you can enter and cancel as many attempts before proceeding with your real PIN. (Personally, I plan to search until I find a memorable pair of words, like "angle burger" or "lazy goose").

As for exhaustive attacks, where all possible PIN prefixes from a particular subject Coldcard are to be captured, the bootloader implements some simple rate-limiting to limit the rate of extracting the words, and the attacker must work through that interface, since pairing secret is unknown. All of the results would need to be stored on-device if they tried to be exhaustive, but assuming a weaker 4-digit PIN prefix, that's only 30k of data. If this type of attack is your concern, we suggest using the longest possible PIN prefix.

For Mark 2 and earlier, we are rate-limiting this as follows: 150ms response time for first 10 values, then 2.5 seconds each for the next 15 (up to 25). At 25 tries, we crash the system and a power cycle will be required to continue. With about 9,999 combinations to cover all 4-digit prefixes, it would take between least 10 hours to generate all 4-digit prefixes. To achieve that, you would have to write custom firmware and get it onto the device. Any successful login resets the rate limiting, so normal users will never see the impact of this limiting.

For Mark 3 hardware, the rate is fixed to 2 seconds per value. See section below on how we assure this by performing multiple rounds of HMAC-SHA256.

How It Works

We've made some bold claims above. How can you be sure we implemented it as described?

First, please learn more about the secure element: the Microchip ATECC508A The full datasheet recently has been made public after being under NDA for years. To get further into our design, you will need to understand the chip's capabilities.

Unfortunately, the datasheet for the ATECC608A is still under NDA. There is nothing we can do about this, and we want the powerful new features enabled by this part. We have explained to Microchip why security by obscurity is bad idea, but they have business reasons.

Mark 2 and Earlier Key Layout

The 508a (Microchip ATECC508A) has 16 key slots. Each can be configured in numerous ways. The chip has two two high-endurance monotonic counters, which we use to track PIN attempts. Additionally, it has OTP memory and general flash storage that we aren't using. None of the Elliptic Curve features are being used in this project, although we have used that on the Opendime project.

The chip starts with a blank "configuration zone" which must be fully defined and locked forever before using the chip. The policy set in the configuration defines the relationships between the keys, and what data is public or private.

See stm32/bootloader/ to understand the contents of the configuration zone. That code establishes this set of keys:

pairing              1      Shared secret with bootloader code.
words                2      Random value used for anti-phishing feature
pin_1                3      PIN for main wallet
pin_2                4      PIN for secondary wallet
lastgood_1           5      Last successful login attempt (main PIN)
lastgood_2           6      Last successful login attempt (secondary PIN)
pin_3                7      Duress PIN for main wallet
pin_4                8      Duress PIN for secondary wallet
secret_1             9      Wallet seed (main): 72 bytes of ultra secrets
secret_2             10     Wallet seed (secondary): 72 bytes of ultra secrets
secret_3             11     Secret for duress (main)
secret_4             12     Secret for duress (secondary)
brickme              13     "Brickme" PIN storage
firmware             14     Hash of flash contents, controls red/green light

Key slots zero and 15 are reserved because of chip limitations.

Each key slot (aka. data slot) can be restricted for reading, writing and "authentication" by depending on another slot. So, for example, the secret_1 slot requires knowledge of the pairing secret (as the AuthKey) and then also knowledge of pin_1 slot before you can read or write (as ReadKey or WriteKey). Each of the PIN slots (1-4) unlocks the next corresponding secret_N slot for read/write of the secret. The lastgood slots are world readable, but need the correspond PIN to change.

Note that all keyslots require the pairing secret to do anything (ie. it is the AuthKey for those slots). The pairing secret itself is not readable, and can be changed only by the brickme PIN. We do this so that we can trash the secure element by "rolling" the pairing secret to a new value and then forgetting what that value is. (In fact we don't calculate the new key value, since we're being destructive.) It should be noted, that only key rolling is permitted (not general write), and so you need to know the previous value of the pairing secret in order to change it.

The 'firmware' key slot holds a hash value, and you must prove knowledge of that value to be able to turn the Genuine light green. Anyone can turn the light to red (it is unauthenticated) but you must know both the pairing secret and the existing value in keyslot 14 to turn the light green. We can capture and store a hash over the entire flash memory at any time, but update it, knowledge of the main PIN is needed.

You may confirm the above configuration details as the configuration zone is not read-protected and it can be read very easily. It can be done from Micropython level or your could connect directly to the chip.

Mark 3 Key Layout

The 608a (Microchip ATECC608A) also has 16 key slots. Unlike the 508a, one of those slots can be used to dynamically change the usage limit of another key. In Mark 3 we removed secondary wallet support, and use a new keyslot for key stretching.

pairing              1      Shared secret with bootloader code.
pin_strech           2      Random value used for anti-phishing feature and key stretching
main_pin             3      PIN for main wallet
lastgood             5      Last successful login attempt (main PIN)
match_count          6      Holds max value for counter0 (or else be a brick)
duress_pin           7      Duress PIN for main wallet
long_secret          8      Long secret: 416 bytes of secret value (new in Mark 3)
secret               9      Wallet seed (main): 72 bytes of ultra secrets
duress_secret        10     Wallet seed (duress): 72 bytes of fake secrets
duress_lastgood      11     Fake 'last good' counter updated when duress PIN used
brickme              13     "Brickme" PIN storage
firmware             14     Hash of flash contents, controls red/green light

The remainder of the background information above applies to the 608a as well.

"Knowledge of" Keys

In this document we say you "need knowledge of" a specific key to be able to do something. What that means in practice, is you have to complete this sequence:

  • pick 20 bytes of nonce (numin)
  • do a Nonce command, which takes that 20 bytes, and returns 32 bytes.
  • the 32 bytes you receive are a random value picked by the chip
  • take the 20 you provided, and 32 from the chip and hash them together to make a shared nonce value
  • take your knowledge of some key you think is in the chip, and hash it with the shared nonce
  • do a CheckMac command, which sends that MAC to the secure element, if it doesn't like the value because it doesn't match the value it calculated itself, then it fails.
  • once a CheckMac is done, you've proven you know the indicated key slot's value

Every key on the chip has been configured so that the above sequence must be completed with the pairing secret. After that, most slots need the same sequence to be repeated with another secret, such as the user-defined PIN. After two CheckMac sequences are done, you may be able to read or update a specific field. The actual read/write data may also be encrypted which involves XOR of the data against a hash generated by a similar sequence of steps, again with a random nonce involved (using the GenDig command).

The fun part of programming this chip is the constant values and other clever things they mix into the digests required at each step. A simple update can require a number of back and forthes, each time creating a new shared nonce. Specific parts of the chip's configuration zone, and the arguments to the specific command are often included in the hashes. Of course, every single bit must be correct or nothing works, and a meaningless error is returned.

Securely Rate-Limiting PIN Retries

In our system, we do not trust the main firmware with any secrets... at least until it proves it knows the PIN. To achieve this separation, the bootloader picks the pairing secret and keeps it secret. We use a hardware firewall feature of this chip: it monitors the internal memory bus, and if it sees an address inside the firewall range, it simply resets the entire chip. That firewall protects the entire bootloader section from any access from other code running on the chip, regardless of how that might happen.

The bootloader configures the firewall, and verifies flash-memory protections when it boots the system. During operation, the main firmware, written in Micropython, can make calls into the bootloader to communicate with the secure element, and do a few other functions. All access to the bootloader's firmware is done indirectly via this "callgate" which opens the firewall in a very limited way. Entry and exit from the callgate is handled carefully, and does things like wipe all the SRAM used by the bootloader with known values both on entry and exit.

In order for the main UI to test a PIN code, it must use the callgate, and the calls involved require a fairly complex call sequence: First, a setup step is needed that loads the retry counter and establishes how long a delay will be required before we will check the PIN attempt. Then the delay must be passed, by another call through the call gate. The delay is done inside the bootloader just so that we know how long it is, and so there is no way to bypass it. We do this in increments of 500ms because we want to maintain a nice display and UX during this potentially-long time period. After the timeout period is completed, we will attempt using the PIN value, and if it's right, provide the secret.

This sequence is implemented with a data structure that is signed by an HMAC generated by the bootloader. The HMAC includes both the pairing secret and also a unique value-per-boot to prevent replays.

Delay Policy (before Mark 3)

Here is the delay you'll be forced into based on the number of failed PIN attempts, since your last successful login:

Failures Forced Delay Between Attempts
1 ... 2 15 seconds
3 ... 4 1 minute
5 ... 9 5 minutes
10 ... 19 30 minutes
20 ... 49 2 hours
more 8 hours

In Mark 3, no extra delay is enforced, but a warning and confirmation is shown after 3 failed attempts, so that you don't burn through your limited attempts too causually. When all attempts have been consumed, the unit bricks itself and must be recycled as e-waste.


(before Mk3)

The main PIN holder can brute-force the secondary wallet's PIN because they can use the API for pin-change without rate limiting. (Some Micropython code would need to be written.) Similarly, the brickme and duress change-pin commands are not rate-limited, so if you have the main PIN (or secondary) you could brute-force the corresponding PIN codes.

Mk3 hardware does not have this weakness. There is no way to accelerate the PIN-attempts, nor to exceed the maximum number of them.

Changes for Mark 3

With the Mk3 hardware, introduced in 2019, we upgraded to the ATECC608A chip in place of the 508a as the secure element.

Because of changes to that part, we have the opportunity to improve Coldcard security as follows:

  • The limited-use counter is now connected to pin attempts inside the 608a chip. So, the 608a compares the number of PIN attempts, and if too many failures have occured, the secure element bricks itself.

  • Using a HMAC-SHA256 inside the chip, we create a HMAC chain using a secret known only to the secure element (and unique per Coldcard) as the HMAC key. The purpose of this is make each login attempt slow to perform. The previous delay/rate limit policy was removed, in favour of the delay enforced by this process.

  • Anti-phishing words are calculated with same HMAC-SHA256 chain, but with higher iteration count, and a different starting value.

  • An additional 416 bytes of secrets storage is enabled (in addition to 72 bytes of storage previously stored).

  • The secondary wallet feature had to be removed, because there is only one limited-use counter. Use BIP39 passphrases instead.

  • The secrets stored in the 608a are encrypted by a one-time pad held in the main micro. This is a defense against any unknown security issues which compromise the secrets in the 608a and not the main micro itself.

  • Successfully using the duress PIN will not cost an attempt on the real PIN. If the duress PIN works, we show zero login failures, but the number of PIN attempts has not actually been reset.

Rate Limiting Method

The user enters a short pin code and we need to convert that into a 32-byte value used to unlock the secrets. We want to understand the upper bounds on the rate at which those "pin attempts" can be done, even though on Mark 3, only a few attempts can be done before it bricks itself.

Here are the steps in pseudo code. We've also written it in Python to check our work, see stm32/bootloader/, and of course the code actually being used is written in 'C' and available in stm32/bootloader/{ae,pins}.[ch].

  • secret values (all 32-bytes long):

    pairing_secret - value shared between 608a and main micro pin_stretch - known only to the 608a pin_attempt - known only to the 608a: linked to usage counter

  • public values

    PURPOSE_NORMAL = hex('58184d33') PURPOSE_WORDS = hex('73676d2e') KDF_ITER_WORDS = 12 KDF_ITER_PIN = 8

  • steps:

    md = SHA256(SHA256(pairing_secret + PURPOSE_NORMAL + input_pin))

    repeat KDF_ITER_PIN times: md = HMAC_SHA256(pin_stretch, md)

    start = md md = HMAC_SHA256(pin_attempt, md)

    final = SHA256(pairing_secret + start + 0x04 + md)

  • for anti-phishing prefix words, the steps are:

    md = SHA256(SHA256(pairing_secret + PURPOSE_WORDS + pin_prefix))

    repeat KDF_ITER_WORDS times: md = HMAC_SHA256(pin_stretch, md)

    (result is upper 22 bits of md)

Of course we all heat our homes with fast SHA256 hashing chips... but the rate limiting factor here is the communication data rate, in and out of the secure element. That can't be avoided since only it knows the HMAC key being applied.

The 608a uses a unique single wire protocol: each bit is serialized as byte-patterns and sent half-duplex, at 230400 bps. To perform an HMAC, we have to unlock the pin_stretch keyslot, by performing a random challenge/response exchange on the pairing_secret slot, and then send the data to be HMAC'ed and read back the result. The resulting traffic looks like this:

  • Send: 1 (OP_Nonce) + 3 (p1, p2) + 20 (rand)
  • (8ms calculation time)
  • Receive: 32 bytes (rand from chip)
  • Send: 1 (OP_CheckMac) + 3 (p1, p2) + 88 (challenge response)
  • (11ms calculation time)
  • Receive: 1 (status)
  • Send: 1 (OP_SHA setup for HMAC) + 3 (p1, p2)
  • (1ms calculation time)
  • Receive: 1 (status)
  • Send: 1 (OP_SHA data + finalize) + 3 (p1, p2) + 32
  • (1ms calculation time)
  • Receive: 32 (result)

This is a total of 220 bytes (and there are some other delays and overhead, not shown). To send and/or receive 220 bytes takes 1760 bits, and at 230400 bps, this takes 8ms. The secure element isn't too fast with it's processing either, so it is adding at least 22ms (best case times, as documented). In total, looks like 30ms is best-case time to complete a single iteration of the stretching. For the main PIN, we are using 8 iterations, so max theoretical rate is about 240ms.

Unfortunately, what isn't shown above is all the SHA256 operations that are needed to do the above dance. Those are implemented in the main micro and are not very fast. As a result, the actual pin-entry delay is about 4 seconds (measured).

It's important to understand that all PIN attempts are further limited by a monotonically-increasing counter implemented in the secure element. Brute forcing PIN codes are also blocked by that process, which limits failed PIN attempts to just 13.

The rate-limiting is more important for the "anti-phsishing" prefix words, see discussion above. For that case, we use 16 iterations, and it runs at about 2 seconds realtime.