Skip to content

Commit 4cba0a6

Browse files
add REPRODUCIBILITY.md and check for just build-unsigned
1 parent 751bb7b commit 4cba0a6

File tree

2 files changed

+246
-1
lines changed

2 files changed

+246
-1
lines changed

REPRODUCIBILITY.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 Foundation Devices, Inc. <hello@foundation.xyz>
3+
SPDX-License-Identifier: GPL-3.0-or-later
4+
-->
5+
6+
# Reproducibility Guide
7+
8+
The instructions below describe how to easily build and verify Prime BLE firmware in a reproducible way.
9+
10+
Please note that this guide has been designed for Linux, so if you're running a different operating system the exact steps here may differ slightly. However, we've done our best to make them as portable as possible for other popular operating systems.
11+
12+
## What to Expect
13+
14+
In this guide we will outline the exact steps necessary to get set up, build firmware directly from the source code, and verify that it properly matches the published build hash and release binaries for any given version of Prime BLE's firmware. Once you've completed the steps outlined here, you'll have verified fully that the source code for a given version does indeed match the binaries we release to you. This ensures that nothing outside of the open-source code has been included in any given release, and that the released binaries are indeed built directly from the publicly available source code.
15+
16+
Security through transparency is the goal here, and firmware reproducibility is a key aspect of that!
17+
18+
## Setup
19+
20+
In order to build and verify the reproducibility of Prime BLE firmware, you will need to:
21+
22+
- Get the source code and checkout the correct tag
23+
- Install Nix package manager with flakes enabled
24+
- Set up the development environment using the flake
25+
- Build the reproducible binaries
26+
- Verify the binaries match the published build hash
27+
28+
We'll walk through every step above in this guide to ensure you can build and verify any version of Prime BLE's firmware easily.
29+
30+
### **Get the Source Code**
31+
32+
The instructions below assume you are installing into your home folder at `~/prime-ble-firmware`. You can choose to install to a different folder, and just update command paths appropriately.
33+
34+
**Important**: Before building, you must checkout the specific git tag you want to reproduce. This ensures you're building the exact same code as the published release.
35+
36+
```bash
37+
cd ~/
38+
git clone https://github.com/Foundation-Devices/prime-ble-firmware.git
39+
cd prime-ble-firmware
40+
git checkout app_v3.2.0 # Replace with the tag you want to reproduce
41+
```
42+
43+
### **Install Nix Package Manager**
44+
45+
Nix is a powerful package manager that provides reproducible builds and development environments. The reproducibility of Prime BLE firmware is guaranteed through the use of our Nix flake, which pins exact versions of all tools and dependencies.
46+
47+
#### Install Nix
48+
49+
Follow the official Nix installation guide for your operating system:
50+
51+
- [Official Nix Installation Guide](https://nixos.org/download.html)
52+
53+
For Linux and macOS, the recommended installer is:
54+
55+
```bash
56+
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
57+
```
58+
59+
#### Enable Flakes
60+
61+
After installing Nix, you need to enable experimental flakes features. Create or edit `~/.config/nix/nix.conf`:
62+
63+
```bash
64+
mkdir -p ~/.config/nix
65+
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
66+
```
67+
68+
Or, you can enable flakes temporarily by setting an environment variable:
69+
70+
```bash
71+
export NIX_CONFIG="experimental-features = nix-command flakes"
72+
```
73+
74+
### **Set Up Development Environment**
75+
76+
Once Nix is installed with flakes enabled, use the flake in this repository to set up the reproducible development environment:
77+
78+
```bash
79+
nix develop
80+
```
81+
82+
This command:
83+
- Downloads the exact toolchain versions specified in `flake.nix`
84+
- Sets up the development environment with all required tools
85+
- Ensures you're using the same toolchain as our official builds
86+
87+
The environment includes:
88+
- Rust toolchain with the exact version from `rust-toolchain.toml`
89+
- Build tools (cargo-binutils, gcc-arm-embedded, etc.)
90+
- Signing tools (cosign2)
91+
- Development utilities (just, git)
92+
93+
## **Building Prime BLE Firmware**
94+
95+
### Architecture and Toolchain Verification
96+
97+
Our build system includes automatic checks to ensure reproducibility:
98+
99+
1. **Architecture Check**: Verifies you're building on `x86_64` - this is required for official reproducible builds
100+
2. **Toolchain Check**: Verifies you're using the Nix-provided toolchain from the nix store
101+
102+
These checks are built into the `build-unsigned` command and will warn you if you're not using the correct environment.
103+
104+
### Build the Firmware
105+
106+
Now that we have everything in place, we can build the firmware binaries for Prime BLE with a simple command:
107+
108+
```bash
109+
just build-unsigned
110+
```
111+
112+
This command:
113+
1. Runs the architecture and toolchain verification checks
114+
2. Builds the bootloader and firmware in release mode
115+
3. Outputs the binaries to `BtPackage/` directory
116+
4. Displays binary sizes and flash usage information
117+
5. Shows the SHA256 hash of the built files for verification
118+
119+
The build process will take a few minutes as it compiles the bootloader and application from source. Once complete, you'll have:
120+
- `BtPackage/bootloader.bin` - The compiled bootloader
121+
- `BtPackage/BT_application.bin` - The compiled application
122+
123+
**Note**: This builds unsigned firmware. If you want to create a fully flashable package, you would need to sign the firmware using our cosign2 process, but for reproducibility verification, the unsigned binaries are sufficient.
124+
125+
## Verifying Prime BLE Firmware
126+
127+
### Verify Build Hash
128+
129+
To verify that the binary produced matches what you should see, you can calculate the SHA256 hash of the built files and compare it to the published hash in the GitHub release notes.
130+
131+
```bash
132+
# Calculate SHA256 hash of the built binaries
133+
sha256sum BtPackage/bootloader.bin BtPackage/BT_application.bin
134+
```
135+
136+
Example output:
137+
```
138+
a1b2c3d4e5f6... BtPackage/bootloader.bin
139+
f6e5d4c3b2a1... BtPackage/BT_application.bin
140+
```
141+
142+
Compare these hashes with the ones published in the GitHub release notes for the tag you checked out. If the hashes match, congratulations! You've successfully verified that the firmware you built exactly matches the source code published on GitHub.
143+
144+
If your hashes do not match for any reason, stop immediately and contact us at [hello@foundation.xyz](mailto:hello@foundation.xyz)! We'll help you investigate the cause of this discrepancy and get to the bottom of the issue.
145+
146+
## Reproducibility Guarantee
147+
148+
The reproducibility of Prime BLE firmware is guaranteed by:
149+
150+
1. **Nix Flakes**: The `flake.nix` file pins exact versions of all dependencies, ensuring the same tools are used every time
151+
2. **Architecture Verification**: Only `amd64` builds are considered official reproducible builds
152+
3. **Toolchain Verification**: The build system checks that the toolchain comes from the Nix store
153+
4. **Deterministic Builds**: The build process avoids timestamps and other non-deterministic factors
154+
155+
When you follow these steps exactly:
156+
- Using the same git tag
157+
- Using the Nix development environment
158+
- Building on amd64 architecture
159+
- Using the provided build commands
160+
161+
You will get bit-for-bit identical binaries every time, and identical to our official builds.
162+
163+
## Installing Firmware
164+
165+
For installing firmware on your device, please use the official signed binaries from the GitHub releases. The unsigned binaries you built are for verification purposes only.
166+
167+
## Conclusion
168+
169+
We want to close out this guide by thanking our fantastic community. Open source and the verifiability and transparency it brings are core to our ethos at Foundation, and the ability to reproducibly build firmware for Prime BLE is a core outpouring of that.
170+
171+
We can't wait to see more of our community take this additional step and remove a little more trust from the process by verifying that each build is reproducible.

xtask/src/main.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use cargo_metadata::MetadataCommand;
55
use clap::{Parser, Subcommand};
66
use consts::{BASE_APP_ADDR, BASE_BOOTLOADER_ADDR, SIGNATURE_HEADER_SIZE};
7-
use std::io::{Read, Write};
7+
use std::io::{self, Read, Write};
88
use std::path::{Path, PathBuf};
99
use std::process::{exit, Command, Stdio};
1010
use std::{env, fs};
@@ -668,6 +668,79 @@ fn print_bootloader_binary_size(binary_path: &Path, description: &str) {
668668
}
669669
}
670670

671+
fn check_toolchain_path() {
672+
tracing::info!("Checking toolchain path for reproducible build");
673+
674+
// Get current architecture
675+
let current_arch = env::consts::ARCH;
676+
677+
// Check if we're on amd64
678+
if current_arch != "x86_64" {
679+
tracing::warn!(
680+
"Current architecture is {}: not the official reproducible build target",
681+
current_arch
682+
);
683+
println!("⚠️ WARNING: This build is not the official reproducible build!");
684+
println!(" Current architecture: {}", current_arch);
685+
println!(" Expected architecture: x86_64");
686+
println!(" Builds on other architectures are not reproducible even with nix");
687+
688+
print!("Do you want to continue? (y/N): ");
689+
io::stdout().flush().unwrap();
690+
691+
let mut input = String::new();
692+
io::stdin().read_line(&mut input).unwrap();
693+
694+
let input = input.trim().to_lowercase();
695+
if input != "y" && input != "yes" {
696+
tracing::info!("Build cancelled by user");
697+
exit(0);
698+
}
699+
} else {
700+
tracing::info!("Architecture check passed: {}", current_arch);
701+
}
702+
703+
// Get the current rustc path
704+
let current_rustc = match Command::new("which").arg("rustc").output() {
705+
Ok(output) => {
706+
if output.status.success() {
707+
String::from_utf8_lossy(&output.stdout).trim().to_string()
708+
} else {
709+
tracing::error!("Could not find rustc path");
710+
exit(-1);
711+
}
712+
}
713+
Err(e) => {
714+
tracing::error!("Failed to run which command: {}", e);
715+
exit(-1);
716+
}
717+
};
718+
719+
// Check if we're in a nix environment by looking for nix store paths
720+
let is_nix_toolchain = current_rustc.contains("/nix/store/");
721+
722+
if !is_nix_toolchain {
723+
tracing::warn!("Current toolchain is not from nix store: {}", current_rustc);
724+
println!("⚠️ WARNING: This build is not the official reproducible build!");
725+
println!(" Current toolchain: {}", current_rustc);
726+
println!(" Expected toolchain should be from nix store for reproducible builds");
727+
728+
print!("Do you want to continue? (y/N): ");
729+
io::stdout().flush().unwrap();
730+
731+
let mut input = String::new();
732+
io::stdin().read_line(&mut input).unwrap();
733+
734+
let input = input.trim().to_lowercase();
735+
if input != "y" && input != "yes" {
736+
tracing::info!("Build cancelled by user");
737+
exit(0);
738+
}
739+
} else {
740+
tracing::info!("Toolchain path check passed: {}", current_rustc);
741+
}
742+
}
743+
671744
fn main() {
672745
// Adding some info tracing just for logging activity
673746
env::set_var("RUST_LOG", "info");
@@ -694,6 +767,7 @@ fn main() {
694767
build_bt_minimal_package();
695768
}
696769
Commands::BuildUnsigned => {
770+
check_toolchain_path();
697771
build_tools_check(args.verbose);
698772
build_bt_bootloader(args.verbose);
699773
build_bt_firmware(args.verbose);

0 commit comments

Comments
 (0)