This document analyzes potential attack vectors in the Atomic Patent swap mechanism and documents mitigations.
Scenario: Seller accepts payment but reveals an invalid decryption key.
Impact: Buyer loses payment, seller keeps money without delivering valid IP.
Mitigation:
reveal_keyverifies the key against the stored commitment hash viaverify_commitment- If verification fails, transaction panics and payment remains in escrow
- Buyer can call
cancel_expired_swapafter expiry to recover funds
Status: ✅ Mitigated
Scenario: Attacker observes a pending reveal_key transaction and attempts to extract the secret before it's confirmed.
Impact: Attacker learns the IP secret without paying.
Mitigation:
- Stellar's transaction ordering is deterministic within a ledger
- Secret is only revealed after payment is locked in escrow
- Once revealed, the swap completes atomically in the same transaction
Status: ✅ Mitigated (blockchain-level protection)
Scenario: Buyer accepts swap and sends payment, but seller never calls reveal_key.
Impact: Buyer's funds locked indefinitely.
Mitigation:
- Swaps have an
expirytimestamp (default: 7 days) - After expiry, buyer can call
cancel_expired_swapto recover full payment - Seller loses reputation but cannot steal funds
Status: ✅ Mitigated
Scenario: Attacker registers the same commitment hash multiple times to claim ownership of someone else's IP.
Impact: IP ownership confusion, potential fraud.
Mitigation:
commit_ipchecksDataKey::CommitmentOwner(hash)before registration- Duplicate hashes are rejected with
CommitmentAlreadyRegisterederror - Each commitment hash can only be registered once globally
Status: ✅ Mitigated
Scenario: Attacker initiates a swap for an IP they don't own.
Impact: Fraudulent sale of someone else's IP.
Mitigation:
initiate_swapcallsregistry.get_ip(ip_id)and verifiesrecord.owner == seller- Seller must provide
require_auth()to initiate - Cross-contract ownership verification prevents forgery
Status: ✅ Mitigated
Scenario: Seller initiates swap for an IP they've already revoked.
Impact: Buyer purchases invalid IP.
Mitigation:
initiate_swapchecksrecord.revokedflag- Revoked IPs cannot be swapped
- Panics with
IpIsRevokederror
Status: ✅ Mitigated
Scenario: Seller creates a swap with price = 0 to transfer IP without payment tracking.
Impact: Off-chain deals bypass protocol fees, potential money laundering.
Mitigation:
initiate_swaprejectsprice <= 0withPriceMustBeGreaterThanZeroerror- All swaps must have positive price
Status: ✅ Mitigated
Scenario: Seller initiates multiple swaps for the same IP simultaneously.
Impact: Multiple buyers pay for the same IP.
Mitigation:
DataKey::ActiveSwap(ip_id)tracks active swaps per IP- Second
initiate_swapfor same IP is rejected withActiveSwapAlreadyExistsForThisIpId - Lock released only when swap reaches
CompletedorCancelled
Status: ✅ Mitigated
Scenario: Attacker replays a previous reveal_key transaction to complete a different swap.
Impact: Unauthorized swap completion.
Mitigation:
- Each swap has a unique
swap_id reveal_keyverifies the secret against the specific IP's commitment hash- Stellar's transaction sequence numbers prevent replay across ledgers
Status: ✅ Mitigated (blockchain-level protection)
Scenario: Buyer uses a malicious token contract that doesn't actually transfer funds.
Impact: Seller reveals key but receives no payment.
Mitigation:
- Seller chooses the token contract address when initiating swap
- Seller should only accept well-known tokens (XLM, USDC, EURC)
- Wallet UIs should warn sellers about unknown tokens
Status:
Scenario: Attacker attempts to brute-force the secret from the commitment hash.
Impact: IP secret revealed without payment.
Mitigation:
- Pedersen commitment scheme uses SHA-256 with blinding factor
- Blinding factor makes brute-force computationally infeasible (2^256 search space)
- Users must generate cryptographically random blinding factors
Status: ✅ Mitigated (cryptographic security)
Scenario: Attacker waits for IP record TTL to expire, then registers the same commitment.
Impact: IP ownership stolen after expiry.
Mitigation:
- All persistent storage uses
LEDGER_BUMP = 6_307_200(~1 year) - Every read/write extends TTL automatically
- Active IPs remain valid indefinitely through normal usage
Status: ✅ Mitigated
Scenario: Whoever controls the oracle_address contract set via
set_oracle returns an arbitrary positive i128 as the price for
initiate_swap_with_oracle_price, with no way for an outside party to verify
it was authentically produced rather than fabricated on the spot.
Impact: A malicious or compromised oracle contract (or whoever can redeploy/upgrade it) can settle swaps at a fabricated price, moving value away from whichever party the price disadvantages.
Mitigation:
set_oraclerecords anoracle_pubkey(Ed25519) alongsideoracle_address— the actual cryptographic trust anchor, not just an address.fetch_oracle_price/fetch_oracle_price_with_staleness_checkverify an Ed25519 signature over(token, price, timestamp)againstoracle_pubkeybefore a price is used for anything. An invalid, missing, or wrong-key signature is rejected, independent of the staleness check.- A configurable
max_deviation_bpsbound rejects a validly-signed price that moves too far from the last accepted price, limiting the damage a single bad or compromised update can do. - The 5-minute staleness fallback (unchanged) only ever serves a cached price that was itself signature-verified when it was fetched, so it doesn't reintroduce an unverified path.
- See atomic-swap.md § Price Oracle Trust Model
for what remains a governance/operational trust assumption (the admin's
choice of which
oracle_pubkeyto register) versus what is cryptographically enforced.
Status: ✅ Mitigated (signature + deviation bound); governance trust in the registered publisher key remains, by design — see linked doc.
The dispute resolution mechanism allows a designated admin to adjudicate contested swaps where on-chain verification alone is insufficient — e.g. a buyer claims the revealed key does not decrypt the promised IP, or off-chain delivery of associated materials is disputed. The admin can rule in favour of either party, triggering fund release or refund. Because this introduces a privileged role, it is the highest-risk surface in the protocol and requires careful operational controls.
Scenario: A single admin account is compromised or acts maliciously, ruling in favour of one party to steal funds or IP rights.
Impact: Fraudulent dispute outcomes; direct loss of buyer funds or seller IP.
Mitigations:
- Per-swap dispute rulings now require an M-of-N arbitrator committee
(
set_arbitrator, minimum 2-of-3 threshold, 3-of-5 or larger supported) —arbitrate_disputerequiresthresholdof the committee'ssignersto jointlyrequire_auth()a ruling in one call; no single key can rule alone. - A ruling is bound to submitted evidence:
arbitrate_disputerejects a ruling ifDisputeEvidenceis empty, and the evidence hashes actually considered (read from storage, not signer-supplied) are recorded inRulingEnteredEventfor outside audit. - A 48-hour time-lock (
execute_ruling,ProtocolConfig.arbitration_ruling_delay_secs) separates ruling entry from fund release;cancel_pending_rulinglets the same committee threshold void a ruling within that window. - All transitions are individually auditable:
ArbitratorCommitteeSetEvent,RulingEnteredEvent,RulingCancelledEvent,RulingExecutedEvent. - Not covered by the above, still open: the contract's own
Adminrole (DataKey::Admin) remains a singleAddress— it alone appoints the arbitrator committee and is unaffected by this mechanism. More directly,resolve_disputestill lets that single admin resolve any disputed swap directly, completely bypassing the committee/evidence/timelock/bond system described above — a real, disclosed gap, recommended as a follow-up issue (either removeresolve_dispute's direct fund-movement path or route it through the same committee/timelock).admin_rollback_swapandauto_refund_timeoutare narrower emergency/timeout escape hatches that remain single-admin/permissionless by design, but now correctly refund (never forfeit) any dispute bond in flight if they fire instead of a committee ruling, so they no longer orphan bonded value. batch_arbitrate_swaps— previously did not validate its caller against any stored arbitrator at all — is now disabled (always reverts) pending a follow-up migration onto the committee model.arbitrate_swap, a collateral-aware duplicate of the old single-keyarbitrate_disputethat never checked swap status, is now permanently unreachable since nothing populates the legacySwapArbitratorkey anymore; this also closes a pre-existing double-payout risk where it could still be called afterresolve_disputehad already paid out a swap.- Admin key rotation is supported via contract upgrade path; rotation procedure must still be documented before mainnet.
Status: Admin role itself is still
single-key and resolve_dispute remains an admin-direct bypass of the new
safeguards.
Scenario: A party raises a dispute in bad faith — buyer disputes a valid key reveal to delay payment release, or seller disputes to stall a refund.
Impact: Counterparty funds locked; griefing / DoS against legitimate swap completion.
Mitigations:
- Disputes require an on-chain evidence hash (
submit_dispute_evidence) — no evidence, no dispute, and (see #13) no evidence meansarbitrate_disputecannot enter a ruling at all. - A non-refundable dispute bond (
MIN_DISPUTE_BOND, the greater of 1 XLM or 10% of swap price) is charged on a party's first evidence submission and forfeited to the contract admin if the committee's ruling goes against that party; refunded in full if the ruling goes their way, or if the dispute is instead resolved via one of the non-committee paths in #13 (no ruling ⇒ no forfeiture). - Disputes must be filed within
dispute_periodledgers of the triggering event; late filings are rejected by the contract - Repeated frivolous filings from the same address are rate-limited by the admin
Status: protocol_config().treasury
resolves to a hardcoded placeholder today — a pre-existing, separate storage
bug, not fixed by this change).
Scenario: A party deliberately stalls — admin never rules, or a party withholds evidence — to keep funds locked indefinitely or force the counterparty to abandon their claim.
Impact: Funds locked beyond intended swap expiry; effective denial of refund or payment.
Mitigations:
- Auto-resolution on timeout: if the admin has not ruled within
dispute_timeoutledgers, the contract automatically refunds the buyer as the safe default - Evidence submission deadline is enforced on-chain; failure to submit within the window is treated as conceding the dispute
dispute_timeoutis set at contract initialisation and is immutable — admin cannot extend it to stall resolution
Status: ✅ Mitigated — provided dispute_timeout is set correctly at deploy time
| Risk | Detail |
|---|---|
| Off-chain evidence integrity | The contract stores only a hash of evidence; the underlying data lives off-chain and could be lost or withheld. Operators should require evidence to be pinned to a content-addressed store (e.g. IPFS). |
| Admin key compromise post-ruling | If the admin key is compromised after a ruling but before the time-lock expires, an attacker could attempt to reverse the ruling. Multi-sig and time-lock together reduce but do not eliminate this window. |
| Governance capture | In future versions where admin is controlled by token vote, a majority token holder could capture dispute outcomes. Quorum and time-lock requirements must be enforced at the governance layer. |
| Concern | Required Action |
|---|---|
| Admin collusion | Deploy with multi-sig admin (2-of-3 minimum); never use a single EOA on mainnet |
| False disputes | Require evidence hash at submission; set bond ≥ max(1 XLM, 10% of swap price) |
| Timeout abuse | Set dispute_timeout ≤ 14 days (~120,960 ledgers); verify auto-resolve defaults to buyer refund |
| Audit trail | Emit on-chain events for all state transitions: DisputeOpened, EvidenceSubmitted, DisputeRuled, DisputeAutoResolved |
| Key rotation | Define and test admin key rotation procedure before mainnet launch; store rotation policy in governance docs |
| Evidence availability | Require disputing parties to pin evidence to IPFS or equivalent; store CID in dispute_evidence field |
Risk: User loses their secret and blinding_factor.
Impact: Cannot prove IP ownership or complete swaps.
Recommendation: Wallets should implement encrypted backup and recovery mechanisms.
Risk: On-chain IP commitment may not be recognized in all jurisdictions.
Impact: Limited legal protection in some countries.
Recommendation: Users should consult local IP attorneys for jurisdiction-specific advice.
Risk: No on-chain mechanism to verify the quality or validity of the IP itself.
Impact: Buyer may purchase worthless or invalid IP.
Recommendation: Buyers should conduct off-chain due diligence before accepting swaps.
For wallet providers:
- Encrypt all stored secrets with user's master password
- Generate blinding factors using
crypto.getRandomValues()or equivalent - Warn users before revealing keys in swaps
- Display swap expiry times prominently
- Implement transaction simulation before submission
For users:
- Backup secrets in multiple secure locations
- Only accept swaps for IPs you've verified off-chain
- Use well-known token contracts (XLM, USDC)
- Monitor swap expiry times
- Internal security review: ✅ Complete
- External audit: ⏳ Pending
- Bug bounty program: Planned for v2.0
See SECURITY.md for responsible disclosure process.
batch_commit_ip_anonymous lets submitters register IP commitments without
linking the transaction to a real identity. This section documents the
cryptographic model, what anonymity properties hold, and where residual risks
remain.
The caller supplies a blinded_owner: BytesN<32> value instead of a real
Address. The recommended construction off-chain is:
blinded_owner = sha256(owner_address_bytes || random_nonce_32_bytes)
Only the blinded_owner hash is written on-chain — never the raw address or
nonce. An observer with access to the full ledger history cannot reverse this
hash to recover the original address without knowing the nonce.
| Property | Guarantee |
|---|---|
| Submitter unlinkability | The IpRecord.owner field is set to the contract address, not the caller. No on-chain index links the record to any Address. |
| Blinded-owner confidentiality | blinded_owner is a one-way hash; the original address + nonce pair cannot be recovered without the nonce. |
| Ownership indexing bypass | Anonymous commits intentionally skip the OwnerIps index, so list_ip_by_owner returns an empty list for any address. |
| Batch grouping resistance | Each batch must use a fresh blinded_owner. The replay protection prevents linking two batches to the same identity via nonce reuse. |
Each blinded_owner value is consumed atomically on first use and stored under
DataKey::UsedBlindedOwner(blinded_owner). A second call with an identical
blinded_owner panics with CommitmentAlreadyRegistered (error code 3).
This means:
- A given
blinded_owner = sha256(address || nonce)submits exactly one batch per nonce. - An attacker who observes the
blinded_owneron-chain cannot replay it to register additional commitments under the same pseudonym. - Submitters who need multiple batches must generate a fresh nonce for each.
Scenario: Attacker copies a blinded_owner from a historical transaction
and attempts to register new commitments under that identity.
Impact: Forged ownership linkage under another party's pseudonym.
Mitigation: UsedBlindedOwner map rejects the second call immediately.
Status: ✅ Mitigated
Scenario: Adversary iterates over known Stellar addresses to find a
match for an observed blinded_owner by computing sha256(address || nonce)
for each candidate.
Impact: De-anonymization of the submitter.
Mitigation:
- The nonce must be 32 bytes of cryptographically random data, making the search space 2^256 even if the address is known.
- Wallets must use a CSPRNG (e.g.
crypto.getRandomValues) for nonce generation; deterministic or low-entropy nonces weaken this guarantee.
Status: ✅ Mitigated — provided callers use a secure nonce
Scenario: Adversary correlates the ledger timestamp and transaction fee payer of an anonymous commit to a known address.
Impact: Partial de-anonymization via side-channel.
Mitigation:
- This is a residual risk. The protocol cannot hide the fee account on Stellar.
- Users requiring stronger anonymity should route submissions through an intermediary account (e.g. a relayer), or batch alongside other users.
Status:
| Concern | Required Action |
|---|---|
| Nonce quality | Enforce 32-byte CSPRNG nonce in all SDKs and wallet integrations; reject user-supplied low-entropy nonces |
| Blinded owner reuse | Document that each batch requires a fresh nonce; warn if SDK detects reuse |
| Fee account exposure | Advise privacy-sensitive users to use a fresh throwaway account as the transaction fee payer |
| Audit trail | "ip_cmt_a" events are emitted per commitment; monitor for unusual batch sizes that may indicate Sybil behaviour |