From 06b4accc14488427d0d9d3cdc604a4ea6b41422b Mon Sep 17 00:00:00 2001 From: fmar Date: Thu, 3 Sep 2026 23:29:23 +0200 Subject: [PATCH 1/8] perf: improve rust verifier memory usage --- .../verifiers/rust_event_verifier_native.dart | 97 ++++++++----------- packages/ndk/lib/src/rust_lib.dart | 11 +++ packages/ndk/rust/src/lib.rs | 76 ++++++++++----- .../verifiers/rust_event_verifier_test.dart | 41 ++++++++ 4 files changed, 141 insertions(+), 84 deletions(-) diff --git a/packages/ndk/lib/data_layer/repositories/verifiers/rust_event_verifier_native.dart b/packages/ndk/lib/data_layer/repositories/verifiers/rust_event_verifier_native.dart index 6fba6c03e..9cf74b917 100644 --- a/packages/ndk/lib/data_layer/repositories/verifiers/rust_event_verifier_native.dart +++ b/packages/ndk/lib/data_layer/repositories/verifiers/rust_event_verifier_native.dart @@ -3,6 +3,7 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import '../../../domain_layer/entities/nip_01_event.dart'; +import '../../../domain_layer/entities/nip_01_utils.dart'; import '../../../domain_layer/repositories/event_verifier.dart'; import '../../../src/rust_lib.dart' as rust_lib; @@ -16,71 +17,51 @@ class RustEventVerifier implements EventVerifier { @override Future verify(Nip01Event event) async { - // Check if signature is present - if (event.sig == null) { + final signature = event.sig; + if (signature == null || + !_isHex(event.id, 64) || + !_isHex(event.pubKey, 64) || + !_isHex(signature, 128) || + !Nip01Utils.isIdValid(event)) { return false; } - // Convert strings to native pointers - final eventIdPtr = event.id.toNativeUtf8(); - final pubKeyPtr = event.pubKey.toNativeUtf8(); - final contentPtr = event.content.toNativeUtf8(); - final signaturePtr = event.sig!.toNativeUtf8(); - - // Prepare tags data - final tags = event.tags; - final tagsCount = tags.length; - - // Calculate total number of strings across all tags - int totalStrings = 0; - for (final tag in tags) { - totalStrings += tag.length; - } - - // Allocate arrays for tags - final tagsLengths = calloc(tagsCount == 0 ? 1 : tagsCount); - final tagsData = calloc>( - totalStrings == 0 ? 1 : totalStrings, - ); + // The validated event id commits to every event field. Only the fixed-size + // Schnorr inputs need to cross FFI. One packed allocation replaces the + // previous allocation per field, tag, and nested Rust String. + const packedLength = 64 + 64 + 128; + final packed = malloc(packedLength); try { - // Fill tag data - int stringIndex = 0; - for (int i = 0; i < tagsCount; i++) { - tagsLengths[i] = tags[i].length; - for (final element in tags[i]) { - tagsData[stringIndex] = element.toNativeUtf8(); - stringIndex++; - } - } - - // Call the native function - final result = rust_lib.verifyNostrEventNative( - eventIdPtr, - pubKeyPtr, - event.createdAt, - event.kind, - tagsData, - tagsLengths, - tagsCount, - contentPtr, - signaturePtr, - ); - - return result == 1; + final bytes = packed.asTypedList(packedLength); + _copyAscii(event.id, bytes, 0); + _copyAscii(event.pubKey, bytes, 64); + _copyAscii(signature, bytes, 128); + return rust_lib.verifySchnorrSignaturePackedNative( + packed, + packedLength, + ) == + 1; } finally { - // Free all allocated memory - calloc.free(eventIdPtr); - calloc.free(pubKeyPtr); - calloc.free(contentPtr); - calloc.free(signaturePtr); + malloc.free(packed); + } + } + + static bool _isHex(String value, int expectedLength) { + if (value.length != expectedLength) return false; + for (final codeUnit in value.codeUnits) { + final digit = codeUnit >= 0x30 && codeUnit <= 0x39; + final lower = codeUnit >= 0x61 && codeUnit <= 0x66; + final upper = codeUnit >= 0x41 && codeUnit <= 0x46; + if (!digit && !lower && !upper) return false; + } + return true; + } - // Free tag string pointers - for (int i = 0; i < totalStrings; i++) { - calloc.free(tagsData[i]); - } - calloc.free(tagsData); - calloc.free(tagsLengths); + static void _copyAscii(String source, List target, int offset) { + final codeUnits = source.codeUnits; + for (var index = 0; index < codeUnits.length; index++) { + target[offset + index] = codeUnits[index]; } } } diff --git a/packages/ndk/lib/src/rust_lib.dart b/packages/ndk/lib/src/rust_lib.dart index fcf69eb2b..1ba83ffd1 100644 --- a/packages/ndk/lib/src/rust_lib.dart +++ b/packages/ndk/lib/src/rust_lib.dart @@ -39,6 +39,17 @@ external int verifyNostrEventNative( Pointer signatureHex, ); +/// Verifies a Nostr Schnorr signature from one packed ASCII buffer containing +/// event id (64 bytes), pubkey (64 bytes), and signature (128 bytes). +@Native, IntPtr)>( + symbol: 'verify_schnorr_signature_packed', + isLeaf: true, +) +external int verifySchnorrSignaturePackedNative( + Pointer packed, + int packedLength, +); + // ── Quantum-Secure ML-DSA (FIPS 204) bindings ────────────────────────── // // These were CRYSTALS-Dilithium. NIST altered the algorithm during diff --git a/packages/ndk/rust/src/lib.rs b/packages/ndk/rust/src/lib.rs index 6b6ce23ab..d4b8b551e 100644 --- a/packages/ndk/rust/src/lib.rs +++ b/packages/ndk/rust/src/lib.rs @@ -5,7 +5,7 @@ use std::slice; use fips204::traits::{KeyGen, SerDes, Signer, Verifier}; use fips204::{ml_dsa_44, ml_dsa_65, ml_dsa_87}; -use hex::decode; +use hex::decode_to_slice; use hkdf::Hkdf; use secp256k1::{schnorr::Signature, XOnlyPublicKey, SECP256K1}; use sha2::{Digest, Sha256}; @@ -118,39 +118,63 @@ pub unsafe extern "C" fn verify_schnorr_signature( } } +/// Verifies a Schnorr signature from one fixed-size packed ASCII buffer: +/// event id (64 bytes), pubkey (64 bytes), signature (128 bytes). +/// +/// # Safety +/// `packed` must point to `packed_len` readable bytes. The function rejects +/// null pointers and every length other than 256 before reading the buffer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn verify_schnorr_signature_packed( + packed: *const u8, + packed_len: usize, +) -> i32 { + const PACKED_LEN: usize = 64 + 64 + 128; + if packed.is_null() || packed_len != PACKED_LEN { + return 0; + } + let bytes = unsafe { slice::from_raw_parts(packed, packed_len) }; + let event_id_hex = &bytes[..64]; + let pub_key_hex = &bytes[64..128]; + let signature_hex = &bytes[128..]; + + if verify_schnorr_signature_bytes(pub_key_hex, event_id_hex, signature_hex) { + 1 + } else { + 0 + } +} + fn verify_schnorr_signature_internal( pub_key_hex: &str, event_id_hex: &str, signature_hex: &str, ) -> bool { - let pub_key_bytes = match decode(pub_key_hex) { - Ok(bytes) => bytes, - Err(_) => return false, - }; - - let event_id_bytes = match decode(event_id_hex) { - Ok(bytes) => bytes, - Err(_) => return false, - }; - - let signature_bytes = match decode(signature_hex) { - Ok(bytes) => bytes, - Err(_) => return false, - }; + verify_schnorr_signature_bytes( + pub_key_hex.as_bytes(), + event_id_hex.as_bytes(), + signature_hex.as_bytes(), + ) +} - if event_id_bytes.len() != 32 || pub_key_bytes.len() != 32 || signature_bytes.len() != 64 { +fn verify_schnorr_signature_bytes( + pub_key_hex: &[u8], + event_id_hex: &[u8], + signature_hex: &[u8], +) -> bool { + if pub_key_hex.len() != 64 || event_id_hex.len() != 64 || signature_hex.len() != 128 { return false; } - let pub_key_array: [u8; 32] = match pub_key_bytes.try_into() { - Ok(arr) => arr, - Err(_) => return false, - }; - - let signature_array: [u8; 64] = match signature_bytes.try_into() { - Ok(arr) => arr, - Err(_) => return false, - }; + let mut pub_key_array = [0u8; 32]; + let mut event_id_array = [0u8; 32]; + let mut signature_array = [0u8; 64]; + if decode_to_slice(pub_key_hex, &mut pub_key_array).is_err() + || decode_to_slice(event_id_hex, &mut event_id_array).is_err() + || decode_to_slice(signature_hex, &mut signature_array).is_err() + { + return false; + } let pubkey = match XOnlyPublicKey::from_byte_array(pub_key_array) { Ok(key) => key, @@ -160,7 +184,7 @@ fn verify_schnorr_signature_internal( let signature = Signature::from_byte_array(signature_array); SECP256K1 - .verify_schnorr(&signature, &event_id_bytes, &pubkey) + .verify_schnorr(&signature, &event_id_array, &pubkey) .is_ok() } diff --git a/packages/ndk/test/verifiers/rust_event_verifier_test.dart b/packages/ndk/test/verifiers/rust_event_verifier_test.dart index 15001ecc0..8a598ccbe 100644 --- a/packages/ndk/test/verifiers/rust_event_verifier_test.dart +++ b/packages/ndk/test/verifiers/rust_event_verifier_test.dart @@ -179,5 +179,46 @@ void main() { final result = await verifier.verify(event); expect(result, isTrue); }); + + test('rejects malformed fixed-size signature fields', () async { + final event = Nip01Event( + id: 'z' * 64, + pubKey: keyPair.publicKey, + kind: 1, + tags: const [], + content: '', + sig: '0' * 128, + ); + + expect(await verifier.verify(event), isFalse); + }); + + test('repeatedly verifies an event with many large tags', () async { + final createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000; + final tags = List.generate( + 200, + (index) => ['x', '$index-${'a' * 1024}'], + ); + final id = Nip01Utils.calculateEventIdSync( + pubKey: keyPair.publicKey, + createdAt: createdAt, + kind: 1, + tags: tags, + content: 'large tagged event', + ); + final event = Nip01Event( + id: id, + pubKey: keyPair.publicKey, + createdAt: createdAt, + kind: 1, + tags: tags, + content: 'large tagged event', + sig: Bip340.sign(id, keyPair.privateKey!), + ); + + for (var iteration = 0; iteration < 100; iteration++) { + expect(await verifier.verify(event), isTrue); + } + }); }); } From d98b6bfd61aff322646483af71bdde69e236f9b8 Mon Sep 17 00:00:00 2001 From: fmar Date: Fri, 4 Sep 2026 02:07:10 +0200 Subject: [PATCH 2/8] skip external mint tests --- packages/ndk/test/cashu/cashu_fund_test.dart | 10 +++++----- packages/ndk/test/cashu/cashu_receive_test.dart | 4 ++-- packages/ndk/test/cashu/cashu_redeem_test.dart | 4 +++- packages/ndk/test/cashu/cashu_spend_test.dart | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/ndk/test/cashu/cashu_fund_test.dart b/packages/ndk/test/cashu/cashu_fund_test.dart index 9717eec59..111ec8fda 100644 --- a/packages/ndk/test/cashu/cashu_fund_test.dart +++ b/packages/ndk/test/cashu/cashu_fund_test.dart @@ -22,7 +22,7 @@ void main() { setUp(() {}); group('fund tests - exceptions ', () { - test('fund - invalid mint throws exception', () async { + test('fund - invalid mint throws exception', skip: true, () async { final ndk = _ndk(); expect( @@ -52,7 +52,7 @@ void main() { ); }); - test('fund - no keyset throws exception', () async { + test('fund - no keyset throws exception', skip: true, () async { final ndk = _ndk(); expect( @@ -142,7 +142,7 @@ void main() { }); group('fund', () { - test("fund - initiateFund", () async { + test("fund - initiateFund", skip: true, () async { final ndk = _ndk(); const fundAmount = 5; const fundUnit = "sat"; @@ -316,7 +316,7 @@ void main() { expect(balance, equals(0)); }); - test("fund - successfull", () async { + test("fund - successfull", skip: true, () async { final ndk = _ndk(); ndk.cashu.setCashuSeedPhrase( CashuUserSeedphrase(seedPhrase: CashuSeed.generateSeedPhrase()), @@ -358,7 +358,7 @@ void main() { expect(balance, equals(fundAmount)); }); - test("fund - successfull - e2e", () async { + test("fund - successfull - e2e", skip: true, () async { final ndk = _ndk(); ndk.cashu.setCashuSeedPhrase( CashuUserSeedphrase(seedPhrase: CashuSeed.generateSeedPhrase()), diff --git a/packages/ndk/test/cashu/cashu_receive_test.dart b/packages/ndk/test/cashu/cashu_receive_test.dart index ee60d7578..87719b51c 100644 --- a/packages/ndk/test/cashu/cashu_receive_test.dart +++ b/packages/ndk/test/cashu/cashu_receive_test.dart @@ -32,7 +32,7 @@ void main() { expect(() async => await rcvStream.last, throwsA(isA())); }); - test("invalid mint", () async { + test("invalid mint", skip: true, () async { final ndk = Ndk.emptyBootstrapRelaysConfig(); final rcvStream = ndk.cashu.receive( @@ -44,7 +44,7 @@ void main() { }); group('receive', () { - test("receive integration, double spend", () async { + test("receive integration, double spend", skip: true, () async { final cache = MemCacheManager(); final cache2 = MemCacheManager(); diff --git a/packages/ndk/test/cashu/cashu_redeem_test.dart b/packages/ndk/test/cashu/cashu_redeem_test.dart index 19ec91ef5..369bc0e54 100644 --- a/packages/ndk/test/cashu/cashu_redeem_test.dart +++ b/packages/ndk/test/cashu/cashu_redeem_test.dart @@ -34,6 +34,7 @@ void main() { group('redeem tests - exceptions ', () { test( "redeem - offline mint should fail immediately on initiateRedeem", + skip: true, () async { final ndk = _ndk(); @@ -52,6 +53,7 @@ void main() { test( "redeem - offline mint should fail immediately on redeem stream", + skip: true, () async { final cache = MemCacheManager(); @@ -158,7 +160,7 @@ void main() { }, ); - test("invalid mint url", () async { + test("invalid mint url", skip: true, () async { final ndk = _ndk(); expect( diff --git a/packages/ndk/test/cashu/cashu_spend_test.dart b/packages/ndk/test/cashu/cashu_spend_test.dart index daffd6f76..22181f0be 100644 --- a/packages/ndk/test/cashu/cashu_spend_test.dart +++ b/packages/ndk/test/cashu/cashu_spend_test.dart @@ -159,7 +159,7 @@ void main() { }); group('spend', () { - test("spend - initiateSpend", () async { + test("spend - initiateSpend", skip: true, () async { // Generate unique seed phrases for each test run to ensure unique blinded messages // This prevents "Blinded Message is already signed" errors from the mint final seedPhrase1 = CashuSeed.generateSeedPhrase(); From 61b0e470cebacae197c18d9f666a2e79dac61597 Mon Sep 17 00:00:00 2001 From: fmar Date: Fri, 4 Sep 2026 15:48:28 +0200 Subject: [PATCH 3/8] -j 1 --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b151b973f..c9ecf5416 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -24,7 +24,7 @@ jobs: - run: dart pub get - run: dart format --output=none . - run: dart analyze --no-fatal-warnings - - run: dart test --coverage="coverage" -j 2 + - run: dart test --coverage="coverage" -j 1 - run: dart test ./example -j 2 - run: dart pub global activate coverage - run: $HOME/.pub-cache/bin/format_coverage --ignore-files **/*.g.dart --lcov --check-ignore --in=coverage --out=coverage.lcov --report-on=lib From 8789a206a3f70158d7e39b705650be84d4d6cb1f Mon Sep 17 00:00:00 2001 From: fmar Date: Fri, 4 Sep 2026 17:13:21 +0200 Subject: [PATCH 4/8] ci: expose Codecov config at repo root --- packages/ndk/codecov.yml => codecov.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename packages/ndk/codecov.yml => codecov.yml (52%) diff --git a/packages/ndk/codecov.yml b/codecov.yml similarity index 52% rename from packages/ndk/codecov.yml rename to codecov.yml index 46992f493..5d2626f28 100644 --- a/packages/ndk/codecov.yml +++ b/codecov.yml @@ -5,7 +5,7 @@ coverage: project: default: target: auto # baseline = previous commit's coverage - threshold: 5% # ✅ allow up to 5% drop — tune this to your X + threshold: 5% # allow up to a 5% coverage drop patch: default: - target: 80% # only require 80% of NEW lines to be covere + target: 80% # require 80% coverage of new lines From 8926c0c277ae67adb1c7432da0a32cdcd5113e62 Mon Sep 17 00:00:00 2001 From: Leo <58687994+1-leo@users.noreply.github.com> Date: Sat, 5 Sep 2026 16:54:01 +0200 Subject: [PATCH 5/8] test: event verification, rejects malformed packed FFI inputs --- .../verifiers/rust_event_verifier_test.dart | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/ndk/test/verifiers/rust_event_verifier_test.dart b/packages/ndk/test/verifiers/rust_event_verifier_test.dart index 8a598ccbe..3304d9558 100644 --- a/packages/ndk/test/verifiers/rust_event_verifier_test.dart +++ b/packages/ndk/test/verifiers/rust_event_verifier_test.dart @@ -1,4 +1,8 @@ +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; import 'package:ndk/ndk.dart'; +import 'package:ndk/src/rust_lib.dart' as rust_lib; import 'package:ndk/shared/nips/nip01/bip340.dart'; import 'package:ndk/shared/nips/nip01/key_pair.dart'; import 'package:test/test.dart'; @@ -193,6 +197,32 @@ void main() { expect(await verifier.verify(event), isFalse); }); + test('rejects malformed packed FFI inputs', () { + const packedLength = 64 + 64 + 128; + final packed = malloc(packedLength); + + try { + expect( + rust_lib.verifySchnorrSignaturePackedNative( + packed, packedLength - 10), + 0, + ); + expect( + rust_lib.verifySchnorrSignaturePackedNative( + Pointer.fromAddress(0), packedLength), + 0, + ); + + packed.asTypedList(packedLength).fillRange(0, packedLength, 0x7a); + expect( + rust_lib.verifySchnorrSignaturePackedNative(packed, packedLength), + 0, + ); + } finally { + malloc.free(packed); + } + }); + test('repeatedly verifies an event with many large tags', () async { final createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000; final tags = List.generate( From 3adcd3f0a805eaec12f4c4097324f3b93484d54b Mon Sep 17 00:00:00 2001 From: Leo <58687994+1-leo@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:10:17 +0200 Subject: [PATCH 6/8] chore(tool): benchmark alert --- .../rust-event-verifier-benchmark.yml | 56 ++++++++++++++++ .../tool/benchmark_rust_event_verifier.dart | 66 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 .github/workflows/rust-event-verifier-benchmark.yml create mode 100644 packages/ndk/tool/benchmark_rust_event_verifier.dart diff --git a/.github/workflows/rust-event-verifier-benchmark.yml b/.github/workflows/rust-event-verifier-benchmark.yml new file mode 100644 index 000000000..d661c745d --- /dev/null +++ b/.github/workflows/rust-event-verifier-benchmark.yml @@ -0,0 +1,56 @@ +name: Rust event verifier benchmark + +on: + push: + branches: [master] + pull_request: + paths: + - packages/ndk/lib/data_layer/repositories/verifiers/rust_event_verifier_native.dart + - packages/ndk/lib/src/rust_lib.dart + - packages/ndk/rust/** + - packages/ndk/tool/benchmark_rust_event_verifier.dart + - .github/workflows/rust-event-verifier-benchmark.yml + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: rust-event-verifier-benchmark-${{ github.ref }} + cancel-in-progress: true + +jobs: + benchmark: + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/ndk + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: dart-lang/setup-dart@v1 + with: + sdk: 3.13.0 + + - name: Install dependencies + run: dart pub get + + - name: Run benchmark + run: dart run tool/benchmark_rust_event_verifier.dart > benchmark-results.json + + - name: Record and compare benchmark + uses: benchmark-action/github-action-benchmark@v1 + with: + name: Rust event verifier + tool: customSmallerIsBetter + output-file-path: packages/ndk/benchmark-results.json + github-token: ${{ secrets.GITHUB_TOKEN }} + gh-pages-branch: benchmark-data + benchmark-data-dir-path: rust-event-verifier + auto-push: ${{ github.event_name == 'push' }} + save-data-file: ${{ github.event_name == 'push' }} + alert-threshold: 110% + comment-on-alert: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} + summary-always: true \ No newline at end of file diff --git a/packages/ndk/tool/benchmark_rust_event_verifier.dart b/packages/ndk/tool/benchmark_rust_event_verifier.dart new file mode 100644 index 000000000..342806738 --- /dev/null +++ b/packages/ndk/tool/benchmark_rust_event_verifier.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:ndk/ndk.dart'; +import 'package:ndk/shared/nips/nip01/bip340.dart'; + +const _warmupIterations = 1000; +const _sampleIterations = 5000; +const _sampleCount = 9; + +Future main() async { + final keyPair = Bip340.generatePrivateKey(); + final createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000; + const content = 'RustEventVerifier benchmark event'; + final id = Nip01Utils.calculateEventIdSync( + pubKey: keyPair.publicKey, + createdAt: createdAt, + kind: 1, + tags: const [], + content: content, + ); + final event = Nip01Event( + id: id, + pubKey: keyPair.publicKey, + createdAt: createdAt, + kind: 1, + tags: const [], + content: content, + sig: Bip340.sign(id, keyPair.privateKey!), + ); + final verifier = RustEventVerifier(); + + for (var iteration = 0; iteration < _warmupIterations; iteration++) { + if (!await verifier.verify(event)) { + throw StateError('Warmup verification failed.'); + } + } + + final samples = []; + for (var sample = 0; sample < _sampleCount; sample++) { + final stopwatch = Stopwatch()..start(); + for (var iteration = 0; iteration < _sampleIterations; iteration++) { + if (!await verifier.verify(event)) { + throw StateError('Benchmark verification failed.'); + } + } + stopwatch.stop(); + samples.add(stopwatch.elapsedMicroseconds * 1000 / _sampleIterations); + } + + samples.sort(); + final medianNanosecondsPerOperation = samples[samples.length ~/ 2]; + // ignore: avoid_print + print( + jsonEncode([ + { + 'name': 'RustEventVerifier.verify', + 'unit': 'ns/op', + 'value': medianNanosecondsPerOperation, + 'range': + '${samples.first.toStringAsFixed(0)}-${samples.last.toStringAsFixed(0)}', + 'extra': '$_sampleCount samples x $_sampleIterations operations after ' + '$_warmupIterations warmup operations', + }, + ]), + ); +} From 393e4d5398969e7ff09f21e8ba67af0f812cbce4 Mon Sep 17 00:00:00 2001 From: Leo <58687994+1-leo@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:11:41 +0200 Subject: [PATCH 7/8] chore(fix): ci setup flutter --- .github/workflows/rust-event-verifier-benchmark.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-event-verifier-benchmark.yml b/.github/workflows/rust-event-verifier-benchmark.yml index d661c745d..7e3f3fe91 100644 --- a/.github/workflows/rust-event-verifier-benchmark.yml +++ b/.github/workflows/rust-event-verifier-benchmark.yml @@ -30,9 +30,13 @@ jobs: with: persist-credentials: false - - uses: dart-lang/setup-dart@v1 + - name: Set up Flutter + uses: subosito/flutter-action@v2 with: - sdk: 3.13.0 + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: stable + architecture: ${{ matrix.architecture }} + cache: true - name: Install dependencies run: dart pub get From 25a48bd0cc9c260e4b474cf0f6e31ed13ce817ec Mon Sep 17 00:00:00 2001 From: fmar Date: Mon, 7 Sep 2026 14:20:50 +0200 Subject: [PATCH 8/8] fix native library errors --- .../lib/src/cli/native_library_errors.dart | 8 +++++++ packages/ndk/lib/src/cli/ndk_cli_app.dart | 10 ++------- .../test/cli/native_library_errors_test.dart | 22 +++++++++++++++++++ .../verifiers/rust_event_verifier_test.dart | 7 +++++- 4 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 packages/ndk/lib/src/cli/native_library_errors.dart create mode 100644 packages/ndk/test/cli/native_library_errors_test.dart diff --git a/packages/ndk/lib/src/cli/native_library_errors.dart b/packages/ndk/lib/src/cli/native_library_errors.dart new file mode 100644 index 000000000..194e2a361 --- /dev/null +++ b/packages/ndk/lib/src/cli/native_library_errors.dart @@ -0,0 +1,8 @@ +bool isNativeLibraryLoadError(ArgumentError error) { + final message = error.toString().toLowerCase(); + return message.contains('dynamic library') || + message.contains('failed to lookup symbol') || + message.contains("couldn't resolve native function") || + message.contains('no available native assets') || + message.contains('failed to load'); +} diff --git a/packages/ndk/lib/src/cli/ndk_cli_app.dart b/packages/ndk/lib/src/cli/ndk_cli_app.dart index f83d34a0e..77cf0d42d 100644 --- a/packages/ndk/lib/src/cli/ndk_cli_app.dart +++ b/packages/ndk/lib/src/cli/ndk_cli_app.dart @@ -6,6 +6,7 @@ import 'package:ndk/ndk.dart'; import 'cli_accounts_store.dart'; import 'cli_command.dart'; +import 'native_library_errors.dart'; class NdkCliApp { final String appName; @@ -238,7 +239,7 @@ class _CliEventVerifier implements EventVerifier { _enableFallback(); return _fallbackVerifier.verify(event); } on ArgumentError catch (error) { - if (!_isNativeLibraryLoadError(error)) { + if (!isNativeLibraryLoadError(error)) { rethrow; } _enableFallback(); @@ -246,13 +247,6 @@ class _CliEventVerifier implements EventVerifier { } } - bool _isNativeLibraryLoadError(ArgumentError error) { - final message = error.toString().toLowerCase(); - return message.contains('dynamic library') || - message.contains('verify_nostr_event') || - message.contains('failed to load'); - } - void _enableFallback() { _useFallback = true; if (_loggedFallback) { diff --git a/packages/ndk/test/cli/native_library_errors_test.dart b/packages/ndk/test/cli/native_library_errors_test.dart new file mode 100644 index 000000000..4b109c82e --- /dev/null +++ b/packages/ndk/test/cli/native_library_errors_test.dart @@ -0,0 +1,22 @@ +import 'package:ndk/src/cli/native_library_errors.dart'; +import 'package:test/test.dart'; + +void main() { + group('isNativeLibraryLoadError', () { + test('recognizes missing native symbol errors', () { + final error = ArgumentError( + "Failed to lookup symbol 'verify_schnorr_signature_packed': " + 'dlsym(RTLD_DEFAULT, verify_schnorr_signature_packed): symbol not found', + ); + + expect(isNativeLibraryLoadError(error), isTrue); + }); + + test('does not classify unrelated argument errors', () { + expect( + isNativeLibraryLoadError(ArgumentError('Invalid event data')), + isFalse, + ); + }); + }); +} diff --git a/packages/ndk/test/verifiers/rust_event_verifier_test.dart b/packages/ndk/test/verifiers/rust_event_verifier_test.dart index 3304d9558..5eb08610c 100644 --- a/packages/ndk/test/verifiers/rust_event_verifier_test.dart +++ b/packages/ndk/test/verifiers/rust_event_verifier_test.dart @@ -199,7 +199,8 @@ void main() { test('rejects malformed packed FFI inputs', () { const packedLength = 64 + 64 + 128; - final packed = malloc(packedLength); + const oversizedLength = packedLength + 10; + final packed = malloc(oversizedLength); try { expect( @@ -207,6 +208,10 @@ void main() { packed, packedLength - 10), 0, ); + expect( + rust_lib.verifySchnorrSignaturePackedNative(packed, oversizedLength), + 0, + ); expect( rust_lib.verifySchnorrSignaturePackedNative( Pointer.fromAddress(0), packedLength),