Skip to content

Audit-readiness pack for SCF Soroban Security Audit Bank submission#1

Open
gnosed wants to merge 20 commits into
mainfrom
audit-readiness
Open

Audit-readiness pack for SCF Soroban Security Audit Bank submission#1
gnosed wants to merge 20 commits into
mainfrom
audit-readiness

Conversation

@gnosed

@gnosed gnosed commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Pre-audit hardening + readiness pack for an SCF Soroban Security Audit Bank engagement. 15 commits, 47 files. Mandatory and bonus items on the SCF readiness checklist are all green.

What changed

Code

  • falcon-512-core (new crate)no_std, soroban-sdk-free Falcon-512 verifier. Extracted from the verifier crate so crypto fixes land in one place. Six unit tests; verify_512 is what both contracts call.
  • soroban-falcon-smart-accountDOMAIN_SEPARATOR-prepended __check_auth, panic-free per-byte copies, new rotate_key (Soroban-auth gated through __check_auth), Error::PublicKeyMissing, falcon::init and falcon::rotate events on storage writes, SIGNED_MESSAGE_LEN const folded out of the runtime path.
  • soroban-falcon-verifierunwrap() calls on per-byte copies replaced with let-else { return false; } (matches the smart-account's panic-free pattern). Lockfile bump keccak 0.1.5 → 0.1.6 to clear RUSTSEC-2026-0012.
  • falcon-512-core/src/verify.rshash_to_point rejection-sampling reduction rewritten as four constant-time field_sub(v, Q) calls instead of while v >= Q { v -= Q; }. Closes F-001 from the constant-time scan; cargo test --release passes 6/6.
  • web-demo@stellar/stellar-sdk ^12 → ^14, SorobanRpc → rpc namespace renames, demo signer prepends the same DOMAIN_SEPARATOR byte-for-byte.

Tooling

  • Makefilemake build / test / e2e / audit-scan / scout-scan / ct-scan.
  • e2e/ — single-file Bun TypeScript harness that deploys the contract, funds it, builds a Falcon-signed transfer, simulates, signs the auth preimage, submits, and writes a JSON receipt. The committed receipt under docs/audit/e2e-receipts/2026-05-05-testnet.json is from a real testnet run that landed 7e53bce25edeb6cd03c6f340dc7e72deca3e1175a070aec7eabf5b1267385a4b (explorer).

Audit-readiness pack — docs/audit/

Document Covers
threat-model.md STRIDE in Stellar's 4-section template. 24 threats, code-cited mitigations, Mermaid data flow diagram, NIST KAT citation in Spoof.1.R.1
constant-time-analysis.md Trail of Bits CT scan across {arm64, x86_64} × {-Oz, -O3}. F-001 (UDIV in hash_to_point) found and remediated in-tree; final scan clean on every cell
dependency-and-lint-scan.md cargo audit + cargo clippy. Three transitive upstream advisories (none reachable in our usage); keccak 0.1.5 remediated by lockfile bump
scout-scan.md CoinFabrik Scout (the Stellar-recommended Soroban analyzer). 1 Critical + 4 Mediums + 3 Enhancements addressed; remaining hits are documented false positives that Scout cannot trace through upstream size gates
remediation-log.md Formal vulnerability registry. 9 entries across CT/dep/Scout sources with severity, status, owner, fix commit. Standing 20-business-day SLA on audit-firm critical/high/medium findings
e2e-receipts/ Real testnet runs as JSON receipts. Independent verification instructions in the README

Validation

  • cargo test --release passes on all three crates, including all 100 official NIST Falcon-512 KAT vectors via tests/kat.rs plus negative test_kat_wrong_message / test_kat_wrong_public_key
  • Constant-time analyzer: 0 errors / 0 warnings at -Oz on both arm64 and x86_64 across both fixtures
  • cargo audit: 3 informational warnings (transitive, upstream-tracked, unreachable in our code)
  • CoinFabrik Scout: post-fix 0 Critical, 1 Medium (FP), 2 Enhancement (FP) on smart-account; 0 Critical, 2 Medium (FP), 1 Enhancement (FP) on verifier — all remaining findings explained in docs/audit/scout-scan.md §F-FP-1..3
  • E2E testnet flow: deploy + fund + Falcon-signed transfer succeeded, receipt committed

Open follow-up items (not blocking submission)

Tracked in docs/audit/remediation-log.md:

  • TM-001 (Medium) — runtime hash check on falcon-wasm + sandboxed worker for the off-chain signer. Pre-mainnet concern, not pre-audit.
  • TM-002 (Low)pause() / unpause() admin pair to defeat the standard key-rotation race. Reviewer-conversation territory; the firm may want to scope this.
  • TM-003 (Informational)rotate_key rate limit. Accepted; relies on per-call gas economics.
  • CI-001 (Informational) — wire cargo audit, cargo clippy, and the constant-time scan into CI so future commits cannot regress on these guarantees.

Test plan

  • make test — all unit + integration + KAT tests pass on all three crates
  • make ct-scan — clean
  • make audit-scan — only upstream-tracked transitive advisories
  • make scout-scan — all in-scope findings resolved or documented as FP
  • make e2e — successful testnet receipt under docs/audit/e2e-receipts/

Reviewer pointer

Start with docs/audit/threat-model.md for the system overview + threats, then walk down docs/audit/{constant-time-analysis, dependency-and-lint-scan, scout-scan, remediation-log}.md for tooling evidence. Finally open the e2e receipt's transfer.explorerUrl to confirm the Falcon-signed tx on testnet.

gnosed added 20 commits February 9, 2026 17:26
Adds **/dist to .gitignore (node_modules was already in working
copy of .gitignore but uncommitted) and removes the previously
tracked web-demo/node_modules and web-demo/dist trees from the
index. Working tree files are preserved.
…anRpc namespace

stellar-sdk v14 moved the SorobanRpc namespace to lowercase rpc
(Server, Api, assembleTransaction, GetTransactionStatus). All
call sites in smart-account.ts and verifier.ts are updated;
no behavioural changes.
…h, key rotation

Hardens the Falcon smart account against several issues spotted in
self-review:

* Domain separation. The account now Falcon-verifies
  DOMAIN_SEPARATOR || signature_payload (where DOMAIN_SEPARATOR =
  b"soroban-falcon-smart-account-v1") instead of the raw payload, so
  a signature produced for the standalone verifier contract cannot be
  replayed against the smart account and vice versa. The web-demo
  signer is updated to prepend the same tag; both sides have a
  matching constant.

* __check_auth no longer panics on bad state. Missing or malformed
  stored pubkey now returns Error::PublicKeyMissing /
  InvalidPublicKeySize, and the per-byte copy loops use ? rather
  than unwrap.

* Signature buffer is sized off FALCON_SIG_MAX_SIZE (666) instead of
  the magic [0u8; 700], removing a stale bound.

* New rotate_key(new_pubkey) routes through require_auth on the
  account itself, so a rotation transaction must be signed with the
  current Falcon key. Rejects bad sizes early.

* Added test_domain_separator_is_fixed so an accidental rename of
  the tag fails CI rather than at deployment.

The standalone verifier contract is intentionally NOT
domain-separated; it is the unsalted Falcon primitive.
…dit report

Adds the SCF Audit Bank constant-time-analysis report under docs/audit/
and applies the single mitigation it recommends.

Finding F-001. Trail of Bits' constant-time-analyzer scans flagged a UDIV
inside FalconVerifier::verify_512 at -Oz on both arm64 and x86_64. The
divide came from LLVM rewriting

    while v >= Q { v -= Q; }

inside hash_to_point's rejection-sampling reduction as `v % Q` and
lowering it to hardware UDIV/DIVW. At -O3 LLVM picks the multiply-by-
magic-constant lowering instead, which is constant-time -- but Soroban
contracts compile with opt-level = "z", so the production WASM does
contain the data-dependent form.

Under the actual on-chain threat model this is informational: w derives
from SHAKE256(public nonce || public message) so the divisor's input is
public, and Soroban's deterministic WASM execution masks any
microarchitectural timing at the network layer. The fix is for code-reuse
scenarios outside Soroban (e.g. desktop wallet validators) and for audit
hygiene -- a clean re-scan is a stronger artifact than a triage note.

Mitigation. Replace the while-subtract with four calls to the existing
ntt::field_sub helper. The accept threshold guarantees w < 5*Q so four
conditional subtractions land v in [0, Q. field_sub is the same
bit-twiddle pattern ()
used throughout the NTT layer, so the fix introduces no new CT idiom.

Verification.
* Re-scan at -Oz on arm64+x86_64: 0 errors, 0 warnings.
* : 6 passed, 0 failed.

The standalone fixtures, reproduction script, and full report all live
under docs/audit/ so reviewers can rerun the analysis without depending
on the original tooling.
EOF
)
…o 0.1.6

Adds a dependency-and-lint scan to the SCF Audit Bank readiness pack.

cargo clippy across all three crates returns 12 warnings, all stylistic
(needless_range_loop, unnecessary_cast, manual_range_contains). Zero
security-relevant lints fired -- no lossy casts, no panicking indexing,
no unwrap/expect in runtime entry points. Indexed loops in the Falcon
NTT inner kernels are intentional and match the reference implementation;
the report explains the design choice so an audit firm does not log it
as a finding.

cargo audit on each crate Cargo.lock surfaces three transitive upstream
maintenance issues, none reachable in our usage:

  * RUSTSEC-2024-0388  derivative 2.2.0  unmaintained (proc-macro,
                                          compile-time only via arkworks)
  * RUSTSEC-2024-0436  paste 1.0.15      unmaintained (proc-macro,
                                          compile-time only via wasmi/ark-ff)
  * RUSTSEC-2026-0097  rand 0.8.5        unsound only with custom logger
                                          + rand::rng(); not used here

These will clear when soroban-sdk rolls forward its pinned deps; no
project action required.

One additional advisory was identified during the initial scan and
remediated in this commit:

  * RUSTSEC-2026-0012  keccak 0.1.5      unsoundness in opt-in ARMv8 ASM
                                          backend (gated, unreachable on
                                          WASM, but the version was also
                                          yanked)

Fix: cargo update -p keccak in the verifier crate, bumping the lockfile
entry to 0.1.6 (semver-compatible; no Cargo.toml change). The
smart-account lockfile already carried 0.1.6, so the two crates are now
aligned. cargo test --release on the verifier passes 4/4 after the bump.

The full report lives at docs/audit/dependency-and-lint-scan.md, with
captured raw outputs and a one-command reproducer under
docs/audit/dep-scan/.
Adds docs/audit/threat-model.md, following the Stellar SCF Audit Bank
4-section STRIDE template (What are we working on? / What can go wrong?
/ What are we going to do about it? / Did we do a good job?).

Coverage:

  System overview, Mermaid data flow diagram with two trust boundaries
  (Browser <-> Network, Network <-> Contract), asset inventory, and an
  explicit table of what signature_payload binds (networkId, nonce,
  signatureExpirationLedger, invocation).

  STRIDE table with 24 concrete threats numbered Spoof.1 through
  Elevation.4, written for this codebase rather than generic.

  Mitigation table with code citations file:line for every claim.
  Most mitigations resolve to existing commits (DOMAIN_SEPARATOR,
  panic-free __check_auth, size gates, F-001 CT fix). Out-of-scope
  items (off-chain signer compromise, Soroban host) are labeled
  rather than hand-waved.

  Four open follow-up items surfaced by the exercise:
   1. Verify falcon-wasm bytes against a pinned hash at signer load
      and move the signer into a sandboxed worker.
   2. Decide on a pause()/unpause() admin pair to defeat the standard
      key-rotation race (Elevation.3).
   3. Track gas burned on failed rotate_key calls if the audit firm
      flags spam as a real concern.
   4. Wire cargo audit / cargo clippy / constant-time scan into CI.

Method notes:
  Cited line numbers were spot-verified against current source after
  draft. One inaccuracy was caught and corrected before commit:
  VITE_WASM_HASH pins the smart-account contract WASM at deploy time,
  not the falcon-wasm signer. The signer is git-vendored (file: dep)
  rather than runtime-hashed; that distinction is now reflected in
  Tamper.4.R.1 with the missing hash check listed as open item #1.

The model intentionally treats the standalone soroban-falcon-verifier
contract as out-of-scope -- it has no authorization surface, just
exposes Falcon verification as a public utility -- so its threats
collapse into the falcon-512-core review already done by the CT
analysis.
…akefile

Adds e2e/run.ts -- a single-file TypeScript harness, runnable by bun,
that exercises the full on-chain flow: deploy the smart-account
contract via the stellar CLI (with __constructor(falcon_pubkey)), fund
it with XLM via the native SAC (Ed25519-signed by SOURCE_SECRET),
build a transfer-out tx, simulate it to obtain the auth invocation +
nonce, build the SorobanAuthorization preimage, prepend
DOMAIN_SEPARATOR, Falcon-512-sign the result, resimulate, and submit.
On success it writes a JSON receipt under runs/ with all tx hashes,
the contract id, the Falcon public key, and click-through explorer
URLs the auditor can verify externally.

The harness intentionally lives in its own directory with its own
package.json so the audit reviewer can `cd e2e && bun install` without
pulling the web-demo's full dep tree. falcon-wasm is referenced as a
file: dep against the same vendored copy the demo uses, so the
on-chain DOMAIN_SEPARATOR + signing primitives stay byte-for-byte
identical. bun.lock is committed for reproducibility.

Documentation:
* e2e/README.md  -- one-time setup, run instructions, receipt schema,
  and an "auditor verification" section explaining how to independently
  reconstruct the auth preimage and verify the recorded Falcon signature
  against the on-chain pubkey.
* e2e/.env.example -- annotated environment with sensible defaults for
  testnet RPC + SAC; only SOURCE_SECRET is required.

Top-level Makefile adds convenience targets:
  make build         build all three contract WASMs
  make test          cargo test on every crate
  make e2e           build the smart-account WASM and run the harness
  make ct-scan       rerun docs/audit/ct-analysis fixtures
  make audit-scan    rerun docs/audit/dep-scan (cargo audit + clippy)

Validation performed in this session (no testnet credentials available
to run the live submission):
  * `bun install` resolves cleanly (212 deps, falcon-wasm linked from
    web-demo/vendor/) and produces a deterministic bun.lock.
  * `bun run.ts` with no SOURCE_SECRET exits 2 with the expected
    "Missing required env var" message.
  * `bun run.ts` with a valid SOURCE_SECRET reaches step 3 (deploy),
    initializing the WASM and producing a 897-byte Falcon-512 public
    key whose first byte is 0x09 (the canonical logn=9 header), then
    errors as expected when the contract WASM does not exist on disk.

The harness deliberately does not run inside this commit -- producing
a successful testnet receipt requires (a) a funded SOURCE_SECRET and
(b) a built contract WASM, both of which the operator provides at
review time. The output receipt is the audit artifact.

Closes the last "integration tests exist + executed" gap on the SCF
audit-readiness checklist for everything except the actual run, which
is operator-driven.
Successfully executed the full Falcon smart-account flow on Stellar
testnet end-to-end:

  Smart account: CANNCY2STTSAR7UQLZ7MVKQNMQ45WCDLJ67ILTOVSO6K3BJTULXSYPC4
  Falcon pubkey: 09657e96502c950f79f902908d09a20b9d903141e25218f602...
  Transfer tx:   7e53bce25edeb6cd03c6f340dc7e72deca3e1175a070aec7eab...
  Falcon sig:    666 bytes (compressed, max-length form)
  Explorer:      https://stellar.expert/explorer/testnet/tx/7e53bce2...

The receipt is committed under docs/audit/e2e-receipts/ as a permanent
audit artifact alongside a README explaining how a third party can
independently re-verify the Falcon signature against the on-chain
public key without re-running the script.

Also fixes a path bug in e2e/run.ts: the harness expected the WASM at
the workspace target/ but each contract crate has its own target/.
Pointed WASM_PATH at the correct location
contracts/soroban-falcon-smart-account/target/wasm32v1-none/release/.

This closes the last "integration tests exist + executed" gap on the
SCF Audit Bank readiness checklist.
Adds docs/audit/remediation-log.md as the standalone registry that
several audit firms (Veridise, Zellic) prefer over scanning multiple
docs to assemble a status picture.

Initial registry covers nine entries:

  F-001   UDIV in hash_to_point                        Fixed   06318c1
  D-001   keccak 0.1.5 (RUSTSEC-2026-0012)             Fixed   f37ac25
  D-002   derivative unmaintained (RUSTSEC-2024-0388)  Out of scope (upstream)
  D-003   paste unmaintained (RUSTSEC-2024-0436)       Out of scope (upstream)
  D-004   rand unsoundness (RUSTSEC-2026-0097)         Out of scope (upstream)
  TM-001  Signer WASM integrity + isolation            Open (Medium)
  TM-002  Key-rotation race                            Open (Low)
  TM-003  rotate_key spam                              Accepted (Informational)
  CI-001  No CI gate for the self-service scans        Open (Informational)

Each Open / Accepted item gets a detail block explaining what, plan,
and why-deferred. The TBD owners on TM-001, TM-002, and CI-001 are
intentional -- they will be assigned during scoping with the audit firm.

Also embeds the application-level standing commitment: critical / high /
medium findings produced during the engagement will be addressed within
20 business days, satisfying the SCF Audit Bank initial-audit refund
condition while making the policy machine-readable in the registry's
header.

This upgrades the last bonus checklist item from warning to green.
The 18-line README didn't mention the falcon-512-core crate, the e2e
harness, or any of the four audit-readiness documents. A reviewer
landing on the repo for the first time would have to crawl into
contracts/ and docs/ to find out what's there.

Rewritten to cover, in order:

  * Project pitch and the unaudited / pre-audit warning, with a
    pointer to the SCF Audit Bank submission timeline.
  * What lives at each top-level path (table form).
  * One-paragraph architecture description -- Falcon pubkey in
    instance storage, DOMAIN_SEPARATOR-prepended __check_auth, what
    signature_payload binds, why the verifier contract is the same
    primitive without the wrapper.
  * Quick-start with the Makefile targets (build / test / e2e /
    audit-scan / ct-scan) and explicit prerequisite list.
  * Repository tree.
  * Audit-readiness section linking each of the four pre-audit
    documents (threat model, CT scan, dep+lint scan, remediation log)
    and the committed e2e receipts.
  * Status table breaking down what is KAT-tested, what is hardened,
    what is testnet-only, and what is explicitly not mainnet-ready.
  * License + a security contact + reference to the remediation
    policy.

The original pitch and KAT-validation paragraph are preserved verbatim.
The audit-readiness docs were undercounting integration tests and
silently omitting the strongest correctness signal in the codebase --
the NIST Falcon-512 KAT replay.

contracts/soroban-falcon-{verifier,smart-account}/tests/kat.rs runs
all 100 official Falcon-512 KAT vectors from falcon512-KAT.rsp against
the FalconVerifier::verify_512 path, plus negative tests
test_kat_wrong_message and test_kat_wrong_public_key that confirm
mutated inputs are rejected. Together with the integration.rs and
benchmark.rs files the three crates ship 35 tests total (13 unit + 22
integration), not the 10 the previous docs implied.

Updates:

  README.md Status table -- replaced the "Three-snapshot integration
  tests" line with an explicit row covering: 35 tests across 3 crates,
  the 100-vector NIST KAT suite, and pointers to falcon512-KAT.rsp.

  threat-model.md Spoof.1.R.1 -- expanded the EUF-CMA argument to cite
  the KAT regression suite as the empirical evidence that our verifier
  matches NIST's reference behaviour. This is the load-bearing
  signal for the spoofing argument and was not previously visible to
  any reviewer reading only the threat model.

No code changes; documentation only.
Adds the missing Soroban-specific scanner from the SCF Audit Bank
ecosystem-tooling list (https://developers.stellar.org/docs/tools/developer-tools/security-tools).
We were previously running general Rust tools (cargo audit, cargo clippy,
constant-time analyzer) but had not run any Soroban-aware static
analyzer. Scout 0.3.16 covers that gap.

Initial scan surfaced:

  smart-account:  1 Critical, 1 Medium, 2 Enhancement
  verifier:       0 Critical, 5 Medium, 1 Enhancement

Remediated in this commit:

  S-001 (Critical, smart-account)  Replaced `domain.len() + payload_array.len()`
        with a compile-time const SIGNED_MESSAGE_LEN plus a static_assert
        that it fits in SIGNED_MESSAGE_MAX. Removes the unchecked + and
        the runtime length check in one move; both invariants now hold
        at compile time. False positive on the threat (operands were
        statically 31+32) but worth fixing for clarity.

  S-002 (Enhancement x2, smart-account)  Added env.events().publish()
        in __constructor and rotate_key with topics
        (falcon, init) and (falcon, rotate). Off-chain indexers can
        now detect rotation without re-reading instance storage.

  V-001 (3x Medium, verifier)  Replaced unwrap() in the per-byte copy
        loops with let-else returning false. The verifier now matches
        the panic-free pattern the smart-account adopted in commit
        133334e. Verify path semantics unchanged for all valid inputs.

Post-fix scan:

  smart-account:  0 Critical, 1 Medium, 2 Enhancement
  verifier:       0 Critical, 2 Medium, 1 Enhancement

Three categories of false positives remain, fully documented in the
report and the remediation registry:

  F-FP-1  dos_unbounded_operation -- Scout cannot trace the upstream
          FALCON_SIG_MAX_SIZE / FALCON_512_PUBKEY_SIZE / FALCON_MAX_MESSAGE_SIZE
          guards that bound each per-byte copy loop.
  F-FP-2  soroban_version enhancement -- Scout tracks the Stellar
          runtime/protocol version, not the soroban-sdk crate version
          (we are on the latest 23.x SDK).
  F-FP-3  assert_violation -- the new const _: () = assert!(...) is a
          compile-time invariant; cannot panic at runtime.

falcon-512-core is intentionally out of scope: Scout requires one of
ink, soroban, or substrate-pallets as a Cargo dependency to know what
it is analyzing, and the core crate is no_std and soroban-sdk-free.
The Trail of Bits constant-time analyzer
(docs/audit/constant-time-analysis.md) covers it instead.

Validation:

  cargo test --release -p soroban-falcon-smart-account
    -> 5 unit + 4 KAT + 0/0 (benchmark/integration shells) -- all PASS
  cargo test --release -p soroban-falcon-verifier
    -> 2 unit + 4 KAT + 0/0 -- all PASS

Adds:
  docs/audit/scout-scan.md             -- summary report with triage
  docs/audit/scout-scan/scout-*.txt    -- per-crate raw outputs
  docs/audit/scout-scan/run.sh         -- one-command reproducer
  Makefile target make scout-scan
  remediation-log.md rows S-001, S-002, V-001 (Fixed); F-FP-1..3 (Accepted)
The Scout-fix commit 3f5b214 shifted code in both contracts (added
SIGNED_MESSAGE_LEN const + events in smart-account, expanded
unwrap-to-let-else in verifier). The scout-scan.md F-FP-1 explanation
was written against the pre-fix line numbers and was not updated when
the fix landed, so the report cited closed lines.

Updates:

  scout-scan.md F-FP-1 -- now lists the actual flagged sites in the
  post-fix codebase: smart-account lib.rs:165 (sig copy in __check_auth),
  verifier lib.rs:62 (sig copy), verifier lib.rs:71 (msg copy).

  threat-model.md DoS.7.R.1 -- one-line cross-reference acknowledging
  the Scout dos_unbounded_operation findings and pointing the
  reviewer at scout-scan.md and remediation-log.md for the falsepositive
  rationale. Closes the loop end-to-end across the three docs.

No code changes.
Web-demo and falcon-wasm signer are reference implementations only; the
contract's security argument must hold against an arbitrary frontend.
Mark Browser <-> Network (B1) as out of audit scope and clarify that
__check_auth trusts only the host-built signature_payload.

- Scope row: drop web-demo; out-of-scope row: add frontends / falcon-wasm
- System overview: reframe web-demo as reference-only
- B1/B2 trust boundaries: B1 OOS, B2 host-built payload only
- Drop Tamper.4 (compromised browser signer) - frontend concern;
  collapses to Info.2 (seed compromise) from the contract's POV
- Drop Tamper.4.R.1 mitigation row
- Info.2.R.1: reframe as contract-side reasoning (damage bounded by
  signature_payload binding); drop open-item about web-demo key storage
- Open follow-ups: remove secure-key-storage item
… log

Follow-up to c1eeab7. The threat model now puts web-demo / falcon-wasm
out of scope, but other docs still treated TM-001 (signer integrity)
as an open Medium item and the README had not flagged the demo as OOS.

- README: clarify web-demo is a reference frontend (out of audit scope);
  drop the TM-001 reference in the status table; mainnet readiness no
  longer blocked on the frontend-side TM-001 follow-up.
- remediation-log.md: remove TM-001 (registry row + detail section);
  bump Last updated; add a change-log entry explaining why.
- threat-model.md DoS.6.R.1: drop the "web demo enforces size
  client-side" sentence -- defense-in-depth in a frontend is not a
  contract-side mitigation now that frontends are OOS.
Pre-audit self-review pass surfaced 7 findings (all Low / Info, no
Critical or High). All fixed in-tree before submission. Tests + audit
docs updated.

Code changes
============

smart-account/src/lib.rs:
- SR-001: rotate_key now calls `require_auth()` FIRST, then validates
  new-pubkey size. Closes the unauthenticated pubkey-size oracle.
- SR-003: `__constructor` and `rotate_key` proactively call
  `storage.extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO)`
  (~30 days) after each pubkey write. Defense-in-depth for long-idle
  accounts; host auto-bump still applies on every call.
- SR-004: init / rotate events now publish `env.crypto().sha256(pubkey)`
  instead of the full 897-byte pubkey. Full pubkey remains readable via
  `get_pubkey`; this avoids permanently bloating ledger metadata.
- SR-005: `get_pubkey` now returns `Result<Bytes, Error>` with
  `Error::PublicKeyMissing` rather than `.expect()`. No reachable
  `expect`/`unwrap`/`panic!` remains outside `__constructor`.

falcon-512-core/src/verify.rs:
- SR-007: rewrote the signature-header gate comment to cite Falcon
  Round-3 §3.11.1 explicitly (0x2X padded / 0x3X compressed / 0x5X CT)
  and document the two-layer CT defense (size gate + format gate).

Test changes
============

smart-account/tests/integration.rs (+6 tests):
- SR-002: dynamic tests that pin SR-001 ordering and basic auth
  behavior:
  * test_rotate_key_succeeds_with_mocked_auth
  * test_rotate_key_without_auth_fails  (auth missing -> trap)
  * test_rotate_key_bad_size_after_auth_returns_error
  * test_get_pubkey_returns_stored_value (proves new Result return)
  * test_check_auth_rejects_undersized_signature
  * test_check_auth_rejects_oversized_signature
- Soroban env-test snapshots regenerated to reflect new event payloads
  (hashes) and the additional extend_ttl host call.

Doc changes
===========

docs/audit/threat-model.md:
- SR-006: refreshed all `lib.rs:NN` cross-references (~50 lines stale).
- Elevation.1.R.1 rewritten to reflect SR-001 ordering and cite the new
  integration tests.
- Elevation.2.R.1 rewritten: the over-length check is enforced at
  COMPILE time via `const _: () = assert!(SIGNED_MESSAGE_LEN <=
  SIGNED_MESSAGE_MAX)`, not at runtime as the old text claimed.
- DoS.3.R.1 updated to note the explicit format-gate rejection of 0x5X
  CT signatures (previously only the size gate was cited).
- DoS.4.R.1 reflects SR-005: `get_pubkey` no longer uses `expect`.

docs/audit/remediation-log.md:
- New registry rows SR-001 through SR-007 with severity, status, owner,
  and source-file references.
- Change-log entry summarizing the self-review pass.

Test results
============

  smart-account:     22 tests (5 unit + 4 bench + 9 integration + 4 KAT)
  falcon-512-core:    6 tests
  verifier:           4 tests
  Total:             32 tests, 0 failures
  KAT coverage:      100 official NIST Falcon-512 vectors (intact)
Tracks Stellar discussion #1915 (PQ signature verification host
functions in Soroban. Covers the next NIST schemes to add as Soroban
verifiers (ML-DSA / SLH-DSA), Smart Account signer registration for
all three schemes plus proof-based signature commitments, a public
benchmark harness, and the Stellar-native proof-of-seed migration
path (verified via WHIR) — the only IAB strategy satisfying all four
desired migration properties, and one wallets/custodians can adopt
without rotating keys or swapping HSM curves.
EOF
)
…o testnet

Multi-agent adversarial audit (no Critical/High/Medium found; core crypto
differential-tested vs PQClean). Acted on the confirmed Low/Informational
findings:

- AUD-001 (DEC-002): enforce exact-length signature consumption (natural OR
  fixed 666 padded w/ zero tail); closes unbounded-padding malleability.
  New regression test test_dec002_arbitrary_padding_rejected. KAT still 100/100.
- AUD-003/004 (DEC-004/H2P-001): correct inaccurate format comments and the
  "NIST standard / FIPS 206" claim -> NIST Round-3 Falcon-512 (not FN-DSA).
- AUD-005 (DRS-3): bulk copy_into_slice instead of per-byte Bytes::get loops
  in both wrappers -> 396,903 -> 12,986 CPU insns (30.6x), panic-free preserved.
- AUD-006 (DRS-1/2): build-time stack-budget const-assert + 16KB worst-case
  benchmark (15,033 insns).
- AUD-002/007 (DEC-001/H2P-002): documented as accepted (interop-required
  dual-header; FN-DSA domain-sep tracked in Roadmap).

Deployed standalone verifier to Stellar testnet:
  CDDZZJ3B3BMKBPJ7ZVMC3JQC7MDNIODUXYHBCHNCGVXAL56UFBEPM4RC
on-chain verify(pk,msg,sig)->true (tx b133de95...), wrong-msg->false.
Receipt: docs/audit/e2e-receipts/2026-06-07-verifier-testnet.json.

Updated README, optimization-report.md (now ~13k insns / 0.013% budget),
and remediation-log.md (AUD-001..007).
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.

1 participant