Skip to content

TST: add unit tests for rust silent-payment scanning - #133

Closed
NAME-ASHWANIYADAV wants to merge 2 commits into
CypherCommons:masterfrom
NAME-ASHWANIYADAV:tst/rust-silent-payment-scanner-tests
Closed

TST: add unit tests for rust silent-payment scanning#133
NAME-ASHWANIYADAV wants to merge 2 commits into
CypherCommons:masterfrom
NAME-ASHWANIYADAV:tst/rust-silent-payment-scanner-tests

Conversation

@NAME-ASHWANIYADAV

Copy link
Copy Markdown

Closes #132

What

Adds 52 unit tests for the BIP-352 scanner in rust_jsi_bridge/src/lib.rs, plus a CI workflow that runs them on every PR.

Two commits:

Commit Files Lines
TST: add unit tests for rust silent-payment scanning rust_jsi_bridge/src/lib.rs +948
OPS: run rust unit tests on pull requests .github/workflows/rust-test.yml +25

Coverage: compute_shared_secret_hash, ecdh_shared_secret, derive_expected_pubkey, parse_scan_tweak, build_output_map, scan_outputs / scan_transaction, process_transactions_parallel, the serde wire contract with modules/RustJsiBridge.ts, and the FFI boundary.

Why

This is the crypto core of the wallet and had no coverage. A silent regression here means missed payments or an unspendable UTXO.

How - the tests are deliberately not circular

Building a fixture with derive_expected_pubkey and then asserting scan_transaction finds it would pass even if the crypto were entirely wrong. Instead every expected value is computed from the sender's side of BIP-352, via different secp256k1 APIs than the scanner uses:

  • ECDH is cross-checked as B_scan * a (mul_tweak) against the scanner's
    A * b_scan (shared_secret_point)
  • P_k = B_spend + t_k*G is rebuilt with from_secret_key + combine against
    the scanner's add_exp_tweak
  • The BIP-340 tagged hash is reimplemented from raw sha256, bypassing the
    sha256t_hash_newtype! macro - this pins the BIP0352/SharedSecret tag
    string rather than restating it

The headline test takes the tweakHex a match returns, derives d = b_spend + t_k, and asserts P(d) equals the matched output's key - i.e. it proves the scanner's output actually lets the wallet spend the coin, which is the real contract with the TypeScript side.

Testing

cargo test    # 52 passed; 0 failed

I also verified the tests can fail, by mutating the production crypto and confirming the suite goes red:

Mutation Tests that caught it
to_be_bytes()to_le_bytes() on the output index 8
Tag string BIP0352/SharedSecretBIP0352/SharedSecretX 16
x-only slice [1..33][0..32] 15

All mutations were reverted; production lines 1–341 are byte-identical to master, and the diff contains zero deletions.

  • No new dependencies; Cargo.toml and Cargo.lock are untouched
  • No production code changed - tests are appended as an inline #[cfg(test)] mod tests, since nearly every function under test is private
  • The Android build path (npm run rust:build) is unaffected

A note on the CI workflow

The workflow runs cargo test only. I left out cargo fmt --check and cargo clippy because both currently fail on existing master:

  • cargo fmt --check reports diffs in several production lines
  • cargo clippy errors on clippy::not_unsafe_ptr_arg_deref (deny-by-default)
    for the extern "C" exports

Allowing just that one lint, clippy is clean - so the new tests introduce no warnings. Reformatting and the clippy fix felt like a separate REF: commit rather than something to bundle into a tests PR, but happy to add either here if you'd prefer.

Follow-ups (characterized, not fixed)

Three existing behaviours are pinned with // KNOWN LIMITATION: comments so the tests document reality. None are fixed here - each is production code and would belong in its own PR:

  1. A numeric isSpent fails the entire batch. helpers/silent-payments/types.ts:8 declares isSpent: boolean | number
    ("0 = false, 1 = true") and modules/RustJsiBridge.ts propagates that union, but Rust deserializes into a strict bool. Since
    parse_transactions_from_ffi decodes the whole array at once, a single numeric isSpent from the indexer fails every transaction in the batch with Invalid JSON: invalid type: integer '0', expected a boolean. The fix is a bool-or-int serde shim. This is the one worth looking at.
  2. transactionId is required but never read - IndexerOutput has no #[serde(default)] on it, yet create_matched_utxo always uses tx.id.
  3. Duplicate output pubKeys collapse in build_output_map (last write wins), so an earlier UTXO would be unrecoverable. Legal in Bitcoin, though real silent payments assign each output a distinct k, so impact is minimal.

@NAME-ASHWANIYADAV

Copy link
Copy Markdown
Author

@chaitika @theanmolsharma @notTanveer PTAL !!

@notTanveer

Copy link
Copy Markdown
Contributor

i actually finished this on my local branch about a month ago, but i've been waiting on the indexer-side deployment while working on scan speed improvements.

as part of that work, i completely removed json from the scan pipeline, which makes some of the tests here no longer relevant.

before opening a pr, it would probably help if you discussed the approach with the team first, either in a github issue or on discord. that way we're all aligned and can avoid duplicated work (saves all of us time and effort).

but yeah, keep up the great work!!

@NAME-ASHWANIYADAV

Copy link
Copy Markdown
Author

@notTanveer! I wasn't aware this was already being worked on locally since there wasn't an open issue tracking it. While the JSON pipeline changes might invalidate some tests, the core crypto math for BIP-352 (ecdh_shared_secret, derive_expected_pubkey, etc.) should still be identical. Do you think we can salvage these core unit tests and integrate them into your new branch?
I agree about syncing up-let's connect on Discord to figure out the best way to merge these efforts and avoid overlapping work in the future

@NAME-ASHWANIYADAV
NAME-ASHWANIYADAV marked this pull request as draft July 20, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TST: add unit tests for the Rust BIP-352 silent-payment scanner

2 participants