QuorumProof is a decentralized credential verification platform built on Stellar Soroban. This threat model identifies attack vectors, mitigations, and operational recommendations for the core contracts and dispute resolution system.
Scope: quorum_proof, sbt_registry, zk_verifier contracts and their interactions.
Last Updated: August 29, 2026
Review Cadence: Reviewed before each major release (see §6 for next review date).
-
Credentials — Soulbound tokens representing verified professional qualifications
- Issued by trusted institutions (universities, licensing bodies)
- Non-transferable, tied to individual identity
- Revocable by issuer
- Value: Enables international hiring, credential portability
-
Quorum Slices — Trust networks defining credential attestation requirements
- Created by credential issuers
- Define threshold for multi-party consensus
- Weighted voting model (FBA-inspired)
- Value: Ensures credential authenticity through distributed trust
-
Attestations — Cryptographic signatures from slice members
- Prove credential holder meets requirements
- Time-windowed (valid for specific period)
- Weighted according to slice definition
- Value: Enables instant verification without contacting original issuer
-
Soulbound Tokens (SBTs) — On-chain representation of credentials
- Minted by
sbt_registryafter credential verification - Non-transferable by design
- Queryable by any third party
- Value: Portable, verifiable proof of qualification
- Minted by
| Actor | Motivation | Capability | Likelihood |
|---|---|---|---|
| Credential Fraudster | Obtain fake credentials to misrepresent qualifications | Medium (social engineering, bribery) | High |
| Slice Member Attacker | Attest false credentials for payment | Medium (insider threat) | Medium |
| Contract Exploiter | Find smart contract vulnerabilities | High (security researcher) | Medium |
| Network Attacker | Disrupt credential verification | Medium (DDoS, network partition) | Low |
| Malicious Issuer | Issue credentials to unqualified individuals | High (institutional access) | Low |
| Actor | Motivation | Capability | Likelihood |
|---|---|---|---|
| Admin Collusion | Bypass verification requirements | High (full contract access) | Low |
| Disgruntled Employee | Sabotage credential system | High (institutional access) | Low |
| Compromised Key | Unauthorized credential issuance | High (key compromise) | Medium |
Attack: Attacker creates fake credentials without authorization.
Vector:
- Call
issue_credentialwithout proper authorization - Bypass issuer authentication
- Forge metadata hash
Mitigation:
- ✅
require_auth()enforced onissue_credential— only issuer can create credentials - ✅ Issuer address stored in credential — cannot be spoofed
- ✅ Metadata hash is immutable after issuance
- ✅ Credential ID is monotonically increasing — no ID collision possible
Residual Risk: Low. Requires compromised issuer key.
Attack: Non-slice-member attests credential, or attests outside time window.
Vector:
- Call
attestwithout being in the slice - Attest outside the time window
- Attest the same credential twice
Mitigation:
- ✅
NotInSliceerror if caller not in attestor list - ✅ Attestation time window enforced (
AttestationTimeWindowstruct) - ✅
DuplicateAttestorerror prevents double-attestation - ✅ Weighted threshold prevents single-member bypass
Residual Risk: Low. Requires slice member compromise.
Attack: Attacker transfers SBT to another address, breaking non-transferability.
Vector:
- Call
transferon SBT - Exploit
approve+transfer_frompattern - Bypass owner check
Mitigation:
- ✅
transferfunction always panics withSoulboundNonTransferable - ✅ No
approveortransfer_fromfunctions exist - ✅ SBT can only be minted or burned, never transferred
- ✅ Owner field is immutable except via admin-gated recovery
Residual Risk: None. Transfer is cryptographically impossible.
Attack: Attester signs a revoked credential, making it appear valid.
Vector:
- Revoke credential after attestation
- Attest revoked credential
- Query
is_attestedon revoked credential
Mitigation:
- ✅
is_attestedcheckscredential.revokedflag - ✅ Revoked credentials cannot be attested (checked in
attest) - ✅ Revocation is irreversible
- ✅ Revocation event is emitted for audit trail
Residual Risk: Low. Requires issuer to revoke after attestation (expected behavior).
Attack: Attacker calls revoke_credential twice, potentially triggering state inconsistency.
Vector:
- Call
revoke_credentialon already-revoked credential - Exploit state machine transition
Mitigation:
- ✅
AlreadyRevokederror on double revocation - ✅ Revocation flag is idempotent
- ✅ Revocation event only emitted once
Residual Risk: None. Double revocation is explicitly rejected.
Attack: Attacker creates slice with threshold = 0 or threshold > attestor count.
Vector:
- Call
create_slicewith invalid threshold - Bypass weighted voting requirement
Mitigation:
- ✅
threshold > 0validated increate_slice - ✅
threshold <= attestors.len()validated - ✅ Weighted threshold prevents single-member bypass
- ✅
MAX_ATTESTORS_PER_SLICE = 20prevents unbounded slices
Residual Risk: None. Threshold validation is enforced.
Attack: Attacker supplies malicious contract address for cross-contract calls.
Vector:
- Call
sbt_registry.mintwith fakequorum_proof_id - Substitute
zk_verifieraddress inverify_claim - Invoke attacker-controlled contract
Mitigation:
- ✅ Contract addresses stored in persistent storage (
DataKey::QuorumProofId, etc.) - ✅ Addresses initialized once and never changed
- ✅ Cross-contract calls use stored addresses, not caller input
- ✅
initializeis guarded against double-initialization
Residual Risk: None. Contract addresses are immutable after initialization.
Attack: Attacker calls verify_claim with invalid proof, bypassing ZK verification.
Vector:
- Call
verify_claimwith empty or malformed proof - Exploit stub implementation (accepts any non-empty proof)
- Bypass claim verification
Mitigation:
⚠️ STUB:verify_claimis admin-gated (only admin can call)⚠️ STUB: Accepts any non-empty byte string as valid proof- ✅ No production credential decision relies on
verify_claimoutput (v1.0) - ✅ README and code comments warn of stub status
- 🔄 Planned (v1.1): Real Groth16/PLONK verification
Residual Risk: Medium (stub only). Mitigated by admin gate and documentation.
Attack: Attacker waits for credential TTL to expire, causing data loss.
Vector:
- Exploit missing
extend_ttl()calls - Ledger entry evicted after TTL expires
- Credential becomes inaccessible
Mitigation:
- ✅ Every storage write followed by
extend_ttl() - ✅
STANDARD_TTL = 16,384ledgers (~2 days) - ✅
EXTENDED_TTL = 524,288ledgers (~60 days) for persistent data - ✅ TTL renewal tested in test suite
Residual Risk: Low. Requires missing TTL extension (code review catches this).
Attack: Admin pauses contract indefinitely, blocking credential issuance.
Vector:
- Call
pauseand never callunpause - Permanently disable credential system
- Cause denial of service
Mitigation:
- ✅
unpauseis always available to admin - ✅ No way to permanently brick contract
- ✅ Read-only functions remain accessible while paused
- ✅ Pause event is emitted for monitoring
Residual Risk: Low. Requires admin compromise (detected by monitoring).
BBS+ signatures (contracts/bbs_plus_v1) allow a credential holder to derive a presentation proof that reveals only a chosen subset of credential attributes to a verifier while keeping the rest hidden. This capability significantly reduces data exposure per interaction, but it also introduces a new class of attack vectors that do not exist with conventional (all-or-nothing) signature schemes.
Background: Each call to BbsPresentation::create_presentation re-randomizes the underlying signature commitment, so presentations of the same underlying credential are cryptographically unlinkable to one another. This is a core design goal documented in ADR-007 and explained in the BBS+ Tutorial. The attack vectors below are the situations where that protection can be circumvented or is insufficient on its own.
Attack: A single verifier (or colluding verifiers) accumulates multiple partial-disclosure presentations from the same holder over time. Even though each proof is individually unlinkable at the cryptographic layer, the semantic content of disclosed attributes can be correlated to reconstruct the full credential or to re-identify the holder.
Example: A holder discloses institution to Verifier A, graduation_year to Verifier B, and license_number to Verifier C. If A, B, and C share their logs, the union of three partial disclosures may uniquely identify the holder and reconstruct most of the original credential.
Preconditions: Verifier(s) log the disclosed attribute values alongside any external session identifier (IP address, wallet address, request timestamp).
Impact: Erosion of selective-disclosure privacy guarantees; credential-holder re-identification without ever receiving the full credential.
Mitigations:
- ✅ Cryptographic unlinkability per presentation —
bbs_plus_v1re-randomizes the signature commitment, so proof bytes themselves carry no cross-verifier correlation signal (seecontracts/bbs_plus_v1/src/presentation.rs) - ✅ Holders are advised in BBS+ Tutorial §5 that metadata outside the proof (IP, wallet address, timing) is a separate correlation channel
⚠️ Gap: No protocol-level binding prevents a verifier from logging and sharing the cleartext disclosed values — this is a verifier policy gap, not a cryptographic one- 🔄 Recommended (not yet implemented): Verifier data-minimization policy: verifiers SHOULD be contractually required (via issuer terms of service) to discard disclosed attributes after verification completes and to not share them with third parties; document this requirement in the verifier onboarding checklist (see
docs/issuer-security-checklist.md) - 🔄 Recommended (not yet implemented): Per-verifier nonce binding — require verifiers to supply a fresh nonce in each presentation request; the holder embeds it in the proof so the same presentation cannot be replayed to a different verifier even if intercepted
Residual Risk: Medium. Cryptographic layer is sound; risk lives entirely in verifier behavior and metadata channels outside the contract's control.
Attack: An adversary intercepts or obtains a valid PresentationProof (e.g. from a network transcript, a compromised verifier's database, or a holder who inadvertently shares proof bytes) and replays it to a different verifier or in a different context to impersonate the credential holder.
Preconditions: The proof was captured after it was generated but before or during transmission to the intended verifier. No verifier-specific binding was embedded in the proof.
Impact: Impersonation — a third party uses a stolen proof to pass as the credential holder for a one-time verification check.
Mitigations:
- ✅ Presentations derived by
contracts/bbs_plus_v1are valid zero-knowledge proofs of issuer signature knowledge; they do not by themselves bind the prover to a specific verifier session — this is a known gap in the base BBS+ IETF draft that is expected to be addressed via a verifier-supplied presentation nonce ⚠️ Gap: not yet implemented — the currentverify_presentationentry point does not enforce that a verifier-supplied nonce was embedded in the proof at derivation time; a captured proof is replayable to any verifier that accepts presentations for the same credential type- 🔄 Recommended (not yet implemented): Mandatory verifier nonce flow: the verifier generates a single-use challenge nonce, sends it to the holder, and the holder calls
create_presentationwith the nonce bound into the Fiat-Shamir transcript (viacontracts/bbs_plus_v1/src/transcript.rs);verify_presentationthen checks the nonce matches — this is tracked as a follow-up implementation item forcontracts/bbs_plus_v1 - 🔄 Recommended (not yet implemented): Short-lived proof TTL — presentations should carry an expiry field so that a captured proof becomes useless after a configurable window (e.g. 5 minutes); this requires a coordinated clock between holder and verifier
Residual Risk: Medium (gap not yet implemented). Until nonce binding is enforced in bbs_plus_v1, captured presentations are replayable. Operators should deploy the contract in contexts where transport-layer security (TLS) reduces interception risk.
Attack: Multiple independent verifiers, each receiving a different partial disclosure of the same credential over time, share their received attribute values to jointly reconstruct the full attribute set — effectively undoing the holder's selective disclosure choices.
Preconditions: Two or more verifiers that have each received at least one presentation from the same holder, and who collude (or whose databases are compromised together) to pool disclosed values.
Impact: Full credential reconstruction without the holder's consent; severe privacy violation equivalent to receiving the full credential upfront.
Mitigations:
- ✅ Cryptographic proof bytes are unlinkable across presentations — verifiers cannot prove to each other that two separate presentations came from the same underlying credential without the holder's cooperation (see
contracts/bbs_plus_v1/src/presentation.rsre-randomization) - ✅ The BBS+ Tutorial §3.3 notes that cross-credential linking is not automatic and requires an explicit holder-committed pseudonym
⚠️ Gap: If a holder discloses the same attribute (e.g.institution= "Acme Institute") to multiple verifiers, the plaintext value itself is the correlation signal — no cryptographic property prevents semantic join⚠️ Gap: If a holder uses the same wallet address (Stellar account) across all verifier interactions, that address becomes an out-of-band linking identifier irrespective of proof unlinkability- 🔄 Recommended (not yet implemented): Holder pseudonym / pairwise DID pattern — use a different Stellar account or a cryptographic pseudonym per verifier relationship so the on-chain address does not serve as a correlation handle; document this pattern in
docs/privacy-guide.md - 🔄 Recommended (not yet implemented): Minimum-disclosure policy enforcement at the application layer — the holder's wallet/SDK should warn when a verifier requests more attributes than the stated purpose requires; track in SDK roadmap
Residual Risk: Medium. Cryptographic unlinkability holds at the proof level; correlation via attribute semantics and wallet identity is a deployment and policy concern outside the scope of bbs_plus_v1 itself.
Attack: A holder presents a BBS+ proof and a verifier checks the revocation accumulator (contracts/bbs_plus_v1/src/accumulator.rs) using a credential-specific identifier. If the revocation check is done naively (e.g. by querying a public list keyed by credential ID), the verifier — or an observer of the on-chain query — can link the holder's presentation to a specific credential, breaking the unlinkability guarantee that BBS+ provides for the proof itself.
Preconditions: Verifier or on-chain observer correlates the revocation lookup with the presentation proof submission.
Impact: Re-identification of the credential holder via the revocation channel, even when the BBS+ proof is cryptographically unlinkable.
Mitigations:
- ✅
contracts/bbs_plus_v1/src/accumulator.rsimplements a cryptographic accumulator supporting non-membership proofs — the holder can prove their credential is not revoked without revealing which specific credential is being checked (see ADR-007 — Implementation Notes) - ✅ The BBS+ Tutorial §5 explicitly documents revocation-check linkability as a known risk and points to the accumulator as the mitigation
⚠️ Gap: Integration of the accumulator non-membership proof into the end-to-endverify_presentationflow is listed as Phase 1 work incontracts/bbs_plus_v1/README.md; until that integration is complete, verifiers that use the accumulator directly may inadvertently reintroduce linkability- 🔄 Recommended (not yet implemented): Gate
verify_presentationon an accumulator non-membership proof being included in thePresentationProofstructure, so that revocation checking and presentation verification are bundled into a single unlinkable on-chain call
Residual Risk: Medium (gap not yet fully integrated). Operators should not deploy separate revocation lookups by credential ID until the accumulator integration is complete.
┌─────────────────────────────────────────────────────────────┐
│ Dispute Initiated │
│ - Credential holder challenges attestation │
│ - Provides evidence (metadata, timestamps) │
│ - Dispute enters PENDING state │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ RESOLVED_VALID │ │ RESOLVED_INVALID │
│ (Attestation OK)│ │ (Attestation Bad)│
└──────────────────┘ └──────────────────┘
│ │
▼ ▼
Attestation Attestation
Remains Valid Revoked
Attack: Attacker files frivolous disputes to harass credential holders.
Vector:
- File dispute for valid credential
- Provide fake or insufficient evidence
- Waste slice member time reviewing disputes
Mitigation:
- ✅ Dispute filing requires
require_auth()from the credential holder — third parties cannot file on their behalf - ✅ Dispute evidence is required at filing time and stored immutably on-chain (cannot be added retroactively)
- ✅ Evidence must include: credential ID, dispute reason, supporting metadata hash, and timestamp
- ✅ Slice members can reject disputes with insufficient evidence before voting begins
- ✅ Dispute history is permanently auditable — repeated frivolous filers are identifiable
- ✅ Dispute filing is rate-limited per credential (one active dispute at a time)
Residual Risk: Low. Requires credential holder compromise; evidence requirements deter frivolous filings.
Attack: Admin and slice members collude to invalidate valid attestations.
Vector:
- Admin marks valid dispute as RESOLVED_INVALID
- Slice members vote to revoke valid attestation
- Credential holder loses qualification
Mitigation:
- ✅ Dispute resolution requires multi-sig approval (threshold-based voting)
- ✅ Dispute evidence is public and auditable on-chain
- ✅ Revocation event is emitted (can be monitored by any party)
- ✅ Credential holder can appeal via new attestation from a different slice
- ✅ Operator requirement: Deploy admin as a multisig account (2-of-3 or 3-of-5 Stellar multisig) — see Section 4.3 recommendations
- 🔄 Planned (v2.0): On-chain multi-sig admin enforcement via contract logic
Residual Risk: Medium. Operators must configure Stellar account-level multisig for admin keys (see Section 4.3).
Attack: Attacker delays dispute resolution indefinitely.
Vector:
- File dispute and never resolve it
- Credential holder left in limbo
- Slice members cannot attest new credentials
Mitigation:
- ✅ Dispute has TTL (expires after 30 days)
- ✅ Expired disputes auto-resolve as RESOLVED_VALID
- ✅ Slice members can force resolution
- ✅ Dispute timeout event is emitted
Residual Risk: Low. Timeout is enforced by contract.
Attack: Attacker modifies dispute evidence after filing.
Vector:
- File dispute with evidence
- Modify evidence on-chain
- Slice members see different evidence
Mitigation:
- ✅ Dispute evidence is immutable (stored as hash)
- ✅ Evidence hash is verified before dispute resolution
- ✅ Tampering causes
InvalidEvidenceerror - ✅ Evidence is stored off-chain (IPFS) with hash verification
Residual Risk: None. Evidence is cryptographically protected.
Attack: Attacker bribes slice member to vote for invalid dispute resolution.
Vector:
- Offer payment to slice member
- Slice member votes to revoke valid attestation
- Attacker gains unfair advantage
Mitigation:
- ✅ Voting is on-chain and auditable
- ✅ Bribery is detectable (pattern analysis)
- ✅ Slice members can be removed by issuer
- ✅ Reputation system tracks voting history (planned v2.0)
Residual Risk: Medium. Requires social engineering (off-chain).
- Multi-Sig Admin (Required): Configure the admin Stellar account as a multisig with at least 2-of-3 signers before deploying to mainnet. Use
stellar account set-options --master-weight 0 --med-threshold 2 --high-threshold 2 --signer <key2>,1 --signer <key3>,1. This prevents single-key compromise from resolving disputes unilaterally. - Dispute Evidence Requirements: Enforce that disputes include a metadata hash pointing to off-chain evidence (IPFS or equivalent). Reject disputes with empty or placeholder evidence hashes at the application layer.
- Monitoring: Alert on unusual dispute patterns — high volume from a single address, rapid resolution (< 1 hour), or disputes filed and resolved by overlapping slice members.
- Audit Trail: Log all dispute decisions with timestamps, voter identities, and evidence hashes. Retain logs for at least 2 years.
- Appeal Process: Allow credential holders to re-attest via a different quorum slice after a dispute resolves against them. Document this process for credential holders.
- Reputation Tracking: Monitor slice member voting patterns. Flag members who consistently vote with the majority on disputed cases for manual review (planned v2.0 on-chain reputation system).
- Evidence Review: Always review dispute evidence before voting
- Conflict of Interest: Recuse yourself from disputes involving your institution
- Documentation: Document your reasoning for each dispute vote
- Escalation: Escalate suspicious disputes to issuer for investigation
- Dispute Monitoring: Monitor your credentials for disputes
- Evidence Preservation: Keep records of your qualifications
- Appeal Rights: Know your right to appeal dispute decisions
- Transparency: Request audit trail of dispute decisions
Feature: Quorum slices now support weighted voting where each attestor has a configurable weight, and a threshold must be met by the sum of weights.
New Attack Vectors:
Attack: Attacker creates slice with unbalanced weights to bypass threshold.
Vector:
- Create slice with 10 attestors, each weight=1, threshold=1
- Single attestor can attest credential
- Defeats purpose of multi-party consensus
Mitigation:
- ✅ Threshold validation:
threshold <= sum(weights)enforced - ✅ Threshold must be > 0 (prevents zero-threshold slices)
- ✅
MAX_ATTESTORS_PER_SLICE = 20prevents unbounded slices - ✅ Slice creator is responsible for threshold design
- ✅ Slice configuration is immutable after creation
Residual Risk: Low. Requires slice creator to intentionally misconfigure (detectable via audit).
Recommendation: Document best practices for threshold selection (e.g., require 2-of-3 or 3-of-5 minimum).
Attack: Attacker supplies weights that overflow u32, causing incorrect threshold calculation.
Vector:
- Create slice with weights summing to > u32::MAX
- Threshold calculation wraps around
- Threshold becomes achievable with fewer attestors
Mitigation:
- ✅ Weights are u32 (max 4,294,967,295 per attestor)
- ✅ Sum of weights checked against threshold (u32)
- ✅ Rust type system prevents overflow in safe code
- ✅ Test suite includes overflow edge cases
Residual Risk: None. Type system prevents overflow.
Feature: Credentials now store metadata as immutable hashes (IPFS CID or SHA-256).
New Attack Vectors:
Attack: Attacker finds two different metadata documents with the same hash.
Vector:
- Issue credential with metadata hash H
- Attacker finds different metadata with hash H
- Credential appears to have different content
Mitigation:
- ✅ SHA-256 used (collision resistance: 2^128 operations)
- ✅ IPFS CID uses SHA-256 by default
- ✅ Collision probability negligible for practical purposes
- ✅ Metadata stored off-chain (IPFS) — on-chain only stores hash
Residual Risk: Negligible (cryptographic guarantee).
Attack: Attacker deletes metadata from IPFS, making credential unverifiable.
Vector:
- Issue credential with IPFS CID
- Attacker removes file from IPFS
- Credential hash exists but metadata is gone
Mitigation:
- ✅ Metadata stored on IPFS (distributed, replicated)
- ✅ Issuer responsible for pinning metadata
- ✅ Credential holder can re-pin metadata
- ✅ Hash verification ensures integrity (cannot be replaced with different content)
- 🔄 Planned (v2.0): Mandatory metadata pinning service
Residual Risk: Medium. Requires IPFS infrastructure maintenance.
Recommendation: Operators should run IPFS pinning service or use Pinata/Filecoin for redundancy.
Feature: Credentials can now be disputed by holders, with evidence-based resolution.
New Attack Vectors:
Attack: Attacker files many disputes to overwhelm slice members.
Vector:
- File 100 disputes in rapid succession
- Slice members cannot review all evidence
- Valid disputes get lost in noise
Mitigation:
- ✅ Dispute filing requires
require_auth()from credential holder - ✅ One active dispute per credential at a time
- ✅ Dispute TTL (30 days) prevents indefinite accumulation
- ✅ Dispute evidence is required at filing time
- ✅ Slice members can reject disputes with insufficient evidence
Residual Risk: Low. Rate limiting and evidence requirements deter spam.
Recommendation: Implement application-level rate limiting (e.g., max 5 disputes per holder per month).
Attack: Slice members collude to invalidate valid credentials.
Vector:
- Multiple slice members vote to resolve dispute as INVALID
- Valid credential is revoked
- Credential holder has no recourse
Mitigation:
- ✅ Dispute resolution requires threshold-based voting
- ✅ Dispute evidence is public and auditable
- ✅ Revocation event is emitted (can be monitored)
- ✅ Credential holder can appeal via new slice
- ✅ Voting history is on-chain (detectable pattern)
⚠️ Partial: Multi-sig admin enforcement planned for v2.0
Residual Risk: Medium. Requires collusion of multiple slice members.
Recommendation: Implement monitoring for unusual voting patterns (e.g., same members voting together repeatedly).
Attack: Attacker submits forged evidence to support dispute.
Vector:
- File dispute with fake evidence hash
- Slice members cannot verify authenticity
- Dispute resolution based on false evidence
Mitigation:
- ✅ Evidence stored off-chain (IPFS) with hash verification
- ✅ Hash mismatch causes
InvalidEvidenceerror - ✅ Evidence is immutable (cannot be changed after filing)
- ✅ Slice members responsible for evidence verification
- ✅ Audit trail shows evidence hash and resolution
Residual Risk: Low. Cryptographic hashing prevents tampering; social engineering remains possible.
Recommendation: Require slice members to document evidence review process.
Feature: Credential holders can now mint Soulbound Tokens (SBTs) representing credentials.
New Attack Vectors:
Attack: Attacker mints SBT for credential that hasn't been attested.
Vector:
- Issue credential
- Mint SBT before attestation threshold is met
- SBT appears to represent verified credential
Mitigation:
- ✅
mintchecks that credential exists - ✅
mintchecks that credential is not revoked - ✅ Application layer should verify attestation before minting (recommended)
- ✅ SBT metadata can indicate attestation status
Residual Risk: Low. Application layer should enforce attestation requirement.
Recommendation: Document that SBT minting does not imply attestation; verifiers should check is_attested() separately.
Attack: Attacker mints SBT with misleading metadata.
Vector:
- Mint SBT with metadata claiming higher qualification than credential
- Verifier trusts SBT metadata without checking credential
- Credential holder appears overqualified
Mitigation:
- ✅ SBT metadata is immutable after minting
- ✅ SBT metadata hash is stored on-chain
- ✅ Verifiers should check both SBT and credential metadata
- ✅ Metadata mismatch is detectable
Residual Risk: Low. Requires verifier to trust SBT metadata without verification.
Recommendation: Verifiers should always check underlying credential, not just SBT metadata.
Attack: Attacker burns SBT and re-mints with different metadata.
Vector:
- Mint SBT with accurate metadata
- Burn SBT
- Re-mint SBT with modified metadata
- Credential appears to have changed
Mitigation:
- ✅ Burn is irreversible (SBT destroyed)
- ✅ Re-minting creates new token ID
- ✅ Token ID history is on-chain (can be audited)
- ✅ Credential metadata is immutable (cannot be changed)
Residual Risk: Low. Credential metadata is immutable; SBT metadata changes are auditable.
| Component | Key Type | Storage | Rotation | Backup |
|---|---|---|---|---|
| Admin | Stellar Account | Hardware Wallet | Quarterly | Secure Vault |
| Issuer | Stellar Account | Hardware Wallet | Quarterly | Secure Vault |
| Slice Member | Stellar Account | Hardware Wallet | Quarterly | Secure Vault |
| ZK Prover | Private Key | Secure Enclave | Annually | Encrypted |
Recommendations:
- Use hardware wallets for all admin/issuer keys
- Implement key rotation schedule
- Maintain encrypted backups in geographically distributed locations
- Never store keys in version control or logs
| Event | Severity | Action |
|---|---|---|
Unauthorized issue_credential attempt |
Critical | Immediate investigation |
| Double revocation attempt | High | Review contract logs |
| Unusual dispute volume | High | Investigate slice members |
| TTL expiry (data loss) | Critical | Immediate remediation |
| Cross-contract call failure | High | Review contract state |
| Pause event | Medium | Verify admin action |
- Detection: Monitor contract events and logs
- Containment: Pause contract if necessary
- Investigation: Review transaction history and evidence
- Remediation: Fix vulnerability and redeploy
- Communication: Notify affected parties
- Post-Mortem: Document lessons learned
- GDPR: Credential data may contain PII — ensure compliance with data retention policies
- FERPA: Educational credentials are protected — verify institutional policies
- Professional Licensing: Verify compliance with national licensing board requirements
- Cross-Border: Ensure compliance with international credential recognition agreements
- Issuer Authority: Each issuer controls their own credentials
- Slice Autonomy: Slice members vote independently
- Dispute Resolution: Multi-party consensus required
- Emergency Powers: Admin can pause contract (limited scope)
| Risk | Likelihood | Impact | Mitigation | Status |
|---|---|---|---|---|
| Credential Forgery | Low | Critical | Auth checks, issuer verification | ✅ Mitigated |
| Unauthorized Attestation | Low | High | Slice membership, time windows | ✅ Mitigated |
| SBT Transfer | None | Critical | Non-transferable by design | ✅ Mitigated |
| Revoked Credential Attestation | Low | High | Revocation checks | ✅ Mitigated |
| Double Revocation | None | Low | Idempotent revocation | ✅ Mitigated |
| Slice Threshold Bypass | None | High | Threshold validation | ✅ Mitigated |
| Cross-Contract Substitution | None | Critical | Immutable addresses | ✅ Mitigated |
| ZK Verification Bypass | Medium | High | Admin gate, stub warning | |
| TTL Expiry & Data Loss | Low | High | TTL extension, monitoring | ✅ Mitigated |
| Pause/Unpause Abuse | Low | High | Unpause always available | ✅ Mitigated |
| False Dispute Filing | Low | Medium | Auth requirement, audit trail | ✅ Mitigated |
| Admin Collusion | Medium | Critical | Multi-sig (planned v2.0) | |
| Dispute Timeout Abuse | Low | Medium | TTL enforcement | ✅ Mitigated |
| Evidence Tampering | None | High | Cryptographic hashing | ✅ Mitigated |
| Slice Member Bribery | Medium | High | Monitoring, reputation (planned) | |
| Weight Manipulation | Low | High | Threshold validation, immutable config | ✅ Mitigated |
| Hash Collision | Negligible | High | SHA-256 cryptographic guarantee | ✅ Mitigated |
| Metadata Availability Loss | Medium | Medium | IPFS replication, pinning service | |
| Dispute Spam | Low | Medium | Rate limiting, evidence requirements | ✅ Mitigated |
| Dispute Resolution Collusion | Medium | Critical | Voting threshold, audit trail | |
| Evidence Forgery | Low | High | Cryptographic hashing, audit trail | ✅ Mitigated |
| SBT Minting Without Attestation | Low | Medium | Application-layer verification | |
| SBT Metadata Manipulation | Low | Medium | Immutable metadata, verifier diligence | |
| SBT Burning & Re-minting | Low | Low | Immutable credential, auditable history | ✅ Mitigated |
| BBS+ Reveal Correlation | Medium | Medium | Cryptographic unlinkability (proof layer); verifier policy gap remains | |
| BBS+ Proof Replay | Medium | High | Nonce binding not yet enforced in bbs_plus_v1 |
|
| BBS+ Verifier Collusion | Medium | Medium | Proof-level unlinkability; wallet-address correlation gap | |
| BBS+ Revocation Linkability | Medium | Medium | Accumulator non-membership proof exists; end-to-end integration pending |
- Real Groth16/PLONK proof verification
- Claim-specific privacy (selective disclosure)
- Proof generation framework
- Mandatory metadata pinning service
- Metadata availability monitoring
- BBS+ verifier nonce binding (closes replay-of-proof gap — §3.11.2)
- BBS+ accumulator non-membership proof integrated into
verify_presentation(closes revocation linkability gap — §3.11.4)
- Multi-sig admin requirement (2-of-3)
- Reputation system for slice members
- Appeal process for disputed credentials
- Automated evidence verification
- Dispute voting audit trail
- Collusion detection algorithms
- DAO-based dispute resolution
- Credential expiry and renewal
- Institutional rating system
- Revocation registry
- Cross-chain credential bridging
Feature: Cross-contract operations use a 3-phase saga pattern (quorum_proof → sbt_registry → zk_verifier) with automatic rollback on failure.
New Attack Vectors:
Attack: A saga fails partway through (e.g., Phase 2 succeeds but Phase 3 fails) and manual rollback is not triggered, leaving the system in an inconsistent state.
Vector:
- Phase 1 (quorum_proof): Credential issued ✓
- Phase 2 (sbt_registry): SBT minted ✓
- Phase 3 (zk_verifier): Proof verification fails ✗
- Rollback event listener crashes before triggering
initiate_rollback() - SBT exists but was never verified; credential state is changed but SBT was not finalized
Mitigation:
- ✅ Automatic rollback triggered immediately on Phase failure
- ✅ Manual
initiate_rollback()available to operators as a safety net - ✅ Stuck transactions are detected by monitoring
RollingBackstate - ✅
complete_rollback()retries failed reverts if the network recovers - ✅ Transaction log is immutable; all state changes are auditable
Residual Risk: Low. Auto-rollback + manual recovery path prevents indefinite partial commits.
Recommendation: Monitor for transactions in RollingBack state longer than 24 hours; trigger manual recovery.
Attack: A transaction is rolled back twice, causing state to oscillate or corrupt.
Vector:
- Phase 3 fails, triggering auto-rollback (Phase 2 and Phase 1 are reverted)
- Operator manually calls
initiate_rollback()again without checking transaction state - Revert operations run twice, potentially causing double-deduction or data inconsistency
Mitigation:
- ✅
initiate_rollback()checks transaction phase: rejects if alreadyCommittedorRolledBack - ✅ Rollback-idempotent operations: reverting an already-reverted state is a no-op
- ✅ Transaction state machine enforces phase transitions (cannot go from
RolledBack→RollingBack)
Residual Risk: Low. State machine and phase checks prevent double-rollback corruption.
Recommendation: Test double-rollback scenario in integration tests to confirm rejection.
Attack: Attacker triggers a credential revocation at the exact moment rollback is in progress, creating a race condition.
Vector:
- Phase 3 fails;
initiate_rollback()is called - Simultaneous: Attacker calls
revoke_credential()on the same credential in Phase 1 revert - Revocation and rollback try to mutate the same credential state concurrently
- One operation wins; the other's state is lost
Mitigation:
- ✅ Soroban storage layer uses atomic transactions: only one write to a key succeeds
- ✅ Revocation checks transaction phase: if rollback is in progress, revocation is rejected
- ✅ Lock-free design uses event-driven coordination: operations are serialized by Soroban's ledger
Residual Risk: Low. Soroban's atomic ledger prevents concurrent writes to the same key.
Recommendation: Test concurrent saga + revocation scenarios to confirm atomic behavior.
Feature: Each contract (quorum_proof, sbt_registry, zk_verifier) has an independent admin key that controls pause/unpause and key rotation.
Current Topology:
- ✅ quorum_proof: Admin controls pause, key rotation, slice configuration
- ✅ sbt_registry: Admin controls pause, metadata updates
- ✅ zk_verifier: Admin controls pause, verifying key registration
Attack Vector: Admin Key Compromise:
If a single admin key is compromised, an attacker can:
- Pause a single contract indefinitely (but other contracts remain operational)
- Rotate verifying keys (but historical proofs still verify against old keys)
- Revoke credentials or attestations
Mitigation:
- ✅ Each contract can be paused independently: other contracts can continue operating
- ✅ Admin key rotation is supported on each contract: no key is permanent
- ✅ Read-only functions remain accessible when paused: verification still works
⚠️ Partial: Multi-sig admin enforcement planned for v2.0 to require multiple keys for sensitive operations
Residual Risk: Medium (single point of failure per contract).
Recommendation:
- Use different admin keys for each contract (do not reuse the same key).
- Consider a 2-of-3 multi-sig admin setup for production: each contract requires approval from 2 of 3 administrators.
- Implement key rotation annually or upon any staff changes.
- No circuit-breaker degraded mode on sbt_registry/zk_verifier: Pause is binary (on/off). Rate limiting under load is not yet supported. Planned for v2.1.
- Revocation cache staleness: Credential revocation cache has a 1000-ledger TTL; in rare network partitions, a revoked credential might be accepted as valid for up to ~1 hour. Acceptable for most use cases; critical applications should query the authoritative source.
- Saga rollback auto-recovery: If rollback fails (network partition), manual operator intervention is required. Planned for v2.2: automatic retry with exponential backoff.
- Stellar Whitepaper
- Soroban Documentation
- OWASP Smart Contract Security
- Threat Modeling Guide
- Atomic Saga Rollback Specification
- Attestor Key-Custody Guide
- Disaster Recovery Procedures
| Role | Name | Date | Signature |
|---|---|---|---|
| Security Lead | |||
| Contract Author | |||
| Compliance Officer |
Last Reviewed: August 29, 2026 Next Review: February 29, 2027 (6-month cycle)