Skip to content

Add Spark V2 mobile spend support - #12

Draft
reubenyap wants to merge 23 commits into
mainfrom
codex/spark-v2-restoration
Draft

Add Spark V2 mobile spend support#12
reubenyap wants to merge 23 commits into
mainfrom
codex/spark-v2-restoration

Conversation

@reubenyap

@reubenyap reubenyap commented Aug 27, 2026

Copy link
Copy Markdown
Member

Problem

SparkMobile only constructs the deployed V1 type-9 spend payload. Firo #1913 restores multi-input Spark spending through the versioned type-11 V2 proof and payload, so mobile callers cannot construct restored spends.

Changes

  • preserve V1 wire bytes and historical verification while adding the componentwise Chaum V2 proof, context binding, and bounded canonical serialization
  • require callers to select V1 or V2 explicitly, bind the extension commitment before proof generation, validate amounts and fees, and account for V2 input size
  • leave network and height-dependent transparent spend limits to callers instead of enforcing the obsolete fixed 10,000-FIRO cap
  • reject malformed recipient addresses, incomplete Bech32 conversion, noncanonical keys, and identity keys before constructing coins
  • propagate coin recovery failures, preserve metadata fields on round trip, and keep production and test parameter sets independent
  • validate Grootle and multiexponent dimensions before indexing attacker-controlled data
  • check cryptographic provider failures, reject truncated AEAD tags, and cleanse temporary secret material
  • avoid redundant cover-set, transaction, proof, and result copies in spend construction and batch verification
  • support native Windows builds and add a least-privilege Ubuntu test workflow without duplicate PR runs

The library keeps its existing combined CoverSetData. No Core state, cache, mempool, RPC, Qt, or provider abstractions were copied. Low-level C++ consumers must rebuild for the versioned V2 API and const-reference signatures.

Downstream callers must select V2 by next-block activation height, set outer nVersion = 3 and type 11 before computing txHashSig, pass same-tip 32-byte cover-set references, emit transparent outputs before zero-valued private outputs, and recheck the tip before broadcast. The Flutter wrapper must also pass the Spark Name ownership digest and explicit spend version instead of constructing a Scalar with SetHex.

Validation

  • built all library sources and all 16 Boost test executables with MSYS2 UCRT64 GCC 16.1.0 from an output path containing spaces
  • ran all 16 suites successfully, 65 test cases total
  • ran make -C secp256k1 check, 2 tests passed
  • ran bash -n build run_all_tests
  • parsed .github/workflows/test.yml and ran git diff --check origin/main...HEAD
  • GitHub Actions passed on head 1d57c4f7efa7eea0819b7996c2f27f4515f51d44
  • independently compared the protocol changes against Firo 4f0c771462b2f327e3a7ff7bf2532bc33c727713

Known validation gap: no transaction was submitted to an H2-enabled firod because no local Firo node binary was available. V1 proof behavior remains unchanged for historical compatibility; any stricter V1 consensus rule requires activation-aware handling in Firo Core.

Related: firoorg/firo#1913

Keep the deployed prefix-only s2 derivation, avoid writes through cleared vectors, and cleanse temporary seed buffers. Pin both derivations so future refactors cannot change wallet addresses.
Preserve the deployed V1 wire format while adding Spark V2's componentwise Chaum proof, context binding, canonical bounded serialization, and explicit historical verification. Cover the new proof and payload rules with tamper, bounds, and round-trip tests.
Require callers to select V1 or V2 explicitly, pass the extension commitment before proof generation, and account for V2 input size. Add Spark Name domain binding and wallet amount guards, then make the complete integration test path fail fast.
@reubenyap
reubenyap marked this pull request as ready for review August 27, 2026 18:22
@codeant-ai

codeant-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 195d354 Aug 27, 2026 · 18:25 18:25
✅ Reviewed your PR 195d354 Aug 27, 2026 · 18:22 18:25
✅ Reviewed your PR 195d354 Aug 27, 2026 · 18:22 18:37

@reubenyap

Copy link
Copy Markdown
Member Author

@CodeAnt-AI review

@codeant-ai codeant-ai Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Aug 27, 2026
@codeant-ai

codeant-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

User description

Problem

SparkMobile only builds the deployed V1 type-9 spend payload. Firo #1913 restores multi-input Spark spending through the versioned type-11 V2 proof and payload, so mobile callers cannot construct restored spends.

Changes

  • preserve V1 bytes and historical verification while adding the componentwise Chaum V2 proof and bounded canonical V2 serialization
  • require explicit V1/V2 selection, binding cover-set references, outputs, fees, transparent value, and the extension commitment before proof generation
  • add V2 size accounting, amount and fee guards, and Spark Name V2 commitment domains
  • preserve deployed SpendKey derivation while removing cleared-buffer undefined behavior
  • run every built test suite and fail fast

The library keeps its existing combined CoverSetData. No Core state, cache, mempool, RPC, Qt, or provider abstractions were copied.

Downstream callers must select V2 by next-block activation height, set outer nVersion = 3 and type 11 before computing txHashSig, pass same-tip 32-byte cover-set references, emit transparent outputs before zero-valued private outputs, and recheck the tip before broadcast.

Validation

  • built all 16 Boost test executables with MSYS2 GCC 16.1.0
  • ./run_all_tests .codex-build/bin passed all 16 suites
  • bash -n build run_all_tests
  • git diff --check origin/main...HEAD
  • independent source comparison against Firo 4f0c771462b2f327e3a7ff7bf2532bc33c727713

Known test gap: the suite checks V2 per-input response rejection, but does not yet port Core's full non-diagonal V1 forgery fixture or submit a transaction to an H2-enabled firod. No local Firo node binary was available.

Related: firoorg/firo#1913


CodeAnt-AI Description

Add versioned Spark V2 spends while preserving historical V1 transactions

What Changed

  • Spark callers can explicitly create and verify V2 spends with multiple inputs, bounded outputs, cover-set references, extension commitments, and context-bound proofs
  • V2 transactions use a versioned, canonical format that rejects malformed, oversized, reordered, missing, or extra data
  • Existing V1 serialization and historical verification remain available, while normal V1 construction continues to require one input
  • Spend and fee validation now rejects overflow, invalid amounts, and recipients too small to cover deducted fees
  • Spark Name V2 commitments and ownership messages are bound to dedicated domains
  • Spend-key derivation preserves existing wallet addresses while cleansing temporary secret data
  • Tests now cover V1 compatibility, V2 construction and verification, tampering, serialization bounds, Spark Name binding, and fail-fast test execution

Impact

✅ Multi-input Spark V2 spends
✅ Rejected malformed or tampered spend payloads
✅ Preserved existing Spark V1 wallet addresses

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai

codeant-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

User description

Problem

SparkMobile only builds the deployed V1 type-9 spend payload. Firo #1913 restores multi-input Spark spending through the versioned type-11 V2 proof and payload, so mobile callers cannot construct restored spends.

Changes

  • preserve V1 bytes and historical verification while adding the componentwise Chaum V2 proof and bounded canonical V2 serialization
  • require explicit V1/V2 selection, binding cover-set references, outputs, fees, transparent value, and the extension commitment before proof generation
  • add V2 size accounting, amount and fee guards, and Spark Name V2 commitment domains
  • preserve deployed SpendKey derivation while removing cleared-buffer undefined behavior
  • run every built test suite and fail fast

The library keeps its existing combined CoverSetData. No Core state, cache, mempool, RPC, Qt, or provider abstractions were copied.

Downstream callers must select V2 by next-block activation height, set outer nVersion = 3 and type 11 before computing txHashSig, pass same-tip 32-byte cover-set references, emit transparent outputs before zero-valued private outputs, and recheck the tip before broadcast.

Validation

  • built all 16 Boost test executables with MSYS2 GCC 16.1.0
  • ./run_all_tests .codex-build/bin passed all 16 suites
  • bash -n build run_all_tests
  • git diff --check origin/main...HEAD
  • independent source comparison against Firo 4f0c771462b2f327e3a7ff7bf2532bc33c727713

Known test gap: the suite checks V2 per-input response rejection, but does not yet port Core's full non-diagonal V1 forgery fixture or submit a transaction to an H2-enabled firod. No local Firo node binary was available.

Related: firoorg/firo#1913


CodeAnt-AI Description

Add versioned Spark V2 mobile spends while preserving existing V1 transactions

What Changed

  • Mobile callers can explicitly create and verify Spark V2 spends with multiple inputs and bounded output counts
  • V2 proofs bind fees, transparent amounts, outputs, extension commitments, and cover-set references to prevent changes after signing
  • Existing V1 serialization, key derivation, and historical verification remain available without changing deployed transaction bytes
  • Invalid amounts, fee deductions, dimensions, references, truncated data, and unsupported versions are rejected
  • Spark Name V2 commitments and ownership messages are supported
  • The test runner now stops on failures and includes Spark Name tests

Impact

✅ Multi-input Spark spends on mobile
✅ Rejection of tampered or malformed V2 transactions
✅ Preserved compatibility with deployed V1 spends

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Comment thread src/sparkname.cpp
Comment thread src/spark.cpp
Comment thread src/chaum_proof.h
@codeant-ai

codeant-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

CodeAnt Nitpicks

1 code suggestion

1. V1 accepts extension commitments that are never serialized or cryptographically bound.

Api mismatch · src/spend_transaction.cpp:42-44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 195d354997

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/spend_transaction.cpp
Comment thread src/spend_transaction.h
Require recipient memo plaintext to match the fixed deployed shape before reading its length. Cover both mint and spend identification with authenticated short-payload regressions.
Sample the S/V equation-binding weight across the scalar field instead of restricting it to 16 bits. Keep the existing nonzero retry required by the verification equation.
Accept the ownership digest and spend version at the script boundary, then derive the proof scalar internally. This makes stale V1 scalar mapping fail at compile time for V2 callers while preserving both proof paths.
@reubenyap
reubenyap marked this pull request as draft August 27, 2026 19:13
Reject non-null extension commitments in the direct V1 constructor because the V1 wire format and Chaum proof do not carry or bind them. Cover the low-level API alongside the existing wrapper guards.
Reject zero and IDs above INT32_MAX at the V2 serialization boundary, matching constructor and Core contextual validation. Cover wire payloads whose ID vectors and reference maps otherwise remain internally consistent.
Build internally consistent invalid-ID fixtures and assert the intended constructor error. Serialize a complete V2 dimension header so EOF cannot satisfy the bounds regression.
Convert every Scalar raw-string throw to std::runtime_error so Spark Name and FFI std::exception boundaries catch failures consistently. Cover the restored V1 ownership path with an overflowing digest.
Sparkmobile has no network or height context, so its fixed 10,000 FIRO rejection can conflict with Core policy. Preserve the public base constant, remove only the unconditional check, and construct and verify an 11,000 FIRO V2 spend.
Return status 1 when either script is missing its output directory or receives an existing file, so CI and callers cannot mistake rejected input for success.
Use otherwise-valid fixtures and exact error predicates for Chaum input caps, amount and fee checks, cover-set IDs, and extraneous hashes. Each regression now fails if its target guard is removed instead of passing on an unrelated later error.
Use separate function-local statics for production and test parameters. Copy the parameter pointer during SpendKey assignment so assigned scalars remain paired with their generators.
Require complete Bech32 conversion, canonical non-identity group keys, and the exact two-byte HRP before accepting an address. Recheck recipient keys at coin construction so programmatically created invalid addresses cannot create unspendable outputs.
Propagate coin-identification failures instead of returning partially initialized metadata. Preserve the coin type and serial context on round trip, and give fundamental metadata fields safe defaults.
Reject impossible cover-set sizes and mismatched generator/scalar vectors before indexing them. Delete unsafe implicit MultiExponent assignment and correct the tests to allocate serialized-root storage before writing.
Own OpenSSL contexts with RAII and check every allocation and operation before using its output. Reject truncated AEAD tags, preserve transcript state on copy failure, and throw standard exceptions when randomness or AES encryption fails.
Clean derived seed bytes before later construction can throw, wipe SpendKeyData and Scalar storage at destruction, and reject null key buffers before copying from the public pointer boundary.
Pass large cover-set data by reference, move owned coin and script results, reserve input storage, and verify transaction batches by reference. Remove the randomized default-key comparison because it cannot validate a caller's key and only adds RNG work.
Use a small local compatibility header when the platform does not provide endian.h. Keep the existing system header path on Unix-like platforms and avoid adding a dependency.
Quote caller paths, let make refresh changed secp256k1 sources, compile shared sources once, and drive every test from one explicit list. Add a least-privilege Ubuntu workflow and document the actual vendored build.
Run push-triggered CI only for main. Pull request branches remain covered by the pull_request event without launching the same job twice.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant