Explorar el Código

feat: add fuzzing to cashu (#1493)

tsk hace 2 semanas
padre
commit
2b9818b379
Se han modificado 55 ficheros con 2216 adiciones y 2 borrados
  1. 149 0
      .github/workflows/fuzz.yml
  2. 4 0
      .gitignore
  3. 3 0
      Cargo.toml
  4. 18 2
      flake.nix
  5. 1509 0
      fuzz/Cargo.lock
  6. 135 0
      fuzz/Cargo.toml
  7. 15 0
      fuzz/fuzz_targets/fuzz_amount.rs
  8. 17 0
      fuzz/fuzz_targets/fuzz_blind_signature.rs
  9. 15 0
      fuzz/fuzz_targets/fuzz_currency_unit.rs
  10. 15 0
      fuzz/fuzz_targets/fuzz_dleq.rs
  11. 35 0
      fuzz/fuzz_targets/fuzz_htlc_witness.rs
  12. 15 0
      fuzz/fuzz_targets/fuzz_keyset_id.rs
  13. 15 0
      fuzz/fuzz_targets/fuzz_melt_request.rs
  14. 11 0
      fuzz/fuzz_targets/fuzz_mint_url.rs
  15. 19 0
      fuzz/fuzz_targets/fuzz_p2pk_witness.rs
  16. 11 0
      fuzz/fuzz_targets/fuzz_payment_request.rs
  17. 16 0
      fuzz/fuzz_targets/fuzz_proof.rs
  18. 18 0
      fuzz/fuzz_targets/fuzz_secret.rs
  19. 38 0
      fuzz/fuzz_targets/fuzz_spending_conditions.rs
  20. 14 0
      fuzz/fuzz_targets/fuzz_swap_request.rs
  21. 11 0
      fuzz/fuzz_targets/fuzz_token.rs
  22. 27 0
      fuzz/fuzz_targets/fuzz_token_raw_bytes.rs
  23. 15 0
      fuzz/fuzz_targets/fuzz_witness.rs
  24. 1 0
      fuzz/seeds/fuzz_amount/json_number
  25. 1 0
      fuzz/seeds/fuzz_amount/large
  26. 1 0
      fuzz/seeds/fuzz_amount/one
  27. 1 0
      fuzz/seeds/fuzz_amount/typical
  28. 1 0
      fuzz/seeds/fuzz_amount/zero
  29. 1 0
      fuzz/seeds/fuzz_blind_signature/blind_sig_tampered
  30. 1 0
      fuzz/seeds/fuzz_blind_signature/blind_sig_with_dleq
  31. 1 0
      fuzz/seeds/fuzz_blind_signature/blinded_message
  32. 1 0
      fuzz/seeds/fuzz_currency_unit/auth
  33. 1 0
      fuzz/seeds/fuzz_currency_unit/custom
  34. 1 0
      fuzz/seeds/fuzz_currency_unit/eur
  35. 1 0
      fuzz/seeds/fuzz_currency_unit/json_sat
  36. 1 0
      fuzz/seeds/fuzz_currency_unit/msat
  37. 1 0
      fuzz/seeds/fuzz_currency_unit/sat
  38. 1 0
      fuzz/seeds/fuzz_currency_unit/usd
  39. 1 0
      fuzz/seeds/fuzz_keyset_id/short_id
  40. 1 0
      fuzz/seeds/fuzz_keyset_id/v1_id
  41. 1 0
      fuzz/seeds/fuzz_keyset_id/v2_id
  42. 1 0
      fuzz/seeds/fuzz_proof/basic_proof
  43. 1 0
      fuzz/seeds/fuzz_proof/htlc_witness
  44. 1 0
      fuzz/seeds/fuzz_proof/p2pk_witness
  45. 1 0
      fuzz/seeds/fuzz_proof/proof_array
  46. 1 0
      fuzz/seeds/fuzz_proof/proof_v2_keyset
  47. 1 0
      fuzz/seeds/fuzz_proof/proof_with_witness
  48. 1 0
      fuzz/seeds/fuzz_secret/htlc_basic
  49. 1 0
      fuzz/seeds/fuzz_secret/htlc_with_pubkeys
  50. 1 0
      fuzz/seeds/fuzz_secret/p2pk_basic
  51. 1 0
      fuzz/seeds/fuzz_secret/p2pk_locktime_refund
  52. 1 0
      fuzz/seeds/fuzz_secret/p2pk_multisig
  53. 1 0
      fuzz/seeds/fuzz_secret/p2pk_sig_all
  54. 1 0
      fuzz/seeds/fuzz_secret/p2pk_with_tags
  55. 60 0
      justfile

+ 149 - 0
.github/workflows/fuzz.yml

@@ -0,0 +1,149 @@
+name: Fuzz
+
+on:
+  schedule:
+    - cron: "0 2 * * *" # Daily at 2am UTC
+  workflow_dispatch: # Allow manual trigger
+
+# Cancel previous runs on same ref
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+env:
+  CARGO_TERM_COLOR: always
+
+permissions:
+  contents: read
+
+jobs:
+  # Group A: Runs on Machine A (fuzz-a labeled runners)
+  fuzz-group-a:
+    name: "Fuzz A: ${{ matrix.target }}"
+    runs-on: [self-hosted, fuzz-a]
+    timeout-minutes: 90
+    strategy:
+      fail-fast: false
+      max-parallel: 4
+      matrix:
+        target:
+          - fuzz_token
+          - fuzz_payment_request
+          - fuzz_secret
+          - fuzz_mint_url
+          - fuzz_keyset_id
+          - fuzz_proof
+          - fuzz_blind_signature
+          - fuzz_amount
+          - fuzz_dleq
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Setup Cachix
+        uses: cachix/cachix-action@v16
+        with:
+          name: cashudevkit
+          authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
+          useDaemon: false
+        continue-on-error: true
+      - name: Fuzz ${{ matrix.target }}
+        run: nix develop -i -L .#nightly --command just fuzz ${{ matrix.target }} 3600 4
+
+      - name: Create Security Advisory on crash
+        if: failure()
+        env:
+          GH_TOKEN: ${{ secrets.SECURITY_ADVISORY_TOKEN }}
+        run: |
+          TARGET="${{ matrix.target }}"
+          ARTIFACT_DIR="fuzz/artifacts/$TARGET"
+          
+          if [ -d "$ARTIFACT_DIR" ] && [ "$(ls -A $ARTIFACT_DIR 2>/dev/null)" ]; then
+            ARTIFACT_DATA=$(tar czf - "$ARTIFACT_DIR" | base64 -w0)
+            
+            gh api repos/${{ github.repository }}/security-advisories \
+              --method POST \
+              -f summary="[Fuzzing] Crash in $TARGET" \
+              -f description="Automated fuzzing found a crash in \`$TARGET\`.
+
+          **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+          **Commit:** ${{ github.sha }}
+
+          ## Crash Artifact (base64 tar.gz)
+
+          \`\`\`
+          $ARTIFACT_DATA
+          \`\`\`
+
+          ## To reproduce locally
+
+          1. Decode: \`echo '<base64-data>' | base64 -d > crash.tar.gz && tar xzf crash.tar.gz\`
+          2. Run: \`cargo fuzz run $TARGET fuzz/artifacts/$TARGET/<crash-file>\`" \
+              -f severity="low" \
+              -f "vulnerabilities[0][package][ecosystem]=other" \
+              -f "vulnerabilities[0][package][name]=cdk"
+          fi
+
+  # Group B: Runs on Machine B (fuzz-b labeled runners)
+  fuzz-group-b:
+    name: "Fuzz B: ${{ matrix.target }}"
+    runs-on: [self-hosted, fuzz-b]
+    timeout-minutes: 90
+    strategy:
+      fail-fast: false
+      max-parallel: 4
+      matrix:
+        target:
+          - fuzz_currency_unit
+          - fuzz_spending_conditions
+          - fuzz_htlc_witness
+          - fuzz_token_raw_bytes
+          - fuzz_p2pk_witness
+          - fuzz_witness
+          - fuzz_swap_request
+          - fuzz_melt_request
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Setup Cachix
+        uses: cachix/cachix-action@v16
+        with:
+          name: cashudevkit
+          authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
+          useDaemon: false
+        continue-on-error: true
+      - name: Fuzz ${{ matrix.target }}
+        run: nix develop -i -L .#nightly --command just fuzz ${{ matrix.target }} 3600 4
+
+      - name: Create Security Advisory on crash
+        if: failure()
+        env:
+          GH_TOKEN: ${{ secrets.SECURITY_ADVISORY_TOKEN }}
+        run: |
+          TARGET="${{ matrix.target }}"
+          ARTIFACT_DIR="fuzz/artifacts/$TARGET"
+          
+          if [ -d "$ARTIFACT_DIR" ] && [ "$(ls -A $ARTIFACT_DIR 2>/dev/null)" ]; then
+            ARTIFACT_DATA=$(tar czf - "$ARTIFACT_DIR" | base64 -w0)
+            
+            gh api repos/${{ github.repository }}/security-advisories \
+              --method POST \
+              -f summary="[Fuzzing] Crash in $TARGET" \
+              -f description="Automated fuzzing found a crash in \`$TARGET\`.
+
+          **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+          **Commit:** ${{ github.sha }}
+
+          ## Crash Artifact (base64 tar.gz)
+
+          \`\`\`
+          $ARTIFACT_DATA
+          \`\`\`
+
+          ## To reproduce locally
+
+          1. Decode: \`echo '<base64-data>' | base64 -d > crash.tar.gz && tar xzf crash.tar.gz\`
+          2. Run: \`cargo fuzz run $TARGET fuzz/artifacts/$TARGET/<crash-file>\`" \
+              -f severity="low" \
+              -f "vulnerabilities[0][package][ecosystem]=other" \
+              -f "vulnerabilities[0][package][name]=cdk"
+          fi

+ 4 - 0
.gitignore

@@ -19,6 +19,10 @@ mutants.out/
 mutants-*.log
 .mutants.lock
 
+# Fuzzing artifacts and runtime corpus (seeds/ is committed)
+fuzz/artifacts/
+fuzz/corpus/
+
 
 # Python ffi
 __pycache__

+ 3 - 0
Cargo.toml

@@ -2,6 +2,9 @@
 members = [
     "crates/*",
 ]
+exclude = [
+    "fuzz",
+]
 resolver = "2"
 
 [workspace.lints.rust]

+ 18 - 2
flake.nix

@@ -92,8 +92,23 @@
         craneLib = (crane.mkLib pkgs).overrideToolchain stable_toolchain;
         craneLibMsrv = (crane.mkLib pkgs).overrideToolchain msrv_toolchain;
 
-        # Source for crane builds (uses nix-gitignore to filter out target/, etc.)
-        src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
+        # Source for crane builds - uses lib.fileset for efficient filtering
+        # This is much faster than nix-gitignore when large directories (like target/) exist
+        # because it uses a whitelist approach rather than scanning everything first
+        src = lib.fileset.toSource {
+          root = ./.;
+          fileset = lib.fileset.intersection
+            (lib.fileset.fromSource (lib.sources.cleanSource ./.))
+            (lib.fileset.unions [
+              ./Cargo.toml
+              ./Cargo.lock
+              ./Cargo.lock.msrv
+              ./README.md
+              ./.cargo
+              ./crates
+              ./fuzz
+            ]);
+        };
 
         # Source for MSRV builds - uses Cargo.lock.msrv with MSRV-compatible deps
         srcMsrv = pkgs.runCommand "cdk-source-msrv" { } ''
@@ -410,6 +425,7 @@
 
             cargo-outdated
             cargo-mutants
+            cargo-fuzz
 
             # Needed for github ci
             libz

+ 1509 - 0
fuzz/Cargo.lock

@@ -0,0 +1,1509 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base58ck"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
+dependencies = [
+ "bitcoin-internals",
+ "bitcoin_hashes",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bech32"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
+
+[[package]]
+name = "bitcoin"
+version = "0.32.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66"
+dependencies = [
+ "base58ck",
+ "base64 0.21.7",
+ "bech32",
+ "bitcoin-internals",
+ "bitcoin-io",
+ "bitcoin-units",
+ "bitcoin_hashes",
+ "hex-conservative",
+ "hex_lit",
+ "secp256k1",
+ "serde",
+]
+
+[[package]]
+name = "bitcoin-internals"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitcoin-io"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953"
+
+[[package]]
+name = "bitcoin-units"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2"
+dependencies = [
+ "bitcoin-internals",
+ "serde",
+]
+
+[[package]]
+name = "bitcoin_hashes"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b"
+dependencies = [
+ "bitcoin-io",
+ "hex-conservative",
+ "serde",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
+name = "cashu"
+version = "0.14.0"
+dependencies = [
+ "bitcoin",
+ "cbor-diag",
+ "ciborium",
+ "lightning",
+ "lightning-invoice",
+ "once_cell",
+ "regex",
+ "serde",
+ "serde_json",
+ "serde_with",
+ "strum",
+ "strum_macros",
+ "thiserror",
+ "tracing",
+ "url",
+ "uuid",
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "cbor-diag"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc245b6ecd09b23901a4fbad1ad975701fd5061ceaef6afa93a2d70605a64429"
+dependencies = [
+ "bs58",
+ "chrono",
+ "data-encoding",
+ "half",
+ "nom",
+ "num-bigint",
+ "num-rational",
+ "num-traits",
+ "separator",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cdk-fuzz"
+version = "0.0.0"
+dependencies = [
+ "cashu",
+ "libfuzzer-sys",
+ "serde_json",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chrono"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
+dependencies = [
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-link",
+]
+
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "darling"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
+
+[[package]]
+name = "deranged"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
+dependencies = [
+ "powerfmt",
+ "serde_core",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dnssec-prover"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec4f825369fc7134da70ca4040fddc8e03b80a46d249ae38d9c1c39b7b4476bf"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "half"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "zerocopy",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex-conservative"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "hex_lit"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.179"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
+dependencies = [
+ "arbitrary",
+ "cc",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "lightning"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4342d07db2b3fe7c9a73849e94d012ebcfa3588c25097daf0b5ff2857c04e0e1"
+dependencies = [
+ "bech32",
+ "bitcoin",
+ "dnssec-prover",
+ "hashbrown 0.13.2",
+ "libm",
+ "lightning-invoice",
+ "lightning-macros",
+ "lightning-types",
+ "possiblyrandom",
+]
+
+[[package]]
+name = "lightning-invoice"
+version = "0.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b85e5e14bcdb30d746e9785b04f27938292e8944f78f26517e01e91691f6b3f2"
+dependencies = [
+ "bech32",
+ "bitcoin",
+ "lightning-types",
+ "serde",
+]
+
+[[package]]
+name = "lightning-macros"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80bd6063f4d0c34320f1db9193138c878e64142e6d1c42bd5f0124936e8764ec"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "lightning-types"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5681708d3075bdff3a1b4daa400590e2703e7871bdc14e94ee7334fb6314ae40"
+dependencies = [
+ "bitcoin",
+]
+
+[[package]]
+name = "litemap"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "memchr"
+version = "2.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "possiblyrandom"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b122a615d72104fb3d8b26523fdf9232cd8ee06949fb37e4ce3ff964d15dffd"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.16",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "secp256k1"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
+dependencies = [
+ "bitcoin_hashes",
+ "rand",
+ "secp256k1-sys",
+ "serde",
+]
+
+[[package]]
+name = "secp256k1-sys"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "separator"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.148"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3084b546a1dd6289475996f182a22aba973866ea8e8b02c51d9f46b1336a22da"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
+dependencies = [
+ "base64 0.22.1",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.12.1",
+ "schemars 0.9.0",
+ "schemars 1.2.0",
+ "serde_core",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "strum"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
+
+[[package]]
+name = "strum_macros"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.113"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
+
+[[package]]
+name = "time-macros"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
+dependencies = [
+ "getrandom 0.3.4",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "writeable"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+
+[[package]]
+name = "yoke"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb2c125bd7365735bebeb420ccb880265ed2d2bddcbcd49f597fdfe6bd5e577"

+ 135 - 0
fuzz/Cargo.toml

@@ -0,0 +1,135 @@
+[package]
+name = "cdk-fuzz"
+version = "0.0.0"
+authors = ["Automatically generated"]
+publish = false
+edition = "2021"
+
+[package.metadata]
+cargo-fuzz = true
+
+[dependencies]
+libfuzzer-sys = "0.4"
+serde_json = "1"
+
+[dependencies.cashu]
+path = "../crates/cashu"
+
+[[bin]]
+name = "fuzz_token"
+path = "fuzz_targets/fuzz_token.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_payment_request"
+path = "fuzz_targets/fuzz_payment_request.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_secret"
+path = "fuzz_targets/fuzz_secret.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_mint_url"
+path = "fuzz_targets/fuzz_mint_url.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_keyset_id"
+path = "fuzz_targets/fuzz_keyset_id.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_proof"
+path = "fuzz_targets/fuzz_proof.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_blind_signature"
+path = "fuzz_targets/fuzz_blind_signature.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_amount"
+path = "fuzz_targets/fuzz_amount.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_currency_unit"
+path = "fuzz_targets/fuzz_currency_unit.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_spending_conditions"
+path = "fuzz_targets/fuzz_spending_conditions.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_htlc_witness"
+path = "fuzz_targets/fuzz_htlc_witness.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_token_raw_bytes"
+path = "fuzz_targets/fuzz_token_raw_bytes.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_p2pk_witness"
+path = "fuzz_targets/fuzz_p2pk_witness.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_witness"
+path = "fuzz_targets/fuzz_witness.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_swap_request"
+path = "fuzz_targets/fuzz_swap_request.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_melt_request"
+path = "fuzz_targets/fuzz_melt_request.rs"
+test = false
+doc = false
+bench = false
+
+[[bin]]
+name = "fuzz_dleq"
+path = "fuzz_targets/fuzz_dleq.rs"
+test = false
+doc = false
+bench = false

+ 15 - 0
fuzz/fuzz_targets/fuzz_amount.rs

@@ -0,0 +1,15 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::Amount;
+
+fuzz_target!(|data: &str| {
+    // Fuzz Amount string parsing
+    let _ = Amount::from_str(data);
+
+    // Fuzz Amount JSON parsing
+    let _: Result<Amount, _> = serde_json::from_str(data);
+});

+ 17 - 0
fuzz/fuzz_targets/fuzz_blind_signature.rs

@@ -0,0 +1,17 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut00::{BlindSignature, BlindedMessage};
+
+fuzz_target!(|data: &str| {
+    // Fuzz BlindSignature parsing
+    let _: Result<BlindSignature, _> = serde_json::from_str(data);
+
+    // Fuzz BlindedMessage parsing
+    let _: Result<BlindedMessage, _> = serde_json::from_str(data);
+
+    // Fuzz arrays of these types
+    let _: Result<Vec<BlindSignature>, _> = serde_json::from_str(data);
+    let _: Result<Vec<BlindedMessage>, _> = serde_json::from_str(data);
+});

+ 15 - 0
fuzz/fuzz_targets/fuzz_currency_unit.rs

@@ -0,0 +1,15 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::CurrencyUnit;
+
+fuzz_target!(|data: &str| {
+    // Fuzz CurrencyUnit string parsing
+    let _ = CurrencyUnit::from_str(data);
+
+    // Fuzz CurrencyUnit JSON parsing
+    let _: Result<CurrencyUnit, _> = serde_json::from_str(data);
+});

+ 15 - 0
fuzz/fuzz_targets/fuzz_dleq.rs

@@ -0,0 +1,15 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut12::{BlindSignatureDleq, ProofDleq};
+
+fuzz_target!(|data: &str| {
+    // Fuzz ProofDleq JSON deserialization
+    // ProofDleq contains: e, s, r (all SecretKey types)
+    let _: Result<ProofDleq, _> = serde_json::from_str(data);
+
+    // Fuzz BlindSignatureDleq JSON deserialization
+    // BlindSignatureDleq contains: e, s (SecretKey types)
+    let _: Result<BlindSignatureDleq, _> = serde_json::from_str(data);
+});

+ 35 - 0
fuzz/fuzz_targets/fuzz_htlc_witness.rs

@@ -0,0 +1,35 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut14::HTLCWitness;
+
+fuzz_target!(|data: &str| {
+    // Fuzz HTLCWitness JSON deserialization
+    // This tests the preimage and signatures field parsing
+    if let Ok(witness) = serde_json::from_str::<HTLCWitness>(data) {
+        // If we successfully parsed an HTLCWitness, fuzz the preimage_data() method
+        // This validates:
+        // - Hex decoding of the preimage
+        // - Exactly 32 bytes requirement
+        let _ = witness.preimage_data();
+    }
+
+    // Fuzz preimage_data() directly with arbitrary preimage strings
+    // This exercises the hex validation and size checking with raw input
+    let witness = HTLCWitness {
+        preimage: data.to_string(),
+        signatures: None,
+    };
+    let _ = witness.preimage_data();
+
+    // Fuzz with signatures field populated
+    // Test various signature list formats
+    if let Ok(sigs) = serde_json::from_str::<Vec<String>>(data) {
+        let witness_with_sigs = HTLCWitness {
+            preimage: String::new(),
+            signatures: Some(sigs),
+        };
+        let _ = witness_with_sigs.preimage_data();
+    }
+});

+ 15 - 0
fuzz/fuzz_targets/fuzz_keyset_id.rs

@@ -0,0 +1,15 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut02::{Id, ShortKeysetId};
+
+fuzz_target!(|data: &str| {
+    // Fuzz full keyset Id parsing
+    let _ = Id::from_str(data);
+
+    // Fuzz short keyset Id parsing
+    let _ = ShortKeysetId::from_str(data);
+});

+ 15 - 0
fuzz/fuzz_targets/fuzz_melt_request.rs

@@ -0,0 +1,15 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut05::MeltRequest;
+
+fuzz_target!(|data: &str| {
+    // Fuzz MeltRequest JSON deserialization
+    // MeltRequest<String> contains:
+    // - quote: String (quote ID)
+    // - inputs: Proofs (Vec<Proof>)
+    // - outputs: Option<Vec<BlindedMessage>>
+    // This is a main API entry point for untrusted data
+    let _: Result<MeltRequest<String>, _> = serde_json::from_str(data);
+});

+ 11 - 0
fuzz/fuzz_targets/fuzz_mint_url.rs

@@ -0,0 +1,11 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::MintUrl;
+
+fuzz_target!(|data: &str| {
+    let _ = MintUrl::from_str(data);
+});

+ 19 - 0
fuzz/fuzz_targets/fuzz_p2pk_witness.rs

@@ -0,0 +1,19 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut11::P2PKWitness;
+
+fuzz_target!(|data: &str| {
+    // Fuzz P2PKWitness JSON deserialization
+    // This tests signature list parsing with various formats
+    let _: Result<P2PKWitness, _> = serde_json::from_str(data);
+
+    // Fuzz with arbitrary signature strings
+    // Test various invalid signature formats
+    if let Ok(sigs) = serde_json::from_str::<Vec<String>>(data) {
+        let witness = P2PKWitness { signatures: sigs };
+        // Verify the witness can be serialized back
+        let _ = serde_json::to_string(&witness);
+    }
+});

+ 11 - 0
fuzz/fuzz_targets/fuzz_payment_request.rs

@@ -0,0 +1,11 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::PaymentRequest;
+
+fuzz_target!(|data: &str| {
+    let _ = PaymentRequest::from_str(data);
+});

+ 16 - 0
fuzz/fuzz_targets/fuzz_proof.rs

@@ -0,0 +1,16 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut00::{Proof, Witness};
+
+fuzz_target!(|data: &str| {
+    // Fuzz single Proof parsing
+    let _: Result<Proof, _> = serde_json::from_str(data);
+
+    // Fuzz Proofs array parsing
+    let _: Result<Vec<Proof>, _> = serde_json::from_str(data);
+
+    // Fuzz Witness parsing (embedded in proofs)
+    let _: Result<Witness, _> = serde_json::from_str(data);
+});

+ 18 - 0
fuzz/fuzz_targets/fuzz_secret.rs

@@ -0,0 +1,18 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut10::Secret as Nut10Secret;
+use cashu::secret::Secret;
+
+fuzz_target!(|data: &str| {
+    // Fuzz the NUT-10 secret JSON parsing (complex structured data)
+    let _: Result<Nut10Secret, _> = serde_json::from_str(data);
+
+    // Also try the conversion path: raw secret -> nut10 secret
+    if let Ok(secret) = Secret::from_str(data) {
+        let _: Result<Nut10Secret, _> = Nut10Secret::try_from(&secret);
+    }
+});

+ 38 - 0
fuzz/fuzz_targets/fuzz_spending_conditions.rs

@@ -0,0 +1,38 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut10::Secret as Nut10Secret;
+use cashu::nuts::nut11::{Conditions, SpendingConditions};
+use cashu::secret::Secret;
+
+fuzz_target!(|data: &str| {
+    // Fuzz HTLC creation with preimage (hex string validation)
+    // This tests the 32-byte preimage requirement and hex decoding
+    let _ = SpendingConditions::new_htlc(data.to_string(), None);
+
+    // Fuzz HTLC creation from hash string directly
+    let _ = SpendingConditions::new_htlc_hash(data, None);
+
+    // Fuzz SpendingConditions extraction from raw Secret
+    // This exercises the full parsing pipeline: Secret -> Nut10Secret -> SpendingConditions
+    if let Ok(secret) = Secret::from_str(data) {
+        let _: Result<SpendingConditions, _> = SpendingConditions::try_from(&secret);
+    }
+
+    // Fuzz SpendingConditions from Nut10Secret
+    if let Ok(nut10_secret) = serde_json::from_str::<Nut10Secret>(data) {
+        let _: Result<SpendingConditions, _> = SpendingConditions::try_from(nut10_secret);
+    }
+
+    // Fuzz Conditions JSON deserialization directly
+    let _: Result<Conditions, _> = serde_json::from_str(data);
+
+    // Fuzz Conditions from tags (Vec<Vec<String>>)
+    // This tests the tag parsing logic for locktime, pubkeys, refund_keys, n_sigs, sigflag
+    if let Ok(tags) = serde_json::from_str::<Vec<Vec<String>>>(data) {
+        let _: Result<Conditions, _> = Conditions::try_from(tags);
+    }
+});

+ 14 - 0
fuzz/fuzz_targets/fuzz_swap_request.rs

@@ -0,0 +1,14 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut03::SwapRequest;
+
+fuzz_target!(|data: &str| {
+    // Fuzz SwapRequest JSON deserialization
+    // SwapRequest contains:
+    // - inputs: Proofs (Vec<Proof>)
+    // - outputs: Vec<BlindedMessage>
+    // This is a main API entry point for untrusted data
+    let _: Result<SwapRequest, _> = serde_json::from_str(data);
+});

+ 11 - 0
fuzz/fuzz_targets/fuzz_token.rs

@@ -0,0 +1,11 @@
+#![no_main]
+
+use std::str::FromStr;
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::Token;
+
+fuzz_target!(|data: &str| {
+    let _ = Token::from_str(data);
+});

+ 27 - 0
fuzz/fuzz_targets/fuzz_token_raw_bytes.rs

@@ -0,0 +1,27 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut00::token::{Token, TokenV4};
+
+fuzz_target!(|data: &[u8]| {
+    let bytes = data.to_vec();
+
+    // Fuzz Token::try_from(&Vec<u8>)
+    // This tests:
+    // - "crawB" prefix validation
+    // - CBOR deserialization via ciborium
+    // - Minimum length check (5 bytes)
+    let _: Result<Token, _> = Token::try_from(&bytes);
+
+    // Fuzz TokenV4::try_from(&Vec<u8>) directly
+    // Same parsing but returns TokenV4 directly
+    let _: Result<TokenV4, _> = TokenV4::try_from(&bytes);
+
+    // Also try with the "crawB" prefix prepended to exercise CBOR parsing
+    // with arbitrary data after the valid prefix
+    let mut prefixed = b"crawB".to_vec();
+    prefixed.extend_from_slice(data);
+    let _: Result<Token, _> = Token::try_from(&prefixed);
+    let _: Result<TokenV4, _> = TokenV4::try_from(&prefixed);
+});

+ 15 - 0
fuzz/fuzz_targets/fuzz_witness.rs

@@ -0,0 +1,15 @@
+#![no_main]
+
+use libfuzzer_sys::fuzz_target;
+
+use cashu::nuts::nut00::Witness;
+
+fuzz_target!(|data: &str| {
+    // Fuzz Witness enum deserialization
+    // The Witness enum uses #[serde(untagged)] which can have edge cases
+    // It dispatches to either P2PKWitness or HTLCWitness based on structure
+    let _: Result<Witness, _> = serde_json::from_str(data);
+
+    // Also try deserializing as a Vec of witnesses
+    let _: Result<Vec<Witness>, _> = serde_json::from_str(data);
+});

+ 1 - 0
fuzz/seeds/fuzz_amount/json_number

@@ -0,0 +1 @@
+42

+ 1 - 0
fuzz/seeds/fuzz_amount/large

@@ -0,0 +1 @@
+18446744073709551615

+ 1 - 0
fuzz/seeds/fuzz_amount/one

@@ -0,0 +1 @@
+1

+ 1 - 0
fuzz/seeds/fuzz_amount/typical

@@ -0,0 +1 @@
+1000000

+ 1 - 0
fuzz/seeds/fuzz_amount/zero

@@ -0,0 +1 @@
+0

+ 1 - 0
fuzz/seeds/fuzz_blind_signature/blind_sig_tampered

@@ -0,0 +1 @@
+{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"0000000000000000000000000000000000000000000000000000000000000001","s":"0000000000000000000000000000000000000000000000000000000000000002"}}

+ 1 - 0
fuzz/seeds/fuzz_blind_signature/blind_sig_with_dleq

@@ -0,0 +1 @@
+{"amount":8,"id":"00882760bfa2eb41","C_":"02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2","dleq":{"e":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73d9","s":"9818e061ee51d5c8edc3342369a554998ff7b4381c8652d724cdf46429be73da"}}

+ 1 - 0
fuzz/seeds/fuzz_blind_signature/blinded_message

@@ -0,0 +1 @@
+{"amount":2,"id":"00bfa73302d12ffd","B_":"038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"}

+ 1 - 0
fuzz/seeds/fuzz_currency_unit/auth

@@ -0,0 +1 @@
+auth

+ 1 - 0
fuzz/seeds/fuzz_currency_unit/custom

@@ -0,0 +1 @@
+custom_unit

+ 1 - 0
fuzz/seeds/fuzz_currency_unit/eur

@@ -0,0 +1 @@
+eur

+ 1 - 0
fuzz/seeds/fuzz_currency_unit/json_sat

@@ -0,0 +1 @@
+sat

+ 1 - 0
fuzz/seeds/fuzz_currency_unit/msat

@@ -0,0 +1 @@
+msat

+ 1 - 0
fuzz/seeds/fuzz_currency_unit/sat

@@ -0,0 +1 @@
+sat

+ 1 - 0
fuzz/seeds/fuzz_currency_unit/usd

@@ -0,0 +1 @@
+usd

+ 1 - 0
fuzz/seeds/fuzz_keyset_id/short_id

@@ -0,0 +1 @@
+00456a94ab4e1c46

+ 1 - 0
fuzz/seeds/fuzz_keyset_id/v1_id

@@ -0,0 +1 @@
+009a1f293253e41e

+ 1 - 0
fuzz/seeds/fuzz_keyset_id/v2_id

@@ -0,0 +1 @@
+01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035

+ 1 - 0
fuzz/seeds/fuzz_proof/basic_proof

@@ -0,0 +1 @@
+{"id":"009a1f293253e41e","amount":2,"secret":"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}

+ 1 - 0
fuzz/seeds/fuzz_proof/htlc_witness

@@ -0,0 +1 @@
+{"preimage":"preimage","signatures":["sig1"]}

+ 1 - 0
fuzz/seeds/fuzz_proof/p2pk_witness

@@ -0,0 +1 @@
+{"signatures":["304402203c6a1d08e5a9d5e8b7e4e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3"]}

+ 1 - 0
fuzz/seeds/fuzz_proof/proof_array

@@ -0,0 +1 @@
+[{"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},{"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}]

+ 1 - 0
fuzz/seeds/fuzz_proof/proof_v2_keyset

@@ -0,0 +1 @@
+{"id":"01adc013fa9d85171586660abab27579888611659d357bc86bc09cb26eee8bc035","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}

+ 1 - 0
fuzz/seeds/fuzz_proof/proof_with_witness

@@ -0,0 +1 @@
+{"id":"009a1f293253e41e","amount":2,"secret":"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea","witness":{"signatures":["sig1"]}}

+ 1 - 0
fuzz/seeds/fuzz_secret/htlc_basic

@@ -0,0 +1 @@
+["HTLC",{"nonce":"7a9128b3f9612549f9278958337a5d7f","data":"5c23fc3aec9d985bd5fc88ca8bceaccc52cf892715dd94b42b84f1b43350751e"}]

+ 1 - 0
fuzz/seeds/fuzz_secret/htlc_with_pubkeys

@@ -0,0 +1 @@
+["HTLC",{"nonce":"d730dd70cd7ec6e687829857de8e70aab2b970712f4dbe288343eca20e63c28c","data":"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5","tags":[["pubkeys","0350cda8a1d5257dbd6ba8401a9a27384b9ab699e636e986101172167799469b14"],["sigflag","SIG_ALL"]]}]

+ 1 - 0
fuzz/seeds/fuzz_secret/p2pk_basic

@@ -0,0 +1 @@
+["P2PK",{"nonce":"5d11913ee0f92fefdc82a6764fd2457a","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198"}]

+ 1 - 0
fuzz/seeds/fuzz_secret/p2pk_locktime_refund

@@ -0,0 +1 @@
+["P2PK",{"nonce":"9ea35553beb18d553d0a53120d0175a0991ca6109370338406eed007b26eacd1","data":"02af21e09300af92e7b48c48afdb12e22933738cfb9bba67b27c00c679aae3ec25","tags":[["locktime","1"],["refund","02637c19143c58b2c58bd378400a7b82bdc91d6dedaeb803b28640ef7d28a887ac","0345c7fdf7ec7c8e746cca264bf27509eb4edb9ac421f8fbfab1dec64945a4d797"],["n_sigs_refund","2"],["sigflag","SIG_ALL"]]}]

+ 1 - 0
fuzz/seeds/fuzz_secret/p2pk_multisig

@@ -0,0 +1 @@
+["P2PK",{"nonce":"04bfd885fc982d553711092d037fdceb7320fd8f96b0d4fd6d31a65b83b94272","data":"0275e78025b558dbe6cb8fdd032a2e7613ca14fda5c1f4c4e3427f5077a7bd90e4","tags":[["pubkeys","035163650bbd5ed4be7693f40f340346ba548b941074e9138b67ef6c42755f3449","02817d22a8edc44c4141e192995a7976647c335092199f9e076a170c7336e2f5cc"],["n_sigs","2"],["sigflag","SIG_ALL"]]}]

+ 1 - 0
fuzz/seeds/fuzz_secret/p2pk_sig_all

@@ -0,0 +1 @@
+["P2PK",{"nonce":"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303","data":"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1","tags":[["sigflag","SIG_ALL"]]}]

+ 1 - 0
fuzz/seeds/fuzz_secret/p2pk_with_tags

@@ -0,0 +1 @@
+["P2PK",{"nonce":"5d11913ee0f92fefdc82a6764fd2457a","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198","tags":[["key","value1","value2"]]}]

+ 60 - 0
justfile

@@ -127,6 +127,66 @@ mutants-quick:
   echo "Running mutations on changed files since HEAD..."
   cargo mutants --in-diff HEAD -vV
 
+# Fuzzing Commands
+
+# Run fuzzing on a specific target
+# Usage: just fuzz <target> [duration] [jobs]
+# Example: just fuzz fuzz_token
+# Example: just fuzz fuzz_token 60
+# Example: just fuzz fuzz_token 60 4  (run for 60s on 4 cores)
+fuzz TARGET DURATION="0" JOBS="1":
+  #!/usr/bin/env bash
+  set -euo pipefail
+  echo "Running fuzzer on target: {{TARGET}} (jobs: {{JOBS}})"
+  cd fuzz
+  # Create corpus directory if it doesn't exist
+  mkdir -p "corpus/{{TARGET}}"
+  # Use seeds directory if it exists
+  SEEDS_DIR=""
+  if [ -d "seeds/{{TARGET}}" ]; then
+    SEEDS_DIR="seeds/{{TARGET}}"
+  fi
+  FORK_FLAG=""
+  if [ "{{JOBS}}" != "1" ]; then
+    FORK_FLAG="-fork={{JOBS}}"
+  fi
+  if [ "{{DURATION}}" = "0" ]; then
+    cargo fuzz run {{TARGET}} corpus/{{TARGET}} $SEEDS_DIR -- $FORK_FLAG
+  else
+    cargo fuzz run {{TARGET}} corpus/{{TARGET}} $SEEDS_DIR -- -max_total_time={{DURATION}} $FORK_FLAG
+  fi
+
+# List available fuzz targets
+fuzz-list:
+  #!/usr/bin/env bash
+  set -euo pipefail
+  echo "Available fuzz targets:"
+  cd fuzz
+  cargo fuzz list
+
+# Run all fuzz targets for a short duration (useful for CI)
+# Usage: just fuzz-ci [duration] [jobs]
+# Example: just fuzz-ci 30 4  (run each target for 30s on 4 cores)
+fuzz-ci DURATION="30" JOBS="1":
+  #!/usr/bin/env bash
+  set -euo pipefail
+  echo "Running all fuzz targets for {{DURATION}} seconds each (jobs: {{JOBS}})..."
+  cd fuzz
+  FORK_FLAG=""
+  if [ "{{JOBS}}" != "1" ]; then
+    FORK_FLAG="-fork={{JOBS}}"
+  fi
+  for target in $(cargo fuzz list); do
+    echo "Fuzzing $target..."
+    mkdir -p "corpus/$target"
+    SEEDS_DIR=""
+    if [ -d "seeds/$target" ]; then
+      SEEDS_DIR="seeds/$target"
+    fi
+    cargo fuzz run "$target" "corpus/$target" $SEEDS_DIR -- -max_total_time={{DURATION}} $FORK_FLAG
+  done
+  echo "All fuzz targets completed!"
+
 # Run mutation tests only on changed code since HEAD
 mutants-diff:
   #!/usr/bin/env bash