From e5a25a3da1a1f2f157449c2df0933c9331064e61 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Wed, 21 Oct 2026 23:55:23 -0600 Subject: [PATCH 1/9] Build the Rust library through dnix: a verified prebuilt by default, Nix otherwise The build hook is dnix's. By default it downloads the library of the release dnix.lock names and verifies its hash; in source mode it builds the same library with Nix and verifies the same hash; in dev mode it builds with Nix for work on the Rust code. dnix.json names the library, the asset, the Rust workspace and crate, the Rust and Xcode versions, the deployment floors, and the eleven targets; dnix.lock records what the source builds to and names release native-a58277144bcb. The flake hands dnix the configuration. OpenSSL, which native-tls uses on Linux and Android, is compiled inside that build from the vendored crate. The prebuilt manifest, the source fingerprint, and their tests and tools go; two-line tools update the lock and verify one platform against the release. --- .gitignore | 3 + dnix.json | 33 ++++ dnix.lock | 17 ++ example/pubspec.lock | 41 +---- flake.lock | 123 +++++++++++++ flake.nix | 10 + hook/build.dart | 20 +- hook/prebuilt.dart | 292 ------------------------------ pubspec.yaml | 6 +- test/source_fingerprint_test.dart | 83 --------- tool/prebuilt_manifest.dart | 34 ---- tool/update_lock.dart | 4 + tool/verify_release.dart | 4 + 13 files changed, 209 insertions(+), 461 deletions(-) create mode 100644 dnix.json create mode 100644 dnix.lock create mode 100644 flake.lock create mode 100644 flake.nix delete mode 100644 hook/prebuilt.dart delete mode 100644 test/source_fingerprint_test.dart delete mode 100644 tool/prebuilt_manifest.dart create mode 100644 tool/update_lock.dart create mode 100644 tool/verify_release.dart diff --git a/.gitignore b/.gitignore index cab43c36..4956b3db 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ rust/target/CACHEDIR.TAG # Cargo output from building a vendored crate on its own. rust/patches/*/target/ rust/patches/*/Cargo.lock + +# Per-checkout dnix build mode override (see README). +.dnix-build diff --git a/dnix.json b/dnix.json new file mode 100644 index 00000000..b5f8354d --- /dev/null +++ b/dnix.json @@ -0,0 +1,33 @@ +{ + "name": "tor_ffi_plugin", + "repository": "https://github.com/cypherstack/tor", + "library": "tor_ffi_plugin", + "asset": "tor_ffi_plugin_bindings_generated.dart", + "rust": { + "workspace": "rust", + "crate": "tor_ffi_plugin" + }, + "toolchains": { + "rust": "1.99.1", + "xcode": "27.0" + }, + "floors": { + "macos": 11, + "ios": 13, + "android": 24, + "glibc": "2.28" + }, + "targets": [ + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "aarch64-apple-ios", + "aarch64-apple-ios-sim", + "x86_64-apple-ios", + "aarch64-linux-android", + "armv7-linux-androideabi", + "x86_64-linux-android", + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-msvc" + ] +} diff --git a/dnix.lock b/dnix.lock new file mode 100644 index 00000000..53923788 --- /dev/null +++ b/dnix.lock @@ -0,0 +1,17 @@ +{ + "release": "a58277144bcb", + "date": "2026-10-22", + "files": { + "aarch64-apple-darwin/libtor_ffi_plugin.dylib": "6fe5e646859757f69cdb353d109fb25142fdd6bb8f2ddfd9b4ae1690ecc0bd17", + "aarch64-apple-ios-sim/libtor_ffi_plugin.dylib": "b8c7163364ef74798c9d9226f3d50d2125b42b5cd06b4a36846ab681dd5d47b7", + "aarch64-apple-ios/libtor_ffi_plugin.dylib": "c97d74128ea8198cfc3e53f82c41020fe88cbf0cdcd376aa9cfa11a92edeee9f", + "aarch64-linux-android/libtor_ffi_plugin.so": "724b8959bb156cab220ca453ffcf828b0714d629bc00298520452dbac77d9239", + "aarch64-unknown-linux-gnu/libtor_ffi_plugin.so": "5a09fb8d6f9aa9ad8ae03d48ef3f678eb8fe10578f512e1b848bf34642141082", + "armv7-linux-androideabi/libtor_ffi_plugin.so": "de8ffd6664c09cf2b9873bc4f022d7ca30c3525281c12b19d3d5b7beac47085d", + "x86_64-apple-darwin/libtor_ffi_plugin.dylib": "0d9721ced60ed08ef2df85fbbe298dd34cc144d7e111327dbc11b7c565506f5c", + "x86_64-apple-ios/libtor_ffi_plugin.dylib": "36f5b5be3e6cdf4781beb360ae2135a0195f80e8427aa96ec2dcc9f1b534613c", + "x86_64-linux-android/libtor_ffi_plugin.so": "bbb79a3c5fb484d1bacb91fc46e54cc8604ea3f3a9df12772d1c06660ff35edc", + "x86_64-pc-windows-msvc/tor_ffi_plugin.dll": "f8a4fc7d8c571255dc9add08402a5795ff7a17bf32f23eebf9f9894b3c596783", + "x86_64-unknown-linux-gnu/libtor_ffi_plugin.so": "6d2b9d5f0f3471a008ed1b3eab5bc37f05abf7321ea7d4dfa2e91fa2ccb98f98" + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 2252ad0d..eb781c01 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -73,6 +73,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + dnix: + dependency: transitive + description: + path: "." + ref: "448c5da6e61b3f0e62b4eae6d08b49158a9e45b2" + resolved-ref: "448c5da6e61b3f0e62b4eae6d08b49158a9e45b2" + url: "https://github.com/cypherstack/dnix.git" + source: git + version: "0.1.0" fake_async: dependency: transitive description: @@ -203,14 +212,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" - native_toolchain_rust: - dependency: transitive - description: - name: native_toolchain_rust - sha256: "8469a0e32a47a5c84b7fd300de9fd0e8082aac76c37922986324f5082a58350d" - url: "https://pub.dev" - source: hosted - version: "1.0.6" objective_c: dependency: transitive description: @@ -283,14 +284,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" - url: "https://pub.dev" - source: hosted - version: "7.0.2" platform: dependency: transitive description: @@ -392,14 +385,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.12" - toml: - dependency: transitive - description: - name: toml - sha256: "35a35f782228656a2af31e8c73d1353cc4ef3d683fd68af1111b44631879c05e" - url: "https://pub.dev" - source: hosted - version: "0.18.0" tor_ffi_plugin: dependency: "direct main" description: @@ -431,14 +416,6 @@ packages: url: "https://pub.dev" source: hosted version: "15.3.0" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" xdg_directories: dependency: transitive description: diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..4e0af1be --- /dev/null +++ b/flake.lock @@ -0,0 +1,123 @@ +{ + "nodes": { + "dnix": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "zig-overlay": "zig-overlay" + }, + "locked": { + "lastModified": 1792648476, + "narHash": "sha256-sNBlb09ut3gmCv2vMg+jEiUdF+slLC+T/9S+WBE6p3A=", + "owner": "cypherstack", + "repo": "dnix", + "rev": "448c5da6e61b3f0e62b4eae6d08b49158a9e45b2", + "type": "github" + }, + "original": { + "owner": "cypherstack", + "repo": "dnix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1792502884, + "narHash": "sha256-IoNisKcGbUjdd45qCqywGMCYmZ5EnkxezvLII0FWwNA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "02c087f3fe673c9e317f206a627cb6b910336dbe", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "dnix": "dnix" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "dnix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1792567368, + "narHash": "sha256-mrM+zFDRO1pI85t2SEq5R739BIq7Nv5XkFtVZK9nkZM=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "83dd732f2428827f35d997deae108a2ced5125f4", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "flake": false, + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "dnix", + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1792585651, + "narHash": "sha256-Kg8Qdi6ur+K5hNJwav0pEqdylGd7vXxEPeBvaKW5nxA=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "98819174f1bb7c9961c3ebbb00d19fc4e1c1960d", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..8884b8f1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,10 @@ +{ + description = "Reproducible reference builds of tor_ffi_plugin's Rust library"; + + inputs.dnix.url = "github:cypherstack/dnix"; + + outputs = { self, dnix, ... }: dnix.lib.package { + src = self; + config = ./dnix.json; + }; +} diff --git a/hook/build.dart b/hook/build.dart index 19d6fd1c..7997f795 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -1,19 +1,3 @@ -import 'package:code_assets/code_assets.dart'; -import 'package:hooks/hooks.dart'; -import 'package:native_toolchain_rust/native_toolchain_rust.dart'; +import 'package:dnix/dnix.dart'; -import 'prebuilt.dart'; - -Future main(List args) async { - await build(args, (input, output) async { - if (!input.config.buildCodeAssets) return; - if (await usePrebuilt(input, output)) return; - - output.dependencies.addAll(await sourceDependencies(input.packageRoot)); - - await const RustBuilder( - assetName: 'tor_ffi_plugin_bindings_generated.dart', - extraCargoBuildArgs: ['--locked'], - ).run(input: input, output: output); - }); -} +void main(List args) => buildNativeLibrary(args); diff --git a/hook/prebuilt.dart b/hook/prebuilt.dart deleted file mode 100644 index b6c321b8..00000000 --- a/hook/prebuilt.dart +++ /dev/null @@ -1,292 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:code_assets/code_assets.dart'; -import 'package:crypto/crypto.dart'; -import 'package:hooks/hooks.dart'; - -// Deployment floors used by the release workflow; zero means not applicable. -const releaseTargets = { - 'armv7-linux-androideabi': 24, - 'aarch64-linux-android': 24, - 'x86_64-linux-android': 24, - 'aarch64-apple-ios': 13, - 'aarch64-apple-ios-sim': 13, - 'x86_64-apple-ios': 13, - 'aarch64-apple-darwin': 11, - 'x86_64-apple-darwin': 11, - 'aarch64-unknown-linux-gnu': 0, - 'x86_64-unknown-linux-gnu': 0, - 'x86_64-pc-windows-msvc': 0, -}; - -String libraryName(String target) => target.contains('windows') - ? 'tor_ffi_plugin.dll' - : 'libtor_ffi_plugin.${target.contains('apple') ? 'dylib' : 'so'}'; - -String releaseFileName(String target) => '$target-${libraryName(target)}'; - -Future fileHash(File file) async => - (await sha256.bind(file.openRead()).first).toString(); - -/// Native source files tracked as a whole. -const _sourceFiles = [ - 'rust/Cargo.toml', - 'rust/Cargo.lock', - 'rust/rust-toolchain.toml', - 'rust/build.rs', - 'hook/build.dart', - 'lib/tor_ffi_plugin_bindings_generated.dart', -]; - -/// Native source trees whose entries are tracked recursively. -const _sourceDirectories = ['rust/src/', 'rust/patches/']; - -/// Native inputs to track for Flutter's hook cache. -/// -/// Directories are included so additions and deletions invalidate the build. -/// Hidden entries are not native inputs and are skipped. -Future> sourceDependencies(Uri root) async => [ - for (final path in [..._sourceFiles, ..._sourceDirectories]) - root.resolve(path), - for (final path in _sourceDirectories) - ...await _sourceEntries(Directory.fromUri(root.resolve(path))), -]..sort((a, b) => a.path.compareTo(b.path)); - -// Hash paths and contents so additions, deletions and ABI changes invalidate -// prebuilts. -Future<({String hash, List dependencies})> sourceFingerprint( - Uri root, -) async { - final dependencies = await sourceDependencies(root); - final hashes = StringBuffer(); - for (final uri in dependencies) { - final type = await FileSystemEntity.type( - uri.toFilePath(), - followLinks: false, - ); - if (type == FileSystemEntityType.link) { - throw StateError('Native source symlinks are unsupported: $uri'); - } - if (type == FileSystemEntityType.directory) continue; - // Git checkouts may use CRLF on Windows. Hash bytes rather than decoded - // text so a file that is not UTF-8 cannot fail the build. - final bytes = _lfLineEndings(await File.fromUri(uri).readAsBytes()); - hashes.writeln( - '${uri.path.substring(root.path.length)}:${sha256.convert(bytes)}', - ); - } - return ( - hash: sha256.convert(utf8.encode(hashes.toString())).toString(), - dependencies: dependencies, - ); -} - -/// Entries under [directory], without descending into skipped directories. -Future> _sourceEntries(Directory directory) async => [ - await for (final entry in directory.list(followLinks: false)) - if (!_isSkipped(entry)) ...[ - entry.uri, - if (entry is Directory) ...await _sourceEntries(entry), - ], -]; - -/// Whether [entry] is hidden, such as .DS_Store, or Cargo output left by -/// building a vendored crate on its own: its `target` directory, or its -/// lockfile, which Cargo ignores for dependencies. -bool _isSkipped(FileSystemEntity entry) { - final name = entry.uri.pathSegments.lastWhere((s) => s.isNotEmpty); - if (name.startsWith('.') || name == 'Cargo.lock') return true; - return entry is Directory && - name == 'target' && - File.fromUri(entry.parent.uri.resolve('Cargo.toml')).existsSync(); -} - -/// [bytes] with each CRLF replaced by LF. -List _lfLineEndings(List bytes) { - if (!bytes.contains(0x0d)) return bytes; - return [ - for (var i = 0; i < bytes.length; i++) - if (bytes[i] != 0x0d || i + 1 == bytes.length || bytes[i + 1] != 0x0a) - bytes[i], - ]; -} - -String rustTarget(CodeConfig code) => switch (( - code.targetOS, - code.targetArchitecture, -)) { - (OS.android, Architecture.arm) => 'armv7-linux-androideabi', - (OS.android, Architecture.arm64) => 'aarch64-linux-android', - (OS.android, Architecture.x64) => 'x86_64-linux-android', - (OS.iOS, Architecture.arm64) => - code.iOS.targetSdk == IOSSdk.iPhoneSimulator - ? 'aarch64-apple-ios-sim' - : 'aarch64-apple-ios', - (OS.iOS, Architecture.x64) - when code.iOS.targetSdk == IOSSdk.iPhoneSimulator => - 'x86_64-apple-ios', - (OS.macOS, Architecture.arm64) => 'aarch64-apple-darwin', - (OS.macOS, Architecture.x64) => 'x86_64-apple-darwin', - (OS.linux, Architecture.arm64) => 'aarch64-unknown-linux-gnu', - (OS.linux, Architecture.x64) => 'x86_64-unknown-linux-gnu', - (OS.windows, Architecture.x64) => 'x86_64-pc-windows-msvc', - _ => throw UnsupportedError( - 'No prebuilt for ${code.targetOS}/${code.targetArchitecture}. Use native_build: source.', - ), -}; - -String _digest(Object? value) { - if (value is! String || !RegExp(r'^[a-fA-F0-9]{64}$').hasMatch(value)) { - throw FormatException('Expected a SHA-256 digest (64 hex characters).'); - } - return value.toLowerCase(); -} - -Future _download( - HttpClient client, - Uri url, - File file, - String hash, - int limit, -) async { - for (var redirects = 0; redirects <= 5; redirects++) { - if (url.scheme != 'https' || - url.host.isEmpty || - url.userInfo.isNotEmpty || - url.hasFragment) { - throw FormatException( - 'Prebuilt URLs require HTTPS without credentials or fragments.', - ); - } - final request = await client - .getUrl(url) - .timeout(const Duration(seconds: 30)); - request.followRedirects = false; - final response = await request.close().timeout(const Duration(seconds: 30)); - if ([301, 302, 303, 307, 308].contains(response.statusCode)) { - final location = response.headers.value(HttpHeaders.locationHeader); - await response.listen(null).cancel(); - if (location == null) { - throw HttpException('Missing redirect location', uri: url); - } - url = url.resolve(location); - continue; - } - if (response.statusCode != HttpStatus.ok || - response.contentLength > limit) { - await response.listen(null).cancel(); - throw HttpException( - 'Invalid prebuilt response: HTTP ${response.statusCode}', - uri: url, - ); - } - final sink = file.openWrite(); - var received = 0; - try { - await sink.addStream( - response.timeout(const Duration(seconds: 30)).map((bytes) { - received += bytes.length; - if (received > limit) { - throw StateError('Prebuilt exceeds expected size.'); - } - return bytes; - }), - ); - } finally { - await sink.close(); - } - if (await fileHash(file) != hash) { - throw StateError('Checksum mismatch: $url'); - } - return; - } - throw HttpException('Too many prebuilt redirects', uri: url); -} - -Future usePrebuilt(BuildInput input, BuildOutputBuilder output) async { - final mode = input.userDefines['native_build'] ?? 'source'; - if (mode == 'source') return false; - if (mode != 'prebuilt') { - throw FormatException('native_build must be source or prebuilt.'); - } - final url = input.userDefines['prebuilt_manifest_url']; - if (url is! String) throw FormatException('Missing prebuilt_manifest_url.'); - final pin = _digest(input.userDefines['prebuilt_manifest_sha256']); - final manifestUrl = Uri.parse(url); - final code = input.config.code; - if (code.linkModePreference == LinkModePreference.static || - code.sanitizer != null) { - throw UnsupportedError( - 'Prebuilts require dynamic linking without sanitizers.', - ); - } - final target = rustTarget(code); - final minimum = switch (code.targetOS) { - OS.android => code.android.targetNdkApi, - OS.iOS => code.iOS.targetVersion, - OS.macOS => code.macOS.targetVersion, - _ => 0, - }; - if (minimum < releaseTargets[target]!) { - throw UnsupportedError( - '$target prebuilts require OS/API ${releaseTargets[target]}.', - ); - } - final source = await sourceFingerprint(input.packageRoot); - output.dependencies.addAll(source.dependencies); - final directory = await Directory.fromUri(input.outputDirectory) - .create(recursive: true); - final temporary = await directory.createTemp('prebuilt-'); - final client = HttpClient()..connectionTimeout = const Duration(seconds: 30); - try { - final manifestFile = File.fromUri(temporary.uri.resolve('manifest.json')); - await _download(client, manifestUrl, manifestFile, pin, 1024 * 1024); - final manifest = - jsonDecode(await manifestFile.readAsString()) as Map; - if (manifest['schema_version'] != 1 || - manifest['package'] != 'tor_ffi_plugin') { - throw FormatException('Unsupported tor_ffi_plugin manifest.'); - } - if (manifest['source_sha256'] != source.hash) { - throw StateError( - 'Prebuilt source mismatch. Use the matching package revision or native_build: source.', - ); - } - final artifact = (manifest['artifacts'] as Map)[target]; - if (artifact is! Map) { - throw StateError('Missing prebuilt for $target.'); - } - final hash = _digest(artifact['sha256']); - final size = artifact['size']; - if (size is! int || size <= 0 || size > 1024 * 1024 * 1024) { - throw FormatException('Invalid prebuilt size for $target.'); - } - final library = File.fromUri(temporary.uri.resolve(libraryName(target))); - await _download( - client, - manifestUrl.resolve(releaseFileName(target)), - library, - hash, - size, - ); - if (await library.length() != size) { - throw StateError('Prebuilt size mismatch.'); - } - final bundled = await library.copy( - directory.uri.resolve(libraryName(target)).toFilePath(), - ); - output.assets.code.add( - CodeAsset( - package: input.packageName, - name: 'tor_ffi_plugin_bindings_generated.dart', - linkMode: DynamicLoadingBundled(), - file: bundled.uri, - ), - ); - return true; - } finally { - client.close(force: true); - await temporary.delete(recursive: true); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index ec548808..7ba63925 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,10 +12,12 @@ dependencies: sdk: flutter ffi: ^2.0.1 code_assets: ^1.2.1 - crypto: ^3.0.7 hooks: ^2.0.2 meta: ^1.16.0 - native_toolchain_rust: ^1.0.6 + dnix: + git: + url: https://github.com/cypherstack/dnix.git + ref: 448c5da6e61b3f0e62b4eae6d08b49158a9e45b2 dev_dependencies: ffigen: ^21.0.0 diff --git a/test/source_fingerprint_test.dart b/test/source_fingerprint_test.dart deleted file mode 100644 index e25de4f5..00000000 --- a/test/source_fingerprint_test.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; - -import '../hook/prebuilt.dart'; - -/// A minimal package tree containing every tracked native input. -Future sourceTree() async { - final root = await Directory.systemTemp.createTemp('tor-sources-'); - addTearDown(() => root.delete(recursive: true)); - for (final path in [ - 'rust/Cargo.toml', - 'rust/Cargo.lock', - 'rust/rust-toolchain.toml', - 'rust/build.rs', - 'hook/build.dart', - 'lib/tor_ffi_plugin_bindings_generated.dart', - 'rust/src/lib.rs', - 'rust/patches/saturating-time/Cargo.toml', - 'rust/patches/saturating-time/src/lib.rs', - ]) { - final file = File.fromUri(root.uri.resolve(path)); - await file.parent.create(recursive: true); - await file.writeAsString(path); - } - return root; -} - -void main() { - test('source dependencies are reusable without calculating hashes', () async { - final root = await sourceTree(); - - final dependencies = await sourceDependencies(root.uri); - final fingerprint = await sourceFingerprint(root.uri); - - expect(dependencies, fingerprint.dependencies); - expect(dependencies, contains(root.uri.resolve('rust/src/lib.rs'))); - expect(fingerprint.hash, hasLength(64)); - }); - - test('a change to a vendored patch changes the fingerprint', () async { - final root = await sourceTree(); - const patched = 'rust/patches/saturating-time/src/lib.rs'; - final before = await sourceFingerprint(root.uri); - - expect(before.dependencies, contains(root.uri.resolve(patched))); - await File.fromUri(root.uri.resolve(patched)).writeAsString('changed'); - final after = await sourceFingerprint(root.uri); - - expect(after.hash, isNot(before.hash)); - }); - - test('CRLF line endings do not change the fingerprint', () async { - final root = await sourceTree(); - final file = File.fromUri(root.uri.resolve('rust/src/lib.rs')); - await file.writeAsBytes('a\nb\r\nc\r'.codeUnits); - final lf = await sourceFingerprint(root.uri); - await file.writeAsBytes('a\r\nb\r\nc\r'.codeUnits); - final crlf = await sourceFingerprint(root.uri); - - expect(crlf.hash, lf.hash); - }); - - test('cargo output beside a vendored patch is not an input', () async { - final root = await sourceTree(); - final before = await sourceFingerprint(root.uri); - - // What `cargo test` leaves behind when run inside the vendored crate. - const crate = 'rust/patches/saturating-time/'; - final target = Directory.fromUri(root.uri.resolve('${crate}target/debug/')); - await target.create(recursive: true); - await File.fromUri(target.uri.resolve('libsaturating_time.rlib')) - .writeAsString('built'); - await File.fromUri(root.uri.resolve('${crate}Cargo.lock')) - .writeAsString('# lock'); - final after = await sourceFingerprint(root.uri); - - expect(after.hash, before.hash); - expect(after.dependencies, isNot(contains(target.uri))); - // The package's own lockfile is still an input. - expect(after.dependencies, contains(root.uri.resolve('rust/Cargo.lock'))); - }); -} diff --git a/tool/prebuilt_manifest.dart b/tool/prebuilt_manifest.dart deleted file mode 100644 index e0b64caf..00000000 --- a/tool/prebuilt_manifest.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import '../hook/prebuilt.dart'; - -// Run with dart --packages=.dart_tool/package_config.json to avoid build hooks. -Future main(List args) async { - if (args.length != 1) { - throw ArgumentError('Usage: prebuilt_manifest.dart '); - } - final directory = Directory(args.single).absolute; - final artifacts = {}; - for (final target in releaseTargets.keys) { - final file = File.fromUri(directory.uri.resolve(releaseFileName(target))); - final size = await file.length(); - if (size <= 0 || size > 1024 * 1024 * 1024) { - throw StateError('Invalid prebuilt size: ${file.path}'); - } - artifacts[target] = {'sha256': await fileHash(file), 'size': size}; - } - final source = await sourceFingerprint(Platform.script.resolve('../')); - final manifest = File.fromUri(directory.uri.resolve('manifest.json')); - final contents = { - 'schema_version': 1, - 'package': 'tor_ffi_plugin', - 'source_sha256': source.hash, - 'artifacts': artifacts, - }; - await manifest.writeAsString( - '${const JsonEncoder.withIndent(' ').convert(contents)}\n', - ); - await File.fromUri(directory.uri.resolve('manifest.sha256')) - .writeAsString('${await fileHash(manifest)} manifest.json\n'); -} diff --git a/tool/update_lock.dart b/tool/update_lock.dart new file mode 100644 index 00000000..73089196 --- /dev/null +++ b/tool/update_lock.dart @@ -0,0 +1,4 @@ +import 'dart:io'; +import 'package:dnix/tools.dart'; + +void main(List args) async => exit(await updateLock(args)); diff --git a/tool/verify_release.dart b/tool/verify_release.dart new file mode 100644 index 00000000..0dd9d24c --- /dev/null +++ b/tool/verify_release.dart @@ -0,0 +1,4 @@ +import 'dart:io'; +import 'package:dnix/tools.dart'; + +void main(List args) async => exit(await verifyRelease(args)); From 0e668411e83e1b121faab2c7facdb1d462dff367 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Wed, 21 Oct 2026 23:55:23 -0600 Subject: [PATCH 2/9] Verify every push on four hosts and publish native releases from the lock dnix's reusable workflow builds the library on Linux x86_64, Linux aarch64, macOS with Xcode 27.0, and Linux inside WSL2 on Windows, rebuilds each sandboxed target, compares every host with every other and with the committed lock, and on main publishes the release the lock names if it does not exist yet. Runs on main are never cancelled, since one may be publishing. The test workflow runs the Flutter tests on every OS, against the prebuilt library once its release exists and from source before that, plus the example's widget tests, the crate's tests, lints and audit on Linux, and the Windows time, IPv6 and file release tests on pull requests, as before. The per-OS build matrix, its smoke tests and the packaged example builds went with the prebuilt pipeline they served. --- .github/workflows/native-prebuilts.yml | 308 ------------------------- .github/workflows/native.yml | 25 ++ .github/workflows/tests.yml | 131 +++++++++++ tool/ci/check_macos_app.sh | 11 - tool/ci/smoke_native.py | 11 - 5 files changed, 156 insertions(+), 330 deletions(-) delete mode 100644 .github/workflows/native-prebuilts.yml create mode 100644 .github/workflows/native.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 tool/ci/check_macos_app.sh delete mode 100644 tool/ci/smoke_native.py diff --git a/.github/workflows/native-prebuilts.yml b/.github/workflows/native-prebuilts.yml deleted file mode 100644 index ef344187..00000000 --- a/.github/workflows/native-prebuilts.yml +++ /dev/null @@ -1,308 +0,0 @@ -name: Native and package validation - -on: - pull_request: - push: - tags: ['native-*'] - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: native-prebuilts-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - verify: - name: Rust, Dart, and lifecycle tests - runs-on: ubuntu-22.04 - timeout-minutes: 60 - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Install Linux prerequisites - run: | - sudo apt-get update - sudo apt-get install -y build-essential libgtk-3-dev liblzma-dev ninja-build pkg-config perl - - name: Install pinned Rust toolchain - run: bash tool/ci/install_rust.sh --component clippy,rustfmt - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - - uses: subosito/flutter-action@1508160852fb97248640997f7cfb38da241df0ba # v2.9.1 - with: - flutter-version: '3.47.2' - channel: stable - - name: Verify Rust - run: | - cargo fmt --manifest-path rust/Cargo.toml --check - cargo test --locked --all-targets --manifest-path rust/Cargo.toml - cargo clippy --locked --all-targets --manifest-path rust/Cargo.toml -- -D warnings - - name: Verify Dart and native lifecycle - run: | - flutter pub get - flutter analyze - flutter test - - name: Test example widgets - working-directory: example - run: flutter test - - name: Cache pinned cargo-audit binary - id: cargo-audit-cache - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - with: - path: ~/.cargo/bin/cargo-audit - key: cargo-audit-${{ runner.os }}-${{ runner.arch }}-0.22.2-${{ hashFiles('rust/rust-toolchain.toml') }} - - name: Install pinned cargo-audit - if: steps.cargo-audit-cache.outputs.cache-hit != 'true' - run: cargo install cargo-audit --version 0.22.2 --locked - - name: Audit Rust dependencies - run: | - cargo audit --file rust/Cargo.lock \ - --deny unsound \ - --deny yanked \ - --ignore RUSTSEC-2021-0145 \ - --ignore RUSTSEC-2023-0071 - - windows-tests: - if: github.event_name == 'pull_request' - name: Windows time, IPv6, and file release tests - runs-on: windows-2022 - timeout-minutes: 60 - env: - NATIVE_TARGET: x86_64-pc-windows-msvc - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Use short Windows build paths - shell: pwsh - run: ./tool/ci/windows_paths.ps1 - - name: Install pinned Rust toolchain - run: bash tool/ci/install_rust.sh --target "$NATIVE_TARGET" - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - key: windows-tests - cache-targets: false - - name: Test time limits, IPv6 fallback, and file release - run: bash tool/ci/test_windows.sh - - build: - if: github.event_name != 'pull_request' && github.event.deleted != true - name: ${{ matrix.target }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 120 - strategy: - fail-fast: false - matrix: - include: - - {target: x86_64-unknown-linux-gnu, runner: ubuntu-22.04} - - {target: aarch64-unknown-linux-gnu, runner: ubuntu-24.04-arm} - - {target: x86_64-pc-windows-msvc, runner: windows-2022} - - {target: aarch64-apple-darwin, runner: macos-15} - - {target: x86_64-apple-darwin, runner: macos-15-intel} - - {target: aarch64-apple-ios, runner: macos-15} - - {target: aarch64-apple-ios-sim, runner: macos-15} - - {target: x86_64-apple-ios, runner: macos-15-intel} - - {target: armv7-linux-androideabi, runner: ubuntu-22.04} - - {target: aarch64-linux-android, runner: ubuntu-22.04} - - {target: x86_64-linux-android, runner: ubuntu-22.04} - env: - NATIVE_TARGET: ${{ matrix.target }} - CARGO_TARGET_DIR: ${{ github.workspace }}/rust/target - MACOSX_DEPLOYMENT_TARGET: '11.0' - IPHONEOS_DEPLOYMENT_TARGET: '13.0' - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Use short Windows build paths - if: runner.os == 'Windows' - shell: pwsh - run: ./tool/ci/windows_paths.ps1 - - name: Install Linux prerequisites - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y build-essential pkg-config perl - - name: Install Android NDK - if: contains(matrix.target, 'android') - run: | - "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" 'ndk;28.2.13676358' - echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> "$GITHUB_ENV" - - name: Install pinned Rust toolchain - run: bash tool/ci/install_rust.sh --target "$NATIVE_TARGET" - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - key: ${{ matrix.target }} - # Windows output lives outside the workspace; cache its downloads only. - cache-targets: ${{ runner.os != 'Windows' }} - - name: Build and package - run: | - case "$NATIVE_TARGET" in - *android*) - ndk_bin="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" - export PATH="$ndk_bin:$PATH" - key=${NATIVE_TARGET//-/_} - clang_target=${NATIVE_TARGET/armv7-/armv7a-} - export "CC_$key=$ndk_bin/${clang_target}24-clang" - export "CXX_$key=$ndk_bin/${clang_target}24-clang++" - export "AR_$key=$ndk_bin/llvm-ar" - export "RANLIB_$key=$ndk_bin/llvm-ranlib" - export "CARGO_TARGET_$(echo "$key" | tr '[:lower:]' '[:upper:]')_LINKER=$ndk_bin/${clang_target}24-clang" - export RUSTFLAGS='-C link-arg=-Wl,-z,max-page-size=16384' - ;; - *apple*) - export RUSTFLAGS='-C link-arg=-Wl,-install_name,@rpath/libtor_ffi_plugin.dylib' - case "$NATIVE_TARGET" in - aarch64-apple-ios) SDKROOT=$(xcrun --sdk iphoneos --show-sdk-path) ;; - *-ios*) SDKROOT=$(xcrun --sdk iphonesimulator --show-sdk-path) ;; - esac - export SDKROOT - ;; - esac - cargo build --locked --release --lib --manifest-path rust/Cargo.toml \ - --target "$NATIVE_TARGET" - case "$NATIVE_TARGET" in - *windows*) library=tor_ffi_plugin.dll ;; - *apple*) library=libtor_ffi_plugin.dylib ;; - *) library=libtor_ffi_plugin.so ;; - esac - mkdir -p artifacts - cp "$CARGO_TARGET_DIR/$NATIVE_TARGET/release/$library" "artifacts/$NATIVE_TARGET-$library" - - name: Test time limits, IPv6 fallback, and file release - if: runner.os == 'Windows' - run: bash tool/ci/test_windows.sh - - name: Smoke test desktop library - if: ${{ !contains(matrix.target, 'android') && !contains(matrix.target, '-ios') }} - run: python tool/ci/smoke_native.py artifacts/* - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: prebuilt-${{ matrix.target }} - path: artifacts/* - if-no-files-found: error - - package: - if: github.event_name == 'pull_request' - name: Package example (${{ matrix.platform }}) - runs-on: ${{ matrix.runner }} - timeout-minutes: 120 - strategy: - fail-fast: false - matrix: - include: - - {platform: linux, runner: ubuntu-22.04} - - {platform: android, runner: ubuntu-22.04} - - {platform: ios, runner: macos-15} - - {platform: macos, runner: macos-15} - - {platform: windows, runner: windows-2022} - defaults: - run: - shell: bash - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Install Linux prerequisites - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y build-essential clang cmake libgtk-3-dev liblzma-dev ninja-build pkg-config perl - - name: Install Android Java - if: matrix.platform == 'android' - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 - with: - distribution: temurin - java-version: '17' - - name: Install Android NDK - if: matrix.platform == 'android' - run: | - "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" 'ndk;28.2.13676358' - echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358" >> "$GITHUB_ENV" - - name: Install pinned Rust toolchain - run: | - case "${{ matrix.platform }}" in - linux) target=x86_64-unknown-linux-gnu ;; - android) target=aarch64-linux-android ;; - ios) target=aarch64-apple-ios ;; - # flutter build macos is universal, so the hook also builds x86_64. - macos) target=aarch64-apple-darwin,x86_64-apple-darwin ;; - windows) target=x86_64-pc-windows-msvc ;; - esac - bash tool/ci/install_rust.sh --target "$target" - - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - with: - workspaces: rust - key: package-${{ matrix.platform }} - - uses: subosito/flutter-action@1508160852fb97248640997f7cfb38da241df0ba # v2.9.1 - with: - flutter-version: '3.47.2' - channel: stable - - name: Build packaged example - working-directory: example - run: | - case "${{ matrix.platform }}" in - linux) flutter build linux --release ;; - android) flutter build apk --release --target-platform android-arm64 ;; - ios) flutter build ios --release --no-codesign ;; - macos) flutter build macos --release ;; - windows) flutter build windows --release ;; - esac - - name: Load packaged macOS library - if: matrix.platform == 'macos' - run: bash tool/ci/check_macos_app.sh - - name: Load packaged Windows library - if: matrix.platform == 'windows' - run: python tool/ci/smoke_native.py example/build/windows/x64/runner/Release/tor_ffi_plugin.dll - - release: - if: github.event_name != 'pull_request' && github.event.deleted != true - needs: [verify, build] - runs-on: ubuntu-22.04 - permissions: - contents: write - steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - pattern: prebuilt-* - path: artifacts - merge-multiple: true - - uses: subosito/flutter-action@1508160852fb97248640997f7cfb38da241df0ba # v2.9.1 - with: - flutter-version: '3.47.2' - channel: stable - - run: flutter pub get - - name: Generate manifest - run: dart --packages=.dart_tool/package_config.json tool/prebuilt_manifest.dart artifacts - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: native-release - path: artifacts/* - if-no-files-found: error - - name: Publish release - if: startsWith(github.ref, 'refs/tags/native-') - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ github.ref_name }} - shell: bash - run: | - [[ "$RELEASE_TAG" =~ ^native-[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || exit 1 - read -r manifest_pin _ < artifacts/manifest.sha256 - cat > "$RUNNER_TEMP/release-notes.md" <> "$GITHUB_OUTPUT" + - name: Choose the build mode + shell: bash + run: echo '${{ steps.release.outputs.published == 'true' && 'prebuilt' || 'source' }}' > .dnix-build + - name: Run flutter test + if: runner.os != 'Windows' || steps.release.outputs.published == 'true' + shell: bash + run: | + if ! flutter test 2>&1 | tee test.log; then + echo "::error::$(tail -c 1500 test.log | tr '\n' ' ')" + exit 1 + fi + - name: Test example widgets + if: runner.os == 'Linux' && runner.arch == 'X64' + working-directory: example + run: flutter test + + # The crate's own checks, as before the native library moved to dnix. + rust: + name: Rust tests, lints, and audit + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install Linux prerequisites + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config perl + - name: Install pinned Rust toolchain + run: bash tool/ci/install_rust.sh --component clippy,rustfmt + - uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + - name: Verify Rust + run: | + cargo fmt --manifest-path rust/Cargo.toml --check + cargo test --locked --all-targets --manifest-path rust/Cargo.toml + cargo clippy --locked --all-targets --manifest-path rust/Cargo.toml -- -D warnings + - name: Cache pinned cargo-audit binary + id: cargo-audit-cache + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/cargo-audit + key: cargo-audit-${{ runner.os }}-${{ runner.arch }}-0.22.2-${{ hashFiles('rust/rust-toolchain.toml') }} + - name: Install pinned cargo-audit + if: steps.cargo-audit-cache.outputs.cache-hit != 'true' + run: cargo install cargo-audit --version 0.22.2 --locked + - name: Audit Rust dependencies + run: | + cargo audit --file rust/Cargo.lock \ + --deny unsound \ + --deny yanked \ + --ignore RUSTSEC-2021-0145 \ + --ignore RUSTSEC-2023-0071 + + windows-tests: + if: github.event_name == 'pull_request' + name: Windows time, IPv6, and file release tests + runs-on: windows-2025 + timeout-minutes: 60 + env: + NATIVE_TARGET: x86_64-pc-windows-msvc + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Use short Windows build paths + shell: pwsh + run: ./tool/ci/windows_paths.ps1 + - name: Install pinned Rust toolchain + run: bash tool/ci/install_rust.sh --target "$NATIVE_TARGET" + - uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + key: windows-tests + cache-targets: false + - name: Test time limits, IPv6 fallback, and file release + run: bash tool/ci/test_windows.sh + diff --git a/tool/ci/check_macos_app.sh b/tool/ci/check_macos_app.sh deleted file mode 100644 index b6374da4..00000000 --- a/tool/ci/check_macos_app.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# Check that the packaged macOS example's library exports and loads. -set -euo pipefail -cd "$(dirname "$0")/../.." -library=$(find -L example/build/macos/Build/Products/Release \ - -type f \ - -path '*/Contents/Frameworks/tor_ffi_plugin.framework/tor_ffi_plugin' \ - -print -quit) -test -n "$library" -nm -gj "$library" | grep -Fx _tor_proxy_stop -python3 tool/ci/smoke_native.py "$library" diff --git a/tool/ci/smoke_native.py b/tool/ci/smoke_native.py deleted file mode 100644 index ec7cd6ea..00000000 --- a/tool/ci/smoke_native.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Load an exact artifact and check its null-safe proxy-stop entry point.""" - -import ctypes -import sys -from pathlib import Path - -library = ctypes.CDLL(str(Path(sys.argv[1]).resolve(strict=True))) -library.tor_proxy_stop.argtypes = [ctypes.c_void_p] -library.tor_proxy_stop.restype = None -library.tor_proxy_stop(None) -print(f"Loaded native library: {sys.argv[1]}") From 2532caed3a0cd7c8a91225ba11298b46c9983931 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Wed, 21 Oct 2026 23:55:23 -0600 Subject: [PATCH 3/9] Describe the native library flow How the hook obtains the library and the three modes, how a change to the Rust code becomes a lock entry and a release, and how anyone rebuilds a platform and compares. --- README.md | 79 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 5c767157..213df999 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,10 @@ SPDX-License-Identifier: GPL-3.0-or-later ## Getting started -Source builds are the default. To use prebuilt libraries without installing Rust, -see [Prebuilt libraries](#prebuilt-libraries). - -### [Install rust](https://www.rust-lang.org/tools/install) - -Use `rustup`, not `homebrew`. The toolchain and cross-compilation targets pinned in `rust/rust-toolchain.toml` are installed automatically on first build. - -### Building - -The Rust library is built and bundled by Flutter's native assets support (see `hook/build.dart`), so just `flutter run` or build as usual. Requires Flutter 3.47.2 or later; Android builds need NDK 27 or later. +Requires Flutter 3.47.2 or later. Nothing else is needed to use the package: +the build hook downloads the Rust library built for this version and checks +its hash before Flutter bundles it, so `flutter run` and `flutter build` work +as usual. See [Native library](#native-library) for building it yourself. ### Starting and stopping Tor @@ -68,48 +62,59 @@ If startup and its cleanup both fail, the original startup exception and stack are preserved. The cleanup failure is printed with its stack and remains available as `lastCleanupError` and `lastCleanupStackTrace` until the next native start attempt. -### Prebuilt libraries +### Native library -Copy the manifest URL and SHA-256 from a native release into your application's -root `pubspec.yaml` (the workspace root for pub workspaces): +The hook obtains the Rust library in one of three modes, chosen with the +`build` user define in the consuming app's root `pubspec.yaml` (the workspace +root for pub workspaces), or for this checkout with a `.dnix-build` file +holding the mode: ```yaml hooks: user_defines: tor_ffi_plugin: - native_build: prebuilt - prebuilt_manifest_url: https://github.com/cypherstack/tor/releases/download/native-YOUR-RELEASE/manifest.json - prebuilt_manifest_sha256: "REPLACE_WITH_RELEASE_MANIFEST_SHA256" + build: prebuilt # the default; or source, or dev ``` -Use the package revision named in the release. The hook verifies the manifest, -native source fingerprint, and library size and checksum before bundling it. -Missing or invalid downloads fail the build. Downloads require public HTTPS. -Flutter can reuse the hook output until `flutter clean` removes it. The normal -Flutter application build tools are still required. Omit these settings or use -`native_build: source` to compile Rust locally. +- `prebuilt` downloads the library of the release `dnix.lock` names and + verifies it against the hash recorded there. A mismatch is an error, never + a fallback. Downloads are cached by hash and need public HTTPS. +- `source` builds the library with Nix, the same build CI uses, and verifies + it against the same hash. Needs Nix on Linux or an Apple Silicon Mac; the + Apple targets need Xcode 27.0. +- `dev` builds with Nix but skips the Xcode pin and the hash check, for + working on the Rust code. The hook notices a `.dnix-build` file from its + next run on, so touch `dnix.json` after creating it. -Prebuilts use dynamic linking without sanitizers and cover: +Only dynamic libraries are provided, without sanitizers: | Platform | Architectures | Minimum | | --- | --- | --- | | Android | armv7, arm64, x64 | API 24; supports 16 KiB pages | -| iOS | arm64 device; arm64/x64 simulator | iOS 13; arm64 simulator 14 | +| iOS | arm64 device; arm64/x64 simulator | iOS 13 | | macOS | arm64, x64 | macOS 11 | -| Linux | x64; arm64 | glibc 2.35 (x64); 2.39 (arm64) | +| Linux | x64, arm64 | glibc 2.28 | | Windows | x64 | MSVC runtime | -Push a new `native-*` tag (for example `native-0.1.0-1`) to build and publish -all targets. Release notes contain the hook settings above with the actual pin. -Existing releases are not overwritten. Manual workflow runs on branches only -upload artifacts. Pull requests run Rust/Dart tests, Windows time/IPv6/file-release -tests, and example packaging checks; the full cross-build matrix runs for tags -and manual dispatches. To generate the manifest locally, put the target-named -libraries in `artifacts/`, run `flutter pub get`, then: +The floors, the Rust and Xcode versions, and the location of the Rust +workspace live in `dnix.json`. -```sh -dart --packages=.dart_tool/package_config.json tool/prebuilt_manifest.dart artifacts -``` +#### Releases + +`dnix.lock` records the hash of every library the Rust source builds to and +names the GitHub release, `native-`, that holds those bytes. After +changing the Rust code, run `dart tool/update_lock.dart` on a Mac with Nix +and Xcode 27.0 and commit the lock with the change; without a Mac, push and +copy the lock CI prints once every host is green. CI +(`.github/workflows/native.yml`) builds on four hosts, fails if any host or +the lock disagrees, and publishes the release when the lock reaches the +default branch. Nothing is committed by CI, and old releases stay. + +Anyone can rebuild one platform with Nix and compare: +`dart tool/verify_release.dart linux` (or android, windows, macos, ios, or a +target triple), on Windows inside WSL2 for the non-Apple platforms. The build +comes from the shared [dnix](https://github.com/cypherstack/dnix) package, +whose documentation explains how the bytes stay identical across hosts. ## Development @@ -125,6 +130,10 @@ Run the Rust tests with the pinned toolchain, then the offline Dart tests: flutter test ``` +Before the lock's release is published, or to test local Rust changes, the +tests need the library built from source: `echo dev > .dnix-build` with Nix +installed (`source` to also check the hash). + The lifecycle regression also verifies stop/restart against the real Tor network. Enable it explicitly (it requires network access and can take several minutes): From 64cfaa2b87121636bd41f045d31879c5fe2ff042 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Thu, 22 Oct 2026 17:44:04 -0600 Subject: [PATCH 4/9] Pin the dnix commit cypherstack merged dnix 7b9ef2d adds the Windows GNU and arm64 targets and stops zig adding its debug-mode sanitizer to C compiled without an optimisation level. Neither changes what this package builds to: every library hashes as before, so dnix.lock and the release it names stay as they are. --- .github/workflows/native.yml | 2 +- example/pubspec.lock | 4 ++-- flake.lock | 6 +++--- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index f6dd84fa..3e92473c 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -20,6 +20,6 @@ concurrency: jobs: native: # The same dnix commit as pubspec.yaml and flake.lock. - uses: cypherstack/dnix/.github/workflows/native.yml@448c5da6e61b3f0e62b4eae6d08b49158a9e45b2 + uses: cypherstack/dnix/.github/workflows/native.yml@7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd with: macos-runner: xcode-27 diff --git a/example/pubspec.lock b/example/pubspec.lock index eb781c01..51000a56 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -77,8 +77,8 @@ packages: dependency: transitive description: path: "." - ref: "448c5da6e61b3f0e62b4eae6d08b49158a9e45b2" - resolved-ref: "448c5da6e61b3f0e62b4eae6d08b49158a9e45b2" + ref: "7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd" + resolved-ref: "7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd" url: "https://github.com/cypherstack/dnix.git" source: git version: "0.1.0" diff --git a/flake.lock b/flake.lock index 4e0af1be..18686c96 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ "zig-overlay": "zig-overlay" }, "locked": { - "lastModified": 1792648476, - "narHash": "sha256-sNBlb09ut3gmCv2vMg+jEiUdF+slLC+T/9S+WBE6p3A=", + "lastModified": 1792694365, + "narHash": "sha256-0LJn3xO9HjoazjttvGentSEzt84bzqDuOGkYDuzcUrk=", "owner": "cypherstack", "repo": "dnix", - "rev": "448c5da6e61b3f0e62b4eae6d08b49158a9e45b2", + "rev": "7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd", "type": "github" }, "original": { diff --git a/pubspec.yaml b/pubspec.yaml index 7ba63925..77a9c5a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: dnix: git: url: https://github.com/cypherstack/dnix.git - ref: 448c5da6e61b3f0e62b4eae6d08b49158a9e45b2 + ref: 7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd dev_dependencies: ffigen: ^21.0.0 From f9f69b9821fd7ce1c3e811c40c5527a6d7615179 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Thu, 22 Oct 2026 19:39:07 -0600 Subject: [PATCH 5/9] Fix the test workflow and the platform table The arm64 Linux test job could never start: subosito/flutter-action finds no arm64 Flutter release for Linux and exits, and main never ran Flutter there either. Analysis covers lib and example again, as flutter analyze did on main; the package root stays out because pub flags the git dependency on dnix there. Actions are pinned by commit, as they were on main. The README gives the arm64 simulator's minimum, iOS 14, again, and the CHANGELOG records the move to dnix. --- .github/workflows/tests.yml | 20 ++++++++++---------- CHANGELOG.md | 12 ++++++++++++ README.md | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4ffc2afa..d6f0f2c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,24 +18,24 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm, xcode-27, windows-latest] + os: [ubuntu-24.04, xcode-27, windows-latest] runs-on: ${{ matrix.os }} timeout-minutes: 90 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: subosito/flutter-action@v2 + - uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 with: flutter-version: 3.47.2 channel: stable cache: true # The hook builds through the Nix flake; before the first native # release that is the only path, and it is unavailable on Windows. - - uses: DeterminateSystems/nix-installer-action@v23 + - uses: DeterminateSystems/nix-installer-action@3138316df39ed29be04236d7ffc686fa525866aa # v23 if: runner.os != 'Windows' - run: flutter pub get - - run: dart analyze hook tool test + - run: dart analyze lib hook tool test example # Prebuilt libraries exist once the lock's release is published from the # default branch of the repository dnix.json names; until then only # hosts with Nix can build them. @@ -68,7 +68,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Install Linux prerequisites @@ -77,7 +77,7 @@ jobs: sudo apt-get install -y build-essential pkg-config perl - name: Install pinned Rust toolchain run: bash tool/ci/install_rust.sh --component clippy,rustfmt - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 with: workspaces: rust - name: Verify Rust @@ -87,7 +87,7 @@ jobs: cargo clippy --locked --all-targets --manifest-path rust/Cargo.toml -- -D warnings - name: Cache pinned cargo-audit binary id: cargo-audit-cache - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.cargo/bin/cargo-audit key: cargo-audit-${{ runner.os }}-${{ runner.arch }}-0.22.2-${{ hashFiles('rust/rust-toolchain.toml') }} @@ -113,7 +113,7 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Use short Windows build paths @@ -121,7 +121,7 @@ jobs: run: ./tool/ci/windows_paths.ps1 - name: Install pinned Rust toolchain run: bash tool/ci/install_rust.sh --target "$NATIVE_TARGET" - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 with: workspaces: rust key: windows-tests diff --git a/CHANGELOG.md b/CHANGELOG.md index c91fa616..9c171e5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased + +* **Breaking change:** the native library now comes from + [dnix](https://github.com/cypherstack/dnix). By default the build hook + downloads the library of the release `dnix.lock` names and verifies its + SHA-256; with the `build` user define set to `source` it builds the same + library with Nix, on Linux or an Apple Silicon Mac. The `native_build`, + `prebuilt_manifest_url` and `prebuilt_manifest_sha256` user defines are + no longer read: pinning this package's commit pins its libraries. +* Every change is built on four hosts, which must produce the bytes + `dnix.lock` records; the default branch publishes the release it names. + ## 0.2.0 * Update the example to socks_socket 2.0.0, await Tor shutdown, and show start diff --git a/README.md b/README.md index 213df999..7131a002 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ Only dynamic libraries are provided, without sanitizers: | Platform | Architectures | Minimum | | --- | --- | --- | | Android | armv7, arm64, x64 | API 24; supports 16 KiB pages | -| iOS | arm64 device; arm64/x64 simulator | iOS 13 | +| iOS | arm64 device; arm64/x64 simulator | iOS 13; arm64 simulator 14 | | macOS | arm64, x64 | macOS 11 | | Linux | x64, arm64 | glibc 2.28 | | Windows | x64 | MSVC runtime | From ee1ce3f52be8d12e35d29acf3508a4cd255d7458 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Fri, 23 Oct 2026 07:25:59 -0600 Subject: [PATCH 6/9] Fail analysis on infos, as flutter analyze did on main dart analyze reports infos without failing; main's flutter analyze failed on them. The package already has none. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d6f0f2c5..0099349f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,7 +35,7 @@ jobs: - uses: DeterminateSystems/nix-installer-action@3138316df39ed29be04236d7ffc686fa525866aa # v23 if: runner.os != 'Windows' - run: flutter pub get - - run: dart analyze lib hook tool test example + - run: dart analyze --fatal-infos lib hook tool test example # Prebuilt libraries exist once the lock's release is published from the # default branch of the repository dnix.json names; until then only # hosts with Nix can build them. From 6a671e013b79bc750010068c0efa4617a45d900b Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Fri, 23 Oct 2026 17:59:49 -0600 Subject: [PATCH 7/9] Pin the dnix commit that builds the same bytes on macOS and Linux slopbot9k/dnix 57efe93, the stable-sort branch, not yet on cypherstack: build-time dependencies are resolved alike on every build host, rustc sorts the same everywhere, bindgen gets fixed headers, and a target keeps only its dynamic library. Every library changes with the first of these; the new lock matches what Linux x64, Linux arm64, and macOS each build. --- .github/workflows/native.yml | 2 +- dnix.lock | 22 +++++++++++----------- example/pubspec.lock | 6 +++--- flake.lock | 10 +++++----- flake.nix | 2 +- pubspec.yaml | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 3e92473c..8b36ec20 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -20,6 +20,6 @@ concurrency: jobs: native: # The same dnix commit as pubspec.yaml and flake.lock. - uses: cypherstack/dnix/.github/workflows/native.yml@7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd + uses: slopbot9k/dnix/.github/workflows/native.yml@57efe93b683e77d86a326a3a96ff217e6d6a4283 with: macos-runner: xcode-27 diff --git a/dnix.lock b/dnix.lock index 53923788..a6d3ef3b 100644 --- a/dnix.lock +++ b/dnix.lock @@ -1,17 +1,17 @@ { - "release": "a58277144bcb", - "date": "2026-10-22", + "release": "cf8ba406c03c", + "date": "2026-10-23", "files": { - "aarch64-apple-darwin/libtor_ffi_plugin.dylib": "6fe5e646859757f69cdb353d109fb25142fdd6bb8f2ddfd9b4ae1690ecc0bd17", - "aarch64-apple-ios-sim/libtor_ffi_plugin.dylib": "b8c7163364ef74798c9d9226f3d50d2125b42b5cd06b4a36846ab681dd5d47b7", - "aarch64-apple-ios/libtor_ffi_plugin.dylib": "c97d74128ea8198cfc3e53f82c41020fe88cbf0cdcd376aa9cfa11a92edeee9f", - "aarch64-linux-android/libtor_ffi_plugin.so": "724b8959bb156cab220ca453ffcf828b0714d629bc00298520452dbac77d9239", - "aarch64-unknown-linux-gnu/libtor_ffi_plugin.so": "5a09fb8d6f9aa9ad8ae03d48ef3f678eb8fe10578f512e1b848bf34642141082", - "armv7-linux-androideabi/libtor_ffi_plugin.so": "de8ffd6664c09cf2b9873bc4f022d7ca30c3525281c12b19d3d5b7beac47085d", + "aarch64-apple-darwin/libtor_ffi_plugin.dylib": "f512791115a4ba6e1418a2e3a6c6272bebe9aaae254e2f8d106e531e7dca8217", + "aarch64-apple-ios-sim/libtor_ffi_plugin.dylib": "d7f5b1d6d1c0ad469c5b4ea0f70ba308ac10db423122ad6c123ce2618a989b23", + "aarch64-apple-ios/libtor_ffi_plugin.dylib": "66c24a9e41dc6770bc251c8c676423b505da9d1fb98d380c432a47933ed6b97a", + "aarch64-linux-android/libtor_ffi_plugin.so": "6e2eb2f975f03d5e1450dcf8a5d20e5f4a77d4216433549be0ba9414a955e237", + "aarch64-unknown-linux-gnu/libtor_ffi_plugin.so": "54fc79e5e572a7e1fc3c47d626fd551e65c22ee97999b49c841b05743c825515", + "armv7-linux-androideabi/libtor_ffi_plugin.so": "428b2debd0062edd534b8b6b5ed2b91b92870d32f4e56d1980283ced3f2df8a6", "x86_64-apple-darwin/libtor_ffi_plugin.dylib": "0d9721ced60ed08ef2df85fbbe298dd34cc144d7e111327dbc11b7c565506f5c", "x86_64-apple-ios/libtor_ffi_plugin.dylib": "36f5b5be3e6cdf4781beb360ae2135a0195f80e8427aa96ec2dcc9f1b534613c", - "x86_64-linux-android/libtor_ffi_plugin.so": "bbb79a3c5fb484d1bacb91fc46e54cc8604ea3f3a9df12772d1c06660ff35edc", - "x86_64-pc-windows-msvc/tor_ffi_plugin.dll": "f8a4fc7d8c571255dc9add08402a5795ff7a17bf32f23eebf9f9894b3c596783", - "x86_64-unknown-linux-gnu/libtor_ffi_plugin.so": "6d2b9d5f0f3471a008ed1b3eab5bc37f05abf7321ea7d4dfa2e91fa2ccb98f98" + "x86_64-linux-android/libtor_ffi_plugin.so": "248b795fd364afa01d1db0117e5c3fba260abda82923d9d81a037e53388182fb", + "x86_64-pc-windows-msvc/tor_ffi_plugin.dll": "a8eb27eb1d1c29e89af487c28a05ebfca0bf1a8f9a42b0ef53cc54bf6011f9b9", + "x86_64-unknown-linux-gnu/libtor_ffi_plugin.so": "9ab79a842ad3adca96b686012eec50ad70163e33f475bfdd545468565d19e692" } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 51000a56..140ec6b3 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -77,9 +77,9 @@ packages: dependency: transitive description: path: "." - ref: "7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd" - resolved-ref: "7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd" - url: "https://github.com/cypherstack/dnix.git" + ref: "57efe93b683e77d86a326a3a96ff217e6d6a4283" + resolved-ref: "57efe93b683e77d86a326a3a96ff217e6d6a4283" + url: "https://github.com/slopbot9k/dnix.git" source: git version: "0.1.0" fake_async: diff --git a/flake.lock b/flake.lock index 18686c96..cd42b425 100644 --- a/flake.lock +++ b/flake.lock @@ -7,15 +7,15 @@ "zig-overlay": "zig-overlay" }, "locked": { - "lastModified": 1792694365, - "narHash": "sha256-0LJn3xO9HjoazjttvGentSEzt84bzqDuOGkYDuzcUrk=", - "owner": "cypherstack", + "lastModified": 1792794500, + "narHash": "sha256-Nf8+4VKQWYAE7telMnxGa2no18Yu4BemMZJ2PCeG+II=", + "owner": "slopbot9k", "repo": "dnix", - "rev": "7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd", + "rev": "57efe93b683e77d86a326a3a96ff217e6d6a4283", "type": "github" }, "original": { - "owner": "cypherstack", + "owner": "slopbot9k", "repo": "dnix", "type": "github" } diff --git a/flake.nix b/flake.nix index 8884b8f1..311ecd58 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Reproducible reference builds of tor_ffi_plugin's Rust library"; - inputs.dnix.url = "github:cypherstack/dnix"; + inputs.dnix.url = "github:slopbot9k/dnix"; outputs = { self, dnix, ... }: dnix.lib.package { src = self; diff --git a/pubspec.yaml b/pubspec.yaml index 77a9c5a6..cbe835da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,8 +16,8 @@ dependencies: meta: ^1.16.0 dnix: git: - url: https://github.com/cypherstack/dnix.git - ref: 7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd + url: https://github.com/slopbot9k/dnix.git + ref: 57efe93b683e77d86a326a3a96ff217e6d6a4283 dev_dependencies: ffigen: ^21.0.0 From 979bf416842592fa92063ce5b47cc9594ca5d64a Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Tue, 27 Oct 2026 15:39:26 -0600 Subject: [PATCH 8/9] Pin the dnix commit cypherstack merged --- .github/workflows/native.yml | 2 +- example/pubspec.lock | 6 +++--- flake.lock | 8 ++++---- flake.nix | 2 +- pubspec.yaml | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 8b36ec20..6752bb64 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -20,6 +20,6 @@ concurrency: jobs: native: # The same dnix commit as pubspec.yaml and flake.lock. - uses: slopbot9k/dnix/.github/workflows/native.yml@57efe93b683e77d86a326a3a96ff217e6d6a4283 + uses: cypherstack/dnix/.github/workflows/native.yml@e91763ef25d084b3a5cf271a0cfee69159b1f779 with: macos-runner: xcode-27 diff --git a/example/pubspec.lock b/example/pubspec.lock index 140ec6b3..6cf19144 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -77,9 +77,9 @@ packages: dependency: transitive description: path: "." - ref: "57efe93b683e77d86a326a3a96ff217e6d6a4283" - resolved-ref: "57efe93b683e77d86a326a3a96ff217e6d6a4283" - url: "https://github.com/slopbot9k/dnix.git" + ref: e91763ef25d084b3a5cf271a0cfee69159b1f779 + resolved-ref: e91763ef25d084b3a5cf271a0cfee69159b1f779 + url: "https://github.com/cypherstack/dnix.git" source: git version: "0.1.0" fake_async: diff --git a/flake.lock b/flake.lock index cd42b425..928b9315 100644 --- a/flake.lock +++ b/flake.lock @@ -7,15 +7,15 @@ "zig-overlay": "zig-overlay" }, "locked": { - "lastModified": 1792794500, + "lastModified": 1793124403, "narHash": "sha256-Nf8+4VKQWYAE7telMnxGa2no18Yu4BemMZJ2PCeG+II=", - "owner": "slopbot9k", + "owner": "cypherstack", "repo": "dnix", - "rev": "57efe93b683e77d86a326a3a96ff217e6d6a4283", + "rev": "e91763ef25d084b3a5cf271a0cfee69159b1f779", "type": "github" }, "original": { - "owner": "slopbot9k", + "owner": "cypherstack", "repo": "dnix", "type": "github" } diff --git a/flake.nix b/flake.nix index 311ecd58..8884b8f1 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Reproducible reference builds of tor_ffi_plugin's Rust library"; - inputs.dnix.url = "github:slopbot9k/dnix"; + inputs.dnix.url = "github:cypherstack/dnix"; outputs = { self, dnix, ... }: dnix.lib.package { src = self; diff --git a/pubspec.yaml b/pubspec.yaml index cbe835da..4808ea9f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,8 +16,8 @@ dependencies: meta: ^1.16.0 dnix: git: - url: https://github.com/slopbot9k/dnix.git - ref: 57efe93b683e77d86a326a3a96ff217e6d6a4283 + url: https://github.com/cypherstack/dnix.git + ref: e91763ef25d084b3a5cf271a0cfee69159b1f779 dev_dependencies: ffigen: ^21.0.0 From 47c7e2c8414a2b1f5a50a07f607708882b77ef8b Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Fri, 30 Oct 2026 10:01:23 -0600 Subject: [PATCH 9/9] Bump tokio, async-compression and ctutils past their advisories The audit fails on advisories published between October 13 and 23 against dependencies this package had not changed: tokio 1.50.0 (RUSTSEC-2026-0386), zstd-safe 7.1.0 (RUSTSEC-2026-0403 and eight unsound warnings), zstd-sys 2.0.11 (RUSTSEC-2026-0410), ctutils 0.4.2 (RUSTSEC-2026-0385) and crypto-bigint 0.5.5 (RUSTSEC-2026-0387). tokio moves to 1.54.1 and ctutils to 0.4.7 within their series. The fixed zstd-safe 8.2.0 comes with zstd 0.14, which async-compression takes from 0.4.45, so it moves to 0.4.51; every async-compression since 0.4.20 reads xz through liblzma rather than xz2, and liblzma-sys claims the same native library as lzma-sys, so the plugin's static xz dependency becomes liblzma-sys. flate2 and miniz_oxide follow, and adler gives way to adler2. crypto-bigint 0.5 stays: only the optional ECDSA support of ssh-key-fork-arti reaches it, no feature arti enables compiles it (cargo tree finds no path on any target), and the elliptic-curve 0.13 series cannot take the fixed 0.7. The audit ignores that advisory until arti moves to elliptic-curve 0.14. The lock is rebuilt: release native-bdccea9785ab, every library changing with its dependencies. Under Rust 1.99.1 the format check, the tests and clippy pass, and the audit is clean apart from the three allowed unmaintained warnings it already reported. --- .github/workflows/tests.yml | 8 +- CHANGELOG.md | 7 ++ dnix.lock | 26 +++---- rust/Cargo.lock | 143 ++++++++++++++++++++++-------------- rust/Cargo.toml | 2 +- 5 files changed, 114 insertions(+), 72 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0099349f..a06373ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,13 +94,19 @@ jobs: - name: Install pinned cargo-audit if: steps.cargo-audit-cache.outputs.cache-hit != 'true' run: cargo install cargo-audit --version 0.22.2 --locked + # RUSTSEC-2026-0387 is crypto-bigint 0.5, which only the optional ECDSA + # support of ssh-key-fork-arti pulls in: no feature arti enables + # compiles it (cargo tree finds no path to it), and the elliptic-curve + # 0.13 series it is locked by cannot take the fixed 0.7 series. Drop + # the ignore once arti moves to elliptic-curve 0.14. - name: Audit Rust dependencies run: | cargo audit --file rust/Cargo.lock \ --deny unsound \ --deny yanked \ --ignore RUSTSEC-2021-0145 \ - --ignore RUSTSEC-2023-0071 + --ignore RUSTSEC-2023-0071 \ + --ignore RUSTSEC-2026-0387 windows-tests: if: github.event_name == 'pull_request' diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c171e5b..d1bc05f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ no longer read: pinning this package's commit pins its libraries. * Every change is built on four hosts, which must produce the bytes `dnix.lock` records; the default branch publishes the release it names. +* Bump tokio to 1.54.1, async-compression to 0.4.51 (zstd 0.14 with + zstd-safe 8.2.0, liblzma in place of xz2) and ctutils to 0.4.7, which + clears the RustSec advisories against tokio 1.50, zstd-safe 7.1, zstd-sys + 2.0 and ctutils 0.4.2. The statically linked xz library now comes from + liblzma-sys, the crate the new async-compression uses. The audit ignores + RUSTSEC-2026-0387 (crypto-bigint 0.5), an optional dependency no enabled + feature compiles. ## 0.2.0 diff --git a/dnix.lock b/dnix.lock index a6d3ef3b..325a8407 100644 --- a/dnix.lock +++ b/dnix.lock @@ -1,17 +1,17 @@ { - "release": "cf8ba406c03c", - "date": "2026-10-23", + "release": "bdccea9785ab", + "date": "2026-10-30", "files": { - "aarch64-apple-darwin/libtor_ffi_plugin.dylib": "f512791115a4ba6e1418a2e3a6c6272bebe9aaae254e2f8d106e531e7dca8217", - "aarch64-apple-ios-sim/libtor_ffi_plugin.dylib": "d7f5b1d6d1c0ad469c5b4ea0f70ba308ac10db423122ad6c123ce2618a989b23", - "aarch64-apple-ios/libtor_ffi_plugin.dylib": "66c24a9e41dc6770bc251c8c676423b505da9d1fb98d380c432a47933ed6b97a", - "aarch64-linux-android/libtor_ffi_plugin.so": "6e2eb2f975f03d5e1450dcf8a5d20e5f4a77d4216433549be0ba9414a955e237", - "aarch64-unknown-linux-gnu/libtor_ffi_plugin.so": "54fc79e5e572a7e1fc3c47d626fd551e65c22ee97999b49c841b05743c825515", - "armv7-linux-androideabi/libtor_ffi_plugin.so": "428b2debd0062edd534b8b6b5ed2b91b92870d32f4e56d1980283ced3f2df8a6", - "x86_64-apple-darwin/libtor_ffi_plugin.dylib": "0d9721ced60ed08ef2df85fbbe298dd34cc144d7e111327dbc11b7c565506f5c", - "x86_64-apple-ios/libtor_ffi_plugin.dylib": "36f5b5be3e6cdf4781beb360ae2135a0195f80e8427aa96ec2dcc9f1b534613c", - "x86_64-linux-android/libtor_ffi_plugin.so": "248b795fd364afa01d1db0117e5c3fba260abda82923d9d81a037e53388182fb", - "x86_64-pc-windows-msvc/tor_ffi_plugin.dll": "a8eb27eb1d1c29e89af487c28a05ebfca0bf1a8f9a42b0ef53cc54bf6011f9b9", - "x86_64-unknown-linux-gnu/libtor_ffi_plugin.so": "9ab79a842ad3adca96b686012eec50ad70163e33f475bfdd545468565d19e692" + "aarch64-apple-darwin/libtor_ffi_plugin.dylib": "5ad0c311ed4da79247c8e2dc645d8a9944431f77c0f45fb5b750a919e2d45d2c", + "aarch64-apple-ios-sim/libtor_ffi_plugin.dylib": "b76b916c06922fa185d9b1e8e696d8814aeac00a412c5bd5208d9887035daaa4", + "aarch64-apple-ios/libtor_ffi_plugin.dylib": "31ce202a0354b1a018247aa514d8273021dbfe3f52b3498cd7e4f2f45b219906", + "aarch64-linux-android/libtor_ffi_plugin.so": "841172a1bac55d8bffef8c79341a60d72c28024db488637e440d7b5f92987aec", + "aarch64-unknown-linux-gnu/libtor_ffi_plugin.so": "36e26458a52921db137c49a08a01e650ba1a3cd57067a7a613724e473efe9897", + "armv7-linux-androideabi/libtor_ffi_plugin.so": "fd67eb4ed118d8d834f9b564e2d33a3336be17a0d58a20ba41fe5f51ae31246c", + "x86_64-apple-darwin/libtor_ffi_plugin.dylib": "828edc5ead111d7441eef4f314fa71307031a95cfb433e32987fc59f81faf529", + "x86_64-apple-ios/libtor_ffi_plugin.dylib": "37cb0a4686e6b943099b04a6d32e8648b2861b004edf75e9c041e93dea61818c", + "x86_64-linux-android/libtor_ffi_plugin.so": "1bd6a8ac0df51b0c479d6c831a5ae790d720de2b82b18830c8755067001b43b4", + "x86_64-pc-windows-msvc/tor_ffi_plugin.dll": "269fc580023948afc0be640d19bbc419a8cb49689c071702de6f404b5d4feaf1", + "x86_64-unknown-linux-gnu/libtor_ffi_plugin.so": "63d545aa4840766791043f8e55c48e30c237445499600e1fdf661d07f370e12c" } } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index fbe410ab..f7c8051c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3,10 +3,10 @@ version = 4 [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -313,18 +313,14 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-compression" -version = "0.4.11" +version = "0.4.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "9d5755640120b24961f8231e05892e4a4f7e0d04e8aeca161508c9c397946970" dependencies = [ - "flate2", - "futures-core", + "compression-codecs", + "compression-core", "futures-io", - "memchr", "pin-project-lite", - "xz2", - "zstd", - "zstd-safe", ] [[package]] @@ -719,6 +715,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae36683101b2899f48bd31f8db9839437356188e3b1ff23096e73dab09ba8fe" +dependencies = [ + "compression-core", + "flate2", + "liblzma", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ebdda89edc534bf8d5dd2857562633b6495557e5846552ade994b99ed13c36a" + [[package]] name = "const-oid" version = "0.9.6" @@ -864,9 +879,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a" dependencies = [ "cmov", ] @@ -1094,7 +1109,7 @@ dependencies = [ "quote", "sha3", "strum 0.28.0", - "syn 2.0.105", + "syn 3.0.5", "unicode-ident", "void", ] @@ -1213,7 +1228,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1460,12 +1475,13 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.30" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -2248,6 +2264,26 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "liblzma" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa980ea6744074a5b5e0ac6b164aca94fc970121869b3e78ae12a2b0097dbea7" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7e3581f367a78d7b7e7ae948d023310556f0cfc13156c2e4e00e25616492b9" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.8" @@ -2309,17 +2345,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "matchers" version = "0.2.0" @@ -2364,23 +2389,24 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ - "adler", + "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2454,7 +2480,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3580,6 +3606,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "simd_cesu8" version = "1.2.0" @@ -3646,7 +3678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -3993,9 +4025,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -4010,13 +4042,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.105", + "syn 3.0.5", ] [[package]] @@ -5229,8 +5261,8 @@ dependencies = [ "glob", "lazy_static", "libc", + "liblzma-sys", "log", - "lzma-sys", "rlimit 0.10.1", "saturating-time", "security-framework", @@ -6094,15 +6126,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - [[package]] name = "yoke" version = "0.8.0" @@ -6201,29 +6224,35 @@ dependencies = [ "syn 2.0.105", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zstd" -version = "0.13.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "2.1.2+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3" dependencies = [ "cc", "pkg-config", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9ac2bcbf..90136417 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -19,7 +19,7 @@ arti-client = { version = "0.47.0", features = ["static", "onion-service-client" arti = { version = "2.7.0", features = ["experimental-api", "static"] } tor-rtcompat = { version = "0.47.0", features = ["static", "native-tls", "tokio"] } log = "0.4.20" -lzma-sys = { version = "0.1.20", features = ["static"] } +liblzma-sys = { version = "0.4.9", features = ["static"] } #android_log-sys = "0.3.1" rlimit = "0.10.1" anyhow = "1.0.79"