From ace2628627601e896d3be0214ad4c282659c3776 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Thu, 22 Oct 2026 00:23:27 -0600 Subject: [PATCH 01/11] 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 ten targets; dnix.lock records what the source builds to and names release native-8164512a97c6. The flake hands dnix the configuration, along with cmake for RandomX, a static libc++ on Android, and the hash of the randomx crate's checkout, whose RandomX submodule Nix's own fetcher cannot reach. Two crates move to branches that build this way. epic_core kept the foundation JSON outside its own directory, which a vendored crate does not have, so the cypherstack/epic branch vendorable-foundation-json keeps a copy inside the crate. randomx-rust master compiled RandomX for the machine doing the build; the branch reproducible-cross-builds builds the same bytes everywhere, cross-compiles to the MSVC target, and links the C++ runtime per target, so Android no longer needs libc++_shared.so shipped beside the library. There is no Windows library: RandomX's C++ build assumes Microsoft's compiler and assembler, which the sandbox's clang-cl and llvm-ml do not accept. The previous workflow built it with Visual Studio on a Windows runner. OpenSSL is vendored only where native-tls links it, Linux and Android. 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 | 43 +++ dnix.lock | 16 + example/pubspec.lock | 33 +-- flake.lock | 123 ++++++++ flake.nix | 10 + hook/build.dart | 300 +------------------ hook/src/prebuilt.dart | 265 ----------------- hook/src/source_fingerprint.dart | 61 ---- pubspec.yaml | 6 +- rust/Cargo.lock | 8 +- rust/Cargo.toml | 14 +- test/build_hook_test.dart | 482 ------------------------------- tool/prebuilt_manifest.dart | 123 -------- tool/update_lock.dart | 4 + tool/verify_release.dart | 4 + 16 files changed, 231 insertions(+), 1264 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/src/prebuilt.dart delete mode 100644 hook/src/source_fingerprint.dart delete mode 100644 test/build_hook_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 1b983d0..cd260ce 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ migrate_working_dir/ build/ /patches/ /prebuilt/ + +# Per-checkout dnix build mode override (see README). +.dnix-build diff --git a/dnix.json b/dnix.json new file mode 100644 index 0000000..e845e5d --- /dev/null +++ b/dnix.json @@ -0,0 +1,43 @@ +{ + "name": "flutter_libepiccash", + "repository": "https://github.com/cypherstack/flutter_libepiccash", + "library": "epic_cash_wallet", + "asset": "src/epic_cash_wallet_bindings.g.dart", + "rust": { + "workspace": "rust", + "crate": "epic-cash-wallet", + "outputHashes": { + "randomx-0.2.0": "sha256-rWChlbclK2SbZtDUVY1zjmpYxqb1NAJ9bgx4Qi3DwXQ=" + }, + "env": { + "CXXSTDLIB_aarch64_linux_android": "static:-bundle=c++", + "CXXSTDLIB_armv7_linux_androideabi": "static:-bundle=c++", + "CXXSTDLIB_x86_64_linux_android": "static:-bundle=c++" + }, + "tools": [ + "cmake" + ] + }, + "toolchains": { + "rust": "1.99.1", + "xcode": "27.0" + }, + "floors": { + "macos": 12, + "ios": 15, + "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" + ] +} diff --git a/dnix.lock b/dnix.lock new file mode 100644 index 0000000..b9711c4 --- /dev/null +++ b/dnix.lock @@ -0,0 +1,16 @@ +{ + "release": "8164512a97c6", + "date": "2026-10-22", + "files": { + "aarch64-apple-darwin/libepic_cash_wallet.dylib": "b9bd894d1e2a3ad0241dc820bb7dfd172308348e793d3030276ef71070a36521", + "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "ffbde5963a5c7cc45ca8ca123474c8a8c8850bdb284b619ce4240e84d5a63f4c", + "aarch64-apple-ios/libepic_cash_wallet.dylib": "d17eb7e08e2cdcadc9d570ca6830154baf18303973ea3ac29f826c3c8ba05b9d", + "aarch64-linux-android/libepic_cash_wallet.so": "dd5f6a9601cda87f0815b1946a11112d90ef5176f1d38b4ade4d530f731aed64", + "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "29f4a08ac8cc5c7b03f03d8aecf89a889f49b95cc80a964341c8ddf62116c166", + "armv7-linux-androideabi/libepic_cash_wallet.so": "812f9fd3e9c205e92411f666d779e4428275f532be9941590b3c8efcdae89cc8", + "x86_64-apple-darwin/libepic_cash_wallet.dylib": "4c73f7c39fe45c95de5298d4aae09df345fe2594538a9b46e2002f3267266324", + "x86_64-apple-ios/libepic_cash_wallet.dylib": "65ad296d118e0fc15f09eb586ec5f6fbbce4f092caf18cc88597ebfe02f70485", + "x86_64-linux-android/libepic_cash_wallet.so": "0f75fdf1dc3863b359db34fa05ef6f8ae4b7a11426d4043010ecc8ae3901e480", + "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "51f95ba2d1d20d7c5a6bb8bb19058b4887de3c9dae31a5e9d31818c398d581bb" + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index fc9f46d..70f53da 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -65,6 +65,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.3" + 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: @@ -255,14 +264,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" - native_toolchain_rust: - dependency: transitive - description: - name: native_toolchain_rust - sha256: "8469a0e32a47a5c84b7fd300de9fd0e8082aac76c37922986324f5082a58350d" - url: "https://pub.dev" - source: hosted - version: "1.0.6" numeric_keyboard: dependency: "direct main" description: @@ -327,14 +328,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: @@ -428,14 +421,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" typed_data: dependency: transitive description: diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..4e0af1b --- /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 0000000..4ff975b --- /dev/null +++ b/flake.nix @@ -0,0 +1,10 @@ +{ + description = "Reproducible reference builds of flutter_libepiccash'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 733f9b9..7997f79 100644 --- a/hook/build.dart +++ b/hook/build.dart @@ -1,299 +1,3 @@ -import 'dart:io'; +import 'package:dnix/dnix.dart'; -import 'package:code_assets/code_assets.dart'; -import 'package:hooks/hooks.dart'; -import 'package:native_toolchain_rust/native_toolchain_rust.dart'; - -import 'src/prebuilt.dart'; - -const _assetName = 'src/epic_cash_wallet_bindings.g.dart'; - -Future main(List args) async { - await build(args, (input, output) async { - if (!input.config.buildCodeAssets) { - return; - } - - final manifest = prebuiltManifest(input); - if (manifest != null) { - final directory = await downloadPrebuilt( - input, - output, - manifest, - target: _rustTarget(input.config.code), - walletName: _prebuiltFileName(input.config.code), - ); - try { - await _bundleDownloaded(input, output, directory.uri); - } finally { - await directory.delete(recursive: true); - } - return; - } - - // Cargo's dep-info lists Rust sources but omits these build inputs. - output.dependencies.addAll([ - for (final path in [ - 'rust/Cargo.toml', - 'rust/Cargo.lock', - 'rust/rust-toolchain.toml', - ]) - input.packageRoot.resolve(path), - ]); - - await RustBuilder( - assetName: _assetName, - cratePath: 'rust', - extraCargoBuildArgs: const ['--locked'], - extraCargoEnvironmentVariables: { - ..._macOsRustToolchainEnvironment(), - ..._cargoEnvironment(input.config.code), - }, - ).run(input: input, output: output); - - if (input.config.code.targetOS == OS.android) { - await _bundleAndroidRuntime(input, output); - } - }); -} - -Future _bundleDownloaded( - BuildInput input, - BuildOutputBuilder output, - Uri directory, -) async { - final code = input.config.code; - await _bundleFile( - input, - output, - source: File.fromUri(directory.resolve(_prebuiltFileName(code))), - assetName: _assetName, - fileName: _bundledFileName(code.targetOS), - ); - if (code.targetOS == OS.android) { - await _bundleFile( - input, - output, - source: File.fromUri( - directory.resolve('libc++_shared-${_rustTarget(code)}.so'), - ), - assetName: 'libc++_shared.so', - fileName: 'libc++_shared.so', - ); - } -} - -Future _bundleAndroidRuntime( - BuildInput input, - BuildOutputBuilder output, -) async { - final code = input.config.code; - final target = _rustTarget(code); - final toolchainRoot = File.fromUri(code.cCompiler!.compiler).parent.parent; - final source = File.fromUri( - toolchainRoot.uri.resolve( - 'sysroot/usr/lib/${_androidSysrootTarget(target)}/libc++_shared.so', - ), - ); - if (!source.existsSync()) { - throw StateError('No NDK Android C++ runtime for $target: ${source.path}'); - } - output.dependencies.add(source.uri); - await _bundleFile( - input, - output, - source: source, - assetName: 'libc++_shared.so', - fileName: 'libc++_shared.so', - ); -} - -Future _bundleFile( - BuildInput input, - BuildOutputBuilder output, { - required File source, - required String assetName, - required String fileName, -}) async { - // Every architecture of one asset must have the same final basename. The - // release filenames contain target details, so copy the selected file to a - // canonical name in the hook output before handing it to Flutter. - final bundled = File.fromUri(input.outputDirectory.resolve(fileName)); - await bundled.parent.create(recursive: true); - await source.copy(bundled.path); - - output.assets.code.add( - CodeAsset( - package: input.packageName, - name: assetName, - linkMode: DynamicLoadingBundled(), - file: bundled.uri, - ), - ); -} - -Map _macOsRustToolchainEnvironment() { - if (!Platform.isMacOS) { - return const {}; - } - - // Preserve native_toolchain_rust's removal of Xcode-injected tool paths. - final paths = (Platform.environment['PATH'] ?? '') - .split(':') - .where((path) => !path.contains('Contents/Developer/')) - .toList(); - if (paths.any((path) => File('$path/rustc').existsSync())) { - return const {}; - } - - // Homebrew can expose only rustup on PATH. Its resolved installation also - // contains the rustc/cargo proxies needed by `rustup run ... cargo build`. - final homeDirectory = Platform.environment['HOME']; - for (final path in [ - ...paths, - if (homeDirectory != null) '$homeDirectory/.cargo/bin', - ]) { - final rustup = File('$path/rustup'); - if (!rustup.existsSync()) { - continue; - } - final toolchainBin = File(rustup.resolveSymbolicLinksSync()).parent.path; - if (File('$toolchainBin/rustc').existsSync()) { - return { - 'PATH': [...paths, toolchainBin].join(':'), - }; - } - } - return const {}; -} - -Map _cargoEnvironment(CodeConfig code) { - if (code.targetOS == OS.android) { - return _androidCargoEnvironment(code); - } - if (code.targetOS == OS.iOS) { - // Flutter 3.47 currently reports iOS 13 to native-assets hooks even when - // the consuming Xcode project has a newer deployment target. Keep the - // native library aligned with this package's documented iOS 15 minimum. - final targetVersion = - code.iOS.targetVersion < 15 ? 15 : code.iOS.targetVersion; - return {'IPHONEOS_DEPLOYMENT_TARGET': '$targetVersion.0'}; - } - if (code.targetOS == OS.macOS) { - // Build for the package floor. A library built for an older deployment - // target remains usable when the consuming application requires newer. - return {'MACOSX_DEPLOYMENT_TARGET': '12.0'}; - } - return const {}; -} - -Map _androidCargoEnvironment(CodeConfig code) { - final compiler = code.cCompiler; - if (compiler == null) { - throw StateError('Flutter did not provide an Android NDK compiler.'); - } - - final api = code.android.targetNdkApi; - if (api < 24) { - throw UnsupportedError( - 'Flutter 3.47 requires Android API 24 or newer; received API $api.', - ); - } - - final target = _rustTarget(code); - final environmentTarget = target.replaceAll('-', '_'); - final ndkTarget = - target == 'armv7-linux-androideabi' ? 'armv7a-linux-androideabi' : target; - final sysrootTarget = _androidSysrootTarget(target); - - final compilerDirectory = File.fromUri(compiler.compiler).parent; - final executableSuffix = Platform.isWindows ? '.cmd' : ''; - final clang = File( - '${compilerDirectory.path}${Platform.pathSeparator}' - '$ndkTarget$api-clang$executableSuffix', - ); - final clangPp = File( - '${compilerDirectory.path}${Platform.pathSeparator}' - '$ndkTarget$api-clang++$executableSuffix', - ); - if (!clang.existsSync() || !clangPp.existsSync()) { - throw StateError( - 'The configured Android NDK does not contain API $api compiler ' - 'wrappers for $target.', - ); - } - - final toolchainRoot = compilerDirectory.parent; - final sysroot = Directory.fromUri(toolchainRoot.uri.resolve('sysroot/')); - final targetInclude = Directory.fromUri( - sysroot.uri.resolve('usr/include/$sysrootTarget/'), - ); - - // bin//prebuilt/llvm/toolchains/ - var ndkRoot = compilerDirectory; - for (var level = 0; level < 5; level++) { - ndkRoot = ndkRoot.parent; - } - - return { - // native_toolchain_rust 1.0.6 currently defaults these entries to API 35. - // Override them with Flutter's consuming application's actual minimum API. - 'CC_$environmentTarget': clang.path, - 'CXX_$environmentTarget': clangPp.path, - 'CARGO_TARGET_${environmentTarget.toUpperCase()}_LINKER': clang.path, - 'BINDGEN_EXTRA_CLANG_ARGS_$environmentTarget': - '--sysroot=${_clangPathArgument(sysroot.path)} ' - '-I${_clangPathArgument(targetInclude.path)}', - 'ANDROID_NDK_HOME': ndkRoot.path, - // CMake-based Rust dependencies discover the NDK through this variable. - 'ANDROID_NDK_ROOT': ndkRoot.path, - }; -} - -String _androidSysrootTarget(String target) => - target == 'armv7-linux-androideabi' ? 'arm-linux-androideabi' : target; - -// Bindgen parses this environment variable with shlex, including on Windows. -// Normalize Windows separators and quote paths to preserve spaces and quotes. -String _clangPathArgument(String path) => - "'${path.replaceAll(r'\', '/').replaceAll("'", r"'\''")}'"; - -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) - when code.iOS.targetSdk == IOSSdk.iPhoneSimulator => - 'aarch64-apple-ios-sim', - (OS.iOS, Architecture.arm64) => 'aarch64-apple-ios', - (OS.iOS, Architecture.x64) => 'x86_64-apple-ios', - (OS.linux, Architecture.arm64) => 'aarch64-unknown-linux-gnu', - (OS.linux, Architecture.x64) => 'x86_64-unknown-linux-gnu', - (OS.macOS, Architecture.arm64) => 'aarch64-apple-darwin', - (OS.macOS, Architecture.x64) => 'x86_64-apple-darwin', - (OS.windows, Architecture.arm64) => 'aarch64-pc-windows-msvc', - (OS.windows, Architecture.x64) => 'x86_64-pc-windows-msvc', - _ => throw UnsupportedError( - 'Unsupported native target: ' - '${code.targetOS}/${code.targetArchitecture}', - ), - }; - -String _prebuiltFileName(CodeConfig code) { - final target = _rustTarget(code); - final extension = switch (code.targetOS) { - OS.android || OS.linux => 'so', - OS.iOS || OS.macOS => 'dylib', - OS.windows => 'dll', - _ => throw UnsupportedError('Unsupported OS: ${code.targetOS}'), - }; - return 'libepic_cash_wallet-$target.$extension'; -} - -String _bundledFileName(OS os) => switch (os) { - OS.android || OS.linux => 'libepic_cash_wallet.so', - OS.iOS || OS.macOS => 'libepic_cash_wallet.dylib', - OS.windows => 'epic_cash_wallet.dll', - _ => throw UnsupportedError('Unsupported OS: $os'), - }; +void main(List args) => buildNativeLibrary(args); diff --git a/hook/src/prebuilt.dart b/hook/src/prebuilt.dart deleted file mode 100644 index 01d4471..0000000 --- a/hook/src/prebuilt.dart +++ /dev/null @@ -1,265 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:code_assets/code_assets.dart'; -import 'package:crypto/crypto.dart'; -import 'package:hooks/hooks.dart'; - -import 'source_fingerprint.dart'; - -typedef ManifestPin = ({Uri url, String sha256}); -typedef _Library = ({String file, String sha256, int size}); -const _maximumBytes = 2 * 1024 * 1024 * 1024; -const _timeout = Duration(seconds: 30); - -ManifestPin? prebuiltManifest(BuildInput input) { - final defines = input.userDefines; - final mode = defines['native_build'] ?? 'source'; - if (mode != 'source' && mode != 'prebuilt') { - throw FormatException('native_build must be source or prebuilt.'); - } - if (mode == 'source') return null; - final url = defines['prebuilt_manifest_url']; - if (url is! String) throw FormatException('Missing prebuilt_manifest_url.'); - final uri = Uri.parse(url); - _requireHttps(uri); - return (url: uri, sha256: _digest(defines['prebuilt_manifest_sha256'])); -} - -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(); -} - -void _requireHttps(Uri uri) { - if (uri.scheme != 'https' || - uri.host.isEmpty || - uri.userInfo.isNotEmpty || - uri.hasFragment) { - throw FormatException( - 'Prebuilt URLs require HTTPS without credentials or fragments.', - ); - } -} - -_Library _library(Object? value, String name) { - if (value is! Map || value['file'] != name) { - throw FormatException('Expected prebuilt library basename: $name'); - } - final size = value['size']; - if (size is! int || size <= 0 || size > _maximumBytes) { - throw FormatException('Invalid prebuilt size for $name.'); - } - return (file: name, sha256: _digest(value['sha256']), size: size); -} - -List<_Library> _select( - Object? manifest, - CodeConfig code, - String target, - String walletName, - String sourceHash, -) { - if (manifest is! Map || - manifest['schema_version'] != 1 || - manifest['package'] != 'flutter_libepiccash') { - throw FormatException('Unsupported flutter_libepiccash manifest.'); - } - if (_digest(manifest['source_sha256']) != sourceHash) { - throw StateError( - 'Prebuilt source fingerprint mismatch. Use a matching release or native_build: source.', - ); - } - final entries = manifest['artifacts']; - if (entries is! List || - entries.any((entry) => entry is! Map)) { - throw FormatException('Manifest artifacts must be a list of objects.'); - } - final matches = entries - .cast>() - .where( - (entry) => entry['target'] == target && entry['link_mode'] == 'dynamic', - ) - .toList(); - if (matches.length != 1) { - throw StateError('Expected exactly one dynamic prebuilt for $target.'); - } - final entry = matches.single; - final minimum = switch (code.targetOS) { - OS.android => code.android.targetNdkApi, - // Keep the existing source hook floor; Flutter can still report iOS 13. - OS.iOS => code.iOS.targetVersion < 15 ? 15 : code.iOS.targetVersion, - OS.macOS => code.macOS.targetVersion, - _ => null, - }; - if (minimum != null) { - final required = entry['minimum_os_version']; - if (required is! int || required <= 0) { - throw FormatException('Missing minimum_os_version for $target.'); - } - if (required > minimum) { - throw UnsupportedError( - 'Prebuilt requires OS/API $required; app targets $minimum.', - ); - } - } - if (code.targetOS == OS.linux) { - final glibc = entry['minimum_glibc_version']; - if (glibc is! String || !RegExp(r'^\d+\.\d+$').hasMatch(glibc)) { - throw FormatException( - 'Linux prebuilt must declare minimum_glibc_version.', - ); - } - } - final android = code.targetOS == OS.android; - final dependencies = entry['dependencies'] ?? []; - if (dependencies is! List || dependencies.length != (android ? 1 : 0)) { - throw FormatException( - 'Expected ${android ? 'one Android runtime' : 'no runtime dependencies'} for $target.', - ); - } - return [ - _library(entry, walletName), - if (android) _library(dependencies.single, 'libc++_shared-$target.so'), - ]; -} - -/// Return a verified directory for the existing bundler; the caller deletes it. -Future downloadPrebuilt( - BuildInput input, - BuildOutputBuilder output, - ManifestPin pin, { - required String target, - required String walletName, -}) async { - final code = input.config.code; - if (code.sanitizer != null || - code.linkModePreference == LinkModePreference.static) { - throw UnsupportedError( - 'Prebuilts require dynamic linking without sanitizers.', - ); - } - if (code.targetOS == OS.iOS && - code.iOS.targetSdk == IOSSdk.iPhoneOS && - code.targetArchitecture != Architecture.arm64) { - throw UnsupportedError('iOS device prebuilts require arm64.'); - } - final source = await sourceFingerprint(input.packageRoot); - output.dependencies.addAll(source.dependencies); - final outputDirectory = Directory.fromUri(input.outputDirectory); - await outputDirectory.create(recursive: true); - final directory = await outputDirectory.createTemp('.prebuilt-'); - final client = HttpClient() - ..autoUncompress = false - ..connectionTimeout = _timeout; - try { - final manifest = File.fromUri(directory.uri.resolve('manifest.json')); - await _download( - client, - pin.url, - manifest, - pin.sha256, - maximum: 1024 * 1024, - ); - final libraries = _select( - jsonDecode(await manifest.readAsString()), - code, - target, - walletName, - source.sha256, - ); - for (final library in libraries) { - await _download( - client, - pin.url.resolve(library.file), - File.fromUri(directory.uri.resolve(library.file)), - library.sha256, - size: library.size, - ); - } - return directory; - } catch (_) { - await directory.delete(recursive: true); - rethrow; - } finally { - client.close(force: true); - } -} - -Future _download( - HttpClient client, - Uri url, - File file, - String digest, { - int? size, - int maximum = _maximumBytes, -}) async { - final transfer = _transfer(client, url, file, size ?? maximum); - try { - await transfer.timeout(const Duration(minutes: 10)); - } on TimeoutException { - // Stop disk/network I/O before removing partial downloads on Windows. - client.close(force: true); - try { - await transfer; - } catch (_) { - /* Preserve the timeout. */ - } - rethrow; - } - if ((size != null && await file.length() != size) || - (await sha256.bind(file.openRead()).first).toString() != digest) { - throw StateError('Prebuilt integrity check failed: $url'); - } -} - -Future _transfer( - HttpClient client, - Uri uri, - File file, - int maximum, -) async { - for (var redirects = 0; redirects <= 5; redirects++) { - _requireHttps(uri); - final request = await client.getUrl(uri).timeout(_timeout); - request.followRedirects = false; - request.headers.set(HttpHeaders.acceptEncodingHeader, 'identity'); - final response = await request.close().timeout(_timeout); - 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('Prebuilt redirect has no Location.'); - } - uri = uri.resolve(location); - continue; - } - if (response.statusCode != HttpStatus.ok || - response.contentLength > maximum) { - await response.listen(null).cancel(); - throw HttpException( - 'Invalid prebuilt response: HTTP ${response.statusCode}, length ${response.contentLength}.', - uri: uri, - ); - } - final sink = file.openWrite(); - var received = 0; - try { - await for (final bytes in response.timeout(_timeout)) { - received += bytes.length; - if (received > maximum) { - throw StateError('Prebuilt exceeds expected size.'); - } - sink.add(bytes); - await sink.flush(); - } - } finally { - await sink.close(); - } - return; - } - throw HttpException('Too many prebuilt redirects.'); -} diff --git a/hook/src/source_fingerprint.dart b/hook/src/source_fingerprint.dart deleted file mode 100644 index 1053efe..0000000 --- a/hook/src/source_fingerprint.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:crypto/crypto.dart' as crypto; - -/// Shared by the build hook and release tooling. Include paths as well as bytes -/// so moving, adding, or deleting native sources invalidates a release. -Future<({String sha256, List dependencies})> sourceFingerprint( - Uri packageRoot, -) async { - final files = {}; - final dependencies = []; - for (final path in [ - 'rust/Cargo.toml', - 'rust/Cargo.lock', - 'rust/rust-toolchain.toml', - 'lib/src/epic_cash_wallet_bindings.g.dart', - 'rust/build.rs', - 'rust/cbindgen.toml', - 'hook/build.dart', - ]) { - files[path] = File.fromUri(packageRoot.resolve(path)); - } - for (final path in ['rust/src/', 'rust/include/']) { - final directory = Directory.fromUri(packageRoot.resolve(path)); - if (await FileSystemEntity.isLink(directory.path)) { - throw StateError('Prebuilt source inputs cannot be symlinks: $path'); - } - dependencies.add(directory.uri); - await for (final entity in directory.list( - recursive: true, - followLinks: false, - )) { - if (entity is Link) { - throw StateError( - 'Prebuilt source inputs cannot be symlinks: ${entity.path}', - ); - } - if (entity is Directory) dependencies.add(entity.uri); - if (entity is File) { - final relative = entity.uri.path.substring(packageRoot.path.length); - files[Uri.decodeComponent(relative)] = entity; - } - } - } - final paths = files.keys.toList()..sort(); - final framed = StringBuffer(); - for (final path in paths) { - final file = files[path]!; - if (await FileSystemEntity.isLink(file.path)) { - throw StateError('Prebuilt source inputs cannot be symlinks: $path'); - } - final digest = await crypto.sha256.bind(file.openRead()).first; - framed.write('$path\u0000$digest\n'); - dependencies.add(file.uri); - } - return ( - sha256: crypto.sha256.convert(utf8.encode(framed.toString())).toString(), - dependencies: dependencies, - ); -} diff --git a/pubspec.yaml b/pubspec.yaml index 62deb4c..d05475b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,13 +9,15 @@ environment: flutter: ">=3.47.0" dependencies: - crypto: ^3.0.7 code_assets: ^1.2.1 flutter: sdk: flutter ffi: ^2.2.0 hooks: ^2.1.0 - native_toolchain_rust: ^1.0.6 + dnix: + git: + url: https://github.com/cypherstack/dnix.git + ref: 448c5da6e61b3f0e62b4eae6d08b49158a9e45b2 decimal: ^2.1.0 mutex: ^3.0.0 uuid: ^4.0.0 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index b3466a1..e5bbb44 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1268,7 +1268,7 @@ dependencies = [ [[package]] name = "epic_core" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=fix%2Frandomx-v0.2.0-api#c592407af7000c0105cf83a0bc7ec7849b9356f4" +source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#9c41799d9a76a9c1bfab3b75fbc3c1fc56b34d51" dependencies = [ "blake2-rfc", "byteorder", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "epic_keychain" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=fix%2Frandomx-v0.2.0-api#c592407af7000c0105cf83a0bc7ec7849b9356f4" +source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#9c41799d9a76a9c1bfab3b75fbc3c1fc56b34d51" dependencies = [ "blake2-rfc", "byteorder", @@ -1394,7 +1394,7 @@ dependencies = [ [[package]] name = "epic_util" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=fix%2Frandomx-v0.2.0-api#c592407af7000c0105cf83a0bc7ec7849b9356f4" +source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#9c41799d9a76a9c1bfab3b75fbc3c1fc56b34d51" dependencies = [ "anyhow", "backtrace", @@ -4055,7 +4055,7 @@ dependencies = [ [[package]] name = "randomx" version = "0.2.0" -source = "git+https://github.com/cypherstack/randomx-rust#5147a05e320a9fb68306ae08a1e94bc9cdd56a77" +source = "git+https://github.com/cypherstack/randomx-rust?branch=reproducible-cross-builds#ea345c7867a7a1bd032550bf0040a07b18a6f454" dependencies = [ "bindgen", "byteorder", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 38cdac4..427352a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -21,7 +21,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1" serde_derive = "1" simplelog = "^0.7.4" -openssl = { version = "0.10", features = ["vendored"] } zeroize = { version = "1.1.0", features = ["derive"] } rand = "0.6" reqwest = { version = "0.11", features = ["blocking", "json"] } @@ -53,6 +52,11 @@ anyhow = "1.0.69" # Pinned for compatibility with the current Epic wallet dependency graph. crunchy = "=0.2.2" +# Built from source where native-tls uses OpenSSL (Linux, Android); the other +# platforms use their own TLS and never link it. +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +openssl = { version = "0.10", features = ["vendored"] } + [dev-dependencies] hex = "0.4.3" cbindgen = "=0.24.3" @@ -68,9 +72,9 @@ crate-type = ["staticlib", "cdylib"] liblmdb-sys = { git = "https://github.com/i1skn/lmdb-rs" } [patch."https://github.com/EpicCash/randomx-rust.git"] -randomx = { git = "https://github.com/cypherstack/randomx-rust" } +randomx = { git = "https://github.com/cypherstack/randomx-rust", branch = "reproducible-cross-builds" } [patch."https://github.com/EpicCash/epic.git"] -epic_core = { git = "https://github.com/cypherstack/epic.git", branch = "fix/randomx-v0.2.0-api" } -epic_util = { git = "https://github.com/cypherstack/epic.git", branch = "fix/randomx-v0.2.0-api" } -epic_keychain = { git = "https://github.com/cypherstack/epic.git", branch = "fix/randomx-v0.2.0-api" } +epic_core = { git = "https://github.com/cypherstack/epic.git", branch = "vendorable-foundation-json" } +epic_util = { git = "https://github.com/cypherstack/epic.git", branch = "vendorable-foundation-json" } +epic_keychain = { git = "https://github.com/cypherstack/epic.git", branch = "vendorable-foundation-json" } diff --git a/test/build_hook_test.dart b/test/build_hook_test.dart deleted file mode 100644 index 899ae01..0000000 --- a/test/build_hook_test.dart +++ /dev/null @@ -1,482 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:code_assets/code_assets.dart'; -import 'package:crypto/crypto.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks/hooks.dart'; - -import '../hook/build.dart' as hook; -import '../hook/src/prebuilt.dart'; -import '../hook/src/source_fingerprint.dart'; -import '../tool/prebuilt_manifest.dart'; - -void main() { - late Directory workspace; - late Directory artifacts; - late Map manifest; - late Map> responses; - late HttpServer server; - late List requests; - late List<_LoopbackClient> clients; - int status = 200; - String? redirect; - bool stall = false; - - setUp(() async { - workspace = await Directory.systemTemp.createTemp('epic-prebuilt-'); - artifacts = await Directory.fromUri(workspace.uri.resolve('artifacts/')) - .create(); - responses = {}; - for (final target in releaseTargets.keys) { - for (final name in [ - releaseFileName(target), - if (target.contains('android')) 'libc++_shared-$target.so', - ]) { - final bytes = utf8.encode('fixture $name'); - responses[name] = bytes; - await File.fromUri(artifacts.uri.resolve(name)).writeAsBytes(bytes); - } - } - manifest = await writeManifest( - artifacts, - inspectGlibc: (_) async => '2.35', - ); - requests = []; - clients = []; - status = 200; - redirect = null; - stall = false; - server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); - server.listen((request) async { - final name = request.uri.pathSegments.last; - requests.add(name); - request.response.statusCode = status; - if (redirect != null) { - request.response.headers.set(HttpHeaders.locationHeader, redirect!); - } - request.response.bufferOutput = !stall; - request.response.add( - responses[name] ?? utf8.encode(jsonEncode(manifest)), - ); - if (stall) { - unawaited(request.response.done.catchError((_) {})); - } else { - await request.response.close(); - } - }); - }); - - tearDown(() async { - for (final client in clients) { - client.close(force: true); - } - await server.close(force: true); - await workspace.delete(recursive: true); - }); - - Future run({ - OS os = OS.macOS, - Architecture arch = Architecture.arm64, - IOSSdk sdk = IOSSdk.iPhoneOS, - int minimum = 24, - LinkModePreference linkMode = LinkModePreference.dynamic, - Map settings = const {}, - Future Function(BuildInput, BuildOutput)? check, - }) async { - // Construct outside HttpOverrides so the delegate remains a real client. - final client = _LoopbackClient(server.port); - clients.add(client); - await testCodeBuildHook( - mainMethod: (args) => HttpOverrides.runZoned( - () => hook.main(args), - createHttpClient: (_) => client, - ), - targetOS: os, - targetArchitecture: arch, - targetIOSSdk: sdk, - targetAndroidNdkApi: minimum, - targetIOSVersion: minimum, - targetMacOSVersion: minimum, - linkModePreference: linkMode, - userDefines: PackageUserDefines( - workspacePubspec: PackageUserDefinesSource( - basePath: workspace.uri, - defines: { - 'native_build': 'prebuilt', - 'prebuilt_manifest_url': 'https://release.invalid/manifest.json', - 'prebuilt_manifest_sha256': sha256 - .convert(utf8.encode(jsonEncode(manifest))) - .toString(), - ...settings, - }, - ), - ), - check: (input, output) async { - // Temporary downloads must not become hook dependencies or survive a run. - expect( - output.dependencies.any((uri) => uri.path.contains('.prebuilt-')), - isFalse, - ); - expect( - Directory.fromUri(input.outputDirectory) - .listSync() - .whereType(), - isEmpty, - ); - await check?.call(input, output); - }, - ); - } - - Map entry(String target) => (manifest['artifacts'] as List) - .cast>() - .singleWhere((entry) => entry['target'] == target); - - test( - 'producer hashes all wallets and Android runtimes and pins the manifest', - () async { - final entries = (manifest['artifacts'] as List) - .cast>(); - expect(entries, hasLength(11)); - for (final entry in entries) { - for (final file in [ - entry, - ...?entry['dependencies'] as List>?, - ]) { - final bytes = responses[file['file']]!; - expect(file['size'], bytes.length); - expect(file['sha256'], sha256.convert(bytes).toString()); - } - } - final document = File.fromUri(artifacts.uri.resolve('manifest.json')); - expect(jsonDecode(await document.readAsString()), manifest); - expect( - await File.fromUri(artifacts.uri.resolve('manifest.sha256')) - .readAsString(), - '${await fileSha256(document)} manifest.json\n', - ); - await expectLater(writeManifest(artifacts), throwsStateError); - await document.delete(); - await File.fromUri(artifacts.uri.resolve('manifest.sha256')).delete(); - await File.fromUri( - artifacts.uri.resolve('libc++_shared-aarch64-linux-android.so'), - ).delete(); - await expectLater( - writeManifest(artifacts, inspectGlibc: (_) async => '2.35'), - throwsStateError, - ); - expect(await document.exists(), isFalse); - }, - ); - - for (final target in [ - ( - OS.macOS, - Architecture.arm64, - IOSSdk.iPhoneOS, - 'aarch64-apple-darwin', - 'libepic_cash_wallet.dylib', - ), - ( - OS.windows, - Architecture.x64, - IOSSdk.iPhoneOS, - 'x86_64-pc-windows-msvc', - 'epic_cash_wallet.dll', - ), - ( - OS.linux, - Architecture.x64, - IOSSdk.iPhoneOS, - 'x86_64-unknown-linux-gnu', - 'libepic_cash_wallet.so', - ), - ( - OS.iOS, - Architecture.arm64, - IOSSdk.iPhoneOS, - 'aarch64-apple-ios', - 'libepic_cash_wallet.dylib', - ), - ( - OS.iOS, - Architecture.arm64, - IOSSdk.iPhoneSimulator, - 'aarch64-apple-ios-sim', - 'libepic_cash_wallet.dylib', - ), - ( - OS.iOS, - Architecture.x64, - IOSSdk.iPhoneSimulator, - 'x86_64-apple-ios', - 'libepic_cash_wallet.dylib', - ), - ( - OS.android, - Architecture.arm, - IOSSdk.iPhoneOS, - 'armv7-linux-androideabi', - 'libepic_cash_wallet.so', - ), - ( - OS.android, - Architecture.arm64, - IOSSdk.iPhoneOS, - 'aarch64-linux-android', - 'libepic_cash_wallet.so', - ), - ( - OS.android, - Architecture.x64, - IOSSdk.iPhoneOS, - 'x86_64-linux-android', - 'libepic_cash_wallet.so', - ), - ]) { - test('downloads and bundles ${target.$4} through the build hook', () async { - for (var attempt = 0; attempt < 2; attempt++) { - await run( - os: target.$1, - arch: target.$2, - sdk: target.$3, - minimum: target.$1 == OS.iOS ? 13 : 24, - check: (input, output) async { - final assets = { - for (final asset in output.assets.code) - asset.file!.pathSegments.last: asset, - }; - expect( - assets.keys, - unorderedEquals([ - target.$5, - if (target.$1 == OS.android) 'libc++_shared.so', - ]), - ); - final wallet = assets[target.$5]!; - expect( - wallet.id, - 'package:flutter_libepiccash/src/epic_cash_wallet_bindings.g.dart', - ); - expect(wallet.linkMode, isA()); - expect( - await File.fromUri(wallet.file!).readAsBytes(), - responses[releaseFileName(target.$4)], - ); - if (target.$1 == OS.android) { - final runtime = assets['libc++_shared.so']!; - expect( - runtime.id, - 'package:flutter_libepiccash/libc++_shared.so', - ); - expect( - await File.fromUri(runtime.file!).readAsBytes(), - responses['libc++_shared-${target.$4}.so'], - ); - } - }, - ); - } - expect(requests, [ - for (var i = 0; i < 2; i++) ...[ - 'manifest.json', - releaseFileName(target.$4), - if (target.$1 == OS.android) 'libc++_shared-${target.$4}.so', - ], - ]); - }); - } - - for (final mode in [null, 'source']) { - test('selects source builds with native_build: $mode', () async { - await testCodeBuildHook( - mainMethod: (args) => build(args, (input, output) async { - expect(prebuiltManifest(input), isNull); - }), - userDefines: PackageUserDefines( - workspacePubspec: PackageUserDefinesSource( - basePath: workspace.uri, - defines: {'native_build': ?mode}, - ), - ), - check: (_, _) {}, - ); - expect(requests, isEmpty); - }); - } - - for (final settings in >[ - {'native_build': 'invalid'}, - {'prebuilt_manifest_url': 'http://release.invalid/manifest.json'}, - { - 'prebuilt_manifest_url': - 'https://user:pass@release.invalid/manifest.json', - }, - {'prebuilt_manifest_sha256': null}, - {'prebuilt_manifest_sha256': 'g' * 64}, - ]) { - test('rejects invalid configuration: $settings', () async { - await expectLater(run(settings: settings), throwsFormatException); - expect(requests, isEmpty); - }); - } - - for (final file in [ - 'manifest.json', - 'libepic_cash_wallet-aarch64-linux-android.so', - 'libc++_shared-aarch64-linux-android.so', - ]) { - test('fails closed for corrupted $file', () async { - responses[file] = [1]; - await expectLater(run(os: OS.android), throwsStateError); - expect(requests.last, file); - }); - } - - test('rejects oversized downloads and HTTP errors', () async { - final wallet = releaseFileName('aarch64-apple-darwin'); - responses[wallet] = List.filled(responses[wallet]!.length + 1, 42); - await expectLater(run(), throwsStateError); - status = 404; - await expectLater(run(), throwsA(isA())); - }); - - test('follows HTTPS redirects but rejects downgrades and loops', () async { - status = 302; - redirect = 'https://release.invalid/loop'; - await expectLater(run(), throwsA(isA())); - expect(requests, hasLength(6)); - requests.clear(); - redirect = 'http://release.invalid/insecure'; - await expectLater(run(), throwsFormatException); - expect(requests, ['manifest.json']); - }); - - test('cancels timed-out downloads before cleanup', () async { - stall = true; - await runZoned( - () => expectLater(run(), throwsA(isA())), - zoneSpecification: ZoneSpecification( - createTimer: (self, parent, zone, duration, callback) { - return parent.createTimer( - zone, - duration == const Duration(minutes: 10) - ? const Duration(milliseconds: 100) - : duration, - callback, - ); - }, - ), - ); - expect(clients.single.forceClosed, isTrue); - }); - - for (final mutate in { - 'source mismatch': () => manifest['source_sha256'] = '0' * 64, - 'wrong package': () => manifest['package'] = 'other', - 'duplicate target': () => - (manifest['artifacts'] as List).add(entry('aarch64-apple-darwin')), - 'path traversal': () => - entry('aarch64-apple-darwin')['file'] = '../outside.dylib', - 'missing OS floor': () => - entry('aarch64-apple-darwin').remove('minimum_os_version'), - 'missing Android runtime': () => - entry('aarch64-linux-android').remove('dependencies'), - }.entries) { - test('rejects manifest ${mutate.key}', () async { - mutate.value(); - await expectLater( - run(os: mutate.key.contains('Android') ? OS.android : OS.macOS), - throwsA(anything), - ); - expect(requests, ['manifest.json']); - }); - } - - test('checks deployment minimums and required static linking', () async { - await expectLater(run(os: OS.android, minimum: 23), throwsUnsupportedError); - await expectLater( - run(linkMode: LinkModePreference.static), - throwsUnsupportedError, - ); - await expectLater( - run(os: OS.iOS, arch: Architecture.x64), - throwsUnsupportedError, - ); - await run(linkMode: LinkModePreference.preferStatic); - }); - - test( - 'fingerprints detect native edits, additions, renames, and symlinks', - () async { - final root = Directory.fromUri(workspace.uri.resolve('source/')); - for (final path in [ - 'rust/Cargo.toml', - 'rust/Cargo.lock', - 'rust/rust-toolchain.toml', - 'rust/build.rs', - 'rust/cbindgen.toml', - 'rust/src/lib.rs', - 'rust/include/epic_cash_wallet.h', - 'lib/src/epic_cash_wallet_bindings.g.dart', - 'hook/build.dart', - ]) { - final file = File.fromUri(root.uri.resolve(path)); - await file.parent.create(recursive: true); - await file.writeAsString('fixture: $path'); - } - var fingerprint = await sourceFingerprint(root.uri); - for (final change in Function()>[ - () async { - await File.fromUri(root.uri.resolve('rust/build.rs')) - .writeAsString('changed'); - }, - () async { - await File.fromUri(root.uri.resolve('rust/src/new.rs')) - .writeAsString('new'); - }, - () async { - await File.fromUri(root.uri.resolve('rust/src/new.rs')) - .rename(root.uri.resolve('rust/src/renamed.rs').toFilePath()); - }, - ]) { - await change(); - final changed = await sourceFingerprint(root.uri); - expect(changed.sha256, isNot(fingerprint.sha256)); - fingerprint = changed; - } - expect(fingerprint.dependencies, contains(root.uri.resolve('rust/src/'))); - if (!Platform.isWindows) { - await Link.fromUri(root.uri.resolve('rust/src/link.rs')) - .create(root.uri.resolve('rust/build.rs').toFilePath()); - await expectLater(sourceFingerprint(root.uri), throwsStateError); - } - }, - ); -} - -// Route HTTPS test requests to loopback without weakening production URL checks. -class _LoopbackClient implements HttpClient { - final int port; - final HttpClient delegate = HttpClient(); - bool forceClosed = false; - _LoopbackClient(this.port); - @override - Future getUrl(Uri url) => delegate.getUrl( - url.replace(scheme: 'http', host: '127.0.0.1', port: port), - ); - @override - set connectionTimeout(Duration? value) => delegate.connectionTimeout = value; - @override - set autoUncompress(bool value) => delegate.autoUncompress = value; - @override - void close({bool force = false}) { - forceClosed |= force; - delegate.close(force: force); - } - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} diff --git a/tool/prebuilt_manifest.dart b/tool/prebuilt_manifest.dart deleted file mode 100644 index 4efb34e..0000000 --- a/tool/prebuilt_manifest.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:crypto/crypto.dart'; - -import '../hook/src/source_fingerprint.dart'; - -const releaseTargets = { - 'aarch64-linux-android': 24, - 'armv7-linux-androideabi': 24, - 'x86_64-linux-android': 24, - 'aarch64-apple-ios': 15, - 'aarch64-apple-ios-sim': 15, - 'x86_64-apple-ios': 15, - 'aarch64-unknown-linux-gnu': null, - 'x86_64-unknown-linux-gnu': null, - 'aarch64-apple-darwin': 12, - 'x86_64-apple-darwin': 12, - 'x86_64-pc-windows-msvc': null, -}; - -String releaseFileName(String target) { - if (!releaseTargets.containsKey(target)) { - throw ArgumentError.value(target, 'target', 'Unsupported release target'); - } - final extension = target.contains('apple') - ? 'dylib' - : target.contains('windows') - ? 'dll' - : 'so'; - return 'libepic_cash_wallet-$target.$extension'; -} - -Future fileSha256(File file) async => - (await sha256.bind(file.openRead()).first).toString(); - -/// Read the actual required symbol versions; do not guess from the build host. -Future minimumGlibc(File file) async { - final result = await Process.run('readelf', ['--version-info', file.path]); - if (result.exitCode != 0) { - throw StateError('readelf failed for ${file.path}: ${result.stderr}'); - } - final versions = RegExp(r'Name: GLIBC_(\d+)\.(\d+)\b') - .allMatches(result.stdout as String) - .map((match) => (int.parse(match[1]!), int.parse(match[2]!))) - .toList() - ..sort( - (a, b) => a.$1 != b.$1 ? a.$1.compareTo(b.$1) : a.$2.compareTo(b.$2), - ); - if (versions.isEmpty) { - throw StateError('No required glibc symbol versions in ${file.path}'); - } - final version = versions.last; - return '${version.$1}.${version.$2}'; -} - -/// Add a manifest to the existing release directory. All targets are required. -Future> writeManifest( - Directory directory, { - Future Function(File) inspectGlibc = minimumGlibc, -}) async { - final manifestFile = File.fromUri(directory.uri.resolve('manifest.json')); - final pinFile = File.fromUri(directory.uri.resolve('manifest.sha256')); - if (await manifestFile.exists() || await pinFile.exists()) { - throw StateError('Manifest output already exists.'); - } - Future> describe(String name) async { - final file = File.fromUri(directory.uri.resolve(name)); - if (await FileSystemEntity.type(file.path, followLinks: false) != - FileSystemEntityType.file) { - throw StateError('Missing or nonregular release artifact: $name'); - } - final size = await file.length(); - if (size <= 0 || size > 2 * 1024 * 1024 * 1024) { - throw StateError('Invalid release artifact size: $name'); - } - return {'file': name, 'size': size, 'sha256': await fileSha256(file)}; - } - - final artifacts = >[]; - final targets = releaseTargets.keys.toList()..sort(); - for (final target in targets) { - final name = releaseFileName(target); - artifacts.add({ - 'target': target, - 'link_mode': 'dynamic', - ...await describe(name), - if (releaseTargets[target] case final int minimum) - 'minimum_os_version': minimum, - if (target.endsWith('linux-gnu')) - 'minimum_glibc_version': await inspectGlibc( - File.fromUri(directory.uri.resolve(name)), - ), - if (target.contains('android')) - 'dependencies': [await describe('libc++_shared-$target.so')], - }); - } - final source = await sourceFingerprint(Directory.current.uri); - final manifest = { - 'schema_version': 1, - 'package': 'flutter_libepiccash', - 'source_sha256': source.sha256, - 'artifacts': artifacts, - }; - await manifestFile.writeAsString( - '${const JsonEncoder.withIndent(' ').convert(manifest)}\n', - ); - await pinFile.writeAsString( - '${await fileSha256(manifestFile)} manifest.json\n', - ); - return manifest; -} - -Future main(List args) async { - if (args.length != 1) { - stderr.writeln( - 'Usage: prebuilt_manifest.dart ', - ); - exitCode = 64; - return; - } - await writeManifest(Directory(args.single).absolute); -} diff --git a/tool/update_lock.dart b/tool/update_lock.dart new file mode 100644 index 0000000..7308919 --- /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 0000000..0dd9d24 --- /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 17b72176c8d0f0d1a227a1c772ef5b7f6650e291 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Thu, 22 Oct 2026 00:23:27 -0600 Subject: [PATCH 02/11] 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 Linux and macOS, against the prebuilt library once its release exists and from source before that, checks the generated bindings, builds the example for macOS and the iOS simulator, and runs the Rust unit tests, as before. The per-platform release builds went with the pipeline they served, and the Windows test host with the Windows library. --- .github/workflows/ci.yml | 132 --------------- .github/workflows/native.yml | 25 +++ .github/workflows/release.yml | 292 ---------------------------------- .github/workflows/tests.yml | 116 ++++++++++++++ 4 files changed, 141 insertions(+), 424 deletions(-) delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/native.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index d064003..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: CI - -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - -permissions: - contents: read - -env: - CARGO_TERM_COLOR: always - -jobs: - host-native-tests: - strategy: - fail-fast: false - matrix: - os: - - ubuntu-24.04 - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - timeout-minutes: 45 - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.99.1" - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: host-${{ matrix.os }} - - - uses: actions/cache@v5 - with: - path: .dart_tool/hooks_runner - key: native-assets-${{ matrix.os }}-${{ hashFiles('rust/Cargo.lock', 'rust/**/*.rs', 'hook/build.dart') }} - - - uses: subosito/flutter-action@v2 - with: - flutter-version: 3.47.2 - channel: stable - cache: true - - - name: Install Linux native prerequisites - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y clang cmake libclang-dev ninja-build pkg-config - - - name: Shorten Windows build path - if: runner.os == 'Windows' - run: | - subst T: "$env:GITHUB_WORKSPACE" - echo 'BUILD_DIR=T:\' >> $env:GITHUB_ENV - - - run: flutter pub get - working-directory: ${{ env.BUILD_DIR || github.workspace }} - - run: flutter test - working-directory: ${{ env.BUILD_DIR || github.workspace }} - - - name: Verify focused analysis and generated bindings - if: runner.os == 'Linux' - run: | - dart analyze hook tool test/build_hook_test.dart lib/epic_cash.dart lib/flutter_libepiccash.dart - dart run ffigen --config ffigen.yaml - git diff --exit-code -- lib/src/epic_cash_wallet_bindings.g.dart - - apple-example-builds: - runs-on: macos-latest - timeout-minutes: 60 - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.99.1" - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: apple-examples - - - uses: actions/cache@v5 - with: - path: example/.dart_tool/hooks_runner - key: native-assets-apple-examples-${{ hashFiles('rust/Cargo.lock', 'rust/**/*.rs', 'hook/build.dart') }} - - - uses: subosito/flutter-action@v2 - with: - flutter-version: 3.47.2 - channel: stable - cache: true - - - run: flutter pub get - - run: flutter pub get - working-directory: example - - run: flutter test - working-directory: example - - run: flutter build macos --debug - working-directory: example - - run: flutter build ios --simulator --debug --no-codesign - working-directory: example - - rust-tests: - runs-on: ubuntu-24.04 - timeout-minutes: 60 - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.99.1" - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: rust-tests - - - name: Install Linux native prerequisites - run: | - sudo apt-get update - sudo apt-get install -y clang cmake libclang-dev ninja-build pkg-config - - - name: Run Rust unit tests - run: cargo test --locked --lib - working-directory: rust diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml new file mode 100644 index 0000000..f6dd84f --- /dev/null +++ b/.github/workflows/native.yml @@ -0,0 +1,25 @@ +# Builds the Rust library on every reference host, fails if any host or the +# committed dnix.lock disagrees, and publishes the release the lock names +# when it reaches the default branch. +name: Native library + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: write + +# A newer push supersedes runs on a branch, never on main: a run there may +# be publishing. +concurrency: + group: native-${{ github.ref }} + cancel-in-progress: ${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }} + +jobs: + native: + # The same dnix commit as pubspec.yaml and flake.lock. + uses: cypherstack/dnix/.github/workflows/native.yml@448c5da6e61b3f0e62b4eae6d08b49158a9e45b2 + with: + macos-runner: xcode-27 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index aa34bbb..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,292 +0,0 @@ -name: Build native artifacts - -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - - "native-*" - workflow_dispatch: - -permissions: - contents: read - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: native-prebuilts-${{ github.ref }} - cancel-in-progress: false - -jobs: - build-linux: - runs-on: ubuntu-24.04 - container: - image: ghcr.io/cypherstack/stackwallet-ci:latest - strategy: - fail-fast: false - matrix: - include: - - target: x86_64-unknown-linux-gnu - asset: libepic_cash_wallet-x86_64-unknown-linux-gnu.so - - target: aarch64-unknown-linux-gnu - asset: libepic_cash_wallet-aarch64-unknown-linux-gnu.so - steps: - - uses: actions/checkout@v6 - - - name: Select Rust toolchain - run: | - rustup toolchain install 1.99.1 --profile minimal --target "${{ matrix.target }}" - rustup default 1.99.1 - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: linux-${{ matrix.target }} - - - name: Build dynamic library - env: - CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc - CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ - run: | - cd rust - cargo build --locked --release --lib --target "${{ matrix.target }}" - cp "target/${{ matrix.target }}/release/libepic_cash_wallet.so" "${{ matrix.asset }}" - - - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.asset }} - path: rust/${{ matrix.asset }} - if-no-files-found: error - - build-android: - runs-on: ubuntu-24.04 - container: - image: ghcr.io/cypherstack/stackwallet-ci:latest - strategy: - fail-fast: false - matrix: - include: - - target: aarch64-linux-android - abi: arm64-v8a - asset: libepic_cash_wallet-aarch64-linux-android.so - - target: armv7-linux-androideabi - abi: armeabi-v7a - asset: libepic_cash_wallet-armv7-linux-androideabi.so - - target: x86_64-linux-android - abi: x86_64 - asset: libepic_cash_wallet-x86_64-linux-android.so - steps: - - uses: actions/checkout@v6 - - - name: Select Rust toolchain - run: | - rustup toolchain install 1.99.1 --profile minimal --target "${{ matrix.target }}" - rustup default 1.99.1 - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: android-${{ matrix.target }} - - - name: Build dynamic library - run: | - cd rust - cargo ndk --target "${{ matrix.target }}" --platform 24 \ - --link-libcxx-shared --output-dir native-libs \ - build --locked --release --lib - cp "target/${{ matrix.target }}/release/libepic_cash_wallet.so" "${{ matrix.asset }}" - cp "native-libs/${{ matrix.abi }}/libc++_shared.so" "libc++_shared-${{ matrix.target }}.so" - - - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.asset }} - path: | - rust/${{ matrix.asset }} - rust/libc++_shared-${{ matrix.target }}.so - if-no-files-found: error - - build-ios: - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - include: - - target: aarch64-apple-ios - sdk: iphoneos - asset: libepic_cash_wallet-aarch64-apple-ios.dylib - - target: aarch64-apple-ios-sim - sdk: iphonesimulator - asset: libepic_cash_wallet-aarch64-apple-ios-sim.dylib - - target: x86_64-apple-ios - sdk: iphonesimulator - asset: libepic_cash_wallet-x86_64-apple-ios.dylib - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.99.1" - targets: ${{ matrix.target }} - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: ios-${{ matrix.target }} - - - name: Build dynamic library - env: - IPHONEOS_DEPLOYMENT_TARGET: "15.0" - run: | - export SDKROOT="$(xcrun --sdk "${{ matrix.sdk }}" --show-sdk-path)" - cd rust - cargo build --locked --release --lib --target "${{ matrix.target }}" - cp "target/${{ matrix.target }}/release/libepic_cash_wallet.dylib" "${{ matrix.asset }}" - - - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.asset }} - path: rust/${{ matrix.asset }} - if-no-files-found: error - - build-macos: - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - target: - - aarch64-apple-darwin - - x86_64-apple-darwin - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.99.1" - targets: ${{ matrix.target }} - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: macos-${{ matrix.target }} - - - name: Build dynamic library - env: - MACOSX_DEPLOYMENT_TARGET: "12.0" - run: | - cd rust - cargo build --locked --release --lib --target "${{ matrix.target }}" - cp "target/${{ matrix.target }}/release/libepic_cash_wallet.dylib" \ - "libepic_cash_wallet-${{ matrix.target }}.dylib" - - - uses: actions/upload-artifact@v4 - with: - name: libepic_cash_wallet-${{ matrix.target }}.dylib - path: rust/libepic_cash_wallet-${{ matrix.target }}.dylib - if-no-files-found: error - - build-windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.99.1" - targets: x86_64-pc-windows-msvc - - - uses: Swatinem/rust-cache@v2 - with: - workspaces: rust - key: windows-x86_64-pc-windows-msvc - - - name: Check native prerequisites - shell: pwsh - run: | - cmake --version - perl --version - - - name: Build dynamic library - shell: pwsh - run: | - Set-Location rust - cargo build --locked --release --lib --target x86_64-pc-windows-msvc - Copy-Item ` - target/x86_64-pc-windows-msvc/release/epic_cash_wallet.dll ` - libepic_cash_wallet-x86_64-pc-windows-msvc.dll - - - uses: actions/upload-artifact@v4 - with: - name: libepic_cash_wallet-x86_64-pc-windows-msvc.dll - path: rust/libepic_cash_wallet-x86_64-pc-windows-msvc.dll - if-no-files-found: error - - release: - needs: - - build-linux - - build-android - - build-ios - - build-macos - - build-windows - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v6 - - - uses: actions/download-artifact@v4 - with: - path: artifacts - merge-multiple: true - - - uses: subosito/flutter-action@v2 - with: - flutter-version: 3.47.2 - channel: stable - cache: true - - - run: flutter pub get - - - name: Assemble manifest and checksums - run: | - dart --packages=.dart_tool/package_config.json tool/prebuilt_manifest.dart artifacts - cp rust/include/epic_cash_wallet.h artifacts/ - cd artifacts - find . -maxdepth 1 -type f ! -name checksums.txt -print0 \ - | sort -z | xargs -0 sha256sum > checksums.txt - - - uses: actions/upload-artifact@v4 - with: - name: native-release - path: artifacts/* - if-no-files-found: error - - - name: Publish a new release - if: startsWith(github.ref, 'refs/tags/') - env: - GH_TOKEN: ${{ github.token }} - RELEASE_TAG: ${{ github.ref_name }} - shell: bash - run: | - [[ "$RELEASE_TAG" =~ ^(native-[A-Za-z0-9][A-Za-z0-9._-]*|v[0-9]+\.[0-9]+\.[0-9]+)$ ]] || { echo 'Invalid release tag'; exit 1; } - source_commit=$(git rev-parse HEAD) - git fetch --depth=1 --no-tags origin "refs/tags/$RELEASE_TAG" - [[ "$(git rev-parse 'FETCH_HEAD^{commit}')" == "$source_commit" ]] || { echo 'Tag moved during this build'; exit 1; } - read -r manifest_pin _ < artifacts/manifest.sha256 - cat > "$RUNNER_TEMP/native-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 + 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: Verify the generated bindings + if: runner.os == 'Linux' && runner.arch == 'X64' + run: | + sudo apt-get update && sudo apt-get install -y libclang-dev + dart run ffigen --config ffigen.yaml + git diff --exit-code -- lib/src/epic_cash_wallet_bindings.g.dart + + # The example app builds for macOS and the iOS simulator, as before the + # native library moved to dnix. Source mode needs the pinned Xcode. + apple-example-builds: + name: Example app on macOS and the iOS simulator + runs-on: xcode-27 + timeout-minutes: 90 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: subosito/flutter-action@v2 + with: + flutter-version: 3.47.2 + channel: stable + cache: true + - uses: DeterminateSystems/nix-installer-action@v23 + - run: flutter pub get + - name: Check whether the lock's release is published + id: release + shell: bash + run: | + repository=$(jq -r .repository dnix.json | sed 's/\.git$//; s#/$##') + tag="native-$(jq -r .release dnix.lock)" + echo "published=$([ "$(curl -s -o /dev/null -w '%{http_code}' "$repository/releases/tag/$tag")" = 200 ] && echo true || echo false)" >> "$GITHUB_OUTPUT" + - name: Choose the build mode + shell: bash + run: echo '${{ steps.release.outputs.published == 'true' && 'prebuilt' || 'source' }}' > .dnix-build + - run: flutter test + working-directory: example + - run: flutter build macos --debug + working-directory: example + - run: flutter build ios --simulator --debug --no-codesign + working-directory: example + + rust: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@1.99.1 + - uses: Swatinem/rust-cache@v2 + with: + workspaces: rust + - name: Install Linux native prerequisites + run: | + sudo apt-get update + sudo apt-get install -y clang cmake libclang-dev ninja-build pkg-config + - name: Run Rust unit tests + run: cargo test --locked --lib + working-directory: rust From ef6b99aae0d53e62a4cf79c090022bf558d6d7e1 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Thu, 22 Oct 2026 00:23:27 -0600 Subject: [PATCH 03/11] 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, how anyone rebuilds a platform and compares, and why there is no Windows library yet. --- README.md | 135 +++++++++++++++++++++++++++++------------------------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index da44863..b5584ac 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,74 @@ # flutter_libepiccash Dart bindings for the Epic Cash wallet library. The Rust library is built and -bundled through Flutter Native Assets, using Flutter's standard asset bundling -instead of the former plugin packaging and platform-specific download scripts. +bundled through Flutter Native Assets. ## Requirements -- Flutter 3.47 or newer -- Dart 3.13 or newer -- For source builds: Rust 1.99.1 through `rustup` (selected by - [`rust/rust-toolchain.toml`](rust/rust-toolchain.toml)) -- The normal native build tools for the target platform: Xcode on Apple - platforms, the Android NDK for Android, a C/C++ toolchain and CMake on - Linux, or Visual Studio Build Tools plus Perl on Windows +- Flutter 3.47 or newer, Dart 3.13 or newer. Nothing else: the build hook + downloads the library built for this version and checks its hash. +- To build the library yourself: Nix on Linux or an Apple Silicon Mac, and + Xcode 27.0 for the Apple targets. OpenSSL and RandomX are compiled from + source inside that build. The example projects target Android API 24, iOS 15, and macOS 12 or newer. -## Build from source +## Native library -Source builds are the default. Once Rust and the platform toolchain are -available, use the package like any other Flutter dependency: +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: -```sh -flutter pub get -flutter run +```yaml +hooks: + user_defines: + flutter_libepiccash: + build: prebuilt # the default; or source, or dev ``` -Flutter runs `hook/build.dart`, compiles the Rust crate for the application's -target architecture, and bundles the resulting dynamic library. The first -build can take several minutes because OpenSSL, RandomX, and the wallet -dependencies are compiled from source. Cargo and Flutter cache subsequent -builds. +- `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. +- `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. + +Only dynamic libraries are provided, for Android API 24, iOS 15, macOS 12, +and glibc 2.28 at minimum, on armv7, arm64, and x64 Android, arm64 and x64 +macOS, iOS devices and simulators, and x64 and arm64 Linux. There is no +Windows library yet: RandomX's C++ build assumes Microsoft's compiler and +assembler, which a build on any other host cannot provide. The Android +libraries link the C++ runtime statically, so no `libc++_shared.so` ships +beside them. The floors, the Rust and Xcode versions, and the location of +the Rust workspace live in `dnix.json`. + +### 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, 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. + +### Tests + +`flutter test` runs the Dart tests against the bundled library, and +`cargo test --locked --lib` in `rust` runs the Rust unit tests. Before the +lock's release is published, or to test local Rust changes, the Dart tests +need the library built from source: `echo dev > .dnix-build` with Nix +installed (`source` to also check the hash). ## Migrate an existing application @@ -47,56 +85,29 @@ previously supplied this permission: ```xml -``` +```## Regenerate and verify the ABI -## Use prebuilt native assets - -To download a release, copy its manifest URL and SHA-256 into the application's -root `pubspec.yaml` (workspace root for pub workspaces): +The checked-in header is the ABI source for generated Dart declarations: -```yaml -hooks: - user_defines: - flutter_libepiccash: - native_build: prebuilt - prebuilt_manifest_url: https://github.com/cypherstack/flutter_libepiccash/releases/download/native-YOUR-RELEASE/manifest.json - prebuilt_manifest_sha256: "REPLACE_WITH_RELEASE_MANIFEST_SHA256" +```sh +dart run ffigen --config ffigen.yaml +flutter test ``` -Use the package revision named in the release. The hook verifies the manifest, -native source fingerprint, deployment minimum, and each library's size and hash, -including Android's C++ runtime. Invalid or missing assets fail the build. -Downloads require public HTTPS and dynamic linking without sanitizers. Rust is -not needed, but Flutter's normal application build tools are still required. - -Downloads live in Flutter's hook output. Each hook run fetches fresh files; -incremental builds can reuse the output until `flutter clean` removes it. -Omit the settings or use `native_build: source` to compile Rust. - -Prebuilts require Android API 24, iOS 15, or macOS 12. Linux deployments must meet -the target's `minimum_glibc_version` in the manifest; musl is unsupported. Windows -releases cover x64. - -## Publish native prebuilts - -Push a new `native-*` tag (for example `native-0.0.1-1`) or `vX.Y.Z` tag. The -release workflow builds all eleven targets and publishes the libraries, header, -checksums, and pinned manifest together. Release notes include consumer settings. -Existing releases are not overwritten; manual branch runs only upload workflow -artifacts. Older releases without a manifest cannot use download mode. - -Manifest generation uses the existing build recipes. To generate one locally, -place all target-named libraries and Android runtimes built from the current -revision in `artifacts/`, then run from the repository root: +After intentionally changing an exported Rust function, regenerate the C +header from the Rust crate before running FFIgen: ```sh -flutter pub get -dart --packages=.dart_tool/package_config.json tool/prebuilt_manifest.dart artifacts +cargo install cbindgen --version 0.24.3 +cd rust +cbindgen --config cbindgen.toml \ + --crate epic-cash-wallet \ + --output include/epic_cash_wallet.h ``` -Linux inspection requires `readelf`. Upload the manifest and libraries together. -Resolver tests run with the existing `flutter test` suite and CI. - +`test/ffi_abi_test.dart` checks exported Rust names and arities against the +header. `test/native_assets_smoke_test.dart` loads the bundled native asset, +calls the mnemonic function, and exercises Rust-owned string deallocation. ## Regenerate and verify the ABI The checked-in header is the ABI source for generated Dart declarations: From 8dc3ae16d505749243e288f4aadf66d7c6e0f505 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Thu, 22 Oct 2026 13:22:15 -0600 Subject: [PATCH 04/11] Build a Windows library for the GNU ABI The library gains x86_64-pc-windows-gnullvm, built with zig's MinGW-w64 through dnix. RandomX's build assumes Microsoft's compiler and assembler on the MSVC side, which no other host provides, while its GCC-style build works unchanged, so the GNU ABI is what Windows gets. The DLL links its runtime pieces statically and imports only system DLLs; an MSVC-built Flutter application loads it like any other. There is no arm64 Windows library: winapi 0.2 in the dependency tree defines its types for x86 only. dnix moves to the commit that supports the GNU targets and what this crate's build script needs of them: CMake driven by the cmake crate, bindgen, and the windows crate's synchronization import library. The tests workflow gains a Windows job that runs flutter test with the prebuilt library once the lock's release is published, the first place the Windows DLL is loaded rather than compared by hash. --- .github/workflows/native.yml | 2 +- .github/workflows/tests.yml | 38 ++++++++++++++++++++++++++++++++++++ README.md | 18 +++++++++++------ dnix.json | 3 ++- dnix.lock | 7 ++++--- example/pubspec.lock | 4 ++-- flake.lock | 6 +++--- pubspec.yaml | 2 +- 8 files changed, 63 insertions(+), 17 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index f6dd84f..3e92473 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/.github/workflows/tests.yml b/.github/workflows/tests.yml index 78a00b7..17b9a85 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,6 +62,44 @@ jobs: dart run ffigen --config ffigen.yaml git diff --exit-code -- lib/src/epic_cash_wallet_bindings.g.dart + # A Windows process can only use the prebuilt library, so this runs once + # the lock's release is published from the default branch, and is the + # one place the Windows DLL is loaded rather than compared by hash. + windows: + name: Windows, prebuilt library + runs-on: windows-2025 + timeout-minutes: 60 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: subosito/flutter-action@v2 + with: + flutter-version: 3.47.2 + channel: stable + cache: true + - name: Check whether the lock's release is published + id: release + run: | + repository=$(jq -r .repository dnix.json | sed 's/\.git$//; s#/$##') + tag="native-$(jq -r .release dnix.lock)" + echo "published=$([ "$(curl -s -o /dev/null -w '%{http_code}' "$repository/releases/tag/$tag")" = 200 ] && echo true || echo false)" >> "$GITHUB_OUTPUT" + - name: Skip until the release exists + if: steps.release.outputs.published != 'true' + run: echo "::notice::No release $(jq -r .release dnix.lock) yet; the Windows tests run once it is published from the default branch." + - name: Run flutter test with the prebuilt library + if: steps.release.outputs.published == 'true' + run: | + flutter pub get + echo prebuilt > .dnix-build + if ! flutter test 2>&1 | tee test.log; then + echo "::error::$(tail -c 1500 test.log | tr '\n' ' ')" + exit 1 + fi + # The example app builds for macOS and the iOS simulator, as before the # native library moved to dnix. Source mode needs the pinned Xcode. apple-example-builds: diff --git a/README.md b/README.md index b5584ac..4e0b6f4 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,18 @@ hooks: Only dynamic libraries are provided, for Android API 24, iOS 15, macOS 12, and glibc 2.28 at minimum, on armv7, arm64, and x64 Android, arm64 and x64 -macOS, iOS devices and simulators, and x64 and arm64 Linux. There is no -Windows library yet: RandomX's C++ build assumes Microsoft's compiler and -assembler, which a build on any other host cannot provide. The Android -libraries link the C++ runtime statically, so no `libc++_shared.so` ships -beside them. The floors, the Rust and Xcode versions, and the location of -the Rust workspace live in `dnix.json`. +macOS, iOS devices and simulators, x64 and arm64 Linux, and x64 Windows. +The Windows library is built for the GNU ABI (`x86_64-pc-windows-gnullvm`, +MinGW-w64 as zig builds it): RandomX's C++ build assumes Microsoft's +compiler and assembler on the MSVC side, which no other host can provide, +while its GCC-style build works unchanged. The DLL links its runtime +pieces statically and imports only system DLLs, so it needs no Visual C++ +redistributable, and a Flutter application loads it like any other. There +is no arm64 Windows library: the dependency tree still carries `winapi` +0.2, which knows no arm64. The Android libraries link the C++ runtime +statically, so no `libc++_shared.so` ships beside them. The floors, the +Rust and Xcode versions, and the location of the Rust workspace live in +`dnix.json`. ### Releases diff --git a/dnix.json b/dnix.json index e845e5d..9b5ad8f 100644 --- a/dnix.json +++ b/dnix.json @@ -38,6 +38,7 @@ "armv7-linux-androideabi", "x86_64-linux-android", "aarch64-unknown-linux-gnu", - "x86_64-unknown-linux-gnu" + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-gnullvm" ] } diff --git a/dnix.lock b/dnix.lock index b9711c4..50aa0d3 100644 --- a/dnix.lock +++ b/dnix.lock @@ -1,16 +1,17 @@ { - "release": "8164512a97c6", + "release": "0fa5b397d984", "date": "2026-10-22", "files": { "aarch64-apple-darwin/libepic_cash_wallet.dylib": "b9bd894d1e2a3ad0241dc820bb7dfd172308348e793d3030276ef71070a36521", "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "ffbde5963a5c7cc45ca8ca123474c8a8c8850bdb284b619ce4240e84d5a63f4c", "aarch64-apple-ios/libepic_cash_wallet.dylib": "d17eb7e08e2cdcadc9d570ca6830154baf18303973ea3ac29f826c3c8ba05b9d", "aarch64-linux-android/libepic_cash_wallet.so": "dd5f6a9601cda87f0815b1946a11112d90ef5176f1d38b4ade4d530f731aed64", - "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "29f4a08ac8cc5c7b03f03d8aecf89a889f49b95cc80a964341c8ddf62116c166", + "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "1b2ff3a6fd00ae9195a20f541e7c84b7dcf3ab480ccb942a103c0880f6a3345f", "armv7-linux-androideabi/libepic_cash_wallet.so": "812f9fd3e9c205e92411f666d779e4428275f532be9941590b3c8efcdae89cc8", "x86_64-apple-darwin/libepic_cash_wallet.dylib": "4c73f7c39fe45c95de5298d4aae09df345fe2594538a9b46e2002f3267266324", "x86_64-apple-ios/libepic_cash_wallet.dylib": "65ad296d118e0fc15f09eb586ec5f6fbbce4f092caf18cc88597ebfe02f70485", "x86_64-linux-android/libepic_cash_wallet.so": "0f75fdf1dc3863b359db34fa05ef6f8ae4b7a11426d4043010ecc8ae3901e480", - "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "51f95ba2d1d20d7c5a6bb8bb19058b4887de3c9dae31a5e9d31818c398d581bb" + "x86_64-pc-windows-gnullvm/epic_cash_wallet.dll": "64d99f37f9696457082f73e004b3c357f89b8d7395759dd6f42afc705d7308f4", + "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "9492091f08ee5fd32b19ae1e06953010ec1f30f613fef88994a9adf6d239e76a" } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 70f53da..03c417b 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -69,8 +69,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 4e0af1b..18686c9 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 d05475b..26be02c 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 decimal: ^2.1.0 mutex: ^3.0.0 uuid: ^4.0.0 From 1c71be738db15995f2c441cb3fe0ce417d920c5b Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Fri, 23 Oct 2026 07:42:35 -0600 Subject: [PATCH 05/11] Pin the trimmed randomx-rust branch and the reworded epic commit The randomx-rust branch now carries only what these targets need: RandomX keeps its default architecture on Apple arm64, and Android links the NDK's libc++ statically. The MSVC changes it also carried are gone, since RandomX's MSVC build cannot work here and Windows builds for the GNU ABI, which needs none of them. So is the narrowing to RandomX's library alone: CMake builds its default target again, test programs included, as master does. The epic commit's message changed, not its files. Every library hashes as before, so dnix.lock and the release it names stay as they are. --- dnix.json | 2 +- rust/Cargo.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dnix.json b/dnix.json index 9b5ad8f..5405038 100644 --- a/dnix.json +++ b/dnix.json @@ -7,7 +7,7 @@ "workspace": "rust", "crate": "epic-cash-wallet", "outputHashes": { - "randomx-0.2.0": "sha256-rWChlbclK2SbZtDUVY1zjmpYxqb1NAJ9bgx4Qi3DwXQ=" + "randomx-0.2.0": "sha256-bh9GvkkIQhYnEWmIFRW7psPqfamB8QDJ5yyJ1dxbP6k=" }, "env": { "CXXSTDLIB_aarch64_linux_android": "static:-bundle=c++", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e5bbb44..3e1e24c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1268,7 +1268,7 @@ dependencies = [ [[package]] name = "epic_core" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#9c41799d9a76a9c1bfab3b75fbc3c1fc56b34d51" +source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#103a972a792d57f70d66f0114ceca63e083e743b" dependencies = [ "blake2-rfc", "byteorder", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "epic_keychain" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#9c41799d9a76a9c1bfab3b75fbc3c1fc56b34d51" +source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#103a972a792d57f70d66f0114ceca63e083e743b" dependencies = [ "blake2-rfc", "byteorder", @@ -1394,7 +1394,7 @@ dependencies = [ [[package]] name = "epic_util" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#9c41799d9a76a9c1bfab3b75fbc3c1fc56b34d51" +source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#103a972a792d57f70d66f0114ceca63e083e743b" dependencies = [ "anyhow", "backtrace", @@ -4055,7 +4055,7 @@ dependencies = [ [[package]] name = "randomx" version = "0.2.0" -source = "git+https://github.com/cypherstack/randomx-rust?branch=reproducible-cross-builds#ea345c7867a7a1bd032550bf0040a07b18a6f454" +source = "git+https://github.com/cypherstack/randomx-rust?branch=reproducible-cross-builds#b26097f78bbeadff5027dbfc2f4993bbb2f4d743" dependencies = [ "bindgen", "byteorder", From 10d4f156982017a6a53306de24fe29315fde33f5 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Fri, 23 Oct 2026 07:42:36 -0600 Subject: [PATCH 06/11] Fix the README and the test workflow, and tidy what the old flow left The README's ABI section had lost its fence and appeared twice, and it claimed the Windows DLL loads in an application before anything had loaded it. It now lists windows among the platforms verify_release rebuilds and tells applications to drop the user defines the old hook read. The CHANGELOG describes the dnix build instead of the manifests it replaced. The arm64 Linux test job could never start, since subosito/flutter-action finds no arm64 Flutter release for Linux, and main never ran Flutter there. .gitattributes gives the reason its LF rules still matter, and the ignored prebuilt and patches directories went with the tools that made them. --- .gitattributes | 3 ++- .github/workflows/tests.yml | 2 +- .gitignore | 2 -- CHANGELOG.md | 14 ++++++++--- README.md | 46 ++++++++++++------------------------- 5 files changed, 29 insertions(+), 38 deletions(-) diff --git a/.gitattributes b/.gitattributes index 42f4ed8..b26e72a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ -# Native source fingerprints must match across checkout platforms. +# Line endings stay LF on every checkout, Windows included, so every host +# builds from the same bytes. /rust/** text eol=lf /hook/build.dart text eol=lf /lib/src/epic_cash_wallet_bindings.g.dart text eol=lf diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 17b9a85..8a66874 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm, xcode-27] + os: [ubuntu-24.04, xcode-27] runs-on: ${{ matrix.os }} timeout-minutes: 90 steps: diff --git a/.gitignore b/.gitignore index cd260ce..087637a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,6 @@ migrate_working_dir/ .dart_tool/ .packages build/ -/patches/ -/prebuilt/ # Per-checkout dnix build mode override (see README). .dnix-build diff --git a/CHANGELOG.md b/CHANGELOG.md index bf04277..3bd3feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ - Pin Rust 1.99.1. Earlier toolchains strip Apple dynamic libraries with a misaligned `__LINKEDIT` string pool, which iOS 27 and macOS 27 refuse to load. -- Add opt-in verified native downloads and release manifests. - Require Flutter 3.47 and Dart 3.13. - Build and bundle the Rust dynamic library with Flutter Native Assets. - Replace runtime `DynamicLibrary` lookup code with generated `@Native` @@ -12,8 +11,17 @@ string. - Remove the legacy Flutter plugin classes, podspecs, CMake targets, copied binaries, and platform build/download scripts. -- Add target-named prebuilt asset support, ABI/smoke tests, and native artifact - release automation. +- Build the native library with [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; the `build` user define selects `source` or + `dev`, which build 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. Every change is built on four hosts, which + must produce the bytes the lock records. +- Build the Windows library for the GNU ABI; it imports only system DLLs. +- Link the C++ runtime statically on Android, so `libc++_shared.so` no longer + ships beside the library. +- Add ABI and smoke tests. ## 0.0.1 diff --git a/README.md b/README.md index 4e0b6f4..17642b9 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,11 @@ MinGW-w64 as zig builds it): RandomX's C++ build assumes Microsoft's compiler and assembler on the MSVC side, which no other host can provide, while its GCC-style build works unchanged. The DLL links its runtime pieces statically and imports only system DLLs, so it needs no Visual C++ -redistributable, and a Flutter application loads it like any other. There -is no arm64 Windows library: the dependency tree still carries `winapi` -0.2, which knows no arm64. The Android libraries link the C++ runtime -statically, so no `libc++_shared.so` ships beside them. The floors, the -Rust and Xcode versions, and the location of the Rust workspace live in -`dnix.json`. +redistributable. There is no arm64 Windows library: the dependency tree +still carries `winapi` 0.2, which knows no arm64. The Android libraries +link the C++ runtime statically, so no `libc++_shared.so` ships beside +them. The floors, the Rust and Xcode versions, and the location of the Rust +workspace live in `dnix.json`. ### Releases @@ -63,10 +62,11 @@ 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, 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. +`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. ### Tests @@ -84,6 +84,11 @@ Native Assets installation rules in desktop applications; see the example's [`windows/CMakeLists.txt`](example/windows/CMakeLists.txt) and [`linux/CMakeLists.txt`](example/linux/CMakeLists.txt). +The `native_build`, `prebuilt_manifest_url`, and `prebuilt_manifest_sha256` +user defines are no longer read; remove them. The library comes from the +release this package's `dnix.lock` names, so pinning the package's commit +pins the library too, and `build: source` builds it from source with Nix. + Android applications must declare Internet access in `android/app/src/main/AndroidManifest.xml`, directly inside ``, so release builds can connect to the node and Epicbox. The removed plugin manifest @@ -91,29 +96,8 @@ previously supplied this permission: ```xml -```## Regenerate and verify the ABI - -The checked-in header is the ABI source for generated Dart declarations: - -```sh -dart run ffigen --config ffigen.yaml -flutter test ``` -After intentionally changing an exported Rust function, regenerate the C -header from the Rust crate before running FFIgen: - -```sh -cargo install cbindgen --version 0.24.3 -cd rust -cbindgen --config cbindgen.toml \ - --crate epic-cash-wallet \ - --output include/epic_cash_wallet.h -``` - -`test/ffi_abi_test.dart` checks exported Rust names and arities against the -header. `test/native_assets_smoke_test.dart` loads the bundled native asset, -calls the mnemonic function, and exercises Rust-owned string deallocation. ## Regenerate and verify the ABI The checked-in header is the ABI source for generated Dart declarations: From 9bc05b63d00c1a5fcfd8728d0fb8439574e6c1b1 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Fri, 23 Oct 2026 08:04:49 -0600 Subject: [PATCH 07/11] Build the iOS libraries for iOS 13 Flutter 3.47 asks every native asset for iOS 13, whatever the application's deployment target, and writes 13.0 into the framework it wraps the library in, so with the floor at 15 dnix's hook refused every iOS build of an application using this package. main built iOS 15 into a framework that said 13.0. Only the three iOS libraries change bytes, so the lock names release native-fd015b3bc09c. --- CHANGELOG.md | 2 ++ README.md | 9 ++++++--- dnix.json | 2 +- dnix.lock | 10 +++++----- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd3feb..c6f4980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ - Build the Windows library for the GNU ABI; it imports only system DLLs. - Link the C++ runtime statically on Android, so `libc++_shared.so` no longer ships beside the library. +- Build the iOS libraries for iOS 13 (the arm64 simulator for 14), the + version Flutter 3.47 gives native assets. - Add ABI and smoke tests. ## 0.0.1 diff --git a/README.md b/README.md index 17642b9..1586ee6 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,12 @@ hooks: working on the Rust code. The hook notices a `.dnix-build` file from its next run on, so touch `dnix.json` after creating it. -Only dynamic libraries are provided, for Android API 24, iOS 15, macOS 12, -and glibc 2.28 at minimum, on armv7, arm64, and x64 Android, arm64 and x64 -macOS, iOS devices and simulators, x64 and arm64 Linux, and x64 Windows. +Only dynamic libraries are provided, for Android API 24, iOS 13 (the arm64 +simulator 14), macOS 12, and glibc 2.28 at minimum, on armv7, arm64, and +x64 Android, arm64 and x64 macOS, iOS devices and simulators, x64 and arm64 +Linux, and x64 Windows. iOS is 13 because Flutter 3.47 asks build hooks +for iOS 13 and writes 13.0 into each native asset's framework as its +minimum; applications can still require a newer iOS. The Windows library is built for the GNU ABI (`x86_64-pc-windows-gnullvm`, MinGW-w64 as zig builds it): RandomX's C++ build assumes Microsoft's compiler and assembler on the MSVC side, which no other host can provide, diff --git a/dnix.json b/dnix.json index 5405038..406dec5 100644 --- a/dnix.json +++ b/dnix.json @@ -24,7 +24,7 @@ }, "floors": { "macos": 12, - "ios": 15, + "ios": 13, "android": 24, "glibc": "2.28" }, diff --git a/dnix.lock b/dnix.lock index 50aa0d3..119223f 100644 --- a/dnix.lock +++ b/dnix.lock @@ -1,15 +1,15 @@ { - "release": "0fa5b397d984", - "date": "2026-10-22", + "release": "fd015b3bc09c", + "date": "2026-10-23", "files": { "aarch64-apple-darwin/libepic_cash_wallet.dylib": "b9bd894d1e2a3ad0241dc820bb7dfd172308348e793d3030276ef71070a36521", - "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "ffbde5963a5c7cc45ca8ca123474c8a8c8850bdb284b619ce4240e84d5a63f4c", - "aarch64-apple-ios/libepic_cash_wallet.dylib": "d17eb7e08e2cdcadc9d570ca6830154baf18303973ea3ac29f826c3c8ba05b9d", + "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "a5c56715003fea4889ba0f60e4fb2ef4692d893e399356bcea9b2aed873b6fc1", + "aarch64-apple-ios/libepic_cash_wallet.dylib": "89b05c605ef176190716c3b5576933c8b5c6f3d6158d5b2fc02e3a8cfb86e5eb", "aarch64-linux-android/libepic_cash_wallet.so": "dd5f6a9601cda87f0815b1946a11112d90ef5176f1d38b4ade4d530f731aed64", "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "1b2ff3a6fd00ae9195a20f541e7c84b7dcf3ab480ccb942a103c0880f6a3345f", "armv7-linux-androideabi/libepic_cash_wallet.so": "812f9fd3e9c205e92411f666d779e4428275f532be9941590b3c8efcdae89cc8", "x86_64-apple-darwin/libepic_cash_wallet.dylib": "4c73f7c39fe45c95de5298d4aae09df345fe2594538a9b46e2002f3267266324", - "x86_64-apple-ios/libepic_cash_wallet.dylib": "65ad296d118e0fc15f09eb586ec5f6fbbce4f092caf18cc88597ebfe02f70485", + "x86_64-apple-ios/libepic_cash_wallet.dylib": "7cc59de3f2452925e7e02cad99c2f486ed0b84c7fdbbdca804ca910a53e0070a", "x86_64-linux-android/libepic_cash_wallet.so": "0f75fdf1dc3863b359db34fa05ef6f8ae4b7a11426d4043010ecc8ae3901e480", "x86_64-pc-windows-gnullvm/epic_cash_wallet.dll": "64d99f37f9696457082f73e004b3c357f89b8d7395759dd6f42afc705d7308f4", "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "9492091f08ee5fd32b19ae1e06953010ec1f30f613fef88994a9adf6d239e76a" From aaceca96d2a27546c849e9bda9e71a843d8d105e Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Fri, 23 Oct 2026 18:49:47 -0600 Subject: [PATCH 08/11] 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. Only the x86_64 Android library changes: randomx's bindings are now generated against the NDK's headers on every host, where macOS used Xcode's. The new lock matches what Linux x64, Linux arm64, and macOS each build. --- .github/workflows/native.yml | 2 +- dnix.lock | 6 +++--- example/pubspec.lock | 6 +++--- flake.lock | 10 +++++----- flake.nix | 2 +- pubspec.yaml | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 3e92473..8b36ec2 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 119223f..e6e9e4f 100644 --- a/dnix.lock +++ b/dnix.lock @@ -1,6 +1,6 @@ { - "release": "fd015b3bc09c", - "date": "2026-10-23", + "release": "3cabde22b1d2", + "date": "2026-10-24", "files": { "aarch64-apple-darwin/libepic_cash_wallet.dylib": "b9bd894d1e2a3ad0241dc820bb7dfd172308348e793d3030276ef71070a36521", "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "a5c56715003fea4889ba0f60e4fb2ef4692d893e399356bcea9b2aed873b6fc1", @@ -10,7 +10,7 @@ "armv7-linux-androideabi/libepic_cash_wallet.so": "812f9fd3e9c205e92411f666d779e4428275f532be9941590b3c8efcdae89cc8", "x86_64-apple-darwin/libepic_cash_wallet.dylib": "4c73f7c39fe45c95de5298d4aae09df345fe2594538a9b46e2002f3267266324", "x86_64-apple-ios/libepic_cash_wallet.dylib": "7cc59de3f2452925e7e02cad99c2f486ed0b84c7fdbbdca804ca910a53e0070a", - "x86_64-linux-android/libepic_cash_wallet.so": "0f75fdf1dc3863b359db34fa05ef6f8ae4b7a11426d4043010ecc8ae3901e480", + "x86_64-linux-android/libepic_cash_wallet.so": "261b8190df1c8255c3b37e1bdccfabca8dc77dfa3284fc8410b11667a6948e96", "x86_64-pc-windows-gnullvm/epic_cash_wallet.dll": "64d99f37f9696457082f73e004b3c357f89b8d7395759dd6f42afc705d7308f4", "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "9492091f08ee5fd32b19ae1e06953010ec1f30f613fef88994a9adf6d239e76a" } diff --git a/example/pubspec.lock b/example/pubspec.lock index 03c417b..33d420c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -69,9 +69,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 18686c9..cd42b42 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 4ff975b..3e50ff6 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Reproducible reference builds of flutter_libepiccash'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 26be02c..ecf4de6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,8 +16,8 @@ dependencies: hooks: ^2.1.0 dnix: git: - url: https://github.com/cypherstack/dnix.git - ref: 7b9ef2d34b6e1aeb05869da0843f85d28fd19bbd + url: https://github.com/slopbot9k/dnix.git + ref: 57efe93b683e77d86a326a3a96ff217e6d6a4283 decimal: ^2.1.0 mutex: ^3.0.0 uuid: ^4.0.0 From 23c2cf1a66a87782504074e54b3ae5eb3bb54abe Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Tue, 27 Oct 2026 09:09:38 -0600 Subject: [PATCH 09/11] Take epic_core without the foundation tables epic_core now embeds its foundation tables only behind a feature that nothing here enables, so its vendored copy needs no JSON beside the source. The patched epic crates follow cypherstack/epic's optional-embedded-foundation branch. Every target was rebuilt; the lock names the new release. --- dnix.lock | 26 +++++++++++++------------- rust/Cargo.lock | 6 +++--- rust/Cargo.toml | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/dnix.lock b/dnix.lock index e6e9e4f..b7f1555 100644 --- a/dnix.lock +++ b/dnix.lock @@ -1,17 +1,17 @@ { - "release": "3cabde22b1d2", - "date": "2026-10-24", + "release": "ab38ec7ecadb", + "date": "2026-10-27", "files": { - "aarch64-apple-darwin/libepic_cash_wallet.dylib": "b9bd894d1e2a3ad0241dc820bb7dfd172308348e793d3030276ef71070a36521", - "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "a5c56715003fea4889ba0f60e4fb2ef4692d893e399356bcea9b2aed873b6fc1", - "aarch64-apple-ios/libepic_cash_wallet.dylib": "89b05c605ef176190716c3b5576933c8b5c6f3d6158d5b2fc02e3a8cfb86e5eb", - "aarch64-linux-android/libepic_cash_wallet.so": "dd5f6a9601cda87f0815b1946a11112d90ef5176f1d38b4ade4d530f731aed64", - "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "1b2ff3a6fd00ae9195a20f541e7c84b7dcf3ab480ccb942a103c0880f6a3345f", - "armv7-linux-androideabi/libepic_cash_wallet.so": "812f9fd3e9c205e92411f666d779e4428275f532be9941590b3c8efcdae89cc8", - "x86_64-apple-darwin/libepic_cash_wallet.dylib": "4c73f7c39fe45c95de5298d4aae09df345fe2594538a9b46e2002f3267266324", - "x86_64-apple-ios/libepic_cash_wallet.dylib": "7cc59de3f2452925e7e02cad99c2f486ed0b84c7fdbbdca804ca910a53e0070a", - "x86_64-linux-android/libepic_cash_wallet.so": "261b8190df1c8255c3b37e1bdccfabca8dc77dfa3284fc8410b11667a6948e96", - "x86_64-pc-windows-gnullvm/epic_cash_wallet.dll": "64d99f37f9696457082f73e004b3c357f89b8d7395759dd6f42afc705d7308f4", - "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "9492091f08ee5fd32b19ae1e06953010ec1f30f613fef88994a9adf6d239e76a" + "aarch64-apple-darwin/libepic_cash_wallet.dylib": "ad9545fa67481ef2d84527b24fa9d844f642771ddebdd8a93d1ebd9f96acfc73", + "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "5b0a5a9158c55eb84fc74f9b24f3c851b16d8440a6663debf61c631657a28041", + "aarch64-apple-ios/libepic_cash_wallet.dylib": "45c3d5f2c5db903e38f6ab25db8fb6ea3f63997ec803fc923f5874977cce8825", + "aarch64-linux-android/libepic_cash_wallet.so": "011b2e53f85a41d79037b8695951490b64a41db416c9746d48cef92c0af2f0cd", + "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "4d70e82e6f96df4e60eb0a24d30ac15a0a8d697d3c79ecffa88f16236e53a94e", + "armv7-linux-androideabi/libepic_cash_wallet.so": "f473261c9531265693f904fa63d92c72a24e0ccc25307a6019ce7dbdac3ef1f2", + "x86_64-apple-darwin/libepic_cash_wallet.dylib": "e8d1c85a28edd58b1f90a0d3eb803a6eeb5dd6b654746beada29ddb7be29fa34", + "x86_64-apple-ios/libepic_cash_wallet.dylib": "efb0ec08830dfd2cab814237cf2ed8603c1d4d15dad306d0a28e85c23636c64c", + "x86_64-linux-android/libepic_cash_wallet.so": "1f1d0bdf16ad9028a2a68ee0d8db8aa64c0250015af36ff19645c124a5096147", + "x86_64-pc-windows-gnullvm/epic_cash_wallet.dll": "198d784936f3dbfd460bdab7c9167fab8f0201efb53fd6f3ae94ad742e9be56b", + "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "8b33880174c44c42cc5d747fcedbeae60fc05dc022d73bd86eeee7a90bc16a48" } } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3e1e24c..2d4d06d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1268,7 +1268,7 @@ dependencies = [ [[package]] name = "epic_core" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#103a972a792d57f70d66f0114ceca63e083e743b" +source = "git+https://github.com/cypherstack/epic.git?branch=optional-embedded-foundation#a30716108e1320ab840b6bba40db0d647f00841c" dependencies = [ "blake2-rfc", "byteorder", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "epic_keychain" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#103a972a792d57f70d66f0114ceca63e083e743b" +source = "git+https://github.com/cypherstack/epic.git?branch=optional-embedded-foundation#a30716108e1320ab840b6bba40db0d647f00841c" dependencies = [ "blake2-rfc", "byteorder", @@ -1394,7 +1394,7 @@ dependencies = [ [[package]] name = "epic_util" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=vendorable-foundation-json#103a972a792d57f70d66f0114ceca63e083e743b" +source = "git+https://github.com/cypherstack/epic.git?branch=optional-embedded-foundation#a30716108e1320ab840b6bba40db0d647f00841c" dependencies = [ "anyhow", "backtrace", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 427352a..d66ad3f 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -75,6 +75,6 @@ liblmdb-sys = { git = "https://github.com/i1skn/lmdb-rs" } randomx = { git = "https://github.com/cypherstack/randomx-rust", branch = "reproducible-cross-builds" } [patch."https://github.com/EpicCash/epic.git"] -epic_core = { git = "https://github.com/cypherstack/epic.git", branch = "vendorable-foundation-json" } -epic_util = { git = "https://github.com/cypherstack/epic.git", branch = "vendorable-foundation-json" } -epic_keychain = { git = "https://github.com/cypherstack/epic.git", branch = "vendorable-foundation-json" } +epic_core = { git = "https://github.com/cypherstack/epic.git", branch = "optional-embedded-foundation" } +epic_util = { git = "https://github.com/cypherstack/epic.git", branch = "optional-embedded-foundation" } +epic_keychain = { git = "https://github.com/cypherstack/epic.git", branch = "optional-embedded-foundation" } From 6538e76372e563aee3a1a99c1aab7757c6b33837 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Tue, 27 Oct 2026 15:39:26 -0600 Subject: [PATCH 10/11] 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 8b36ec2..6752bb6 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 33d420c..f01d0e9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -69,9 +69,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 cd42b42..928b931 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 3e50ff6..4ff975b 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "Reproducible reference builds of flutter_libepiccash'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 ecf4de6..d6f1dad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,8 +16,8 @@ dependencies: hooks: ^2.1.0 dnix: git: - url: https://github.com/slopbot9k/dnix.git - ref: 57efe93b683e77d86a326a3a96ff217e6d6a4283 + url: https://github.com/cypherstack/dnix.git + ref: e91763ef25d084b3a5cf271a0cfee69159b1f779 decimal: ^2.1.0 mutex: ^3.0.0 uuid: ^4.0.0 From f99c07df3ae989161ef5b0e5dfa1c041c5482050 Mon Sep 17 00:00:00 2001 From: slopbot9k Date: Tue, 27 Oct 2026 16:25:50 -0600 Subject: [PATCH 11/11] Take epic_core and randomx from the branches cypherstack merged The epic and randomx-rust pull requests landed as merge commits on fix/randomx-v0.2.0-api and master, so the patch lines now name those branches, as main does, and the lock pins the merge commits. The branch name is part of cargo's metadata hash, so every library changed and dnix.lock names a new release. --- dnix.lock | 24 ++++++++++++------------ rust/Cargo.lock | 8 ++++---- rust/Cargo.toml | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/dnix.lock b/dnix.lock index b7f1555..8ccb68c 100644 --- a/dnix.lock +++ b/dnix.lock @@ -1,17 +1,17 @@ { - "release": "ab38ec7ecadb", + "release": "ccc49989ad00", "date": "2026-10-27", "files": { - "aarch64-apple-darwin/libepic_cash_wallet.dylib": "ad9545fa67481ef2d84527b24fa9d844f642771ddebdd8a93d1ebd9f96acfc73", - "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "5b0a5a9158c55eb84fc74f9b24f3c851b16d8440a6663debf61c631657a28041", - "aarch64-apple-ios/libepic_cash_wallet.dylib": "45c3d5f2c5db903e38f6ab25db8fb6ea3f63997ec803fc923f5874977cce8825", - "aarch64-linux-android/libepic_cash_wallet.so": "011b2e53f85a41d79037b8695951490b64a41db416c9746d48cef92c0af2f0cd", - "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "4d70e82e6f96df4e60eb0a24d30ac15a0a8d697d3c79ecffa88f16236e53a94e", - "armv7-linux-androideabi/libepic_cash_wallet.so": "f473261c9531265693f904fa63d92c72a24e0ccc25307a6019ce7dbdac3ef1f2", - "x86_64-apple-darwin/libepic_cash_wallet.dylib": "e8d1c85a28edd58b1f90a0d3eb803a6eeb5dd6b654746beada29ddb7be29fa34", - "x86_64-apple-ios/libepic_cash_wallet.dylib": "efb0ec08830dfd2cab814237cf2ed8603c1d4d15dad306d0a28e85c23636c64c", - "x86_64-linux-android/libepic_cash_wallet.so": "1f1d0bdf16ad9028a2a68ee0d8db8aa64c0250015af36ff19645c124a5096147", - "x86_64-pc-windows-gnullvm/epic_cash_wallet.dll": "198d784936f3dbfd460bdab7c9167fab8f0201efb53fd6f3ae94ad742e9be56b", - "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "8b33880174c44c42cc5d747fcedbeae60fc05dc022d73bd86eeee7a90bc16a48" + "aarch64-apple-darwin/libepic_cash_wallet.dylib": "0a7c8c9b831a36a7609d11deb36d0a31d058993fb4aeb1122f0f914f6545a447", + "aarch64-apple-ios-sim/libepic_cash_wallet.dylib": "87765aa07e4b0a53187330a2f2689ba44dd8a36900ddc98baff1ab7825c76f28", + "aarch64-apple-ios/libepic_cash_wallet.dylib": "0abb37b695b81f9cca74f89eaa751255bf710c0fe5ba94bb8a1b8de2bd4b8344", + "aarch64-linux-android/libepic_cash_wallet.so": "6404d615975dd014d5abc2b6e29c489ad8308343dd4585cf38cdd1a917f31eda", + "aarch64-unknown-linux-gnu/libepic_cash_wallet.so": "57ad01d7d780e66f02c3b218cc023301ecaaf5fe33afcafaad1400444bb2e510", + "armv7-linux-androideabi/libepic_cash_wallet.so": "1b58e0ee45c3661b165123b7cdd2ef38c7660aa384ca307236e7745445ba8ad0", + "x86_64-apple-darwin/libepic_cash_wallet.dylib": "b89164c1625022d5ef37b264cd121356f44cead282e0faed57d33bf6048c195f", + "x86_64-apple-ios/libepic_cash_wallet.dylib": "a57f674ad46b98c0deb04e6038090893ae9c972f084d30ed5fbd86da8ff40278", + "x86_64-linux-android/libepic_cash_wallet.so": "f29a24b3679298b6b22bcdc96fb8807b0569a452435fff55c54275f5dfd646c9", + "x86_64-pc-windows-gnullvm/epic_cash_wallet.dll": "3642bc87648ecbed119a28c9419938d78ba5d24023c6ef33562f5969354dd8a1", + "x86_64-unknown-linux-gnu/libepic_cash_wallet.so": "8b0fd9c8731e9e66053c193311b46bf7b175adb7f92936ff35abaaa4c1facc4c" } } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 2d4d06d..4bb43b8 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1268,7 +1268,7 @@ dependencies = [ [[package]] name = "epic_core" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=optional-embedded-foundation#a30716108e1320ab840b6bba40db0d647f00841c" +source = "git+https://github.com/cypherstack/epic.git?branch=fix%2Frandomx-v0.2.0-api#21ac1683e9df755bf737624f8550e2f618c5d78b" dependencies = [ "blake2-rfc", "byteorder", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "epic_keychain" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=optional-embedded-foundation#a30716108e1320ab840b6bba40db0d647f00841c" +source = "git+https://github.com/cypherstack/epic.git?branch=fix%2Frandomx-v0.2.0-api#21ac1683e9df755bf737624f8550e2f618c5d78b" dependencies = [ "blake2-rfc", "byteorder", @@ -1394,7 +1394,7 @@ dependencies = [ [[package]] name = "epic_util" version = "4.0.0" -source = "git+https://github.com/cypherstack/epic.git?branch=optional-embedded-foundation#a30716108e1320ab840b6bba40db0d647f00841c" +source = "git+https://github.com/cypherstack/epic.git?branch=fix%2Frandomx-v0.2.0-api#21ac1683e9df755bf737624f8550e2f618c5d78b" dependencies = [ "anyhow", "backtrace", @@ -4055,7 +4055,7 @@ dependencies = [ [[package]] name = "randomx" version = "0.2.0" -source = "git+https://github.com/cypherstack/randomx-rust?branch=reproducible-cross-builds#b26097f78bbeadff5027dbfc2f4993bbb2f4d743" +source = "git+https://github.com/cypherstack/randomx-rust#f030c36ab6ab8a8f5f69c3bb5a850f1464e10b5e" dependencies = [ "bindgen", "byteorder", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index d66ad3f..c4492a1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -72,9 +72,9 @@ crate-type = ["staticlib", "cdylib"] liblmdb-sys = { git = "https://github.com/i1skn/lmdb-rs" } [patch."https://github.com/EpicCash/randomx-rust.git"] -randomx = { git = "https://github.com/cypherstack/randomx-rust", branch = "reproducible-cross-builds" } +randomx = { git = "https://github.com/cypherstack/randomx-rust" } [patch."https://github.com/EpicCash/epic.git"] -epic_core = { git = "https://github.com/cypherstack/epic.git", branch = "optional-embedded-foundation" } -epic_util = { git = "https://github.com/cypherstack/epic.git", branch = "optional-embedded-foundation" } -epic_keychain = { git = "https://github.com/cypherstack/epic.git", branch = "optional-embedded-foundation" } +epic_core = { git = "https://github.com/cypherstack/epic.git", branch = "fix/randomx-v0.2.0-api" } +epic_util = { git = "https://github.com/cypherstack/epic.git", branch = "fix/randomx-v0.2.0-api" } +epic_keychain = { git = "https://github.com/cypherstack/epic.git", branch = "fix/randomx-v0.2.0-api" }