I was able to successfully compile the firmware and verify the release but discovered some issues along the way. I documented the steps I took below (highlighting the differences from the instructions in bold) as it may help others:
Compile and Compare Instructions
Initial instructions are here: https://github.com/Blockstream/Jade/blob/master/REPRODUCIBLE.md
Below assumes docker is installed and user added to docker group (so "sudo docker" is not needed)
Compile the firmware from the source
- Clone Jade master repository here: https://github.com/Blockstream/Jade/tree/master
cd
git clone https://github.com/Blockstream/Jade.git
- Checkout release tag to be verified (https://github.com/Blockstream/Jade/tags)
cd Jade
git checkout 1.0.40
- Pull in missing dependances (change from instructions)
git submodule update --init --recursive
- Create docker image
DOCKER_BUILDKIT=1 docker build -f ./Dockerfile -t jade_builder .
- Run docker image (added rm option from instructions to delete container when done)
docker run --rm -v ${PWD}:/builds/blockstream/jade --name jade_builder -it jade_builder bash
- Prepare the environment inside docker container
(Note command ". /root/esp/esp-idf/export.sh" no longer works but is not needed)
cd /builds/blockstream/jade
git config --global --add safe.directory /builds/blockstream/jade
- Confirm "git describe" now works
- Generate config file for hardware / build type. From instructions:
| Jade Hardware Type |
Configuration |
Command |
| Jade 1.0 (true wheel) |
BLE-enabled |
./tools/switch_to.sh jade |
|
no-radio |
./tools/switch_to.sh jade --noradio |
| Jade 1.1 (rocker/jog-wheel) |
BLE-enabled |
./tools/switch_to.sh jade_v1_1 |
|
no-radio |
./tools/switch_to.sh jade_v1_1 --noradio |
| Jade 2.0 (two buttons) |
BLE-enabled |
./tools/switch_to.sh jade_v2 |
|
no-radio |
./tools/switch_to.sh jade_v2 --noradio |
For example, execute:
./tools/switch_to.sh jade_v2
- Build firmware (target will be here: ./build/jade.bin)
- Sign target and exit container (note signin key filename change from instructions)
espsecure.py sign_data --keyfile ./release/scripts/dev_fw_signing_key_A.pem --version 2 --output ./build/jade_signed.bin ./build/jade.bin
exit
- Compare signed and unsigned to understand signing block location
cd
xxd ./Jade/build/jade.bin ./jade.xxd
xxd ./Jade/build/jade_signed.bin ./jade_signed.xxd
diff -C 0 jade.xxd jade_signed.xxd
Diff shows only difference is added data end of file
Output snippet example:
*** jade.xxd 2026-08-17 10:03:54.425821995 -0500
--- jade_signed.xxd 2026-08-17 10:04:02.499384205 -0500
***************
*** 81920 ****
--- 81921,82176 ----
+ 00140000: e702 0000 0869 ac1f 08c1 b344 9ae3 b1f3 .....i.....D....
Note location of new data (e.g. lines 81921 to 82176 for above example).
Download official firmware to check vs compiled code.
- Get expected hashes from offical json
Use from HW table in instructions and note hw-target value
| Jade Hardware Type |
flag |
| Jade 1.0 (true wheel) |
--hw-target jade |
| Jade 1.1 (rocker/jog-wheel) |
--hw-target jade1.1 |
| Jade 2.0 (two buttons) |
--hw-target jade2.0 |
| Jade 2.0c (2.0, no camera/battery) |
--hw-target jade2.0c |
Display stable versions available and checksums (note URL has hw-target). For example:
curl https://jadefw.blockstream.com/bin/jade2.0/index.json | jq ".stable"
Copy built version info desired (e.g. jade2.0 above was the following for latest ble stable):
{
"filename": "1.0.40_ble_1314816_fw.bin",
"version": "1.0.40",
"config": "ble",
"fwsize": 1314816,
"cmphash": "6623dadf8090b3debc9e47902ab8d927109ccfa58662f7d5e6bde4b46fb8e64e",
"fwhash": "6c1a0f4e932bfb05b8876bb3e6a4a541b55377db75453999b346d6dad1bcfff9"
}
- Download firmware
Two options to download the firmware:
- Option 1: Download firmware directly from server using the above file name in step 12 (note hw-target is in URL)
curl -O https://jadefw.blockstream.com/bin/jade2.0/1.0.40_ble_1314816_fw.bin
- Option 2: Download firmware using jade_ota.py per instructions (Note latest jade_ota.py does not work)
Use 1.0.36 version of jade_ota.py to download firmware:
cd Jade
git checkout 1.0.36
docker run --rm -v ${PWD}:/builds/blockstream/jade --name jade_builder -it jade_builder bash
Run jade_ota.py (Note hw-target as described above in step 12):
cd /builds/blockstream/jade
python ./jade_ota.py --skipserial --skipble --write-compressed --download-firmware --release stable --hw-target jade2.0
Output:
The fw file will be downloaded/verified but the actual OTA will be skipped
Downloading firmware index file https://jadefw.blockstream.com/bin/jade2.0/index.json
Full firmwares
1) 1.0.40 - noradio
2) 1.0.40 - ble
-
Delta patches
-
Select firmware: 2
Downloading firmware https://jadefw.blockstream.com/bin/jade2.0/1.0.40_ble_1314816_fw.bin
Downloaded 892251 byte firmware
Downloaded file hash verified
Written file build/1.0.40_ble_1314816_fw.bin
Written file build/1.0.40_ble_1314816_fw.bin.hash
Got fw file of length 892251 with expected uncompressed final fw length 1314816
Final fw hash: 6c1a0f4e932bfb05b8876bb3e6a4a541b55377db75453999b346d6dad1bcfff9
Exit docker container:
Copy firmware into home directory:
cd
cp ./Jade/build/1.0.40_ble_1314816_fw.bin .
- After either Option 1 or Option 2, continue to the next step
- Compute compressed fm hash:
sha256sum 1.0.40_ble_1314816_fw.bin
6623dadf8090b3debc9e47902ab8d927109ccfa58662f7d5e6bde4b46fb8e64e 1.0.40_ble_1314816_fw.bin
Compare output with cmphash downloaded above in step 12
- Compute uncompressed hash
install pigz if needed per instructions
apt update && apt install pigz
Uncompress firmware
cp 1.0.40_ble_1314816_fw.bin downloaded.bin.gz
pigz -z -d downloaded.bin.gz
Compute uncompressed hash:
6c1a0f4e932bfb05b8876bb3e6a4a541b55377db75453999b346d6dad1bcfff9 downloaded.bin
Compare output with fwhash above in step 12
- Compare signed downloaded with unsigned compiled
xxd downloaded.bin downloaded.xxd
diff -C 0 jade.xxd downloaded.xxd
Example output snippet:
*** jade.xxd 2026-08-17 10:03:54.425821995 -0500
--- downloaded.xxd 2026-08-18 10:31:24.209831450 -0500
***************
*** 81920 ****
--- 81921,82176 ----
+ 00140000: e702 0000 0869 ac1f 08c1 b344 9ae3 b1f3 .....i.....D....
Diff shows new data at 81921 to 82176 (just like signed and unsigned diff above in step 11)
I was able to successfully compile the firmware and verify the release but discovered some issues along the way. I documented the steps I took below (highlighting the differences from the instructions in bold) as it may help others:
Compile and Compare Instructions
Initial instructions are here: https://github.com/Blockstream/Jade/blob/master/REPRODUCIBLE.md
Below assumes docker is installed and user added to docker group (so "sudo docker" is not needed)
Compile the firmware from the source
(Note command ". /root/esp/esp-idf/export.sh" no longer works but is not needed)
1.0.40For example, execute:
Diff shows only difference is added data end of file
Output snippet example:
Note location of new data (e.g. lines 81921 to 82176 for above example).
Download official firmware to check vs compiled code.
Use from HW table in instructions and note hw-target value
Display stable versions available and checksums (note URL has hw-target). For example:
Copy built version info desired (e.g. jade2.0 above was the following for latest ble stable):
Two options to download the firmware:
Use 1.0.36 version of jade_ota.py to download firmware:
Run jade_ota.py (Note hw-target as described above in step 12):
Output:
Exit docker container:
Copy firmware into home directory:
6623dadf8090b3debc9e47902ab8d927109ccfa58662f7d5e6bde4b46fb8e64e 1.0.40_ble_1314816_fw.binCompare output with cmphash downloaded above in step 12
install pigz if needed per instructions
Uncompress firmware
Compute uncompressed hash:
6c1a0f4e932bfb05b8876bb3e6a4a541b55377db75453999b346d6dad1bcfff9 downloaded.binCompare output with fwhash above in step 12
Example output snippet:
Diff shows new data at 81921 to 82176 (just like signed and unsigned diff above in step 11)