Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 57 additions & 57 deletions .github/workflows/native-build.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
name: Native build (cross-platform spark_frost)
name: Native build (spark_frost from pinned Lightspark source)

# Builds libspark_frost / spark_frost.dll for every supported RID directly
# on the native CI runners (macos-latest, ubuntu-latest, windows-latest)
# and emits a consolidated runtimes/ tree + SHA256SUMS.txt as a single
# artifact. Run manually before a release to refresh the binaries
# committed under src/NSpark/runtimes/.
# Rebuilds libspark_frost from buildonspark/spark at a PINNED commit, on native runners, and
# checks it against the committed C# bindings. The output is the runtimes/ tree to commit under
# src/NSpark/runtimes/, plus SHA256SUMS.txt and build-provenance attestations.
#
# Code-signing (Authenticode / macOS notarization) is intentionally NOT
# performed here for v0.x; the binaries ship unsigned with SHA-256
# integrity check via runtimes/SHA256SUMS.txt and SLSA provenance on the
# overall NuGet package. Add signing in a separate workflow once OrkLabs
# acquires the certs.
# - Linux RIDs build inside Amazon Linux 2023 (glibc 2.34) so the library loads on the AWS
# Lambda dotnet runtime and on every newer distro.
# - The build logic lives in scripts/build-native.sh so it can be reproduced locally.
# - Bump SPARK_SHA (and the protos re-vendored from the same commit) in one PR.
#
# Supported RIDs: linux-x64, linux-arm64, osx-arm64. Other RIDs are not shipped.

on:
workflow_dispatch:
inputs:
spark_ref:
description: buildonspark/spark git ref to build (branch, tag, or SHA)
required: false
default: main

permissions:
contents: read

env:
SPARK_REPO: buildonspark/spark
SPARK_REF: ${{ github.event.inputs.spark_ref || 'main' }}
SPARK_SHA: 0b3a32a05c9ac06cc411683551dd1f1bde9d0caa
AL2023_IMAGE: amazonlinux:2023@sha256:5b29412077a463b4a3a8fbc99a8cdf4b929f38a3ecc8dac10328d8f36b0099b8
UNIFFI_BINDGEN_CS_TAG: v0.9.2+v0.28.3

jobs:
build:
Expand All @@ -35,71 +31,75 @@ jobs:
fail-fast: false
matrix:
include:
- { rid: osx-arm64, os: macos-latest, target: aarch64-apple-darwin, artifact: libspark_frost.dylib }
- { rid: osx-x64, os: macos-13, target: x86_64-apple-darwin, artifact: libspark_frost.dylib }
- { rid: linux-x64, os: ubuntu-latest, target: x86_64-unknown-linux-gnu, artifact: libspark_frost.so }
- { rid: linux-arm64, os: ubuntu-24.04-arm,target: aarch64-unknown-linux-gnu, artifact: libspark_frost.so }
- { rid: win-x64, os: windows-latest, target: x86_64-pc-windows-msvc, artifact: spark_frost.dll }
- { rid: win-arm64, os: windows-latest, target: aarch64-pc-windows-msvc, artifact: spark_frost.dll }
- { rid: linux-x64, os: ubuntu-24.04 }
- { rid: linux-arm64, os: ubuntu-24.04-arm }
- { rid: osx-arm64, os: macos-14 }
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
with:
path: nspark

- uses: actions/checkout@v4
with:
repository: ${{ env.SPARK_REPO }}
ref: ${{ env.SPARK_REF }}
ref: ${{ env.SPARK_SHA }}
path: spark

- name: Setup Rust toolchain (pinned by spark/signer/rust-toolchain.toml)
- name: Build (Amazon Linux 2023 container)
if: runner.os == 'Linux'
run: |
rustup show
rustup target add ${{ matrix.target }}
working-directory: spark/signer
shell: bash
docker run --rm \
-v "$PWD/spark:/spark:ro" -v "$PWD/nspark/scripts:/scripts:ro" -v "$PWD/out:/out" \
"$AL2023_IMAGE" bash /scripts/build-native.sh ${{ matrix.rid }} /spark /out

- name: Build
run: cargo build --release -p spark-frost-uniffi --target ${{ matrix.target }}
working-directory: spark/signer
- name: Build (native macOS)
if: runner.os == 'macOS'
run: nspark/scripts/build-native.sh ${{ matrix.rid }} spark out

- name: Strip (Linux)
if: runner.os == 'Linux'
run: strip --strip-unneeded spark/signer/target/${{ matrix.target }}/release/${{ matrix.artifact }}

- name: Hash artifact
shell: bash
- name: Bindings match the committed SparkFrostBindings.cs
if: matrix.rid == 'linux-x64'
run: |
mkdir -p out/${{ matrix.rid }}/native
cp "spark/signer/target/${{ matrix.target }}/release/${{ matrix.artifact }}" \
"out/${{ matrix.rid }}/native/${{ matrix.artifact }}"
( cd out && shasum -a 256 "${{ matrix.rid }}/native/${{ matrix.artifact }}" ) | tee out/${{ matrix.rid }}.sha256
cargo install uniffi-bindgen-cs --git https://github.com/NordSecurity/uniffi-bindgen-cs \
--tag "$UNIFFI_BINDGEN_CS_TAG" --locked --root "$RUNNER_TEMP/bindgen"
"$RUNNER_TEMP/bindgen/bin/uniffi-bindgen-cs" spark/signer/spark-frost-uniffi/src/spark_frost.udl \
--lib-file out/runtimes/linux-x64/native/libspark_frost.so --out-dir "$RUNNER_TEMP/gen"
diff -u nspark/src/NSpark/Signer/SparkFrostBindings.cs "$RUNNER_TEMP/gen/spark_frost.cs"

- name: Upload artifact
uses: actions/upload-artifact@v7
- uses: actions/upload-artifact@v4
with:
name: native-${{ matrix.rid }}
path: out/
path: out/runtimes/
if-no-files-found: error

consolidate:
name: Consolidate + SHA256SUMS.txt
name: SHA256SUMS + provenance
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@v8
- uses: actions/download-artifact@v4
with:
pattern: native-*
path: runtimes-staging
path: runtimes
merge-multiple: true

- name: Generate consolidated SHA256SUMS.txt
- name: SHA256SUMS.txt
run: |
cd runtimes-staging
find . -type f \( -name "*.dll" -o -name "*.so" -o -name "*.dylib" \) \
| sort | xargs shasum -a 256 \
| sed 's| \./| |' > SHA256SUMS.txt
cd runtimes
find . -type f \( -name "*.so" -o -name "*.dylib" \) | sort | xargs sha256sum | sed 's| \./| |' > SHA256SUMS.txt
cat SHA256SUMS.txt

- name: Upload consolidated runtimes/ tree
uses: actions/upload-artifact@v7
- uses: actions/attest-build-provenance@v2
with:
subject-path: |
runtimes/**/*.so
runtimes/**/*.dylib

- uses: actions/upload-artifact@v4
with:
name: runtimes
path: runtimes-staging/
path: runtimes/
if-no-files-found: error
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ env:
jobs:
release:
name: Build, attest, publish
# Publishing to nuget.org is upstream's job; forks (e.g. Elenpay/nspark) never publish.
if: github.repository == 'orklabs/nspark'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
Expand Down
58 changes: 40 additions & 18 deletions docs/native-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,53 @@ yourself or add support for a new RID.

## Supported runtime identifiers

| RID | Status today | Target for v1 |
This fork ships three RIDs, all rebuilt from Lightspark's source at a pinned
commit by `scripts/build-native.sh` (locally) and
`.github/workflows/native-build.yml` (CI):

| RID | Built on | Notes |
|---|---|---|
| `osx-arm64` | ✅ committed (native build on Apple Silicon) | unsigned for v0.x, notarized once Apple Developer cert is acquired |
| `osx-x64` | ✅ committed (cross-compiled from arm64 via `rustup target add x86_64-apple-darwin`) | unsigned for v0.x, notarized once Apple Developer cert is acquired |
| `linux-x64` | ✅ committed | stripped |
| `linux-arm64` | ✅ committed (cross-compiled via `cargo-zigbuild`) | stripped |
| `win-x64` | ✅ committed (cross-compiled via `cargo-xwin`) | unsigned for v0.x, Authenticode-signed once cert is acquired |
| `win-arm64` | ✅ committed (cross-compiled via `cargo-xwin`) | unsigned for v0.x, Authenticode-signed once cert is acquired |

SHA-256 hashes of every shipped binary live in
`src/NSpark/runtimes/SHA256SUMS.txt` and are also packed into the NuGet at
the same path. Generated by `find … | shasum -a 256` against the binaries
themselves; verify with:
| `linux-x64` | Amazon Linux 2023 container | glibc ≤ 2.34 (AWS Lambda floor), stripped |
| `linux-arm64` | Amazon Linux 2023 container | glibc ≤ 2.34, stripped |
| `osx-arm64` | macOS, `MACOSX_DEPLOYMENT_TARGET=11.0` | stripped, ad-hoc signed |

`osx-x64`, `win-x64` and `win-arm64` are not shipped: their upstream binaries
were built outside CI from a different Spark revision, and loading them with
the current bindings is not verified.

Every build pins `SPARK_SHA` (the same commit the protos are vendored from),
uses `cargo build --locked`, a pinned `protoc`, and the Rust toolchain from
`signer/rust-toolchain.toml`, and checks:

- the 34 `uniffi_spark_frost_checksum_func_*` exports the bindings verify at load;
- on Linux, that no symbol needs a glibc newer than 2.34;
- that `uniffi-bindgen-cs` regenerates `SparkFrostBindings.cs` byte-for-byte.

The CI workflow also writes `SHA256SUMS.txt` and attaches build-provenance
attestations to each library. Verify a committed tree with:

```bash
( cd src/NSpark/runtimes && shasum -a 256 -c SHA256SUMS.txt )
```

The release CI workflow `.github/workflows/native-build.yml` rebuilds and
signs every binary on the matching native runner whenever the appropriate
secrets are configured.
### Rebuilding locally

```bash
SPARK_SRC=~/spark # buildonspark/spark checked out at SPARK_SHA (see native-build.yml)
# Per-platform digests of the pinned amazonlinux:2023 index (Docker can't pull a
# second platform of an index digest it already holds).
AL2023_ARM64=amazonlinux@sha256:442a915d07801a5805b765f4f676f455a0007af70628aa34cd73a735db5740b3
AL2023_AMD64=amazonlinux@sha256:2851878b108218cccbe7af6ab7dfb87a5883a725623384e19e6256f4072027a3
docker run --rm --platform linux/arm64 -v "$SPARK_SRC:/spark:ro" -v "$PWD/scripts:/scripts:ro" \
-v "$PWD/out:/out" "$AL2023_ARM64" bash /scripts/build-native.sh linux-arm64 /spark /out
docker run --rm --platform linux/amd64 -v "$SPARK_SRC:/spark:ro" -v "$PWD/scripts:/scripts:ro" \
-v "$PWD/out:/out" "$AL2023_AMD64" bash /scripts/build-native.sh linux-x64 /spark /out
scripts/build-native.sh osx-arm64 "$SPARK_SRC" out
```

The `NSpark.csproj` `<None Include=… Condition="Exists(…)" />` rules pack
whatever binaries are present. Missing RIDs do not break the local build;
they break consumers on that platform.
Copy
`out/runtimes/<rid>/native/*` into `src/NSpark/runtimes/` and regenerate
`SHA256SUMS.txt`. The sections below are upstream's original manual notes.

## Prerequisites

Expand Down
91 changes: 91 additions & 0 deletions scripts/build-native.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# Builds libspark_frost (buildonspark/spark signer/spark-frost-uniffi) for one RID, reproducibly,
# and checks it against the committed C# bindings. Used by .github/workflows/native-build.yml and
# runnable locally:
#
# Linux RIDs build inside Amazon Linux 2023 (glibc 2.34, the AWS Lambda floor):
# docker run --rm --platform linux/arm64 -v "$SPARK_SRC:/spark:ro" -v "$PWD:/nspark" \
# "$AL2023_IMAGE" bash /nspark/scripts/build-native.sh linux-arm64 /spark /nspark/out
# macOS arm64 builds natively:
# scripts/build-native.sh osx-arm64 "$SPARK_SRC" out
#
# The Spark checkout must be at SPARK_SHA (see native-build.yml). The script never touches it:
# the cargo target dir lives under the output dir.
set -euo pipefail

RID="${1:?usage: build-native.sh <rid> <spark-src> <out-dir>}"
SRC="$(cd "${2:?spark source dir}" && pwd)"
OUT="${3:?output dir}"
mkdir -p "$OUT" && OUT="$(cd "$OUT" && pwd)"

PROTOC_VERSION=29.3
GLIBC_MAX=2.34
EXPECTED_CHECKSUMS=34

case "$RID" in
linux-x64) TARGET=x86_64-unknown-linux-gnu; LIB=libspark_frost.so; PROTOC_ARCH=linux-x86_64; PROTOC_SHA=3e866620c5be27664f3d2fa2d656b5f3e09b5152b42f1bedbf427b333e90021a ;;
linux-arm64) TARGET=aarch64-unknown-linux-gnu; LIB=libspark_frost.so; PROTOC_ARCH=linux-aarch_64; PROTOC_SHA=6427349140e01f06e049e707a58709a4f221ae73ab9a0425bc4a00c8d0e1ab32 ;;
osx-arm64) TARGET=aarch64-apple-darwin; LIB=libspark_frost.dylib; PROTOC_ARCH=osx-aarch_64; PROTOC_SHA=2b8a3403cd097f95f3ba656e14b76c732b6b26d7f183330b11e36ef2bc028765 ;;
*) echo "unsupported RID: $RID" >&2; exit 2 ;;
esac

TOOLS="$OUT/.tools/$RID"
mkdir -p "$TOOLS"

if [[ "$RID" == linux-* ]] && command -v dnf > /dev/null; then
# Amazon Linux 2023 build container: compiler, binutils and the tools below.
dnf install -y -q gcc make tar gzip unzip findutils binutils git > /dev/null
fi

# Pinned protoc (spark-frost's build.rs runs prost-build).
if [ ! -x "$TOOLS/protoc/bin/protoc" ]; then
curl -fsSL -o "$TOOLS/protoc.zip" \
"https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${PROTOC_ARCH}.zip"
echo "$PROTOC_SHA $TOOLS/protoc.zip" | (command -v sha256sum > /dev/null && sha256sum -c - || shasum -a 256 -c -)
rm -rf "$TOOLS/protoc" && mkdir -p "$TOOLS/protoc" && unzip -q "$TOOLS/protoc.zip" -d "$TOOLS/protoc"
fi
export PROTOC="$TOOLS/protoc/bin/protoc"

# Rust toolchain pinned by spark/signer/rust-toolchain.toml.
if ! command -v rustup > /dev/null; then
curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none > /dev/null
# shellcheck disable=SC1091
source "${CARGO_HOME:-$HOME/.cargo}/env"
fi

cd "$SRC/signer"
rustup toolchain install > /dev/null
rustup target add "$TARGET" > /dev/null

# Native host builds: override signer/.cargo/config.toml's cross linkers.
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=gcc
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=gcc
export CARGO_TARGET_DIR="$OUT/.target/$RID"
export RUSTFLAGS="--remap-path-prefix=$SRC=/spark --remap-path-prefix=${CARGO_HOME:-$HOME/.cargo}=/cargo"
[[ "$RID" == osx-* ]] && export MACOSX_DEPLOYMENT_TARGET=11.0

cargo build --locked --release -p spark-frost-uniffi --target "$TARGET"

DEST="$OUT/runtimes/$RID/native"
mkdir -p "$DEST"
cp "$CARGO_TARGET_DIR/$TARGET/release/$LIB" "$DEST/$LIB"

if [[ "$RID" == linux-* ]]; then
strip --strip-unneeded "$DEST/$LIB"
glibc="$(objdump -T "$DEST/$LIB" | grep -oE 'GLIBC_[0-9.]+' | sed 's/GLIBC_//' | sort -Vu | tail -1)"
if [ "$(printf '%s\n%s\n' "$glibc" "$GLIBC_MAX" | sort -V | tail -1)" != "$GLIBC_MAX" ]; then
echo "$LIB needs GLIBC_$glibc, above the $GLIBC_MAX floor" >&2; exit 1
fi
echo "glibc floor: $glibc (max allowed $GLIBC_MAX)"
checksums="$(nm -D --defined-only "$DEST/$LIB" | grep -c uniffi_spark_frost_checksum_func_)"
else
strip -x "$DEST/$LIB"
codesign -f -s - "$DEST/$LIB"
checksums="$(nm -gU "$DEST/$LIB" | grep -c uniffi_spark_frost_checksum_func_)"
fi

if [ "$checksums" -ne "$EXPECTED_CHECKSUMS" ]; then
echo "expected $EXPECTED_CHECKSUMS uniffi checksum functions, found $checksums" >&2; exit 1
fi
echo "uniffi checksum functions: $checksums"
(cd "$OUT" && (command -v sha256sum > /dev/null && sha256sum "runtimes/$RID/native/$LIB" || shasum -a 256 "runtimes/$RID/native/$LIB"))
15 changes: 0 additions & 15 deletions src/NSpark/NSpark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,26 +81,11 @@
CopyToOutputDirectory="PreserveNewest"
Link="runtimes/linux-x64/native/libspark_frost.so"
Condition="Exists('runtimes/linux-x64/native/libspark_frost.so')" />
<None Include="runtimes/osx-x64/native/libspark_frost.dylib"
Pack="true" PackagePath="runtimes/osx-x64/native/"
CopyToOutputDirectory="PreserveNewest"
Link="runtimes/osx-x64/native/libspark_frost.dylib"
Condition="Exists('runtimes/osx-x64/native/libspark_frost.dylib')" />
<None Include="runtimes/linux-arm64/native/libspark_frost.so"
Pack="true" PackagePath="runtimes/linux-arm64/native/"
CopyToOutputDirectory="PreserveNewest"
Link="runtimes/linux-arm64/native/libspark_frost.so"
Condition="Exists('runtimes/linux-arm64/native/libspark_frost.so')" />
<None Include="runtimes/win-x64/native/spark_frost.dll"
Pack="true" PackagePath="runtimes/win-x64/native/"
CopyToOutputDirectory="PreserveNewest"
Link="runtimes/win-x64/native/spark_frost.dll"
Condition="Exists('runtimes/win-x64/native/spark_frost.dll')" />
<None Include="runtimes/win-arm64/native/spark_frost.dll"
Pack="true" PackagePath="runtimes/win-arm64/native/"
CopyToOutputDirectory="PreserveNewest"
Link="runtimes/win-arm64/native/spark_frost.dll"
Condition="Exists('runtimes/win-arm64/native/spark_frost.dll')" />
</ItemGroup>

<!-- Optional integrity manifest: SHA-256 of every shipped native binary. -->
Expand Down
9 changes: 3 additions & 6 deletions src/NSpark/runtimes/SHA256SUMS.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
7e5aed606a8aee2fb41a69a8ac57011747c03282262413d555ff933ddcef197c linux-arm64/native/libspark_frost.so
62fff70176a36a972ede575bfcc3dc5e2951c5d24fbbcce08179a014e1447496 linux-x64/native/libspark_frost.so
0e935fa9c2de1f28b7b77681f8860ea081eed0252d986b058295cb59c7a67abd osx-arm64/native/libspark_frost.dylib
82a1e33519ca3dcdb5e5350bfb6c5dd559c45d207d88f4a9308c8a3b9b6dcc04 osx-x64/native/libspark_frost.dylib
491c0c5c328a45cad06a9b3da37db0c3303644c56f4edaea57d16d078b1f5e5d win-arm64/native/spark_frost.dll
a044adacf57a52f45dd5a64a044db2b2d1255bdfa80368943490cf9c3b70fd43 win-x64/native/spark_frost.dll
7afe8a4c273db9957684d30ec7cb2dd414f7019669430ac417e8332b5acec808 linux-arm64/native/libspark_frost.so
9031ecc6d769d739bb3899f8ed00a79c943609dd9524b73db639d074f5eea264 linux-x64/native/libspark_frost.so
aaee55f4d3b492420b9a4d41feb5c1f06f5d68a4dc41865370b52c0d76761bdf osx-arm64/native/libspark_frost.dylib
Binary file modified src/NSpark/runtimes/linux-arm64/native/libspark_frost.so
Binary file not shown.
Binary file modified src/NSpark/runtimes/linux-x64/native/libspark_frost.so
Binary file not shown.
Binary file modified src/NSpark/runtimes/osx-arm64/native/libspark_frost.dylib
Binary file not shown.
Binary file not shown.
Binary file removed src/NSpark/runtimes/win-arm64/native/spark_frost.dll
Binary file not shown.
Binary file removed src/NSpark/runtimes/win-x64/native/spark_frost.dll
Binary file not shown.
Loading