首页
/ Bitcoin Core 的 Guix 引导式构建指南:可审计、可复现的多平台二进制构建与签名验证全流程

Bitcoin Core 的 Guix 引导式构建指南:可审计、可复现的多平台二进制构建与签名验证全流程

2026-09-06 21:08:04作者:仰钰奇

本文以 Bitcoin Core 仓库中 contrib/guix/README.md 文档为核心,系统讲解如何借助 Guix 函数式包管理器完成"引导式构建"(Bootstrappable Build):从磁盘与安装要求、macOS SDK 准备、guix-build 全流程构建,到 guix-codesign 代码签名、guix-clean 清理、SHA256SUMS 收集,再到 guix-attest / guix-verify 的 GPG 认证与交叉验证,并结合仓库内脚本与清单源码剖析其确定性实现细节与三种安全模型选择。读完本文,你将能够独立完成一次可复现的 Bitcoin Core 多平台构建,理解 HOSTSJOBSSOURCE_DATE_EPOCH 等环境变量的实际作用,并掌握替代包(substitutes)的信任模型配置方法。

一、什么是引导式构建(Bootstrappability)

contrib/guix/README.md 开宗明义:该目录包含执行引导式 Bitcoin Core 构建所需的全部文件。引导式构建通过"审计并复现"工具链,而非盲目信任二进制下载,来强化二进制的安全保证。Bitcoin Core 的实现方式是采用 Guix 作为函数式包管理器——所有构建依赖(编译器、交叉工具链、glibc 等)都由 Guix 从源码清单定义出发构建,其输入(源码 + 清单)完全可审计,从而让最终二进制可追溯到可审计的源头。

这一设计在仓库中有清晰的源码体现:

  • 构建环境清单 manifest_build.scm 中定义了完整的交叉工具链生成流程:make-cross-toolchain 按"交叉 binutils → 不带 libc 的交叉 GCC → 交叉内核头(linux-libre-headers-6.1)→ 交叉 glibc(2.31)→ 最终交叉 GCC"四步逐级构建,并默认使用打了补丁的 gcc-14 作为基础编译器;
  • GUI 依赖由 manifest_gui.scm 按目标平台条件注入:mingw 目标追加 zipnsis-x86_64,linux 目标追加 bisongawkpkg-config,darwin 目标追加 zip
  • 签名环境清单 manifest_codesign.scm 则引入 osslsigncode(Windows 签名)、python-oscrypto / python-asn1crypto(macOS 签名)等专用工具。

二、环境需求与安装准备

磁盘空间需求

文档给出的保守估计如下:

需求项 说明
/gnu/store 所在分区 至少 16GB 可用空间(Guix 包存储)
每个平台三元组(HOST) 每个计划构建的目标平台各需 8GB 可用空间

源码层面还有更精细的校验。guix-build 在构建前会按目标平台累加估算所需空间,并与 df -Pk 实测可用空间比对:darwin 目标约 440000 KiB,mingw 目标约 7600000 KiB(约 7.2GiB),其余 Linux 目标各约 6400000 KiB(约 6.1GiB),空间不足时直接中止并提示(见 contrib/guix/guix-build#L148-L164)。

安装 Guix

若尚未安装并配置 Guix,请先按同目录的 INSTALL.md 操作(支持源码安装、发行版包、Shell 安装脚本等多种途径,各途径对替代包签名键的授权行为略有差异,后文安全模型一节会用到)。

此外,所有脚本必须从仓库顶层目录调用——prelude.bash 在开头就会校验 PWDgit_root 是否一致,否则报错退出;并且 guix-build / guix-codesign 会检查 guix-daemon 可达性(guix gc --list-failures)、系统服务数据库(getent services http https ftp)等前置条件。

三、为 macOS 交叉编译准备 Xcode SDK

macOS 目标(x86_64-apple-darwinarm64-apple-darwin)包含在默认构建目标集合中,因此需要准备 macOS SDK 包。解包工具位于 macdeploy 目录(其中 "SDK Extraction" 一节说明了如何从 Xcode.app 生成 SDK tarball)。

SDK 有两种放置方式:

方式一:用 SDK_PATH 指向已解包的 SDK 所在父目录

# 将 SDK tarball 解包到 Xcode-<foo>-<bar>-extracted-SDK-with-libcxx-headers 目录
tar -C /path/to/parent/dir/of/extracted/SDK -xaf /path/to/Xcode-<foo>-<bar>-extracted-SDK-with-libcxx-headers.tar

# 指明 SDK 位置(注意:指向解包目录的“父”目录,而非 SDK 目录本身)
export SDK_PATH=/path/to/parent/dir/of/extracted/SDK

方式二:解包进 depends/SDKs

mkdir -p depends/SDKs
tar -C depends/SDKs -xaf /path/to/SDK/tarball

guix-build 对 darwin 目标会执行 make -C depends HOST=<host> print-OSX_SDK 探测 SDK 位置,找不到即报错退出。注意 SDK_PATH 必须指向实际 SDK 目录的父目录(例如应为 $HOME/Downloads/macOS-SDKs 而非 .../macOS-SDKs/Xcode-26.1.1-17B100-extracted-SDK-with-libcxx-headers),且该路径必须是真实目录而不能是指向目录的符号链接——guix-build 会对所有"珍贵目录"(precious dirs)执行 mkdir -p / 符号链接 / 目录类型三重校验。

四、执行构建:guix-build 全流程

在开始之前,强烈建议先通读下文"常见调用模式与示例"和"识别的环境变量"两节。

干净仓库的顶层执行:

./contrib/guix/guix-build

构建前的强制检查(源码佐证)

guix-build 在真正构建前执行一系列防御性检查,理解它们能避免大部分"诡异"失败:

  1. 禁止脏工作区git diff-index --quiet HEAD 不干净则中止(可用 FORCE_DIRTY_WORKTREE 强制覆盖);
  2. 拒绝意外设置的 SOURCE_DATE_EPOCHprelude.bash 中的 check_source_date_epoch 发现该变量已设置且未指定 FORCE_SOURCE_DATE_EPOCH 时会直接退出——因为误设会破坏可复现性;
  3. 拒绝残留构建目录:若 distsrc-* 已存在(说明该 commit 之前构建过),中止并提示使用 guix-clean
  4. 拒绝 GUIX_BUILD_OPTIONS 非空:该变量会覆盖脚本自身的细粒度 flag 机制,脚本明确要求改用 ADDITIONAL_GUIX_*_FLAGS 系列变量。

版本目录与确定性锚点

prelude.bash 定义了构建目录布局:VERSION 默认取 git_head_version(可用 FORCE_VERSION 覆盖),DISTNAME 默认为 bitcoin-${VERSION},其下依次为:

  • guix-build-<版本>/distsrc-<版本>-<HOST>:各平台工作目录(DISTSRC);
  • guix-build-<版本>/output/<HOST>/:产物输出目录(OUTDIR);
  • guix-build-<版本>/var/profiles/<HOST>/:Guix 环境 profile 目录。

时间锚点 SOURCE_DATE_EPOCH 默认取 git log --format=%at -1(当前 HEAD 的最后一次提交时间戳),用于 tar 时间戳等确定性控制。容器内 setup.sh 还会导出统一的 TAR_OPTIONS="--no-same-owner --owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name" 并固定 TZ=UTCumask 0022,这些是逐位复现的关键细节。

Guix 环境与隔离容器

guix-build 通过 time-machine 函数调用固定 commit 的 Guix(当前钉在 prelude.bash 中的 c5eee3336cc1d10a3cc1c97fde2809c3451624d3),保证"跨时间"的可复现性。对每个 HOST,它用 guix shell 在隔离容器中执行 contrib/guix/libexec/ 下的构建脚本,关键 flag 的意图在脚本注释中有详细解释:

  • --container --writable-root:在隔离容器中运行,最小化机器间差异;
  • --pure --no-cwd:清空继承的环境变量、不共享宿主机当前目录($PWD 是复现性污染源);
  • --share="$PWD"=/bitcoin:把工作区固定映射到容器内 /bitcoin 路径;
  • --share="$DISTSRC_BASE"=/distsrc-base--share="$OUTDIR_BASE"=/outdir-base:共享工作目录与输出目录;
  • --expose="$(git rev-parse --git-common-dir)":让容器内脚本能访问 .gitgit archive 需要);
  • --keep-failed:保留失败构建的构建树以便调试;
  • --fallback:允许构建本机不存在的替代包。

每个平台还会运行两轮 guix shell:第一轮用 manifest_build.scm 构建无 GUI 二进制,第二轮用 manifest_gui.scm 构建 GUI 版本,分别调用 build_<linux|macos|win>_gui.sh 与非 GUI 版本脚本。Linux 目标的构建脚本还会在打包阶段执行 security-check.py(二进制安全特性检查)与 symbol-check.py(动态符号白名单检查)——见 package.sh 开头部分。

五、构建产物代码签名:guix-codesign

guix-codesign 将签名者产生的分离式代码签名(detached codesignatures)附加到已构建的、未签名的产物上。更多背景见 release-process.md 的 "Codesigning" 一节。

它尊重 guix-build 的大多数环境变量,但有 两个关键差异

  1. HOSTS 默认值不同:只有 Windows 与 macOS 产物需要代码签名,因此默认为 x86_64-w64-mingw32 x86_64-apple-darwin arm64-apple-darwin(见 guix-codesign 中的 HOSTS 初始化);
  2. DETACHED_SIGS_REPO 为必需变量:指向当前版本分离式签名的存放目录(即签名者发布的 bitcoin-detached-sigs 仓库克隆)。

默认选项调用示例:

env DETACHED_SIGS_REPO=<path/to/bitcoin-detached-sigs> ./contrib/guix/guix-codesign

脚本会先在输出目录中查找各平台的 codesigning tarball(bitcoin-<版本>-win64-codesigning.tar.gzbitcoin-<版本>-<darwin三元组>-codesigning.tar.gz),缺失即报错;随后在 manifest_codesign.scm 环境中挂载该签名仓库(--share="$DETACHED_SIGS_REPO"=/detached-sigs),运行 codesign.sh,产物写入带 codesigned 后缀的独立目录。脚本同样强制:签名仓库工作区干净、无残留 distsrc-<...>-codesigned 目录、可连接 guix-daemon。

六、清理中间工作目录:guix-clean

默认情况下 guix-build 会在构建结束后保留全部中间文件(depends/workguix-build-*/distsrc-* 等)供调试使用,但这些目录通常占用大量磁盘。guix-clean 提供一键清理:

./contrib/guix/guix-clean

从源码看,它本质是对当前 git 工作区执行 git clean -xdff,但会先从各版本目录下记录的 precious_dirs 文件读取"珍贵目录"(SOURCES_PATHBASE_CACHESDK_PATHOUTDIR_BASEPROFILES_BASE 的实际生效值)并逐一加入 --exclude 白名单,从而不误删下载缓存、依赖缓存、SDK 与构建产物。默认先以 -n(dry-run)预览并等待确认;传 --force 可跳过确认直接清理。

七、收集构建产物 SHA256

构建成功后,各架构输出目录中会生成名为 SHA256SUMS(构建期以 SHA256SUMS.part 片段形式落盘,见 package.sh 的打包逻辑)的文件。若要汇总所有摘要输出到控制台(例如粘贴到 Guix 依赖的 pull request 评论中),可运行文档给出的命令:

source contrib/shell/git-utils.bash && uname -m && find guix-build-$(git_head_version)/output/ -type f -print0 | env LC_ALL=C sort -z | xargs -r0 sha256sum

git_head_version 来自 contrib/shell/git-utils.bash,保证在 tag 与 commit 两种场景下都能取到正确的版本标识。

八、认证构建产物:guix-attest

与 Gitian 构建用 gitian.sigs 仓库做认证一样,Guix 构建产物在独立的 guix.sigs 仓库中认证。克隆该仓库后,对当前工作区的 commit/tag 执行认证:

env GUIX_SIGS_REPO=<path/to/guix.sigs> SIGNER=<gpg-key-name> ./contrib/guix/guix-attest

./contrib/guix/guix-attest --help(即缺参时的用法输出)展示了更多调用方式,均可从 guix-attestcmd_usage 中确认:

  • SIGNER=GPG_KEY_NAME[=SIGNER_NAME]:用 = 可覆盖签名者目录名(如 SIGNER=0x96AB007F1A7ED999=dongcarl),缺省时签名者名即密钥名;
  • NO_SIGN=1:只生成 SHA256SUMS 清单文件、不做 GPG 签名。

从源码可确认其认证逻辑:脚本汇总 $OUTDIR_BASE/*/SHA256SUMS.part,按目录名区分 codesigned 与 noncodesigned 两组,去重排序后经 basenameify_SHA256SUMSsed 把相对文件名替换为 basename)写入签名仓库的 <版本>/<签名者>/noncodesigned.SHA256SUMS(及存在 codesigned 产物时的 all.SHA256SUMS),再用 gpg --detach-sign --digest-algo sha256 --armor 生成对应 .asc。若同名清单已存在但内容不一致(比如你之前只认证了部分 HOST 的构建),脚本会打印 diff 并提示删除旧认证后重试;若 codesigned 产物尚缺(分离签名尚未发布),只提示 INFO 而非报错。

九、验证认证签名:guix-verify

当至少另一位签名者已向 guix.sigs 上传签名后:

git -C <path/to/guix.sigs> pull
env GUIX_SIGS_REPO=<path/to/guix.sigs> ./contrib/guix/guix-verify

guix-verify 的验证逻辑(源码可查):

  1. 以某个签名者的清单为基准(可用 SIGNER=<signer> 指定基准,缺省取找到的第一个);
  2. <版本>/ 下每个签名者的 noncodesigned.SHA256SUMSall.SHA256SUMS:先 gpg --verify 校验其 .asc 签名,再 diff 对比清单内容是否逐字节一致;
  3. 额外做完整性自检:noncodesigned.SHA256SUMS 中不允许存在 all.SHA256SUMS 中没有的行(comm -23 检查),否则判定"出大问题了"并退出;
  4. 任一签名或内容校验失败即整体返回非零。

配合 release-process.md 的流程,签名者将 noncodesigned.SHA256SUMS{,.asc} 与 codesigned 认证分别提交到 guix.sigs,待 6 人以上独立构建且结果一致后即可推进发布。

十、常见 guix-build 调用模式与示例

1. 把缓存与 SDK 放在工作区之外

如果你频繁构建、维护多个 worktree,可以把 depends 的下载缓存、构建缓存与 SDK 移出工作区,避免重复下载和重复构建。guix-build 会识别并透传 SOURCES_PATHBASE_CACHESDK_PATH 三个变量:

env SOURCES_PATH="$HOME/depends-SOURCES_PATH" BASE_CACHE="$HOME/depends-BASE_CACHE" SDK_PATH="$HOME/macOS-SDKs" ./contrib/guix/guix-build

注意:这些路径必须是目录,且不能是指向目录的符号链接(脚本中 elif [ -L "$precious_dir_path" ] 会显式拦截)。这三个路径同时会被 --share 映射进构建容器(容器无网络访问,depends 源码必须在容器外预下载——guix-build 先在容器内执行 make -C depends download-<linux|osx|win>)。

2. 只构建部分平台三元组

用空格分隔的 HOSTS 覆盖默认列表:

env HOSTS='x86_64-w64-mingw32 x86_64-apple-darwin' ./contrib/guix/guix-build

3. 控制 guix 构建命令的线程数

./contrib/guix 下的脚本默认以 --cores="$JOBS" 调用所有 guix 构建命令,$JOBS 缺省为容器外 $(nproc)。而 guix 构建命令还接受一个 --max-jobs= 参数(未指定时默认为 1),两者区别如下(把 "derivation" 理解为 "package" 即可):

参数 含义
--cores= 控制构建每个 derivation 使用的 CPU 核数,即传给 make--jobs=
--max-jobs= 控制同时并行构建多少个 derivation,默认为 1

因此默认行为是:一次构建一个 derivation,每个 derivation 使用 $JOBS 个线程。只设置 $JOBS 只影响 --cores=;要改 --max-jobs= 需通过 $ADDITIONAL_GUIX_COMMON_FLAGS。例如内存充裕时可以:

export ADDITIONAL_GUIX_COMMON_FLAGS='--max-jobs=8'

允许最多 8 个 derivation 并行,各自使用 $JOBS 个线程;或者为避免单包内部并行导致的偶发构建失败、但依赖图允许时仍希望多包并行,可以:

export JOBS=1 ADDITIONAL_GUIX_COMMON_FLAGS='--max-jobs=8'

十一、识别的环境变量(完整清单)

变量 作用 默认值 / 约束
HOSTS 覆盖待构建平台三元组的空格分隔列表 x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu riscv64-linux-gnu powerpc64-linux-gnu x86_64-w64-mingw32 x86_64-apple-darwin arm64-apple-darwin
SOURCES_PATH depends 树源码下载缓存目录,透传给 depends。跨多次构建共用可消除重复下载 必须为目录,不可为符号链接
BASE_CACHE depends 树已构建包的缓存目录,透传给 depends。共用可消除重复构建 必须为目录,不可为符号链接
SDK_PATH 已解包 SDK 的查找路径,透传给 depends;应设为实际 SDK 的父目录 必须为目录,不可为符号链接
JOBS 并行任务数,内存有限机器上可调低。会传递给 guix--cores=)、make --jobs=cmake --build -jxargs -P 容器外 nproc 的值
SOURCE_DATE_EPOCH 覆盖用于逐位复现的参考 UNIX 时间戳,变量名遵循可复现构建社区的标准命名 $(git log --format=%at -1);注意脚本会拒绝"意外"设置,除非同时设 FORCE_SOURCE_DATE_EPOCH
V 非空即向所有 make 调用传 V=1 使输出冗长。只看是否为空:V=(空串)等同未设置,V=0V=1 效果相同
SUBSTITUTE_URLS 空白分隔的预构建包下载 URL 列表;仅当对应签名键已授权时生效 见安全模型一节
ADDITIONAL_GUIX_COMMON_FLAGS 传给所有 guix 命令的附加 flag
ADDITIONAL_GUIX_TIMEMACHINE_FLAGS 传给 guix time-machine 的附加 flag
ADDITIONAL_GUIX_ENVIRONMENT_FLAGS 传给 guix time-machine 内部 guix shell 调用的附加 flag

另外,guix-attest 需要 GUIX_SIGS_REPOSIGNER(可选 NO_SIGN),guix-codesign 需要 DETACHED_SIGS_REPO,均见上文对应章节。

十二、选择你的安全模型:substitutes(替代包)三方案

无论以何种方式安装 Guix,构建前都需要决定使用 Guix 构建包的安全模型。Guix 允许用 CPU 时间从零构建一切来获得更强的二进制安全性,但用户可以选择是否使用 substitutes(预构建包)

方案 1:使用 substitutes 构建

第一步:授权签名键

不同安装途径下你可能已经授权了 Guix 构建农场的键:官方 Shell 安装脚本会询问是否安装该键,Debian 发行版包在安装时已授权。当前授权列表可查看 /etc/guix/acl。仅授权了 Guix 构建农场键时,其大致形如:

(acl
 (entry
  (public-key
   (ecc
    (curve Ed25519)
    (q #8D156F295D24B0D9A86FA5741A840FF2D24F60F7B6C4134814AD55625971B394#)
    )
   )
  (tag
   (guix import)
   )
  )
 )

若官方构建农场键尚未授权且你想授权它,以 root 执行:

guix archive --authorize < /var/guix/profiles/per-user/root/current-guix/share/guix/ci.guix.gnu.org.pub

若该路径不存在,尝试:

guix archive --authorize < <PREFIX>/share/guix/ci.guix.gnu.org.pub

其中 <PREFIX> 通常是:发行版包安装时为 /usr;从源码安装且未向 ./configure 传入前缀修改参数时为 /usr/local

移除已授权键:直接编辑 /etc/guix/acl,删除对应的 (entry (public-key ...)) 条目即可。

第二步:指定 substitute 服务器

键被授权后,除非提供 --no-substitutes,官方 Guix 构建农场将被自动使用。该默认服务器列表可在 guix-daemon 级别和每次 guix 命令调用时覆盖:

  • 修改默认列表:以 --substitute-urls 选项启动 guix-daemon(通常需要编辑 init 脚本):
guix-daemon <cmd> --substitute-urls='https://bordeaux.guix.gnu.org https://ci.guix.gnu.org'
  • 覆盖单次 guix 命令调用的列表:
guix <cmd> --substitute-urls='https://bordeaux.guix.gnu.org https://ci.guix.gnu.org'
  • ./contrib/guix 下的脚本,设置 SUBSTITUTE_URLS 环境变量(脚本会以 --substitute-urls="$SUBSTITUTE_URLS" 透传给 guix 命令,见 prelude.bashguix-build 中的 ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"}):
export SUBSTITUTE_URLS='https://bordeaux.guix.gnu.org https://ci.guix.gnu.org'

方案 2:临时禁用 substitutes

若不想使用任何 substitute,确保提供 --no-substitutes。首次构建会比较慢,但产出的包会缓存供后续构建使用。

  • 直接调用 guix
guix <cmd> --no-substitutes
  • ./contrib/guix/ 下的脚本:
export ADDITIONAL_GUIX_COMMON_FLAGS='--no-substitutes'

方案 3:默认禁用 substitutes

guix-daemon 接受 --no-substitutes 参数,确保除被命令行显式覆盖外一律不使用 substitutes。若通过 init 脚本启动 guix-daemon,可直接在脚本中加入该参数。

十三、小结:目录、脚本与源码地图

围绕 contrib/guix/ 的完整工具链可归纳为:

脚本 / 文件 职责
guix-build 多平台引导式构建入口,含全部前置检查与 guix shell 环境编排
guix-codesign 将分离式代码签名附加到 win/darwin 产物
guix-clean 带白名单排除的 git clean 式工作区清理
guix-attest 生成并 GPG 签名 noncodesigned.SHA256SUMS / all.SHA256SUMS
guix-verify 交叉验证各签名者清单的签名与内容一致性
manifest_build.scm / manifest_gui.scm / manifest_codesign.scm 三类 Guix 环境清单:核心构建、GUI 依赖、签名工具
libexec/prelude.bash / setup.sh 公共前置检查、time-machine 钉版、确定性环境变量(TAR_OPTIONS、TZ、umask)
libexec/build_*.sh / codesign.sh / package.sh 各平台容器内构建/签名/打包脚本,含安全与符号检查
security-check.py / symbol-check.py 产物二进制的 NX/RELRO 等安全特性与动态符号白名单检查
INSTALL.md Guix 安装步骤

整体流程即:准备 SDK 与环境 → guix-build 在隔离容器中逐平台确定性构建并生成 SHA256SUMS → (签名者)guix-codesign 附加分离签名 → 所有构建者 guix-attest 提交认证 → 各方 guix-verify 交叉验证 → 磁盘紧张时随时用 guix-clean 回收空间。所有环节均可从上述仓库文件逐行追溯,这正是"审计工具链而非信任二进制"这一安全目标的落地方式。

登录后查看全文
热门项目推荐
相关项目推荐