Skip to content

v13.1 Windows noncodesigned.exe: hash mismatch when building from source via Nix flakes #2090

@xrviv

Description

@xrviv

Summary

We are WalletScrutiny, an independent project that verifies whether published wallet binaries can be reproduced from source. We attempted to reproduce liana-13.1-noncodesigned.exe from the v13.1 source tag using the Nix flake build
(nix build .#x86_64-pc-windows-gnu) and found a hash mismatch against the official release artifact. We want to share our findings and ask whether we are missing a step in the build environment setup.

We are open to the possibility that we made an error. If there is something about your build environment that we should be replicating, we would appreciate the guidance.


Environment

Our build
Host OS Ubuntu 24.04 LTS (x86_64)
Container nixos/nix:2.24.10 (Docker, --privileged)
Nix config experimental-features = nix-command flakes, sandbox = false
Build command nix build .#x86_64-pc-windows-gnu
Source git clone --depth=1 --branch v13.1 → commit 282520ffeba1d56a255bc2d6e8dcb1011441e75c
flake.lock Used as-is from the tag — no modifications

Hash Comparison

SHA256 Size
Our build (liana-gui.exe) e6c63491f54454182b0bf19ecb55480a81f1d25a90a909b46e6c1552b2e33c1f 75,598,728 bytes
Official (liana-13.1-noncodesigned.exe) d417e014eca2f2c2d74d630086f80707a5b5ed1b8d2df4d364d2e8b521c26f06 75,599,580 bytes
Delta −852 bytes

Our build was self-consistent: running the same nix build .#x86_64-pc-windows-gnu command
twice (once via our automated script, once manually inside a fresh container) produced the
same hash both times.


Investigation Steps

1. Verified the correct build attribute

We inspected flake.nix and contrib/release/release.sh. The release script calls
nix build .#release and then copies the exe from the result:

# release.sh line 45–46
nix build .#release
NIX_BUILD_DIR="$(nix path-info .#release)"

# line 68
cp "$NIX_BUILD_DIR/x86_64-pc-windows-gnu/liana-gui.exe" "$RELEASE_DIR/$LIANA_PREFIX-noncodesigned.exe"

flake.nix lines 199–206 show that .#release is a pkgs.buildEnv wrapping the
x86_64-pc-windows-gnu derivation — not a separate build. So the exe inside .#release
should be byte-identical to the standalone nix build .#x86_64-pc-windows-gnu output.

We attempted nix build .#release but it requires the Xcode 12.2 SDK for the macOS
targets, which we cannot provide. We could not test the .#release path end-to-end.

2. Verified no post-processing

release.sh applies only a plain cp to the exe — no strip, objcopy, wine, or
PE-modifying step. The published noncodesigned.exe should be byte-identical to the Nix
store output.

3. Binary diff analysis

Total differing bytes:  22,820,507 out of 75,598,728 (~30% of binary)
First differing offset: 0x000000d8 (PE header area)
Sampled section mapping (first 40 diff positions):
  PE header / DOS stub  →  8 differing bytes, first at 0x000000d8
  .text                 →  32 differing bytes, first at 0x00000418

The scale of the diff (~30% of the binary) and its presence in the .text section from
the very start of the code segment suggests a code generation difference rather than a
metadata-only difference (e.g. a PE timestamp would be only 4 bytes). Note: the section
mapping above covers only the first 40 sampled positions, not the full binary.

Both binaries embed the same crane vendor placeholder (eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee)
in embedded source paths, though a strings comparison also revealed differing Nix store
path strings that we could not fully interpret due to adjacent binary context bytes.


Hypothesis

With flake.lock pinning all inputs, we would expect the build to be deterministic across
environments. Our current hypothesis is that the Nix binary version used at release time
differs from 2.24.10, and that this produces a different evaluation outcome or different
binary cache substitutes are fetched. We have not been able to confirm this.

We may be wrong. There could be a build step we are not replicating — for example,
the nix develop .#release shell environment exporting variables that affect the build, or
a requirement to run on a specific OS or kernel version.


Questions

  1. What version of Nix was used to produce the v13.1 release artifacts?
  2. Was the release built inside nix develop .#release before calling nix build, and if
    so, does that shell export any variables that affect the Windows build derivation?
  3. Is there any known reason why nix build .#x86_64-pc-windows-gnu would not reproduce
    the exe shipped in the release, given the same flake.lock?

Note on Linux artifacts

For reference, the Linux artifacts for v13.1 did reproduce successfully in our testing:

  • liana-13.1-x86_64-linux-gnu.tar.gz — all three binaries (lianad, liana-cli,
    liana-gui) matched the official release byte-for-byte via the Guix build pipeline.
  • liana-13.1-1_amd64.deb — binaries extracted from the deb matched the Guix output.

The non-reproducibility appears isolated to the Windows exe.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions