build: automatic block height update
What changed, and why it matters
This commit automates how the COLDCARD firmware records the latest Bitcoin block height during the build process. It fetches the current block height from two public Bitcoin explorer websites (mempool.space and blockstream.info) and stores it in a generated source file. That value is then used as a minimum block height for some COLDCARD security feature (likely related to 'SSSP/CCC'). The change itself is a build tooling update, not a fix for a known vulnerability, but it introduces a new external network dependency during firmware builds.
Review whether build-time fetching of a security-relevant consensus parameter from third-party APIs is acceptable for the threat model. Consider adding redundancy, HTTPS certificate pinning, signature verification, or using a locally trusted Bitcoin node. Ensure the build remains deterministic and reproducible, and document the new dependency for auditors and reproducible-build verifiers.
Security signals we found
New build-time network dependency fetching data from two third-party APIs
Fetched value is compiled into firmware and used as a consensus/security parameter (ccc_min_block)
No cryptographic verification of the fetched block height (HTTP only, no signature checks)
Makefile auto-commits the generated file when it changes
Potential for build non-determinism or supply-chain influence if API endpoints are compromised or return inconsistent data
Evidence from the diff
The commit adds shared/block_height.py (auto-generated constant BLOCK_HEIGHT = 932301), stm32/make_block_height.py (build script that queries mempool.space and blockstream.info APIs, compares results, and updates the constant), and wires the script into the Makefile release targets. shared/chains.py now imports BLOCK_HEIGHT and uses it for BitcoinMain.ccc_min_block instead of a hard-coded value. The script exits with code 1 when it updates the file, triggering a git commit in the Makefile. There are also minor typo fixes in stm32/make_filetime.py.
Changed components
COLDCARD firmware build systemshared/chains.py (BitcoinMain.ccc_min_block)shared/block_height.pystm32/make_block_height.pystm32/shared.mkInspect captured patch +110 / −6
diff --git a/shared/block_height.py b/shared/block_height.py
new file mode 100644
index 0000000..cc57e64
--- /dev/null
+++ b/shared/block_height.py
@@ -0,0 +1,9 @@
+# (c) Copyright 2026 by Coinkite Inc. This file is covered by license found in COPYING-CC.
+#
+# AUTO-generated.
+#
+# Updated: 2026-06-19 14:13:23 UTC
+
+BLOCK_HEIGHT = 932301
+
+# EOF
diff --git a/shared/chains.py b/shared/chains.py
index 67b4060..1531b74 100644
--- a/shared/chains.py
+++ b/shared/chains.py
@@ -8,6 +8,7 @@ from ubinascii import hexlify as b2a_hex
from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2TR
from public_constants import AF_P2SH, AF_P2WSH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH
from public_constants import AFC_PUBKEY, AFC_SEGWIT, AFC_BECH32, AFC_SCRIPT
+from block_height import BLOCK_HEIGHT
from serializations import hash160, ser_compact_size, disassemble
from ucollections import namedtuple
from opcodes import OP_RETURN, OP_1, OP_16
@@ -297,7 +298,7 @@ class BitcoinMain(ChainsBase):
# see <https://github.com/bitcoin/bitcoin/blob/master/src/chainparams.cpp#L140>
ctype = 'BTC'
name = 'Bitcoin Mainnet'
- ccc_min_block = 939464 # Mar 5/2026
+ ccc_min_block = BLOCK_HEIGHT
slip132 = {
AF_CLASSIC: Slip132Version(0x0488B21E, 0x0488ADE4, 'x'),
diff --git a/shared/manifest.py b/shared/manifest.py
index a2cc842..d8fa284 100644
--- a/shared/manifest.py
+++ b/shared/manifest.py
@@ -6,6 +6,7 @@ freeze_as_mpy('', [
'address_explorer.py',
'auth.py',
'backups.py',
+ 'block_height.py',
'callgate.py',
'ccc.py',
'chains.py',
diff --git a/stm32/make_block_height.py b/stm32/make_block_height.py
new file mode 100644
index 0000000..6dd75ca
--- /dev/null
+++ b/stm32/make_block_height.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+#
+# (c) Copyright 2026 by Coinkite Inc. This file is covered by license found in COPYING-CC.
+#
+# Capture current (mainnet) block height for SSSP/CCC features
+#
+import sys, time, datetime
+import urllib.request
+
+
+FILE_NAME = "../shared/block_height.py"
+
+
+def _get_block_height(url):
+ with urllib.request.urlopen(url) as response:
+ height_data = response.read().decode().strip()
+ return int(height_data)
+
+
+def get_block_height(url):
+ try:
+ return _get_block_height(url)
+ except:
+ time.sleep(2)
+ return _get_block_height(url)
+
+
+def parse_block_height_file():
+ with open(FILE_NAME, "r") as f:
+ for l in f.readlines():
+ if l.startswith("BLOCK_HEIGHT ="):
+ return int(l.split("=")[-1].strip())
+
+ return None
+
+
+def write_block_height_file(block_height):
+ now = datetime.datetime.now(datetime.timezone.utc)
+ with open(FILE_NAME, "wt") as f:
+ f.write('''\
+# (c) Copyright 2026 by Coinkite Inc. This file is covered by license found in COPYING-CC.
+#
+# AUTO-generated.
+#
+
+# As of %s UTC
+BLOCK_HEIGHT = %d
+
+# EOF
+''' % (now.strftime("%Y-%m-%d %H:%M:%S"), block_height))
+
+
+def main():
+ current_height = None
+ for _ in range(2):
+ bh_a = get_block_height("https://mempool.space/api/blocks/tip/height")
+ bh_b = get_block_height("https://blockstream.info/api/blocks/tip/height")
+ if bh_a == bh_b:
+ current_height = bh_a
+ break
+
+ time.sleep(5)
+
+ if current_height is None:
+ raise RuntimeError("Could not get current block height")
+
+ file_block_height = parse_block_height_file()
+ if file_block_height is None:
+ raise RuntimeError("Could not parse block height from file")
+
+ if current_height > file_block_height:
+ write_block_height_file(current_height)
+ sys.exit(1)
+ else:
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
+
+# EOF
diff --git a/stm32/make_filetime.py b/stm32/make_filetime.py
index f795dc8..6ab9dfe 100755
--- a/stm32/make_filetime.py
+++ b/stm32/make_filetime.py
@@ -5,14 +5,14 @@
# Capture build time and version number into a number used as the timestamp on
# all created files for that Coldcard version.
#
-import os, sys, time, datetime
+import os, sys, datetime
out_fname, version = sys.argv[1:]
assert out_fname.endswith('.c'), out_fname
if os.path.exists(out_fname):
- # to help deterministic builds, don't replace the file from git if verison # is right
+ # to help deterministic builds, don't replace the file from git if version # is right
with open(out_fname, 'rt') as fd:
if ('// version: %s\n' % version) in fd.read():
print("==> %s already version %s; not changing it" % (out_fname, version))
@@ -22,7 +22,7 @@ if os.path.exists(out_fname):
today = datetime.date.today()
value = ((today.year - 1980) << 25) | (today.month << 21) | (today.day << 16)
-# only 2second resolution for times, so can only support minor verion up to x.x.5 and hard to see
+# only 2second resolution for times, so can only support minor version up to x.x.5 and hard to see
# anyway, let's omit ... worst case, use the date instead
ver = ''.join(v for v in version if v in '0123456789.') # strip letter codes from end
h, m, _ = [int(x) for x in ver.split('b')[0].split('.')]
diff --git a/stm32/shared.mk b/stm32/shared.mk
index 6bf29a3..e0e03d2 100644
--- a/stm32/shared.mk
+++ b/stm32/shared.mk
@@ -82,6 +82,18 @@ $(BOARD)/file_time.c: make_filetime.py *-Makefile shared.mk
./make_filetime.py $(BOARD)/file_time.c $(VERSION_STRING)
cp $(BOARD)/file_time.c .
+
+.PHONY: block_height
+
+block_height:
+ @python3 make_block_height.py; \
+ if [ $$? -eq 0 ]; then \
+ echo "Block Height file already up-to-date."; \
+ else \
+ echo "Block Height file updated."; \
+ git commit -m "update block height" ../shared/block_height.py; \
+ fi
+
# Make a factory release: using key #1
# - when executed in a repro w/o the required key, it defaults to key zero
# - and that's what happens inside the Docker build
@@ -91,7 +103,7 @@ production.bin: firmware-signed.bin Makefile
SUBMAKE = $(MAKE) -f $(PARENT_MKFILE)
.PHONY: release
-release: submods-match code-committed
+release: submods-match code-committed block_height
$(SUBMAKE) clean
$(SUBMAKE) repro
test -f built/production.bin
@@ -118,7 +130,7 @@ rc1:
rc2: RC2_TIMESTAMP = $(shell date "+%F_%H%M")
rc2: RC2_FNAME = ./RC2-$(RC2_TIMESTAMP)-$(HW_MODEL)-coldcard.dfu
rc2: RC2_FNAME_FACT = ./RC2-$(RC2_TIMESTAMP)-$(HW_MODEL)-factory.dfu
-rc2: submods-match code-committed
+rc2: submods-match code-committed block_height
$(SUBMAKE) clean
$(SUBMAKE) repro
test -f built/production.bin
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.