From 39f46a1314e0fa1aaa212e5d50a1d29bea97b70c Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 12:06:52 -0400 Subject: [PATCH 1/7] pytest: let either HTLC claim reveal the preimage in test_onchain_middleman_fulfill_after_fail --- tests/test_closing.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_closing.py b/tests/test_closing.py index 06928c6a6053..52aac82dc4ab 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -2558,6 +2558,10 @@ def test_onchain_middleman_fulfill_after_fail(node_factory, bitcoind): partid=1, groupid=1) l3.daemon.wait_for_log('holding htlc {}'.format(rhash)) + # A block between the two gives them different deadlines. + bitcoind.generate_block(1) + sync_blockheight(bitcoind, [l1, l2, l3]) + # And it fails this one. l1.rpc.sendpay(l1.single_route(l3.info['id'], amt2), rhash, amount_msat=amt1 + amt2, @@ -2578,9 +2582,12 @@ def test_onchain_middleman_fulfill_after_fail(node_factory, bitcoind): # Now l3 learns the preimage, and spends what it's owed. l3.rpc.releasehtlc(preimage) - _, txid, _ = l3.wait_for_onchaind_tx('OUR_HTLC_SUCCESS_TX', - 'OUR_UNILATERAL/THEIR_HTLC') - bitcoind.generate_block(1, wait_for_mempool=[txid]) + l3.wait_for_onchaind_tx('OUR_HTLC_SUCCESS_TX', + 'OUR_UNILATERAL/THEIR_HTLC') + # Both HTLC claims spend l3's only wallet UTXO, and their deadlines + # differ, so only the higher-feerate one stays in the mempool. Either + # reveals the preimage. + bitcoind.generate_block(1, wait_for_mempool=1) l2.daemon.wait_for_log('THEIR_UNILATERAL/OUR_HTLC gave us preimage') # Both payments must succeed, including the one l3 said it failed. From dd77edbfa8528595f4de1ddae809e26ad14e21e0 Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sat, 24 Oct 2026 22:19:23 -0400 Subject: [PATCH 2/7] reprobuild: pin and hash-check pyenv, uv, Rust and protoc downloads --- contrib/reprobuild/Dockerfile.jammy | 40 +++++++++++++++++++++----- contrib/reprobuild/Dockerfile.noble | 39 ++++++++++++++++++++----- contrib/reprobuild/Dockerfile.resolute | 39 ++++++++++++++++++++----- tools/repro-build.armv7.sh | 9 +++++- 4 files changed, 105 insertions(+), 22 deletions(-) diff --git a/contrib/reprobuild/Dockerfile.jammy b/contrib/reprobuild/Dockerfile.jammy index ca6478c5f8cc..52eea872f9b8 100644 --- a/contrib/reprobuild/Dockerfile.jammy +++ b/contrib/reprobuild/Dockerfile.jammy @@ -6,6 +6,9 @@ ENV SOURCE_DATE_EPOCH=1672531200 ENV RUST_PROFILE=release ENV PATH=/root/.pyenv/shims:/root/.pyenv/bin:/root/.cargo/bin:/root/.local/bin:$PATH ENV PROTOC_VERSION=29.4 +ENV PYENV_COMMIT=c7eaf99f36d04ddc1b9a9b4faca3b7bf9a296041 +ENV UV_VERSION=0.13.5 +ENV RUST_VERSION=1.85.1 RUN sed -i '/updates/d' /etc/apt/sources.list && \ sed -i '/security/d' /etc/apt/sources.list @@ -32,6 +35,7 @@ RUN apt-get update && \ zip \ && cd /tmp \ && wget https://github.com/kristapsdz/lowdown/archive/refs/tags/VERSION_1_0_2.tar.gz \ + && echo "049b7883874f8a8e528dc7c4ed7b27cf7ceeb9ecf8fe71c3a8d51d574fddf84b VERSION_1_0_2.tar.gz" | sha256sum -c \ && tar -xzf VERSION_1_0_2.tar.gz \ && cd lowdown-VERSION_1_0_2 \ && ./configure \ @@ -48,6 +52,7 @@ RUN chown root:root /usr/libexec/sudo/sudoers.so # Install Python3.10 (more reproducible than relying on python3-setuptools) RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv && \ + git -C /root/.pyenv checkout --detach ${PYENV_COMMIT} && \ apt-get install -y --no-install-recommends \ libbz2-dev \ libffi-dev \ @@ -58,21 +63,42 @@ RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv && \ pyenv install 3.10.0 && \ pyenv global 3.10.0 -RUN wget -qO- https://astral.sh/uv/install.sh | sh +# Downloads below are checked against pinned hashes. uv's match the +# .sha256 files published beside them, so they only detect a change since +# pinning, as protoc's and lowdown's do. Rust's match its signed +# channel-rust-${RUST_VERSION}.toml. +RUN cd /tmp/ && \ + case "$(dpkg --print-architecture)" in \ + arm64) UV_ARCH=aarch64 UV_SHA256=4bb69cf95ed13f23713bb7bc09f241896ea0c75b7c557e615a20604b82f9c403 ;; \ + *) UV_ARCH=x86_64 UV_SHA256=f68d291c75783122dad58ab77def0e13f3233f358731db5d6d7e8715603b928f ;; \ + esac && \ + wget https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}-unknown-linux-gnu.tar.gz && \ + echo "${UV_SHA256} uv-${UV_ARCH}-unknown-linux-gnu.tar.gz" | sha256sum -c && \ + tar -xzf uv-${UV_ARCH}-unknown-linux-gnu.tar.gz && \ + mkdir -p /root/.local/bin && \ + mv uv-${UV_ARCH}-unknown-linux-gnu/uv uv-${UV_ARCH}-unknown-linux-gnu/uvx /root/.local/bin/ && \ + rm -rf uv-${UV_ARCH}-unknown-linux-gnu* -RUN wget https://sh.rustup.rs -O rustup-install.sh && \ - bash rustup-install.sh --default-toolchain none --quiet -y && \ - rm rustup-install.sh && \ - /root/.cargo/bin/rustup install 1.85 +RUN cd /tmp/ && \ + case "$(dpkg --print-architecture)" in \ + arm64) RUST_ARCH=aarch64 RUST_SHA256=6945d648b19f62052ee619b05e79e8ab8a2e18b5d5310c6c0899d552669ee61b ;; \ + *) RUST_ARCH=x86_64 RUST_SHA256=912c76f602f5f630138fca2388b2688c2c1aabcf823d71890b2b9155f743792e ;; \ + esac && \ + wget https://static.rust-lang.org/dist/rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz && \ + echo "${RUST_SHA256} rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz" | sha256sum -c && \ + tar -xJf rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz && \ + rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu/install.sh --prefix=/usr/local && \ + rm -rf rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu* # Download protoc manually, it is in the update repos which we # disabled above, so `apt-get` can't find it anymore. RUN cd /tmp/ && \ case "$(dpkg --print-architecture)" in \ - arm64) PROTOC_ARCH=aarch_64 ;; \ - *) PROTOC_ARCH=x86_64 ;; \ + arm64) PROTOC_ARCH=aarch_64 PROTOC_SHA256=c219a9213a8fa26bc9f644e36e7d756a5b8a7c8a629b60f9d0d1248e59d2fa31 ;; \ + *) PROTOC_ARCH=x86_64 PROTOC_SHA256=3074ee900792fa7bf06ab56bd14af83048016cac08abf2a2ae34aaa571a9c110 ;; \ esac && \ wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip && \ + echo "${PROTOC_SHA256} protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" | sha256sum -c && \ unzip protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip && \ mv bin/protoc /usr/local/bin && \ rm -rf include bin protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip diff --git a/contrib/reprobuild/Dockerfile.noble b/contrib/reprobuild/Dockerfile.noble index ee64a30e283b..9f8646d3c06c 100644 --- a/contrib/reprobuild/Dockerfile.noble +++ b/contrib/reprobuild/Dockerfile.noble @@ -6,6 +6,9 @@ ENV SOURCE_DATE_EPOCH=1672531200 ENV RUST_PROFILE=release ENV PATH=/root/.pyenv/shims:/root/.pyenv/bin:/root/.cargo/bin:/root/.local/bin:$PATH ENV PROTOC_VERSION=29.4 +ENV PYENV_COMMIT=c7eaf99f36d04ddc1b9a9b4faca3b7bf9a296041 +ENV UV_VERSION=0.13.5 +ENV RUST_VERSION=1.85.1 RUN sed -i '/updates/d' /etc/apt/sources.list && \ sed -i '/security/d' /etc/apt/sources.list && \ @@ -39,6 +42,7 @@ RUN git config --global --add safe.directory /repo/.git # Install Python3.10 (more reproducible than relying on python3-setuptools) RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv && \ + git -C /root/.pyenv checkout --detach ${PYENV_COMMIT} && \ apt-get install -y --no-install-recommends \ libbz2-dev \ libffi-dev \ @@ -48,21 +52,42 @@ RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv && \ pyenv install 3.10.0 && \ pyenv global 3.10.0 -RUN wget -qO- https://astral.sh/uv/install.sh | sh +# Downloads below are checked against pinned hashes. uv's match the +# .sha256 files published beside them, so they only detect a change since +# pinning, as protoc's and lowdown's do. Rust's match its signed +# channel-rust-${RUST_VERSION}.toml. +RUN cd /tmp/ && \ + case "$(dpkg --print-architecture)" in \ + arm64) UV_ARCH=aarch64 UV_SHA256=4bb69cf95ed13f23713bb7bc09f241896ea0c75b7c557e615a20604b82f9c403 ;; \ + *) UV_ARCH=x86_64 UV_SHA256=f68d291c75783122dad58ab77def0e13f3233f358731db5d6d7e8715603b928f ;; \ + esac && \ + wget https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}-unknown-linux-gnu.tar.gz && \ + echo "${UV_SHA256} uv-${UV_ARCH}-unknown-linux-gnu.tar.gz" | sha256sum -c && \ + tar -xzf uv-${UV_ARCH}-unknown-linux-gnu.tar.gz && \ + mkdir -p /root/.local/bin && \ + mv uv-${UV_ARCH}-unknown-linux-gnu/uv uv-${UV_ARCH}-unknown-linux-gnu/uvx /root/.local/bin/ && \ + rm -rf uv-${UV_ARCH}-unknown-linux-gnu* -RUN wget https://sh.rustup.rs -O rustup-install.sh && \ - bash rustup-install.sh --default-toolchain none --quiet -y && \ - rm rustup-install.sh && \ - /root/.cargo/bin/rustup install 1.85 +RUN cd /tmp/ && \ + case "$(dpkg --print-architecture)" in \ + arm64) RUST_ARCH=aarch64 RUST_SHA256=6945d648b19f62052ee619b05e79e8ab8a2e18b5d5310c6c0899d552669ee61b ;; \ + *) RUST_ARCH=x86_64 RUST_SHA256=912c76f602f5f630138fca2388b2688c2c1aabcf823d71890b2b9155f743792e ;; \ + esac && \ + wget https://static.rust-lang.org/dist/rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz && \ + echo "${RUST_SHA256} rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz" | sha256sum -c && \ + tar -xJf rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz && \ + rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu/install.sh --prefix=/usr/local && \ + rm -rf rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu* # Download protoc manually, it is in the update repos which we # disabled above, so `apt-get` can't find it anymore. RUN cd /tmp/ && \ case "$(dpkg --print-architecture)" in \ - arm64) PROTOC_ARCH=aarch_64 ;; \ - *) PROTOC_ARCH=x86_64 ;; \ + arm64) PROTOC_ARCH=aarch_64 PROTOC_SHA256=c219a9213a8fa26bc9f644e36e7d756a5b8a7c8a629b60f9d0d1248e59d2fa31 ;; \ + *) PROTOC_ARCH=x86_64 PROTOC_SHA256=3074ee900792fa7bf06ab56bd14af83048016cac08abf2a2ae34aaa571a9c110 ;; \ esac && \ wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip && \ + echo "${PROTOC_SHA256} protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" | sha256sum -c && \ unzip protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip && \ mv bin/protoc /usr/local/bin && \ rm -rf include bin protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip diff --git a/contrib/reprobuild/Dockerfile.resolute b/contrib/reprobuild/Dockerfile.resolute index 092e7d6dddf1..55f14cfdb8bc 100644 --- a/contrib/reprobuild/Dockerfile.resolute +++ b/contrib/reprobuild/Dockerfile.resolute @@ -6,6 +6,9 @@ ENV SOURCE_DATE_EPOCH=1672531200 ENV RUST_PROFILE=release ENV PATH=/root/.pyenv/shims:/root/.pyenv/bin:/root/.cargo/bin:/root/.local/bin:$PATH ENV PROTOC_VERSION=29.4 +ENV PYENV_COMMIT=c7eaf99f36d04ddc1b9a9b4faca3b7bf9a296041 +ENV UV_VERSION=0.13.5 +ENV RUST_VERSION=1.85.1 RUN sed -i '/updates/d' /etc/apt/sources.list && \ sed -i '/security/d' /etc/apt/sources.list && \ @@ -39,6 +42,7 @@ RUN git config --global --add safe.directory /repo/.git # Install Python3.10 (more reproducible than relying on python3-setuptools) RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv && \ + git -C /root/.pyenv checkout --detach ${PYENV_COMMIT} && \ apt-get install -y --no-install-recommends \ libbz2-dev \ libffi-dev \ @@ -48,21 +52,42 @@ RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv && \ pyenv install 3.10.0 && \ pyenv global 3.10.0 -RUN wget -qO- https://astral.sh/uv/install.sh | sh +# Downloads below are checked against pinned hashes. uv's match the +# .sha256 files published beside them, so they only detect a change since +# pinning, as protoc's and lowdown's do. Rust's match its signed +# channel-rust-${RUST_VERSION}.toml. +RUN cd /tmp/ && \ + case "$(dpkg --print-architecture)" in \ + arm64) UV_ARCH=aarch64 UV_SHA256=4bb69cf95ed13f23713bb7bc09f241896ea0c75b7c557e615a20604b82f9c403 ;; \ + *) UV_ARCH=x86_64 UV_SHA256=f68d291c75783122dad58ab77def0e13f3233f358731db5d6d7e8715603b928f ;; \ + esac && \ + wget https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}-unknown-linux-gnu.tar.gz && \ + echo "${UV_SHA256} uv-${UV_ARCH}-unknown-linux-gnu.tar.gz" | sha256sum -c && \ + tar -xzf uv-${UV_ARCH}-unknown-linux-gnu.tar.gz && \ + mkdir -p /root/.local/bin && \ + mv uv-${UV_ARCH}-unknown-linux-gnu/uv uv-${UV_ARCH}-unknown-linux-gnu/uvx /root/.local/bin/ && \ + rm -rf uv-${UV_ARCH}-unknown-linux-gnu* -RUN wget https://sh.rustup.rs -O rustup-install.sh && \ - bash rustup-install.sh --default-toolchain none --quiet -y && \ - rm rustup-install.sh && \ - /root/.cargo/bin/rustup install 1.85 +RUN cd /tmp/ && \ + case "$(dpkg --print-architecture)" in \ + arm64) RUST_ARCH=aarch64 RUST_SHA256=6945d648b19f62052ee619b05e79e8ab8a2e18b5d5310c6c0899d552669ee61b ;; \ + *) RUST_ARCH=x86_64 RUST_SHA256=912c76f602f5f630138fca2388b2688c2c1aabcf823d71890b2b9155f743792e ;; \ + esac && \ + wget https://static.rust-lang.org/dist/rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz && \ + echo "${RUST_SHA256} rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz" | sha256sum -c && \ + tar -xJf rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu.tar.xz && \ + rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu/install.sh --prefix=/usr/local && \ + rm -rf rust-${RUST_VERSION}-${RUST_ARCH}-unknown-linux-gnu* # Download protoc manually, it is in the update repos which we # disabled above, so `apt-get` can't find it anymore. RUN cd /tmp/ && \ case "$(dpkg --print-architecture)" in \ - arm64) PROTOC_ARCH=aarch_64 ;; \ - *) PROTOC_ARCH=x86_64 ;; \ + arm64) PROTOC_ARCH=aarch_64 PROTOC_SHA256=c219a9213a8fa26bc9f644e36e7d756a5b8a7c8a629b60f9d0d1248e59d2fa31 ;; \ + *) PROTOC_ARCH=x86_64 PROTOC_SHA256=3074ee900792fa7bf06ab56bd14af83048016cac08abf2a2ae34aaa571a9c110 ;; \ esac && \ wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip && \ + echo "${PROTOC_SHA256} protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" | sha256sum -c && \ unzip protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip && \ mv bin/protoc /usr/local/bin && \ rm -rf include bin protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip diff --git a/tools/repro-build.armv7.sh b/tools/repro-build.armv7.sh index 5c3b789abfb1..8a3d26c29eab 100755 --- a/tools/repro-build.armv7.sh +++ b/tools/repro-build.armv7.sh @@ -291,8 +291,15 @@ sha256sum -c /tmp/SHASUMS # shellcheck disable=SC2046 $INST $(cut -c66- < /tmp/SHASUMS) +# The hash is for the RUST_VERSION pinned in contrib/reprobuild/Dockerfile.*, +# so bump both together. RUST_TARGET=armv7-unknown-linux-gnueabihf -rustup target add "$RUST_TARGET" +RUST_STD=rust-std-${RUST_VERSION}-${RUST_TARGET} +wget -q -O "/tmp/$RUST_STD.tar.xz" "https://static.rust-lang.org/dist/$RUST_STD.tar.xz" +echo "994c3c192dd312e422ba0008bf97b3f121692658658390882558d34d29927d45 /tmp/$RUST_STD.tar.xz" | sha256sum -c +tar -C /tmp -xJf "/tmp/$RUST_STD.tar.xz" +"/tmp/$RUST_STD/install.sh" --prefix=/usr/local +rm -rf "/tmp/$RUST_STD" "/tmp/$RUST_STD.tar.xz" CARGO_BUILD_TARGET=$RUST_TARGET CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc export CARGO_BUILD_TARGET CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER From cef60249ef80efde6aa1df528a9d3a460170ef42 Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sat, 24 Oct 2026 22:20:01 -0400 Subject: [PATCH 3/7] ci: give arm64 release tarballs their own SHA256SUMS manifest --- .github/workflows/release-blake2b.yml | 22 ++++++++++++++----- README.md | 8 ++++--- SECURITY.md | 2 ++ .../security-policy.md | 2 ++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release-blake2b.yml b/.github/workflows/release-blake2b.yml index fa828cc28daa..42feadddafbc 100644 --- a/.github/workflows/release-blake2b.yml +++ b/.github/workflows/release-blake2b.yml @@ -1,9 +1,11 @@ name: Release BLAKE2b # Builds the reproducible Ubuntu amd64 and arm64 tarballs for a -# vX.Y.Z-blake2b.N tag and drafts its GitHub release with an unsigned -# SHA256SUMS. The release captain rebuilds, signs SHA256SUMS with their own -# key, uploads the .asc and publishes the draft by hand. +# vX.Y.Z-blake2b.N tag and drafts its GitHub release with unsigned +# SHA256SUMS manifests, one for amd64 and one for arm64. The release captain +# rebuilds, signs only the manifests whose tarballs they rebuilt, uploads the +# .asc files, deletes any manifest left unsigned with its tarballs, and +# publishes the draft by hand. on: push: tags: @@ -166,8 +168,15 @@ jobs: echo "::error::Expected exactly 6 tarballs for $VERSION, found: $(ls)" exit 1 fi - LC_ALL=C sha256sum "$@" > SHA256SUMS-"$VERSION" - cat SHA256SUMS-"$VERSION" + # One manifest per architecture, so a signer signs only what they + # rebuilt. + LC_ALL=C sha256sum clightning-"$VERSION"-*-amd64.tar.xz > SHA256SUMS-"$VERSION" + LC_ALL=C sha256sum clightning-"$VERSION"-*-arm64.tar.xz > SHA256SUMS-"$VERSION"-arm64 + if [ "$(wc -l < SHA256SUMS-"$VERSION")" -ne 3 ] || [ "$(wc -l < SHA256SUMS-"$VERSION"-arm64)" -ne 3 ]; then + echo "::error::Expected 3 amd64 and 3 arm64 tarballs for $VERSION" + exit 1 + fi + cat SHA256SUMS-"$VERSION" SHA256SUMS-"$VERSION"-arm64 cp ../contrib/keys/privkeyio.txt privkeyio-signing-key.asc - name: Upload release files @@ -212,4 +221,5 @@ jobs: fi cd release gh release create "$VERSION" --draft --verify-tag --title "$VERSION" --notes "" \ - clightning-"$VERSION"-*.tar.xz SHA256SUMS-"$VERSION" privkeyio-signing-key.asc + clightning-"$VERSION"-*.tar.xz SHA256SUMS-"$VERSION" SHA256SUMS-"$VERSION"-arm64 \ + privkeyio-signing-key.asc diff --git a/README.md b/README.md index 6a474f990c83..d3384e3a0560 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,16 @@ git clone https://github.com/privkeyio/lightning.git ## Releases -Published under [Releases](https://github.com/privkeyio/lightning/releases) with signed checksum files covering every artifact: +Published under [Releases](https://github.com/privkeyio/lightning/releases) with signed checksum files covering every artifact, one for amd64 (`SHA256SUMS-`) and one for arm64 (`SHA256SUMS--arm64`). For amd64: ```bash gpg --import privkeyio-signing-key.asc -gpg --verify SHA256SUMS-*.asc SHA256SUMS-* -sha256sum -c SHA256SUMS-* --ignore-missing +gpg --verify SHA256SUMS-.asc SHA256SUMS- +sha256sum -c SHA256SUMS- --ignore-missing ``` +For arm64, use `SHA256SUMS--arm64` and its `.asc` the same way. + The signing key is `A47D 99B6 DB0D 715D 40C5 9A20 23AE 8A8E A7E2 4E38`. From `v26.06.9-blake2b.8` on, releases are signed by its subkey `1004 97C5 8727 107D 9B9D A0FA F160 CC14 F64F 8EF8`. For StartOS, the packaged build lives at [privkeyio/cln-startos](https://github.com/privkeyio/cln-startos). diff --git a/SECURITY.md b/SECURITY.md index dd7e32ea1e55..ec13ac9596d3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -52,3 +52,5 @@ gpg --import privkeyio-signing-key.asc gpg --verify SHA256SUMS-.asc SHA256SUMS- sha256sum -c --ignore-missing SHA256SUMS- ``` + +arm64 tarballs have their own manifest, `SHA256SUMS--arm64`, verified the same way. Releases up to `v26.06.9-blake2b.9` have a single `SHA256SUMS-` covering both. diff --git a/doc/contribute-to-core-lightning/security-policy.md b/doc/contribute-to-core-lightning/security-policy.md index 6e0d28d3837c..5114d2703b97 100644 --- a/doc/contribute-to-core-lightning/security-policy.md +++ b/doc/contribute-to-core-lightning/security-policy.md @@ -58,3 +58,5 @@ gpg --import privkeyio-signing-key.asc gpg --verify SHA256SUMS-.asc SHA256SUMS- sha256sum -c --ignore-missing SHA256SUMS- ``` + +arm64 tarballs have their own manifest, `SHA256SUMS--arm64`, verified the same way. Releases up to `v26.06.9-blake2b.9` have a single `SHA256SUMS-` covering both. From 34d979b30b1fb5ca1ab97e3c31209875caa7340f Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 04:47:02 -0400 Subject: [PATCH 4/7] contrib: let cl-repro.sh build with rootless Docker and for arm64 under emulation --- contrib/cl-repro.sh | 46 ++++++++++++++++----- doc/getting-started/advanced-setup/repro.md | 2 + 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/contrib/cl-repro.sh b/contrib/cl-repro.sh index 5e8ee36930c0..c106a43592d7 100755 --- a/contrib/cl-repro.sh +++ b/contrib/cl-repro.sh @@ -7,6 +7,11 @@ # It automates the process described here: # https://docs.corelightning.org/docs/repro#build-environment-setup # https://docs.corelightning.org/docs/repro#builder-image-setup +# +# ROOTLESS=1 builds without root, for rootless Docker. PLATFORM=linux/arm64 +# builds the arm64 images on an amd64 host, under qemu-user emulation. + +set -e # Assuming script will be used from the root of lightning directory LIGHTNING_DIR=$PWD @@ -14,18 +19,39 @@ LIGHTNING_DIR=$PWD LIGHTNING_DIR=$(echo "$LIGHTNING_DIR" | sed 's|/contrib$||') echo "Lightning Directory: $LIGHTNING_DIR" +SUDO=sudo +NODEV="" +if [ -n "$ROOTLESS" ]; then + SUDO="" + # A rootless container cannot create device nodes, so skip debootstrap's + # device setup: docker provides /dev when the image runs. Packages are + # still checked against the signed Release file. + NODEV="sed -i -e 's/^check_sane_mount () {/check_sane_mount () { return 0;/' -e 's/^setup_devices () {/setup_devices () { return 0;/' /usr/share/debootstrap/functions &&" +fi +PLAT="" +case "$PLATFORM" in + "") ;; + linux/amd64|linux/arm64) PLAT="--platform $PLATFORM" ;; + *) echo "Unsupported PLATFORM $PLATFORM: use linux/amd64 or linux/arm64" >&2; exit 1 ;; +esac + +# arm64 images are suffixed so both architectures can coexist, and so the +# bin-Ubuntu--arm64 targets resolve. amd64 keeps its bare name. +SUFFIX="" +case "${PLATFORM:-$(uname -m)}" in + linux/arm64|aarch64|arm64) SUFFIX="-arm64" ;; +esac + for v in jammy noble resolute; do echo "Building base image for $v" - sudo docker run -v "$LIGHTNING_DIR":/build ubuntu:$v \ - bash -c "apt-get update && apt-get install -y debootstrap && debootstrap $v /build/$v" - sudo tar -C $v -c . | sudo docker import - $v + # shellcheck disable=SC2086 + $SUDO docker run --rm $PLAT -v "$LIGHTNING_DIR":/build ubuntu:$v \ + bash -c "rm -rf /build/$v && apt-get update && apt-get install -y debootstrap && $NODEV debootstrap $v /build/$v" + # shellcheck disable=SC2086 + $SUDO docker run --rm $PLAT -v "$LIGHTNING_DIR":/build ubuntu:$v tar -C /build/$v -c . | $SUDO docker import $PLAT - $v echo "$v release:" - sudo docker run ubuntu:$v cat /etc/lsb-release + $SUDO docker run --rm $v cat /etc/lsb-release echo "Building CL repro $v:" - # arm64 images are suffixed so both architectures can coexist, and so the - # bin-Ubuntu--arm64 targets resolve. amd64 keeps its bare name. - SUFFIX="" - [ "$(dpkg --print-architecture)" = arm64 ] && SUFFIX="-arm64" - # shellcheck disable=SC2024 - sudo docker build --no-cache -t cl-repro-$v$SUFFIX - < "$LIGHTNING_DIR"/contrib/reprobuild/Dockerfile.$v + # shellcheck disable=SC2024,SC2086 + $SUDO docker build $PLAT --no-cache -t cl-repro-$v$SUFFIX - < "$LIGHTNING_DIR"/contrib/reprobuild/Dockerfile.$v done diff --git a/doc/getting-started/advanced-setup/repro.md b/doc/getting-started/advanced-setup/repro.md index 5116ad570056..1a49a7d2ea5a 100644 --- a/doc/getting-started/advanced-setup/repro.md +++ b/doc/getting-started/advanced-setup/repro.md @@ -28,6 +28,8 @@ The build environments are a set of docker images that are created directly from The script `contrib/cl-repro.sh` covers below `Base image creation` and `Builder image setup` steps. You can skip these steps by simply running the `contrib/cl-repro.sh` script. +With rootless Docker, run `ROOTLESS=1 contrib/cl-repro.sh`. A rootless container cannot create device nodes, so this skips debootstrap's device setup; Docker provides `/dev` when the image runs, and packages are still checked against Ubuntu's signed `Release` file. To build the arm64 images on an amd64 host under qemu-user emulation (with `qemu-aarch64` registered in binfmt_misc with the `F` flag), add `PLATFORM=linux/arm64`. Both produce the same tarballs as the default path. + ## Base image creation Depending on the distribution that we want to build for the instructions to create a base image can vary. In the following sections we discuss the specific instructions for each distribution, whereas the instructions are identical again once we have the base image. From 1037ac4392f9d4883cdd0effcf0115daa2f40d05 Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 04:47:02 -0400 Subject: [PATCH 5/7] README: sign only rebuilt tarballs and document how to rebuild a release --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3384e3a0560..eabc78be7d97 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ git clone https://github.com/privkeyio/lightning.git ## Releases -Published under [Releases](https://github.com/privkeyio/lightning/releases) with signed checksum files covering every artifact, one for amd64 (`SHA256SUMS-`) and one for arm64 (`SHA256SUMS--arm64`). For amd64: +Published under [Releases](https://github.com/privkeyio/lightning/releases) with signed checksum files, one for amd64 (`SHA256SUMS-`) and one for arm64 (`SHA256SUMS--arm64`). Releases up to `v26.06.9-blake2b.9` have a single `SHA256SUMS-` covering both. For amd64: ```bash gpg --import privkeyio-signing-key.asc @@ -46,10 +46,20 @@ gpg --verify SHA256SUMS-.asc SHA256SUMS- sha256sum -c SHA256SUMS- --ignore-missing ``` -For arm64, use `SHA256SUMS--arm64` and its `.asc` the same way. +For arm64, use `SHA256SUMS--arm64` and its `.asc` the same way. A manifest without an `.asc` is not published. The signing key is `A47D 99B6 DB0D 715D 40C5 9A20 23AE 8A8E A7E2 4E38`. From `v26.06.9-blake2b.8` on, releases are signed by its subkey `1004 97C5 8727 107D 9B9D A0FA F160 CC14 F64F 8EF8`. +A manifest is only signed by someone who rebuilt every tarball it lists and got the same hashes. To rebuild a release yourself, download and verify its manifest as above into the repository root, check out its tag and run: + +```bash +ROOTLESS=1 contrib/cl-repro.sh +tools/build-release.sh --without-zip --force-version --force-mtime bin-Ubuntu +(cd release && sha256sum -c --ignore-missing ../SHA256SUMS-) +``` + +`` is the tag's commit date in UTC, from `TZ=UTC git log -1 --format=%cd --date=format-local:%Y-%m-%d `. Drop `ROOTLESS=1` on rootful Docker. `v26.06.9-blake2b.8` and `v26.06.9-blake2b.9` predate the pinned builder images, so build their images from `blake2b-unified` before checking out the tag. For arm64, add `PLATFORM=linux/arm64` on an amd64 host, build `bin-Ubuntu-jammy-arm64 bin-Ubuntu-noble-arm64 bin-Ubuntu-resolute-arm64`, and check `release-arm64/` against `SHA256SUMS--arm64`, or the single manifest for releases up to `v26.06.9-blake2b.9`. + For StartOS, the packaged build lives at [privkeyio/cln-startos](https://github.com/privkeyio/cln-startos). --- From 854935798859f5fd69760f723d59eadb05b9ec4d Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 04:53:15 -0400 Subject: [PATCH 6/7] reprobuild: give debootstrap the Ubuntu archive keyring explicitly --- .github/workflows/release-blake2b.yml | 2 +- contrib/cl-repro.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-blake2b.yml b/.github/workflows/release-blake2b.yml index 42feadddafbc..8652b7746055 100644 --- a/.github/workflows/release-blake2b.yml +++ b/.github/workflows/release-blake2b.yml @@ -123,7 +123,7 @@ jobs: sudo docker run --rm -v "$(pwd)":/build "ubuntu:$distribution" bash -c "\ apt-get update && \ apt-get install -y debootstrap && \ - debootstrap $distribution /build/$distribution" + debootstrap --keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg $distribution /build/$distribution" sudo tar -C "$distribution" -c . | docker import - "$distribution" docker build -t "cl-repro-$distribution$suffix" - < "contrib/reprobuild/Dockerfile.$distribution" diff --git a/contrib/cl-repro.sh b/contrib/cl-repro.sh index c106a43592d7..a40d66e3a8b2 100755 --- a/contrib/cl-repro.sh +++ b/contrib/cl-repro.sh @@ -46,7 +46,7 @@ for v in jammy noble resolute; do echo "Building base image for $v" # shellcheck disable=SC2086 $SUDO docker run --rm $PLAT -v "$LIGHTNING_DIR":/build ubuntu:$v \ - bash -c "rm -rf /build/$v && apt-get update && apt-get install -y debootstrap && $NODEV debootstrap $v /build/$v" + bash -c "rm -rf /build/$v && apt-get update && apt-get install -y debootstrap && $NODEV debootstrap --keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg $v /build/$v" # shellcheck disable=SC2086 $SUDO docker run --rm $PLAT -v "$LIGHTNING_DIR":/build ubuntu:$v tar -C /build/$v -c . | $SUDO docker import $PLAT - $v echo "$v release:" From 7ba1a914ebaaabee2ce6cecb93d24995b71fbbb0 Mon Sep 17 00:00:00 2001 From: Kyle Santiago Date: Sun, 25 Oct 2026 12:14:50 -0400 Subject: [PATCH 7/7] contrib: run cl-repro.sh only from the repo root and import the rootfs from a file --- contrib/cl-repro.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/contrib/cl-repro.sh b/contrib/cl-repro.sh index a40d66e3a8b2..ee735b4a4e20 100755 --- a/contrib/cl-repro.sh +++ b/contrib/cl-repro.sh @@ -18,6 +18,11 @@ LIGHTNING_DIR=$PWD # Remove '/contrib' from the end if it exists LIGHTNING_DIR=$(echo "$LIGHTNING_DIR" | sed 's|/contrib$||') echo "Lightning Directory: $LIGHTNING_DIR" +# The base image steps delete $LIGHTNING_DIR/ as root. +if [ ! -f "$LIGHTNING_DIR"/contrib/cl-repro.sh ]; then + echo "Run this from the root of the lightning repository" >&2 + exit 1 +fi SUDO=sudo NODEV="" @@ -42,16 +47,21 @@ case "${PLATFORM:-$(uname -m)}" in linux/arm64|aarch64|arm64) SUFFIX="-arm64" ;; esac +ROOTFS=$(mktemp -d) for v in jammy noble resolute; do echo "Building base image for $v" # shellcheck disable=SC2086 $SUDO docker run --rm $PLAT -v "$LIGHTNING_DIR":/build ubuntu:$v \ bash -c "rm -rf /build/$v && apt-get update && apt-get install -y debootstrap && $NODEV debootstrap --keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg $v /build/$v" # shellcheck disable=SC2086 - $SUDO docker run --rm $PLAT -v "$LIGHTNING_DIR":/build ubuntu:$v tar -C /build/$v -c . | $SUDO docker import $PLAT - $v + $SUDO docker run --rm $PLAT -v "$LIGHTNING_DIR":/build -v "$ROOTFS":/out ubuntu:$v tar -C /build/$v -cf /out/$v.tar . + # shellcheck disable=SC2086 + $SUDO docker import $PLAT "$ROOTFS/$v.tar" $v + $SUDO rm -f "$ROOTFS/$v.tar" echo "$v release:" $SUDO docker run --rm $v cat /etc/lsb-release echo "Building CL repro $v:" # shellcheck disable=SC2024,SC2086 $SUDO docker build $PLAT --no-cache -t cl-repro-$v$SUFFIX - < "$LIGHTNING_DIR"/contrib/reprobuild/Dockerfile.$v done +rmdir "$ROOTFS"