IWA is a financial product.
Security is a release gate, not a final checklist item.
No component should be described as secure merely because:
- tests pass
- a transaction succeeds
- the contract compiles
- the UI works
- an agent says the code looks safe
Security claims must map to concrete properties that were reviewed or tested.
IWA must protect:
- user funds
- private financial relationships
- contribution integrity
- payout integrity
- credential integrity
- admin boundaries
- protocol configuration
- signing and secret material
- reliability history
- recovery paths
IWA admins, backend services and operational infrastructure must not have unilateral control over user funds.
The system must not require the IWA team to hold:
- wallet seeds
- private keys
- user signing keys
- viewing keys
- custodial withdrawal authority
Where a protocol interaction requires signing, the signing authority must be explicitly documented and minimized.
Major boundaries:
User
↓
Wallet
↓
STRK20
↓
IWA Cairo contracts
↓
Starknet state
Supporting systems:
Frontend
Backend / Indexer
Admin Dashboard
RPC Providers
Deployment Tooling
CI/CD
Each boundary must be reviewed separately.
The following must not leak through the backend, frontend logs, analytics or admin tools:
- private contribution details
- private payout relationships
- viewing keys
- wallet private keys
- signing secrets
- raw private transfer graph
- raw credential evidence
- unnecessary wallet-to-person mappings
Public exposure must be minimized to what protocol correctness and usability require.
These are not leaks; they are unavoidable properties of the current design and must be described honestly rather than defended as private:
- the invite secret, once submitted in
join_circlecalldata - the
member_refcommitment, in the payout order, events and settlement paths - the correlation between the joining wallet and that
member_ref, because the join transaction has a public sender - circle existence, size, cadence, asset and round progression
- deposit and withdrawal edges at the STRK20 boundary, transaction timing, helper invocation, and open-note amounts
STRK20 gives IWA private settlement transfers. It does not give IWA anonymous membership. Product copy, UI and marketing must not imply otherwise.
Do not implement STRK20 behavior from memory.
Before protocol code is written:
- read the installed STRK20 skill
- read its bundled references
- confirm exact transaction ordering
- confirm helper/anonymizer contract expectations
- confirm caller validation requirements
- confirm note handling
- confirm output handling
- confirm supported disclosure behavior
- confirm current mainnet addresses
If documentation and assumptions conflict, documentation wins until independently verified.
The official STRK20 pool address used for the sprint must be verified from current upstream documentation before deployment.
Never rely only on:
- an old conversation
- copied notes
- agent memory
- a stale environment file
Deployment and runtime configuration should fail closed when the configured pool does not match the expected verified pool.
If IwaStrk20Helper is used, it is a high-risk boundary.
Review at minimum:
- allowed caller
- expected STRK20 pool
- supported function selectors
- token allowlist
- circle existence
- round state
- contribution obligation
- amount validation
- replay protection
- note/output validation
- duplicate-state transitions
- external calls
- failure/revert behavior
The helper must not become a generic arbitrary-call proxy.
Sensitive functions must explicitly validate their caller.
Never assume that because a function is intended to be called by STRK20, only STRK20 will call it.
Where required by protocol architecture:
caller == verified STRK20 pool
must be enforced.
Other privileged methods require similarly explicit role validation.
Public member_ref commitments and invite secrets observed in join calldata
are not contribution credentials. Each member instead registers a separate
IWA authentication public key during join and retains the private key.
The key uses the Stark curve and Cairo corelib's established ECDSA verifier.
IWA additionally enforces the verifier's documented r/s range requirements
and canonical low-s form. No custom signature algorithm is implemented.
The signed contribution authorization is domain-separated with
IWA_CONTRIBUTION_V1 and binds circle_id, round, member_ref, exact
amount, and nonce. It is not bound to get_caller_address() and must not
contain a wallet private key, viewing key, or invite secret.
First release:
- USDC
- STRK
Reject unknown assets.
Do not treat arbitrary token addresses supplied by a user as trusted.
When future assets are added, each asset must receive explicit configuration and review.
These properties must hold regardless of transaction ordering or user behavior.
Once the first active contribution begins:
- payout order cannot be changed
- recipient order cannot be rewritten by admin
- arbitrary recipient substitution is forbidden
A contribution obligation cannot be satisfied more than once.
Replay of a previously accepted contribution must fail.
A payout for a round cannot execute more than once.
A completed contribution state cannot be silently rewritten.
Examples:
MISSED_DEFAULT → ON_TIME
must not be possible through admin intervention.
Rounds cannot finalize before all required deterministic conditions are met.
Round numbers cannot:
- skip unexpectedly
- move backward
- finalize twice
An unresolved deficit follows predefined rules.
Admin cannot:
- ignore it arbitrarily
- reroute locked payout
- fabricate a cure
- change the member's historical state
The exact accounting invariant depends on the final STRK20 integration model.
Before mainnet, define and test an equation describing all value entering and leaving each circle.
Conceptually:
accepted value
=
unsettled value
+ successfully distributed value
+ valid recoveries/withdrawals
+ explicitly accounted protocol fees
There must be no unexplained value creation or loss.
Every new fee path must be included in the invariant.
Grace behavior must be deterministic.
Review:
- Starknet timestamp semantics
- boundary timestamps
- exact due-time comparison
- exact grace-expiry comparison
- late contribution at boundary
- sequencer timestamp assumptions
Avoid ambiguous logic such as multiple components calculating the deadline differently.
Any contribution or payout operation that could be replayed must include a unique state-bound identifier.
Possible components include:
circle id
round
member commitment/ref
obligation identifier
action type
nonce/nullifier/protocol identifier
Exact mechanics must match STRK20's supported model.
For IWA contributions, Task 6D must store nonce consumption atomically with the obligation transition. A valid signature without an unused, state-bound nonce is insufficient. Task 8 must call that transition only from the verified STRK20 helper route while preserving the same signature and nonce checks.
Minimize external calls.
For every external call:
- document target
- document authority
- document expected return/failure behavior
- consider reentrancy/state-ordering issues
- update protected state in a safe sequence
No user-supplied arbitrary destination/call-data feature may be introduced without a separate security design.
Admin is operational, not custodial.
Admin may have controlled abilities such as:
- narrow emergency pause
- operational configuration where explicitly safe
- support tooling
- monitoring
- approved feature toggles
Admin must not be able to:
- seize funds
- spend user balances
- alter payout order
- choose payout recipients
- falsify contributions
- erase defaults
- rewrite completed rounds
- forge credential results
- reveal viewing keys
- reveal private transfer history
Administrative roles should be separated where it materially reduces risk.
Potential future roles:
OPERATIONS
PAUSER
CONFIG_MANAGER
AUDITOR
SUPPORT
Do not introduce role complexity merely for appearance.
The first implementation may use a smaller role model if it still preserves least privilege.
The pause must be narrowly scoped.
Preferred behavior:
Pause:
- new joins
- new contributions
- newly initiated risky operations when necessary
Do not automatically pause safe recovery paths unless the discovered vulnerability affects them.
The pause must not:
- transfer funds to admin
- change ownership
- rewrite state
- redirect payout
- create unlimited freeze authority without recovery design
Every pause/unpause action should be auditable.
Recovery paths must avoid converting IWA into a custodial service.
Where funds can safely be recovered:
- authorization should remain with the rightful user or deterministic contract logic
- amounts must derive from verified state
- recovery must not bypass payout/accounting invariants
- recovery actions must be replay protected
The exact recovery path must be specified before production deployment.
A credential must only prove facts derivable from valid protocol history.
Threats include:
- forged reliability
- replayed credentials
- stale claims
- cross-user credential reuse
- credential copied outside its intended context
- excess disclosure
- verifier correlation
Credential design should consider:
- claim scope
- user binding
- validity period
- verifier/context binding where useful
- replay model
- revocation/expiry model if required
- minimal disclosure
Do not publish raw contribution history just to make credential verification easy.
The legacy Circom/Groth16 code is not automatically trusted because it previously worked.
If reused:
- re-audit circuit constraints
- check unconstrained signals
- check public/private inputs
- check field conversions
- review witness generation
- review proof binding
- review replay behavior
- review verifier correctness
- review trusted setup assumptions where applicable
If STRK20 makes the separate proof system unnecessary, remove the unnecessary cryptographic surface rather than maintaining it for complexity.
The frontend must not leak private information through:
- console logs
- analytics payloads
- error-reporting tools
- URLs/query strings
- browser storage
- copied debug payloads
- wallet error messages
- screenshot-friendly debug panels
Review browser storage deliberately.
Do not persist secret material in:
localStorage
sessionStorage
IndexedDB
cookies
unless the relevant protocol/library explicitly requires a secure pattern that has been reviewed.
Never request:
- seed phrase
- raw private key
IWA should use wallet authorization.
The application must clearly verify:
- connected chain
- intended account
- transaction intent
- supported wallet behavior
Wrong-network actions must fail closed.
Backend stores only public/non-sensitive information unless a future feature receives a new explicit security design.
Required protections:
- authentication for admin endpoints
- authorization per privileged action
- input validation
- rate limiting where useful
- secure secret storage
- database access controls
- audit logging
- dependency review
- no sensitive data in logs
Admin dashboard must not expose private user data merely because it is privileged.
Every sensitive admin mutation must be:
- authenticated
- authorized
- attributable
- logged
Financial-control actions not allowed by this architecture must not exist as hidden UI controls.
Future B2B verification APIs should use:
- scoped API credentials
- usage limits
- rate limiting
- revocation
- audit logs
- claim-minimizing responses
An API client should not gain access to full user savings history.
Never commit:
- private keys
- wallet seeds
- API secrets
- database passwords
- auth secrets
- deployment signing material
- private RPC credentials
- viewing keys
Before every meaningful push:
git diff --cachedmust be reviewed for secrets and unrelated changes.
Where available, run automated secret scanning.
If a secret is exposed, treat it as compromised and rotate it.
Real environment files should not be committed.
Prefer:
.env
.env.local
.env.*.local
in .gitignore.
Commit only sanitized examples such as:
.env.example
with placeholder values.
Do not deploy merely because the code compiles.
Mainnet gate requires:
- unit tests pass
- integration tests pass
- invariant/fuzz tests pass
- manual contract review completed
- STRK20 integration reviewed
- privacy review completed
- access-control review completed
- deployment config verified
- secrets scan clean
- roles verified
- class hashes verified
- addresses verified
- minimal-value walkthrough planned
Initial mainnet interactions should use the smallest practical amount.
Do not move significant funds merely to satisfy hackathon transaction requirements.
Before execution verify:
- chain is Starknet Mainnet /
SN_MAIN - STRK20 pool address
- USDC address
- STRK address
- expected class hash
- deployed contract address
- owner/admin address
- pause authority
- RPC target
- frontend config
- backend config
If any value is ambiguous:
stop deployment and verify it.
Only successful, verified transactions should be written into strk20.json.
For every qualifying transaction confirm:
- correct network
- successful receipt
- correct STRK20 pool interaction
- expected IWA state transition
- no unexpected events
Do not insert hashes simply because they exist.
Before introducing a dependency:
- verify its purpose
- prefer official/current package
- avoid duplicates
- review package reputation/source
- pin/version deliberately where appropriate
Do not add a package merely because an agent recognizes its name.
Required checks should eventually include relevant combinations of:
format
lint
typecheck
unit tests
contract tests
integration tests
fuzz/invariant tests
build
secret scanning
A green build does not replace manual review.
Security review happens throughout development.
- threat model
- trust boundaries
- invariants
- tests before/minimally alongside behavior
- review each security-critical diff
- narrow commits
- interface assumptions reviewed
- dedicated full-system audit
- verify deployed bytecode/class hashes and configuration
Use at least:
CRITICAL
HIGH
MEDIUM
LOW
INFORMATIONAL
Each finding should contain:
Title
Severity
Affected component
Impact
Attack path / failure mode
Evidence
Recommended fix
Fix status
Verification
Do not downgrade a finding because a deadline is near.
The new Starknet implementation has not been written yet.
Therefore:
- no claim of Starknet contract security exists
- STRK20 integration is not yet validated
- mainnet deployment has not occurred
- legacy Stellar/Soroban security assumptions must not be inherited automatically
- legacy Circom/prover components remain unapproved for reuse until reviewed
For security-critical code:
- understand intended invariant
- write or identify a failing test
- implement minimal behavior
- run test
- run neighboring/regression tests
- inspect diff
- run security-specific review
- commit only verified change
Agents must never:
- invent security verification
- claim an audit happened when it did not
- expose secrets
- bypass tests to save time
- disable safety checks without explicit justification
- create arbitrary admin powers
- silently expand scope
- treat a successful transaction as proof of correctness
- delete old security-relevant logic before its behavior is understood
If IWA cannot satisfy the security gate safely before a deadline:
reduce scope instead of weakening the security model.
IwaCircle pins the STRK20 pool and a deployment-only setup authority in its
constructor. The helper starts unset. Only that authority may initialize one
non-zero helper, exactly once; successful initialization locks the helper and
clears the authority to zero. The authority has no circle, membership,
financial, pause, token, pool, or replacement capability. Neither organizer
nor admin can replace or redirect the helper. Every financial-settlement
function rejects before initialization and then requires
caller == settlement_helper.
Deployment is valid only after the circle is deployed, the helper is deployed with that circle pinned, the authority initializes the helper, and onchain reads confirm the exact helper, locked initialization, and cleared authority.
The helper-only APIs are narrowly typed for contribution, cure, payout, and
recovery. There is no arbitrary target, selector, calldata, liability setter,
mark_paid, or mark_recovered surface. Public payout authorization remains
preparatory only. Public callers cannot consume financial contribution/cure
state or produce Paid/Recovered.
All financial signatures bind circle, round, member, helper, pool, token, exact amount, and nonce under distinct domains. Payout and recovery also bind the member-authorized open-note id.
Solvency is enforced per (circle, round, token). Only helper-confirmed
contributions and cures credit that round, and only its payout or recovery may
debit it. A debit exceeding the same round's funded liability fails closed.
Another token, another round, donations, organizer funds, or hypothetical
insurance are not protocol backing.
The helper is an ordinary ERC20 holder, so anyone can send it tokens it never
agreed to hold. Accounted custody for a token is exactly token_liability,
the sum of that token's legitimate round liabilities; anything above it is
surplus.
Surplus is never backing. credit_liability and debit_liability are the only
writers of custody or round liability, and both are reachable only from the
four pool-authorized settlement branches of privacy_invoke. Inbound
settlement still requires balance == accounted custody + exact amount, so a
donation can never stand in for a member's contribution or cure.
Because that check is exact, a surplus previously blocked every later inbound
settlement in that token, permanently and with no recovery path. That was
confirmed against the real pool and is now fixed by
normalize_surplus(token):
- accepts only the two configured assets, so there is no general token rescue
- requires
balance >= accounted custody, then moves exactly the difference - leaves
balance == accounted custody, so legitimate backing cannot be swept - reverts when there is no surplus, rather than silently draining anything
- writes no storage at all: no liability, circle, round, member, note, nonce, payout recipient, default, or cure history is touched
- sends to
surplus_sink, an immutable address pinned at deployment and validated non-zero and distinct from the helper, the pool, and both tokens
The destination is never a parameter, so a caller cannot direct value and gains nothing by calling. There is no setter, no admin path, and no upgradeable destination. Anyone may call it, which is what removes the denial of service.
Residual, accepted: an attacker can still donate immediately before a settlement transaction to make that one transaction revert, then repeat. This is transient and repeatable rather than permanent, costs the attacker real tokens plus gas each time, and is cleared by any permissionless normalization. Folding normalization into the settlement path would remove it entirely, at the cost of a token transfer inside every contribution and cure; that trade has not been taken.
Uncured final accounting stores net-funded recovery separately: nominal payout minus exact uncured deficits in that round. Nominal payout and defaults are not rewritten.
A zero derived recovery is terminal NoFundedRecovery, not a zero-value token
operation. It can arise only during deterministic final preparation, preserves
the recipient and nominal amount, consumes no settlement nonce or signature,
and cannot transition to RecoveryPending, Paid, or Recovered. Positive
derived recovery alone uses RecoveryPending and requires later real movement.