reproducible builds
feature request: reproducible builds
https://github.com/OneKeyHQ/firmware/issues/17 was imo wasn't particularly well handled to address the concern raised.
This resulted in a "not verifiable" rating on https://walletscrutiny.com/hardware/onekey/ (which I am not affiliated with ).
https://help.onekey.so/hc/en-us/articles/6113121891599 doesn't clearly state "reproducible builds" or that users should get the same hash after complication.
All our code is open source, you can compile and generate your own binaries, and we will add HASH values in future versions.
Are binaries byte for byte deterministically reproducible, resulting in the same hash values for official and user-made builds, aka reproducible builds?
references:
- https://en.wikipedia.org/wiki/Reproducible_builds
- https://reproducible-builds.org/
Hi @adrelanos. Thanks for the clear description of the problem.
All of our official firmware released to users is released by github actions, so can the compilation process be more clearly defined through the script executed each time?
For example, taking classic as an example, you can see the CI script for each execution compilation here. https://github.com/OneKeyHQ/firmware/blob/touch/.github/workflows/build-classic.yml.We use the ubuntu 20.04 version, after pulling the corresponding branch code, set five environment variables, BOOT_VERSION, FIRMWARE_VERSION, BUILD_DATE, SHORT_HASH, ARTIFACTS_URL, and then install the nixos into CI server, and running the scripts, so that you can get the release target under legacy folder.
Similarly, you can find all the firmware information we have released so far in this script location:
- classic: https://github.com/OneKeyHQ/firmware/blob/touch/.github/workflows/build-classic.yml
- mini: https://github.com/OneKeyHQ/firmware/blob/touch/.github/workflows/build-mini.yml
- touch: https://github.com/OneKeyHQ/firmware/blob/touch/.github/workflows/build-touch.yml
For external users, the last step of the script Notify to Slack may fail, but it does not affect the content of the compilation result. You can still download the compilation result in the task corresponding to the github action link.
Are the steps to reproduce compilation clear?
Hello,
thank you for your reply!
This isn't a feature request on how to reproduce compilation. This isn't "how do I compile" question.
The feature request is "reproducible builds".
You compile it, you get hash "A". I compile it, I get hash "B". Another user compiles it, and gets hash "C". That's an issue. That's the legacy way to do it.
How to proof that the source code that was claimed to use for compilation is the actual source code used for compilation? For that a feature called "reproducible builds" is required.
Bitcoin Core is fully reproducible. If original upstream developers compile it or any other user from the same source code, the resulting binary will have the very same hash. This proves that there's no extra hidden source code or other weirdness / differences in the official binaries.
Debian, Qubes OS and many others are also working on reproducible builds.
https://binarywatch.org/ shows some other crypto currency software and hardware wallets which are already reproducible.
If you build official builds in CI versus locally is kinda unrelated. Ideally, you would compile in a few different environments (since weird things such as different file systems, operating systems, system clock, timezones can lead to different hashes of the final binary). Once you're confident you're getting the same hash everywhere, upload the build and the hash. In short:
- upload an official binary and it's hash ("
sha512sums /path/to/binary") - explain how any third-party can locally compile and get the very same hash
This might (easily) only be possible prior signing of the binaries in case you're using embedded signatures. So first step would be making it possible for unsigned binaries. Later, some solution for signatures could be found, but these are details for later.
Once accomplished, you can advertise supporting reproducible builds which is / is becoming an industry standard and a very good security feature, already supported by many similar projects.
The reason for using nixOS is to compile exactly the same results on different platforms as much as possible. Of course, our official version is released by ubuntu. Regarding whether other systems and various versions are consistent, we need to use more CI server or a third-party server for compilation verification.
Our official binary is all here: https://github.com/OneKeyHQ/firmware/releases
For the hash problem of each version mentioned in it, this can be provided. But on the other hand, it is also very clear at the end of this tutorial.https://help.onekey.so/hc/en-us/articles/6113121891599 In fact, use the command tail -c +1024 /path/to/bin | shasum -a 256 to remove our official signature and self-compiled The results are verified. At present, I understand that all the information in this wiki fully meets your verification needs, and can be verified and compared. Of course, I will also supplement the documentation based on what you said you want to do.
The reason for using nixOS is to compile exactly the same results on different platforms as much as possible.
That's good.
Of course, our official version is released by ubuntu.
That's also OK.
Regarding whether other systems and various versions are consistent, we need to use more CI server or a third-party server for compilation verification.
That's a commendable goal, though might be above on beyond the emerging industry standard. For example Debian's "hello" package is "only" reproducible on Debian. Bitcoin Core was previously "only" reproducible with gitian and nowadays "only" with Guix.
To be fair and only make a realistic feature request, reproduciblity needs to be supported only on 1 operating system of your choice so you can checkmark and advertise the "reproducible builds are supported" security feature.
Ideally compilation is done on an operating system which itself is or aspires to be reproducible in the future such as Debian, but even that is optional and not a requirement in any definition that I've seen yet.
As far as I understand reproducible builds,
- when building on nixOS, everyone gets the same hash.
- Or of building on Ubuntu, everyone gets the same has.
- But someone building on Ubuntu and then expecting to get the same hash as on nixOS might be asking for too much.
So in summary, if official versions are built on Ubuntu, then it would be sufficient to have instructions what has is expected and how to reproduce that. Doing the same with nixOS would be optional.
In fact, if you only want to verify that the hashes of our officially released Ubuntu version are consistent, you only need to follow up our officially released action.
Take our newly released classic v3.0.0 firmware as an example.
- github release: https://github.com/OneKeyHQ/firmware/releases/tag/classic%2Fv3.0.0
- github action link: https://github.com/OneKeyHQ/firmware/actions/runs/4943501853
If you want to compile it, you need to be as consistent as possible with our CI environment. The execution environment of CI is completely transparent. You can follow the log of the internal compilation process to see to all information.
Preparation
- Ubuntu 22.04
Compilation process
- Checkout
0bf60dd95dd2a9c3bed40ea07582682e741fc322commit code as base - Set environment variables
- Running scripts and Install compilation dependencies (like libprotobuf-lite17_3.6.1.3-2ubuntu5.2_amd64.deb & libprotoc17_3.6.1.3-2ubuntu5.2_amd64.deb)
- Upload artifacts, so that you can download it
All open source code and open source firmware are compiled to make the release more transparent. You can see all the installed dependency versions and installation process before the release in the log of this github action. If you use the same code hash, the same environment, and the same version of all dependencies (such as executing the same github action multiple times on the same code hash), then the final content must be the same result.
If you use the same code hash, the same environment, and the same version of all dependencies (such as executing the same github action multiple times on the same code hash), then the final content must be the same result.
That's very good! I am taking your word for it. In that case, you can advertise on your website that you support reproducible builds and perhaps encourage the community doing reproduction.
You might get added on websites such as walletscrutiny.com and/or BinaryWatch.org. (Which I am not affiliated with.)
A few detail enhancements. Users could if it was documented how to do so...:
- rebuild their own firmware from source code
- compare with your official unsigned firmware hash
- attach your official embedded signature (extracted from your official build)
- verify that the checksum of the user build (now with the appended official signature) matches the official signed build
- flash the firmware
I followed the instructions and did the same thing as the configured Github Actions using a bash script. Here is the script I used:
#!/bin/bash
### provide this script with the version without "v" and device type (classic, mini)
### and short release date (mmdd like 0511) which is the suffix of the released binary
version=$1
type=$2
short_release_date=$3
if [[ $type == mini ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./legacy/bootloader/version.h)"
build_script="nix-shell --run \"poetry run ./legacy/script/setup\"
nix-shell --run \"export ONEKEY_MINI=1 && poetry run ./legacy/script/cibuild\"
cp ./legacy/firmware/${type}*Stable*.bin /firmware"
elif [[ $type == classic ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./legacy/bootloader/version.h)"
build_script="nix-shell --run \"poetry run ./legacy/script/setup\"
nix-shell --run \"poetry run ./legacy/script/cibuild\"
cp ./legacy/firmware/${type}*Stable*.bin /firmware"
elif [[ $type == touch ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./core/embed/bootloader/version.h)"
build_script="git submodule update --init --recursive
nix-shell --run \"poetry run make -C core build_boardloader\"
nix-shell --run \"poetry run make -C core build_bootloader\"
nix-shell --run \"poetry run make -C core build_firmware\"
nix-shell --run \"poetry run core/tools/headertool.py -h core/build/firmware/touch*Stable*.bin\"
cp ./core/build/firmware/${type}*Stable*.bin /firmware"
fi
rm -rf /tmp/onekey_firmware
mkdir /tmp/onekey_firmware
chmod 777 /tmp/onekey_firmware
cd /tmp/onekey_firmware
podman run --rm -v ${PWD}:/firmware ubuntu:20.04 bash -x -c "
apt update
apt -y upgrade
apt install -y curl xz-utils sudo git wget g++
useradd -m nixuser
groupadd -r nixbld
usermod -aG nixbld nixuser
mkdir /nix
install -d -m755 -o \$(id nixuser -u) -g \$(id nixuser -g) /nix
sudo -H -u nixuser bash -c -x 'sh <(curl -L https://nixos.org/nix/install) --no-daemon
. ~/.nix-profile/etc/profile.d/nix.sh
cd ~
git clone https://github.com/OneKeyHQ/firmware
cd firmware
git checkout ${type}/v${version}
$boot_version_variable
FIRMWARE_VERSION=${version}
BUILD_DATE=\$(git --no-pager log -1 --format=%cd --date=format:\"%Y%m%d\" ${type}/v${version})
SHORT_HASH=\$(git rev-parse --short HEAD)
PRODUCTION=1
nix-shell --run \"poetry install\"
$build_script
cd /firmware
wget -O \"downloaded-firmware.bin\" \
\"https://github.com/OneKeyHQ/firmware/releases/download/${type}%2Fv${version}/${type}.${version}-Stable-${short_release_date}-\${SHORT_HASH:0:-2}.signed.bin\"'"
sha256sum *
Run the script:
$ ./onekey.sh 3.0.0 classic 0511
The result of sha256sum would be:
1b0e3382adc909fd85b2225f83cf6cd3886e73ff1bf2901ed8ccb1a6414366fd classic.3.0.0-Stable-0511-0bf60dd.bin
a5d4ac8b98c1249f839fba018850df7deb66a3720f13a01c5d94250e426a0a71 downloaded-firmware.bin
I transformed the binaries to hex format to see the diff and expected to only see a signature difference. But I got too many lines of diff (hundreds of lines). You can try it by:
$ xxd classic.3.0.0-Stable-0511-0bf60dd.bin > classic.3.0.0-Stable-0511-0bf60dd.hex
$ xxd downloaded-firmware.bin > downloaded-firmware.hex
$ diff classic.3.0.0-Stable-0511-0bf60dd.hex downloaded-firmware.hex
I ran the same script for mini firmware:
$ ./onekey.sh 3.0.0 mini 0518
...
88b76f05d95e6718d0bf3d4dabb12cf2403cfed91c351008441fe2a33b1cd9ae downloaded-firmware.bin
c67ebca150ff32c38f4115fee198855f8129e2af1015fecab789817e99ee0799 mini.3.0.0-Stable-0511-b860d10.bin
Again transformed the binaries to hex to see a diff:
$ xxd downloaded-firmware.bin > downloaded-firmware.hex
$ xxd mini.3.0.0-Stable-0511-b860d10.bin > mini.3.0.0-Stable-0511-b860d10.hex
$ diff mini.3.0.0-Stable-0511-b860d10.hex downloaded-firmware.hex
the diff for Mini version was much less than Classic but it's still too much to say the firmware is reproducible. The same happens for Touch firmware.
Please check the script and let me know if I did something wrong or fix the reproducibility problem. We would be happy to mark OneKey device firmwares as reproducible in Walletscrutiny.
- Use the following script
#!/usr/bin/env bash
set -e -o pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
REPOSITORY="https://github.com/OneKeyHQ/onekey-firmware.git"
CONTAINER_NAME="onekey-build"
FAMILY=""
BRANCH=""
TAG=""
BUILD_DATE=""
USER=$(stat -c "%u" . 2>/dev/null || stat -f "%u" .)
GROUP=$(stat -c "%g" . 2>/dev/null || stat -f "%g" .)
DIR=$(pwd)
function print_usage() {
echo "Usage: ./build-docker.sh -f <family name> -t <tag>"
echo " -f family name <touch|mini|classic>"
echo " -t tag"
echo " -d date"
echo "example:"
echo " ./build-docker.sh -f touch -t touch/v4.6.0 -d 2023-09-17"
echo " ./build-docker.sh -f mini -t mini/v3.4.0 -d 2023-09-17"
echo " ./build-docker.sh -f classic -t classic/v3.4.0 -d 2023-09-17"
}
function build_touch() {
mkdir -p build/touch
PULL_SCRIPT_NAME=".nix.sh"
cat <<EOF > "build/$PULL_SCRIPT_NAME"
cd /tmp
git clone "$REPOSITORY" onekey-firmware
cd onekey-firmware
git checkout $TAG
git submodule update --init --recursive
/root/.nix-profile/bin/nix-shell --run "poetry install"
/root/.nix-profile/bin/nix-shell --run "export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 && export FAKETIME='$BUILD_DATE' && export PRODUCTION=1 && poetry run make -C core build_embed"
cp ./core/build/firmware/touch*Stable*.bin /local/build/touch
EOF
echo
echo ">>> DOCKER RUN build touch"
echo
echo "tag $TAG"
echo
docker run -it --network=host --rm \
-v "$DIR:/local" \
-v "$DIR/build":/build:z \
--init \
--user root \
"$CONTAINER_NAME" \
bash /local/build/$PULL_SCRIPT_NAME
}
function build_mini() {
mkdir -p build/mini
PULL_SCRIPT_NAME=".pull.sh"
cat <<EOF > "build/$PULL_SCRIPT_NAME"
cd /tmp
git clone -b mini "$REPOSITORY" onekey-firmware
cd onekey-firmware
git checkout $TAG
/root/.nix-profile/bin/nix-shell --run "poetry install"
/root/.nix-profile/bin/nix-shell --run "poetry run ./legacy/script/setup"
/root/.nix-profile/bin/nix-shell --run "export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 && export FAKETIME='$BUILD_DATE' && export ONEKEY_MINI=1 && poetry run ./legacy/script/cibuild"
cp ./legacy/firmware/mini*Stable*.bin /local/build/mini
EOF
echo
echo ">>> DOCKER RUN build mini"
echo
echo "tag $TAG"
echo
docker run -it --network=host --rm \
-v "$DIR:/local" \
-v "$DIR/build":/build:z \
--init \
--user root \
"$CONTAINER_NAME" \
bash /local/build/$PULL_SCRIPT_NAME
}
function build_classic() {
mkdir -p build/classic
PULL_SCRIPT_NAME=".pull.sh"
cat <<EOF > "build/$PULL_SCRIPT_NAME"
set -e -o pipefail
cd /tmp
git clone -b bixin_dev "$REPOSITORY" onekey-firmware
cd onekey-firmware
git checkout $TAG
/root/.nix-profile/bin/nix-shell --run "poetry install"
/root/.nix-profile/bin/nix-shell --run "poetry run ./legacy/script/setup"
/root/.nix-profile/bin/nix-shell --run "export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 && export FAKETIME='$BUILD_DATE' && poetry run ./legacy/script/cibuild"
cp ./legacy/firmware/classic*Stable*.bin /local/build/classic
EOF
echo
echo ">>> DOCKER RUN build classic"
echo
echo "tag $TAG"
echo
docker run -it --network=host --rm \
-v "$DIR:/local" \
-v "$DIR/build":/build:z \
--init \
--user root \
"$CONTAINER_NAME" \
bash /local/build/$PULL_SCRIPT_NAME
}
# -f : Family Name
# -t : Tag
# -d : Build Date
while getopts 'f:t:d:' OPT; do
case $OPT in
f)
FAMILY="$OPTARG";;
t)
TAG="$OPTARG";;
d)
BUILD_DATE="$OPTARG";;
?)
print_usage
exit -1;;
esac
done
if [ "$TAG" == "" ]; then
echo "Invalid tag."
print_usage
exit -1
fi
if [ "$BUILD_DATE" == "" ]; then
echo "Invalid build date."
print_usage
exit -1
fi
BUILD_DATE="$BUILD_DATE 23:30:00"
if [ "$FAMILY" == "touch" ]; then
build_touch
elif [ "$FAMILY" == "mini" ]; then
build_mini
elif [ "$FAMILY" == "classic" ]; then
build_classic
else
echo "Invalid family name $IN_IMAGE."
print_usage
exit -1
fi
exit 0
- The docker image can be rebuilt with the following Dockerfile command
FROM ubuntu:focal
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --no-install-recommends -y git wget make autoconf automake libtool curl make g++ unzip scons llvm-dev libclang-dev clang libsdl2-dev libsdl2-image-dev xz-utils vim ca-certificates && apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -m 0755 /nix && groupadd -r nixbld && chown root /nix \
&& for n in $(seq 1 10); do useradd -c "Nix build user $n" -d /var/empty -g nixbld -G nixbld -M -N -r -s "$(command -v nologin)" "nixbld$n"; done
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -o pipefail && curl -L https://nixos.org/nix/install | bash
ENV USER=root
RUN . "$HOME/.nix-profile/etc/profile.d/nix.sh"
RUN echo "source $HOME/.nix-profile/etc/profile.d/nix.sh" >> "$HOME/.bashrc"
RUN wget --no-check-certificate https://github.com/wolfcw/libfaketime/archive/refs/tags/v0.9.10.tar.gz
RUN tar xf v0.9.10.tar.gz && cd libfaketime-0.9.10/ && make && make install && rm -r libfaketime-0.9.10 && rm v0.9.10.tar.gz
- Reproducible build
build-docker.sh -f classic -t classic/v3.4.0 -d 2023-09-17
build-docker.sh -f mini -t mini/v3.4.0 -d 2023-09-17
build-docker.sh -f touch -t touch/v4.6.0 -d 2023-09-17
- Verifying
tail -c +1024 classic.3.4.0-Stable-0917-d38a468.signed.bin | shasum -a 256
tail -c +1024 classic.3.4.0-Stable-0917-d38a468.bin | shasum -a 256
The default architecture of Github Actions is x64, make sure that the rebuilt hosts are on that architecture too!
@lyxyx Thank you, I will inform the WS team to try it.
docker build fails:
Dockerfile should be:
FROM ubuntu:focal
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --no-install-recommends -y git wget make autoconf automake libtool curl make g++ unzip scons llvm-dev libclang-dev clang libsdl2-dev libsdl2-image-dev xz-utils vim ca-certificates && apt-get clean && rm -rf /var/lib/apt/lists/* \
&& mkdir -m 0755 /nix && groupadd -r nixbld && chown root /nix \
&& for n in $(seq 1 10); do useradd -c "Nix build user $n" -d /var/empty -g nixbld -G nixbld -M -N -r -s "$(command -v nologin)" "nixbld$n"; done
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -o pipefail && curl -L https://nixos.org/nix/install | bash
ENV USER=root
RUN . "$HOME/.nix-profile/etc/profile.d/nix.sh"
RUN echo "source $HOME/.nix-profile/etc/profile.d/nix.sh" >> "$HOME/.bashrc"
RUN wget --no-check-certificate https://github.com/wolfcw/libfaketime/archive/refs/tags/v0.9.10.tar.gz && \
tar xf v0.9.10.tar.gz && \
cd libfaketime-0.9.10 && \
make && \
make install && \
cd .. && \
rm -rf libfaketime-0.9.10 v0.9.10.tar.gz
Disclaimer: I'm just the WS intern trying to learn how to do this.
Currently stuck with libfaketime errors
Trying again.
I'm getting the same recursion. Assuming that @xrviv used the exact script, I'll not dig through my minor refactoring I did for readability.
The error:
building '/nix/store/76760pd0afb8cs96chvn6r5a817spmkw-rust-minimal-1.70.0-nightly-2023-04-14-bin.drv'...
Creating virtualenv trezor-firmware in /tmp/onekey-firmware/.venv
Installing dependencies from lock file
Setting `experimental.new-installer` to false is deprecated and slated for removal in an upcoming minor release.
(Despite of the setting's name the new installer is not experimental!)
Package operations: 86 installs, 1 update, 0 removals
...
- Installing trezor (0.13.6 /tmp/onekey-firmware/python)
- Installing vulture (2.6)
- Installing yamllint (1.26.3)
make: Entering directory '/tmp/onekey-firmware/core'
scons -Q -j 4 CFLAGS="-DSCM_REVISION='\"\xe2\x4a\x9d\xca\xc7\x6c\x28\xf1\x13\xa7\xd9\xa7\x02\x22\xb0\xc5\xb6\x0e\x64\x52\"' -DBUILD_ID='\"touch.4.6.0-Stable-0922-e24a9dc\"'" PRODUCTION="1" TREZOR_MODEL="T" PRODUCTION_MODEL="H" build/boardloader/boardloader.bin
libfaketime: Unexpected recursive calls to clock_gettime() without proper initialization. Trying alternative.
libfaketime: Cannot recover from unexpected recursive calls to clock_gettime().
libfaketime: Please check whether any other libraries are in use that clash with libfaketime.
libfaketime: Returning -1 on clock_gettime() to break recursion now... if that does not work, please check other libraries' error handling.
My Dockerfile:
FROM ubuntu:focal
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install --no-install-recommends -y git wget make autoconf \
automake libtool curl make g++ unzip scons llvm-dev libclang-dev clang \
libsdl2-dev libsdl2-image-dev xz-utils vim ca-certificates libfaketime && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
mkdir -m 0755 /nix && \
groupadd -r nixbld && \
chown root /nix && \
for n in $(seq 1 10); do \
useradd \
--comment "Nix build user $n" \
--home-dir /var/empty \
--gid nixbld \
--groups nixbld \
--no-create-home \
--no-user-group \
--system \
--shell "$(command -v nologin)" \
"nixbld$n"; \
done
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -o pipefail && curl -L https://nixos.org/nix/install | bash
ENV USER=root
RUN . "$HOME/.nix-profile/etc/profile.d/nix.sh"
RUN echo "source $HOME/.nix-profile/etc/profile.d/nix.sh" >> "$HOME/.bashrc"
RUN wget --no-check-certificate \
https://github.com/wolfcw/libfaketime/archive/refs/tags/v0.9.10.tar.gz
RUN tar xf v0.9.10.tar.gz && \
cd libfaketime-0.9.10/ && \
make && make install && \
rm -rf libfaketime-0.9.10/ v0.9.10.tar.gz
My script:
#!/usr/bin/env bash
set -e -o pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
REPOSITORY="https://github.com/OneKeyHQ/onekey-firmware.git"
CONTAINER_NAME="onekey-build"
FAMILY=""
BRANCH=""
TAG=""
BUILD_DATE=""
USER=$(stat -c "%u" . 2>/dev/null || stat -f "%u" .)
GROUP=$(stat -c "%g" . 2>/dev/null || stat -f "%g" .)
DIR=$(pwd)
PULL_SCRIPT_NAME=".pull.sh"
function print_usage() {
echo "Usage: ./onekey.sh -f <family name> -t <tag> -d <date>"
echo " -f family name <touch|mini|classic>"
echo " -t tag"
echo " -d date"
echo "example:"
echo " ./onekey.sh -f touch -t touch/v4.6.0 -d 2023-09-17"
echo " ./onekey.sh -f mini -t mini/v3.4.0 -d 2023-09-17"
echo " ./onekey.sh -f classic -t classic/v3.4.0 -d 2023-09-17"
}
function build_touch() {
mkdir -p build/touch
cat <<EOF > "build/$PULL_SCRIPT_NAME"
cd /tmp
git clone "$REPOSITORY" onekey-firmware
cd onekey-firmware
git checkout $TAG
git submodule update --init --recursive
/root/.nix-profile/bin/nix-shell --run "poetry install"
/root/.nix-profile/bin/nix-shell --run "export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 && export FAKETIME='$BUILD_DATE' && export PRODUCTION=1 && poetry run make -C core build_embed"
cp ./core/build/firmware/touch*Stable*.bin /local/build/touch
EOF
echo
echo ">>> DOCKER RUN build touch"
echo
echo "tag $TAG"
echo
docker run -it --network=host --rm \
-v "$DIR:/local" \
-v "$DIR/build":/build:z \
--init \
--user root \
"$CONTAINER_NAME" \
bash /local/build/$PULL_SCRIPT_NAME
}
function build_mini() {
mkdir -p build/mini
cat <<EOF > "build/$PULL_SCRIPT_NAME"
cd /tmp
git clone -b mini "$REPOSITORY" onekey-firmware
cd onekey-firmware
git checkout $TAG
/root/.nix-profile/bin/nix-shell --run "poetry install"
/root/.nix-profile/bin/nix-shell --run "poetry run ./legacy/script/setup"
/root/.nix-profile/bin/nix-shell --run "export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 && export FAKETIME='$BUILD_DATE' && export ONEKEY_MINI=1 && poetry run ./legacy/script/cibuild"
cp ./legacy/firmware/mini*Stable*.bin /local/build/mini
EOF
echo
echo ">>> DOCKER RUN build mini"
echo
echo "tag $TAG"
echo
docker run -it --network=host --rm \
-v "$DIR:/local" \
-v "$DIR/build":/build:z \
--init \
--user root \
"$CONTAINER_NAME" \
bash /local/build/$PULL_SCRIPT_NAME
}
function build_classic() {
mkdir -p build/classic
cat <<EOF > "build/$PULL_SCRIPT_NAME"
set -e -o pipefail
cd /tmp
git clone -b bixin_dev "$REPOSITORY" onekey-firmware
cd onekey-firmware
git checkout $TAG
/root/.nix-profile/bin/nix-shell --run "poetry install"
/root/.nix-profile/bin/nix-shell --run "poetry run ./legacy/script/setup"
/root/.nix-profile/bin/nix-shell --run "export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 && export FAKETIME='$BUILD_DATE' && poetry run ./legacy/script/cibuild"
cp ./legacy/firmware/classic*Stable*.bin /local/build/classic
EOF
echo
echo ">>> DOCKER RUN build classic"
echo
echo "tag $TAG"
echo
docker run -it --network=host --rm \
-v "$DIR:/local" \
-v "$DIR/build":/build:z \
--init \
--user root \
"$CONTAINER_NAME" \
bash /local/build/$PULL_SCRIPT_NAME
}
# -f : Family Name
# -t : Tag
# -d : Build Date
while getopts 'f:t:d:' OPT; do
case $OPT in
f)
FAMILY="$OPTARG";;
t)
TAG="$OPTARG";;
d)
BUILD_DATE="$OPTARG";;
?)
print_usage
exit -1;;
esac
done
if [ "$TAG" == "" ]; then
echo "Invalid tag."
print_usage
exit -1
fi
if [ "$BUILD_DATE" == "" ]; then
echo "Invalid build date."
print_usage
exit -1
fi
BUILD_DATE="$BUILD_DATE 23:30:00"
if [ "$FAMILY" == "touch" ]; then
build_touch
elif [ "$FAMILY" == "mini" ]; then
build_mini
elif [ "$FAMILY" == "classic" ]; then
build_classic
else
echo "Invalid family name $IN_IMAGE."
print_usage
exit -1
fi
exit 0
Third attempt to corroborate @Giszmo 's findings. In this asciinema cast file, you'll find that I used @Giszmo's Dockerfile settings and script.
Same errors are reported.
I haven't reproduced the problem on my end, but you can integrate libfaketime into @mohammad's script, similarly:
#!/bin/bash
### provide this script with the version without "v" and device type (classic, mini)
### and short release date (mmdd like 0511) which is the suffix of the released binary
version=$1
type=$2
short_release_date=$3
release_date=$4
BUILD_DATE="$release_date 23:30:00"
if [[ $type == mini ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./legacy/bootloader/version.h)"
build_script="nix-shell --run \"poetry run ./legacy/script/setup\"
nix-shell --run \"export ONEKEY_MINI=1 && poetry run ./legacy/script/cibuild\"
cp ./legacy/firmware/${type}*Stable*.bin /firmware"
elif [[ $type == classic ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./legacy/bootloader/version.h)"
build_script="nix-shell --run \"poetry run ./legacy/script/setup\"
nix-shell --run \"poetry run ./legacy/script/cibuild\"
cp ./legacy/firmware/${type}*Stable*.bin /firmware"
elif [[ $type == touch ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./core/embed/bootloader/version.h)"
build_script="git submodule update --init --recursive
nix-shell --run \"poetry run make -C core build_boardloader\"
nix-shell --run \"poetry run make -C core build_bootloader\"
nix-shell --run \"poetry run make -C core build_firmware\"
nix-shell --run \"poetry run core/tools/headertool.py -h core/build/firmware/touch*Stable*.bin\"
cp ./core/build/firmware/${type}*Stable*.bin /firmware"
fi
rm -rf /tmp/onekey_firmware
mkdir /tmp/onekey_firmware
chmod 777 /tmp/onekey_firmware
cd /tmp/onekey_firmware
podman run --rm -v ${PWD}:/firmware ubuntu:20.04 bash -x -c "
apt update
apt -y upgrade
apt install -y curl xz-utils sudo git wget g++ faketime
useradd -m nixuser
groupadd -r nixbld
usermod -aG nixbld nixuser
mkdir /nix
install -d -m755 -o \$(id nixuser -u) -g \$(id nixuser -g) /nix
sudo -H -u nixuser bash -c -x 'sh <(curl -L https://nixos.org/nix/install) --no-daemon
export LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1
export FAKETIME=$BUILD_DATE
. ~/.nix-profile/etc/profile.d/nix.sh
cd ~
git clone https://github.com/OneKeyHQ/firmware
cd firmware
git checkout ${type}/v${version}
$boot_version_variable
FIRMWARE_VERSION=${version}
BUILD_DATE=\$(git --no-pager log -1 --format=%cd --date=format:\"%Y%m%d\" ${type}/v${version})
SHORT_HASH=\$(git rev-parse --short HEAD)
PRODUCTION=1
nix-shell --run \"poetry install\"
$build_script
cd /firmware
wget -O \"downloaded-firmware.bin\" \
\"https://github.com/OneKeyHQ/firmware/releases/download/${type}%2Fv${version}/${type}.${version}-Stable-${short_release_date}-\${SHORT_HASH:0:-2}.signed.bin\"'"
sha256sum *
onekey.sh 3.4.0 classic 0917 2023-09-17
Do you mean you use the scripts presented above without issue or that you have not tried the exact recipe? After all, Danny had found a bug in the Dockerfile. If this works for you from scratch (building the docker image freshly, too), I'm happy to try again but as it fails for both Danny and me and the issue is with faketime at a step involved in both Mohammad's and your script, I'm not expecting a different result.
This is a problem with the path where libfaketime is installed on different systems,
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
please try this script,
#!/bin/bash
### provide this script with the version without "v" and device type (classic, mini)
### and short release date (mmdd like 0511) which is the suffix of the released binary
version=$1
type=$2
short_release_date=$3
release_date=$4
BUILD_DATE="$release_date 23:30:00"
echo "$BUILD_DATE"
if [[ $type == mini ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./legacy/bootloader/version.h)"
build_script="nix-shell --run \"poetry run ./legacy/script/setup\"
nix-shell --run \"export ONEKEY_MINI=1 && poetry run ./legacy/script/cibuild\"
cp ./legacy/firmware/${type}*Stable*.bin /firmware"
elif [[ $type == classic ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./legacy/bootloader/version.h)"
build_script="nix-shell --run \"poetry run ./legacy/script/setup\"
nix-shell --run \"poetry run ./legacy/script/cibuild\"
cp ./legacy/firmware/${type}*Stable*.bin /firmware"
elif [[ $type == touch ]]; then
boot_version_variable="BOOT_VERSION=\$(./tools/version.sh ./core/embed/bootloader/version.h)"
build_script="git submodule update --init --recursive
nix-shell --run \"poetry run make -C core build_boardloader\"
nix-shell --run \"poetry run make -C core build_bootloader\"
nix-shell --run \"poetry run make -C core build_firmware\"
nix-shell --run \"poetry run core/tools/headertool.py -h core/build/firmware/touch*Stable*.bin\"
cp ./core/build/firmware/${type}*Stable*.bin /firmware"
fi
rm -rf /tmp/onekey_firmware
mkdir /tmp/onekey_firmware
chmod 777 /tmp/onekey_firmware
cd /tmp/onekey_firmware
podman run --rm -v ${PWD}:/firmware ubuntu:20.04 bash -x -c "
apt update
apt -y upgrade
apt install -y curl xz-utils sudo git wget g++ faketime
useradd -m nixuser
groupadd -r nixbld
usermod -aG nixbld nixuser
mkdir /nix
install -d -m755 -o \$(id nixuser -u) -g \$(id nixuser -g) /nix
sudo -H -u nixuser bash -c -x 'sh <(curl -L https://nixos.org/nix/install) --no-daemon
export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
export FAKETIME=\"$BUILD_DATE\"
date
. ~/.nix-profile/etc/profile.d/nix.sh
cd ~
git clone https://github.com/OneKeyHQ/firmware.git
cd firmware
git checkout ${type}/v${version}
$boot_version_variable
FIRMWARE_VERSION=${version}
BUILD_DATE=\$(git --no-pager log -1 --format=%cd --date=format:\"%Y%m%d\" ${type}/v${version})
SHORT_HASH=\$(git rev-parse --short HEAD)
PRODUCTION=1
nix-shell --run \"poetry install\"
$build_script
cd /firmware
wget -O \"downloaded-firmware.bin\" \
\"https://github.com/OneKeyHQ/firmware/releases/download/${type}%2Fv${version}/${type}.${version}-Stable-${short_release_date}-\${SHORT_HASH:0:-2}.signed.bin\"'"
sha256sum *
So the binary downloadable from CI Actions are not signed at all (all 0s in the headr of generated .bin)?
And how could we flash the signed binary by ourselves rather than the web tool, like any python script like trezorcli? Allowing the customer to manully upgrade the firmware is also important for making the whole process more transparency.
The latest script is sending mixed messages, showing both Failed and Success messages on the same line.
Checking again.
+ export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
+ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
+ export 'FAKETIME= 23:30:00'
+ FAKETIME=' 23:30:00'
+ date
Failed to parse FAKETIME timestamp: Success
+ . /home/nixuser/.nix-profile/etc/profile.d/nix.sh
++ '[' -n /home/nixuser ']'
++ '[' -n nixuser ']'
++ NIX_LINK=/home/nixuser/.nix-profile
++ '[' -n '' ']'
++ NIX_LINK_NEW=/home/nixuser/.local/state/nix/profile
++ '[' -e /home/nixuser/.local/state/nix/profile ']'
++ '[' -t 2 ']'
++ export 'NIX_PROFILES=/nix/var/nix/profiles/default /home/nixuser/.nix-profile'
++ NIX_PROFILES='/nix/var/nix/profiles/default /home/nixuser/.nix-profile'
++ '[' -z '' ']'
++ export XDG_DATA_DIRS=/usr/local/share:/usr/share:/home/nixuser/.nix-profile/share:/nix/var/nix/profiles/default/share
++ XDG_DATA_DIRS=/usr/local/share:/usr/share:/home/nixuser/.nix-profile/share:/nix/var/nix/profiles/default/share
++ '[' -e /etc/ssl/certs/ca-certificates.crt ']'
++ export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
++ NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
++ '[' -n '' ']'
++ export PATH=/home/nixuser/.nix-profile/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
++ PATH=/home/nixuser/.nix-profile/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
++ unset NIX_LINK NIX_LINK_NEW
+ cd /home/nixuser
+ git clone https://github.com/OneKeyHQ/firmware.git
Failed to parse FAKETIME timestamp: Success
+ cd firmware
bash: line 7: cd: firmware: No such file or directory
+ git checkout 4.7.0/vtouch
Failed to parse FAKETIME timestamp: Success
+ FIRMWARE_VERSION=touch
++ git --no-pager log -1 --format=%cd --date=format:%Y%m%d 4.7.0/vtouch
Failed to parse FAKETIME timestamp: Success
+ BUILD_DATE=
++ git rev-parse --short HEAD
Failed to parse FAKETIME timestamp: Success
+ SHORT_HASH=
+ PRODUCTION=1
+ nix-shell --run 'poetry install'
Failed to parse FAKETIME timestamp: No such file or directory
+ cd /firmware
bash: line 17: -2: substring expression < 0
sha256sum: '*': No such file or directory
I'm a bit confused:
./onekey.sh 4.7.0 touch 1227 and ./onekey.sh 4.7.0 touch 1227 2023-12-27
I've also tried
./onekey.sh 4.7.0 touch 0117 and ./onekey.sh 4.7.0 touch 0117 2024-01-17
All lead to the same problem.
After trying the latest script with ./onekey.sh 4.7.0 touch 1227 2023-12-27, I get:
+ export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
+ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1
+ export 'FAKETIME=2023-12-27 23:30:00'
+ FAKETIME='2023-12-27 23:30:00'
+ date
Wed Dec 27 23:30:00 UTC 2023
+ . /home/nixuser/.nix-profile/etc/profile.d/nix.sh
++ '[' -n /home/nixuser ']'
++ '[' -n nixuser ']'
++ NIX_LINK=/home/nixuser/.nix-profile
++ '[' -n '' ']'
++ NIX_LINK_NEW=/home/nixuser/.local/state/nix/profile
++ '[' -e /home/nixuser/.local/state/nix/profile ']'
++ '[' -t 2 ']'
++ export 'NIX_PROFILES=/nix/var/nix/profiles/default /home/nixuser/.nix-profile'
++ NIX_PROFILES='/nix/var/nix/profiles/default /home/nixuser/.nix-profile'
++ '[' -z '' ']'
++ export XDG_DATA_DIRS=/usr/local/share:/usr/share:/home/nixuser/.nix-profile/share:/nix/var/nix/profiles/default/share
++ XDG_DATA_DIRS=/usr/local/share:/usr/share:/home/nixuser/.nix-profile/share:/nix/var/nix/profiles/default/share
++ '[' -e /etc/ssl/certs/ca-certificates.crt ']'
++ export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
++ NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
++ '[' -n '' ']'
++ export PATH=/home/nixuser/.nix-profile/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
++ PATH=/home/nixuser/.nix-profile/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
++ unset NIX_LINK NIX_LINK_NEW
+ cd /home/nixuser
to: -c: line 8: syntax error: unexpected end of file
sha256sum: '*': No such file or directory
What I don't understand as a software engineering student is why is it so difficult to get a reproducible build? I thought if everyone knows the build instructions every build and thus binary is exactly the same? So either the dev team of OneKey really needs to improve their documentation regarding this or some info is missing. Please correct me if I'm wrong.
I hope everyone figures it out. I wanted to buy a OneKey Pro wallet to use as a secondary wallet next to my Trezor but now I'm doubting this until this gets resolved. I take https://walletscrutiny.com very seriously.
-
Non-Deterministic Build Processes: Some build processes incorporate elements that can vary between builds, such as timestamps, filesystem paths, or data from the build environment. These variations can lead to differences in the binary output even if the source code hasn't changed.
-
Toolchain Variability: The compilers, linkers, and other tools used to build the software might differ in versions or configurations across environments. Even minor differences in toolchain components can lead to variations in the resulting binaries.
-
External Dependencies: Many projects depend on libraries and other software components that might not be built reproducibly themselves. If these dependencies are not identical (in both version and build) across builds, the final binaries might differ.
-
Parallel Builds: Modern build systems often parallelize tasks to speed up the build process. However, this can introduce non-determinism if the order of operations affects the final output, such as when files are concatenated in an order determined by the speed of task completion.
-
Embedded Metadata: Some build processes automatically embed metadata into the binary, such as the build date or version control information. This metadata can vary from one build to the next, affecting reproducibility.
-
Environment Differences: Differences in the build environment, including operating system versions, file system characteristics, and available system resources, can influence the build process and the final binaries.
Non-Deterministic Build Processes: Some build processes incorporate elements that can vary between builds, such as timestamps, filesystem paths, or data from the build environment. These variations can lead to differences in the binary output even if the source code hasn't changed.
Toolchain Variability: The compilers, linkers, and other tools used to build the software might differ in versions or configurations across environments. Even minor differences in toolchain components can lead to variations in the resulting binaries.
External Dependencies: Many projects depend on libraries and other software components that might not be built reproducibly themselves. If these dependencies are not identical (in both version and build) across builds, the final binaries might differ.
Parallel Builds: Modern build systems often parallelize tasks to speed up the build process. However, this can introduce non-determinism if the order of operations affects the final output, such as when files are concatenated in an order determined by the speed of task completion.
Embedded Metadata: Some build processes automatically embed metadata into the binary, such as the build date or version control information. This metadata can vary from one build to the next, affecting reproducibility.
Environment Differences: Differences in the build environment, including operating system versions, file system characteristics, and available system resources, can influence the build process and the final binaries.
Thank you so much for your explanation! π So, for reproducible builds it's best to use something like docker so everyone has the same build tools and the same versions of those build tools for example?
So, if I understand correctly OneKey needs to document the build tools, the versions of those build tools, on which platform they build it, the file structure, basically everything you mentioned here?
Could also some of these be avoided? Such as embedded metadata, or the order? Can the order be fixed? I assume that this depends on the build tools right?
That's quite the task TBH π Still, it's needed for transparency.
So, for reproducible builds it's best to use something like docker so everyone has the same build tools and the same versions of those build tools for example?
Yes
If you clone the walletscrutiny repository on gitlab - https://gitlab.com/walletscrutiny/walletScrutinyCom. You will find there some existing Dockerfile configurations for wallets such as blue wallet, bitcoin schildbach, etc.
So, if I understand correctly OneKey needs to document the build tools, the versions of those build tools, on which platform they build it, the file structure, basically everything you mentioned here?
Isn't that fun!? Basically their team just has to document what they are already doing and digest it to make it easier for others to follow their procedure.
Could also some of these be avoided? Such as embedded metadata, or the order? Can the order be fixed? I assume that this depends on the build tools right?
There are things that cannot be avoided and some stuff that come up with differences routinely. I know it sounds a bit "perfectionist" but it is possible. You can find more background in reproducible-builds.org.
That's quite the task TBH π Still, it's needed for transparency.
A little documentation won't hurt anybody :)
Docker isn't great because it requires downloading a huge binary image, which should not be trusted. If the docker image isn't trusted (because it's not included in the chain of reproduciblity), then anything created using it (OneKeyHQ firmware) is conceptually not to be trusted either.
So if going the docker route, that also would need to be build reproduciblity from soruce code.
@adrelanos you are right to worry about the images themselves being compromised and luckily work is being done on that front, too. At WalletScrutiny I try to avoid using images under the control of respective wallet providers and use generic, minimal images instead to build on. Of course the whole build process pulls in a lot of binary stuff in a myriad of ways and more research has to be done to avoid compromises but containers are still a great way to define exact build environments. Nix appears to be even better but you are right, if we agree on the same binary blobs, we have our single points of failure right there. The alternative is bootstrapability of all the binary that touches the result.
Is onekey still making progress recently? It seems there has been no response or progress for a while. As a onekey user, I am also very concerned about this matter.
Is onekey still making progress recently? It seems there has been no response or progress for a while. As a onekey user, I am also very concerned about this matter.
Yeah, seems like they want others to figure it out while not providing reproducible build, and no build instructions and whatnot.
It would be more constructive for us to contribute to the discussion in walletscrutiny's gitlab issue for onekey. They have, in the past, replied extensively: https://gitlab.com/walletscrutiny/walletScrutinyCom/-/issues/469
It would be good if OneKey could give an update here or on gitlab. Itβs been a while and is concerning given the intended use case.
Yes. Well-known hardware wallets have passed the consistency test, such as Trezor, coldcard, etc. This is especially troubling considering Onekey is made in China.