From 9d998206b1912e94fec13ab69249c24eb4e28de6 Mon Sep 17 00:00:00 2001 From: Jared Tobin Date: Tue, 2 Dec 2025 12:53:26 +0400 Subject: [PATCH 1/7] scripts: add utility installation checks We rely on a few utilities in our release toolchain scripts that may be missing in not-terribly-uncommon situations. get-git-tag-name.sh implicitly requires GNU grep due to the use of Perl-compatible regular expressions (-P, in particular due to the \K assertion, which has no extended regexp equivalent in BSD grep). We now check that the user is running the required grep variant. zip(1) commonly, and Perl somewhat less commonly, may be missing on Linux, so checks are added to scripts/release.sh to confirm their presence as well. If shasum is not present, we check for coreutils and, if it's available, swap in sha256sum instead. --- scripts/get-git-tag-name.sh | 6 ++++++ scripts/release.sh | 28 ++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/scripts/get-git-tag-name.sh b/scripts/get-git-tag-name.sh index b31849412..0a9ce77b8 100755 --- a/scripts/get-git-tag-name.sh +++ b/scripts/get-git-tag-name.sh @@ -15,6 +15,12 @@ get_git_tag_name() { exit 1 fi + grep_version=$(grep --version) + if [[ ! "$grep_version" =~ "GNU grep" ]]; then + echo "GNU grep is required, but cannot be found." + exit 1 + fi + # Read and parse the version fields. We interpret these fields using regex # matching which effectively serves as a basic sanity check. local app_major diff --git a/scripts/release.sh b/scripts/release.sh index 03e1eaf0d..04ed65824 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -28,7 +28,7 @@ function reproducible_tar_gzip() { # work properly. tar_version=$(tar --version) if [[ ! "$tar_version" =~ "GNU tar" ]]; then - if ! command -v "gtar"; then + if ! command -v "gtar" &>/dev/null; then echo "GNU tar is required but cannot be found!" echo "On MacOS please run 'brew install gnu-tar' to install gtar." exit 1 @@ -56,6 +56,13 @@ function reproducible_zip() { # Pin down file name encoding and timestamp time zone. export TZ=UTC + # 'zip' is commonly missing on a number of Linux distributions, and + # may be missing in containers. + if ! command -v "zip" &>/dev/null; then + echo "zip(1) is required, but cannot be found." + exit 1 + fi + # Set the date of each file in the directory that's about to be packaged to # the same timestamp and make sure the same permissions are used everywhere. chmod -R 0755 "${dir}" @@ -125,6 +132,19 @@ function build_release() { local buildtags=$3 local ldflags=$4 local goversion=$5 + local shasum_cmd="shasum -a 256" + + # If we don't have a Perl installation available, then check for + # coreutils's 'sha256sum'. + if ! command -v "shasum" &>/dev/null; then + if ! command -v "sha256sum" &>/dev/null; then + echo "Either a Perl or GNU coreutils installation is required." + exit 1 + else + # coreutils is installed, so use sha256sum instead. + shasum_cmd="sha256sum" + fi + fi # Check if the active Go version matches the specified Go version. active_go_version=$(go version | awk '{print $3}' | sed 's/go//') @@ -159,7 +179,7 @@ required Go version ($goversion)." tar -xf "${package_source}.tar" -C ${package_source} rm "${package_source}.tar" reproducible_tar_gzip ${package_source} - mv "${package_source}.tar.gz" "${package_source}-$tag.tar.gz" + mv "${package_source}.tar.gz" "${package_source}-$tag.tar.gz" for i in $sys; do os=$(echo "$i" | cut -f1 -d-) @@ -189,7 +209,7 @@ required Go version ($goversion)." # Add the hashes for the individual binaries as well for easy verification # of a single installed binary. - shasum -a 256 "${dir}/"* >> "manifest-$tag.txt" + $shasum_cmd "${dir}/"* >> "manifest-$tag.txt" if [[ $os == "windows" ]]; then reproducible_zip "${dir}" @@ -199,7 +219,7 @@ required Go version ($goversion)." done # Add the hash of the packages too, then sort by the second column (name). - shasum -a 256 $PACKAGE-* vendor* >> "manifest-$tag.txt" + $shasum_cmd $PACKAGE-* vendor* >> "manifest-$tag.txt" LC_ALL=C sort -k2 -o "manifest-$tag.txt" "manifest-$tag.txt" cat "manifest-$tag.txt" } From 81f787a6d7d76a9a021244833c9dd27fa01a720c Mon Sep 17 00:00:00 2001 From: Jared Tobin Date: Tue, 2 Dec 2025 13:36:09 +0400 Subject: [PATCH 2/7] .github: use docker build in release workflow The release workflow previously called 'make release' to produce the release artifacts. This commit changes the workflow to first build our helper Docker image and then use it to produce the release artifacts, ensuring they're constructed in a consistent environment that can be easily reproduced outside of CI. Building the Docker image here adds on perhaps 2-3 minutes to the workflow, but release workflows are performed very infrequently (only on a v* tag push) . Note also that this eliminates a location where Go toolchain version state needs to be tracked. --- .github/workflows/release.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a8d10c40a..67a240fdd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,16 +37,16 @@ jobs: exit 1 fi - - name: setup go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 - with: - go-version: '${{ env.GO_VERSION }}' - - name: checkout release tag run: git checkout ${{ env.RELEASE_VERSION }} + - name: build release helper docker image + run: | + docker build -t taproot-assets-release-helper \ + -f make/builder.Dockerfile make/ + - name: build release for all architectures - run: make release tag=${{ env.RELEASE_VERSION }} + run: make docker-release tag=${{ env.RELEASE_VERSION }} - name: Create Release uses: lightninglabs/gh-actions/action-gh-release@2024.07.24.00 From b920f60b7f1c16ca8bc758df9ad07f15f6a1809b Mon Sep 17 00:00:00 2001 From: Jared Tobin Date: Tue, 2 Dec 2025 14:39:44 +0400 Subject: [PATCH 3/7] docs/release: update copy Modernizes the text of this document, primarily by: * Recommending the use of 'make docker-release' for builds, * Pointing at the 'Assets' section of GitHub releases, and * Fleshing out the section on signing a manifest. --- docs/release.md | 187 ++++++++++++++++++++++-------------------------- 1 file changed, 87 insertions(+), 100 deletions(-) diff --git a/docs/release.md b/docs/release.md index d45a78f10..27111ac5c 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,125 +1,112 @@ # `taproot-assets`'s Reproducible Build System -This package contains the build script that the `taproot-assets` project uses in -order to build binaries for each new release. As of `go1.13`, with some new -build flags, binaries are now reproducible, allowing developers to build the -binary on distinct machines, and end up with a byte-for-byte identical binary. -However, this wasn't _fully_ solved in `go1.13`, as the build system still -includes the directory the binary is built into the binary itself. As a result, -our scripts utilize a work around needed until `go1.13.2`. +Our release artifacts are designed to be reproducible, meaning, for any +release version: -## Building a New Release +1) Release binaries produced for any target architecture correspond + byte-for-byte, independent of what host machine or architecture was + used to perform the build. For example: if a darwin-arm64 machine and + a linux-amd64 machine each build release binaries for windows-amd64, + then those produced binaries will match exactly. -### MacOS +2) These binaries, as well as the source and vendored dependencies + used to build them, are packaged in archives that can be reproduced + exactly. -The first requirement is to have [`docker`](https://www.docker.com/) -installed locally and running. The second requirement is to have `make` -installed. Everything else (including `golang`) is included in the release -helper image. +In each case, we confirm an "exact match" by comparing SHA256 digests. -To build a release, run the following commands: +## Building a Release + +To build a release, ensure `make` and `git` are installed, and that +[`Docker`](https://www.docker.com/) is installed locally and running. +Then: ```shell -$ git clone https://github.com/lightninglabs/taproot-assets.git -$ cd taproot-assets -$ git checkout # is the name of the next release/tag -$ make docker-release tag= +$ git clone https://github.com/lightninglabs/taproot-assets.git +$ cd taproot-assets +$ git checkout "$TAG" +$ make docker-release tag="$TAG" ``` -Where `` is the name of the next release of `taproot-assets`. +Where `$TAG` is the name of the desired release of `taproot-assets`. -### Linux/Windows (WSL) +Note that you can also run `make release` to avoid the dependency on +Docker and any base images, but, for verification purposes, you must +ensure to use the same Go toolchain used by the Docker image in order to +produce release binaries consistent with it. -No prior set up is needed on Linux or macOS is required in order to build the -release binaries. However, on Windows, the only way to build the release -binaries at the moment is by using the Windows Subsystem Linux. One can build -the release binaries following these steps: +## Verifying a Release -```shell -$ git clone https://github.com/lightninglabs/taproot-assets.git -$ cd taproot-assets -$ git checkout # is the name of the next release/tag -$ make release tag= -``` +To manually verify a release, ensure that `gpg`/`gpg2`, `shasum`, and +`tar`/`unzip` are installed locally, and then proceed with the following +steps: -This will then create a directory of the form `taproot-assets-` containing -archives of the release binaries for each supported operating system and -architecture, and a manifest file containing the hash of each archive. +1. Download the release manifest (`manifest-$TAG.txt`), as + well as any desired detached signatures that have been made + for it (typically named `manifest-$SIGNER-$TAG.sig`). These + are typically available from the Taproot Assets + [Releases page][ghrelease] on GitHub, under the 'Assets' header for + any given release. -## Verifying a Release + Note that the tag itself can be verified via: + + `git verify-tag "$TAG"` + +2. Verify the detached signature of the manifest file with: + + `gpg --verify "manifest-$SIGNER-$TAG.sig" "manifest-$TAG.txt"` -With `go1.13`, it's now possible for third parties to verify release binaries. -Before this version of `go`, one had to trust the release manager(s) to build the -proper binary. With this new system, third parties can now _independently_ run -the release process, and verify that all the hashes of the release binaries -match exactly that of the release binaries produced by said third parties. - -To verify a release, one must obtain the following tools (many of these come -installed by default in most Unix systems): `gpg`/`gpg2`, `shashum`, and -`tar`/`unzip`. - -Once done, verifiers can proceed with the following steps: - -1. Acquire the archive containing the release binaries for one's specific - operating system and architecture, and the manifest file along with its - signature. -2. Verify the signature of the manifest file with `gpg --verify - manifest-.txt.sig`. This will require obtaining the PGP keys which - signed the manifest file, which are included in the release notes. -3. Recompute the `SHA256` hash of the archive with `shasum -a 256 `, - locate the corresponding one in the manifest file, and ensure they match - __exactly__. - -At this point, verifiers can use the release binaries acquired if they trust -the integrity of the release manager(s). Otherwise, one can proceed with the -guide to verify the release binaries were built properly by obtaining `shasum` -and `go` (matching the same version used in the release): - -4. Extract the release binaries contained within the archive, compute their - hashes as done above, and note them down. -5. Ensure `go` is installed, matching the same version as noted in the release - notes. -6. Obtain a copy of `taproot-assets`'s source code with `git clone - https://github.com/lightninglabs/taproot-assets` and checkout the source code of the - release with `git checkout `. -7. Proceed to verify the tag with `git verify-tag ` and compile the - binaries from source for the intended operating system and architecture with - `make release sys=OS-ARCH tag=`. -8. Extract the archive found in the `taproot-assets-` directory created by - the release script and recompute the `SHA256` hash of the release binaries - (`tapd` and `tapcli`) with `shasum -a 256 `. These should match - __exactly__ as the ones noted above. - -## Verifying Docker Images - -To verify the `tapd` and `tapcli` binaries inside the -[official provided docker images](https://hub.docker.com/r/lightninglabs/taproot-assets) -against the signed, reproducible release binaries, there is a verification -script in the image that can be called (before starting the container for -example): + PGP public keys of Taproot Assets developers can be found in the + scripts/keys subdirectory of the repository root. + +3. Procure the other release artifacts, either by building the release from + source as [described above](#building-a-release), or by downloading + the desired archive(s) containing the release binaries from the + Taproot Assets [Releases page][ghrelease] on GitHub. + +4. Recompute the SHA256 hash of each artifact with e.g. `shasum -a 256 + `, locate the corresponding digest in the manifest file, + and ensure they match exactly. + +## Verifying Release Binaries in Official Docker Images + +To verify the `tapd` and `tapcli` binaries inside the [official provided +Docker images](https://hub.docker.com/r/lightninglabs/taproot-assets) +against the signed, reproducible release binaries, there is a +verification script in the image that can be called (before starting the +container for example): ```shell -$ docker run --rm --entrypoint="" lightninglabs/taproot-assets:v0.3.0 /verify-install.sh v0.3.0 -$ OK=$? -$ if [ "$OK" -ne "0" ]; then echo "Verification failed!"; exit 1; done -$ docker run lightninglabs/taproot-assets [command-line options] +$ docker run --rm --entrypoint="" \ + lightninglabs/taproot-assets:"$TAG" /verify-install.sh "$TAG" +$ OK=$? +$ if [ "$OK" -ne "0" ]; then echo "Verification failed!"; exit 1; done +$ docker run lightninglabs/taproot-assets [command-line options] ``` -# Signing an Existing Manifest File +Note that Docker images published for versions v0.7.0 and earlier don't +support this script. -If you're a developer of `taproot-assets` and are interested in attaching your -signature to the final release archive, the manifest MUST be signed in a manner -that allows your signature to be verified by our verify script -`scripts/verify-install.sh`. +# Attesting a Manifest File -Assuming you've done a local build for _all_ release targets, then you should -have a file called `manifest-TAG.txt` where `TAG` is the actual release tag -description being signed. The release script expects a particular file name for -each included signature, so we'll need to modify the name of our output -signature during signing. +If you're a developer of `taproot-assets` and want to attest a +build manifest, the manifest MUST be signed in a manner that +allows your signature to be verified by our verify script +`scripts/verify-install.sh`. + +You will first need to make a pull request adding your signing public +key, named `$SIGNER.asc`, to the scripts/keys subdirectory. Then, build +the release artifacts for *all* targets as described in [Building +a Release](#building-a-release). This will include the checksummed +artifacts manifest, `manifest-$TAG.txt`. + +To generate a detached signature for the manifest, perform the following: -Assuming `USERNAME` is your current nick as a developer, then the following -command will generate a proper signature: ```shell -$ gpg --detach-sig --output manifest-USERNAME-TAG.sig manifest-TAG.txt +$ gpg --detach-sig --output "manifest-$SIGNER-$TAG.sig" "manifest-$TAG.txt" ``` + +and then upload it to the 'Assets' of the target release [on +GitHub][ghrelease]. + +[ghrelease]: https://github.com/lightninglabs/taproot-assets/releases From 435df2514515d27849a595a5d1d266da56be3b55 Mon Sep 17 00:00:00 2001 From: Jared Tobin Date: Tue, 2 Dec 2025 15:14:34 +0400 Subject: [PATCH 4/7] Dockerfile: copy keys and verify-install.sh These were missing from the constructed image, so a recommended verification method in our docs didn't work. Now $ docker run --rm --entrypoint="" \ "$IMAGE" /verify-install.sh "$TAG" will run the verification script on the binaries in the image, as expected. --- Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dockerfile b/Dockerfile index a0e5f39d4..bc762b2f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,6 +39,14 @@ RUN apk --no-cache add \ COPY --from=builder /go/bin/tapcli /bin/ COPY --from=builder /go/bin/tapd /bin/ +# Copy the verification script and keys for signature verification. +COPY --from=builder \ + /go/src/github.com/lightninglabs/taproot-assets/scripts/verify-install.sh \ + /verify-install.sh +COPY --from=builder \ + /go/src/github.com/lightninglabs/taproot-assets/scripts/keys \ + /keys + # Store the SHA256 hash of the binaries that were just produced for later # verification. RUN sha256sum /bin/tapd /bin/tapcli > /shasums.txt \ From f568e465570980c9ffa50b41be835afa86aace0e Mon Sep 17 00:00:00 2001 From: Jared Tobin Date: Tue, 2 Dec 2025 16:06:08 +0400 Subject: [PATCH 5/7] build: pin base docker image by sha256 digest Since 81f787a6d7d76a9a021244833c9dd27fa01a720c, this image is used to build release artifacts, so pinning it by digest is good paranoia practice. --- make/builder.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/builder.Dockerfile b/make/builder.Dockerfile index 625949338..46efc10e6 100644 --- a/make/builder.Dockerfile +++ b/make/builder.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.9-bookworm +FROM golang:1.24.9-bookworm@sha256:737b40b61ce956d738bed59f18ba854d8d67e7a4c4fa63f64437f4c70247ac5b MAINTAINER Olaoluwa Osuntokun From 6f8ad5e9023a0297002c1f729b8b1c46ca101993 Mon Sep 17 00:00:00 2001 From: Jared Tobin Date: Mon, 8 Dec 2025 13:17:25 +0400 Subject: [PATCH 6/7] make: remove -it flag from docker We don't need an interactive environment, so there's no need for this flag. --- make/release_flags.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/make/release_flags.mk b/make/release_flags.mk index 45defe716..d4e333a1f 100644 --- a/make/release_flags.mk +++ b/make/release_flags.mk @@ -9,7 +9,6 @@ VERSION_CHECK = ./scripts/release.sh check-tag "$(VERSION_TAG)" "$(VERSION_GO_FI endif DOCKER_RELEASE_HELPER = docker run \ - -it \ --rm \ --user $(shell id -u):$(shell id -g) \ -v $(shell pwd):/tmp/build/taproot-assets \ From 653b6b7664c4a4fce2c55d3d251dc654c817de13 Mon Sep 17 00:00:00 2001 From: Jared Tobin Date: Mon, 8 Dec 2025 13:22:52 +0400 Subject: [PATCH 7/7] make: fix docker warnings These fixes stop Docker from warning about the deprecated MAINTAINER instruction and legacy key/value format. --- make/builder.Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/make/builder.Dockerfile b/make/builder.Dockerfile index 46efc10e6..070583d2b 100644 --- a/make/builder.Dockerfile +++ b/make/builder.Dockerfile @@ -1,10 +1,10 @@ FROM golang:1.24.9-bookworm@sha256:737b40b61ce956d738bed59f18ba854d8d67e7a4c4fa63f64437f4c70247ac5b -MAINTAINER Olaoluwa Osuntokun +LABEL maintainer="Olaoluwa Osuntokun " # Golang build related environment variables that are static and used for all # architectures/OSes. -ENV GODEBUG netdns=cgo +ENV GODEBUG=netdns=cgo ENV CGO_ENABLED=0 # Set up cache directories. Those will be mounted from the host system to speed