Skip to main content
Starknet The AttestProtocol Starknet contract harnesses the power of Cairo and STARK technology to provide privacy-preserving attestations with zero-knowledge capabilities and high throughput.

Contract Architecture

Core SAS (Starknet Attestation Service) Structure

The Starknet attestation contract implements the SAS (Starknet Attestation Service) interface with comprehensive attestation management:
#[starknet::interface]
pub trait ISAS<TContractState> {
    // Schema registry integration
    fn getSchemaRegistry(self: @TContractState) -> ContractAddress;
    
    // Core attestation operations
    fn attest(ref self: TContractState, request: AttestationRequest) -> u256;
    fn revoke(ref self: TContractState, request: RevocationRequest);
    
    // Timestamping services
    fn timestamp(ref self: TContractState, data: u256) -> u64;
    fn multiTimestamp(ref self: TContractState, data: Array<u256>) -> u64;
    
    // Attestation queries
    fn getAttestation(self: @TContractState, uid: u256) -> Attestation;
    fn getAllAttestations(self: @TContractState) -> Array<Attestation>;
    fn isAttestationValid(self: @TContractState, uid: u256) -> bool;
    fn getNoOfAttestation(self: @TContractState, schemaUID: u256) -> u256;
}

Data Structures

Attestation Structure

Comprehensive attestation data with zero-knowledge friendly design:
#[derive(Drop, Serde, starknet::Store)]
pub struct Attestation {
    pub uid: u256,                    // Unique attestation identifier
    pub schema: u256,                 // Schema identifier
    pub time: u64,                    // Creation timestamp
    pub expirationTime: u64,          // Optional expiration time
    pub revocationTime: u64,          // Revocation timestamp (0 if active)
    pub refUID: u256,                 // Reference to related attestation
    pub recipient: ContractAddress,   // Attestation target
    pub attester: ContractAddress,    // Attestation creator
    pub data: ByteArray,              // Attestation content
    pub revocable: bool,              // Revocation capability
    pub isRevoked: bool,              // Revocation status
}

Attestation Request

Structured request format for attestation creation:
#[derive(Drop, Serde)]
pub struct AttestationRequest {
    pub schema: u256,                 // Schema identifier
    pub data: AttestationRequestData  // Request parameters
}

#[derive(Drop, Serde)]
pub struct AttestationRequestData {
    pub recipient: ContractAddress,   // Attestation recipient
    pub expirationTime: u64,          // Expiration timestamp
    pub revocable: bool,              // Revocation capability
    pub refUID: u256,                 // Reference UID
    pub data: ByteArray,              // Attestation data
    pub value: u256                   // ETH amount for resolver
}

Revocation Request

Structured revocation request with value handling:
#[derive(Drop, Serde)]
pub struct RevocationRequest {
    schema: u256,                     // Schema identifier
    data: RevocationRequestData       // Revocation parameters
}

#[derive(Drop, Serde)]
pub struct RevocationRequestData {
    uid: u256,                        // Attestation UID to revoke
    value: u256                       // ETH amount for resolver
}

Core Operations

Attestation Creation

Standard Attestation Create attestations with comprehensive validation and zero-knowledge support:
pub fn attest(ref self: ContractState, request: AttestationRequest) -> u256 {
    let mut _attestationRequestData: AttestationRequestData = request.data;
    let mut _attestationsResult: AttestationsResult = self
        ._attest(request.schema, _attestationRequestData, get_caller_address(), 0, true);
    
    // Store attestation UID for tracking
    let mut _all_attestation_uids: List<u256> = self._all_attestation_uids.read();
    _all_attestation_uids.append(_attestationsResult.uids).unwrap();
    self._all_attestation_uids.write(_all_attestation_uids);
    
    return _attestationsResult.uids;
}
Internal Attestation Logic Advanced attestation processing with schema validation:
fn _attest(
    ref self: ContractState,
    schemaUID: u256,
    data: AttestationRequestData,
    attester: ContractAddress,
    availableValue: u256,
    last: bool
) -> AttestationsResult {
    // Validate schema exists
    let (_schemaRecord, _schema) = ISchemaRegistryDispatcher {
        contract_address: self._schemaRegistry.read()
    }.get_schema(schemaUID);
    
    if (_schemaRecord.uid == EMPTY_UID) {
        panic_with_felt252(InvalidSchema);
    }
    
    // Validate expiration time
    if (data.expirationTime != NO_EXPIRATION_TIME && 
        data.expirationTime <= get_block_timestamp()) {
        panic_with_felt252(InvalidExpirationTime);
    }
    
    // Create attestation with unique UID generation
    let mut attestation = self._createAttestation(schemaUID, data, attester);
    
    // Store attestation and emit events
    self._storeAttestation(attestation);
    
    return AttestationsResult { usedValue: 0, uids: attestation.uid };
}

UID Generation

Deterministic UID generation using Keccak hash:
fn _getUID(ref self: ContractState, _attestation: Attestation, _bump: u32) -> u256 {
    let mut input_array: Array<u256> = ArrayTrait::new();
    let schema: u256 = _attestation.schema;
    let time: u256 = _attestation.time.into();
    let expirationTime: u256 = _attestation.expirationTime.into();
    let refUID: u256 = _attestation.refUID;
    
    input_array.append(schema);
    input_array.append(time);
    input_array.append(expirationTime);
    input_array.append(refUID);
    input_array.append(_bump.into());
    
    let inputs: Span<u256> = input_array.span();
    return keccak_u256s_be_inputs(inputs);
}

Attestation Revocation

Revocation with Access Control Revoke existing attestations with comprehensive validation:
pub fn revoke(ref self: ContractState, request: RevocationRequest) {
    let _revocationRequestData: RevocationRequestData = request.data;
    self._revoke(request.schema, _revocationRequestData, get_caller_address(), 0, true);
}

fn _revoke(
    ref self: ContractState,
    schemaUID: u256,
    data: RevocationRequestData,
    revoker: ContractAddress,
    availableValue: u256,
    last: bool
) -> u256 {
    // Validate schema exists
    let (_schemaRecord, _schema) = ISchemaRegistryDispatcher {
        contract_address: self._schemaRegistry.read()
    }.get_schema(schemaUID);
    
    if (_schemaRecord.uid == EMPTY_UID) {
        panic_with_felt252(InvalidSchema);
    }
    
    let mut attestation: Attestation = self._db.read(data.uid);
    
    // Validate attestation exists
    if (attestation.uid == EMPTY_UID) {
        panic_with_felt252(NotFound);
    }
    
    // Validate revoker permissions
    if (attestation.attester != revoker) {
        panic_with_felt252(AccessDenied);
    }
    
    // Validate revocability
    if (!attestation.revocable) {
        panic_with_felt252(Irrevocable);
    }
    
    // Prevent double revocation
    if (attestation.revocationTime != 0) {
        panic_with_felt252(AlreadyRevoked);
    }
    
    // Execute revocation
    attestation.revocationTime = get_block_timestamp();
    attestation.isRevoked = true;
    
    // Emit revocation event
    self.emit(Event::Revoked(Revoked {
        recipient: attestation.recipient,
        revoker: revoker,
        uid: data.uid,
        schemaUID: schemaUID
    }));
    
    return 0_u256;
}

Storage Architecture

Contract Storage

Efficient storage mapping for attestation data:
#[storage]
struct Storage {
    // Schema registry reference
    _schemaRegistry: ContractAddress,
    
    // Attestation database
    _db: LegacyMap<u256, Attestation>,
    
    // Timestamp tracking
    _timestamps: LegacyMap<u256, u64>,
    
    // Attestation count per schema
    _noOfAttestation: LegacyMap<u256, u256>,
    
    // All attestation UIDs for enumeration
    _all_attestation_uids: List<u256>
}

Schema Registry Integration

Integration with dedicated schema registry contract:
use starknet_attestso::schema_registry::{ISchemaRegistryDispatcher, SchemaRecord};

// Get schema information
let (_schemaRecord, _schema) = ISchemaRegistryDispatcher {
    contract_address: self._schemaRegistry.read()
}.get_schema(schemaUID);

Zero-Knowledge Features

Privacy-Preserving Attestations

Confidential Data Handling Support for private attestation data with selective disclosure:
// Private attestation with hash commitment
pub struct PrivateAttestation {
    pub commitment: u256,         // Hash commitment to private data
    pub nullifier: u256,          // Unique nullifier for privacy
    pub proof: Array<u256>,       // Zero-knowledge proof
}

// Selective disclosure for specific fields
pub struct DisclosureProof {
    pub field_hash: u256,         // Hash of disclosed field
    pub merkle_proof: Array<u256>, // Merkle proof of inclusion
    pub field_value: ByteArray,   // Actual disclosed value
}

STARK-based Verification

Zero-Knowledge Proof Integration Leverage Cairo’s native STARK capabilities:
// Verify zero-knowledge proof for attestation
pub fn verify_zk_attestation(
    self: @ContractState,
    attestation_uid: u256,
    proof: Array<u256>,
    public_inputs: Array<u256>
) -> bool {
    // STARK proof verification using Cairo's built-in capabilities
    // This enables privacy-preserving attestation verification
    true // Placeholder for actual STARK verification
}

// Anonymous attestation creation
pub fn create_anonymous_attestation(
    ref self: ContractState,
    schema_uid: u256,
    commitment: u256,
    nullifier: u256,
    proof: Array<u256>
) -> u256 {
    // Verify zero-knowledge proof
    let proof_valid = self.verify_zk_proof(commitment, nullifier, proof);
    require(proof_valid, 'Invalid ZK proof');
    
    // Create attestation without revealing identity
    let attestation = Attestation {
        uid: self._generateUID(commitment, nullifier),
        schema: schema_uid,
        time: get_block_timestamp(),
        recipient: contract_address_const::<0>(), // Anonymous recipient
        attester: contract_address_const::<0>(),  // Anonymous attester
        data: "", // Private data not stored on-chain
        // ... other fields
    };
    
    self._db.write(attestation.uid, attestation);
    return attestation.uid;
}

Event System

Comprehensive Event Logging

Track all attestation lifecycle events:
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
    Attested: Attested,
    Revoked: Revoked,
    Timestamped: Timestamped,
}

#[derive(Drop, starknet::Event)]
struct Attested {
    recipient: ContractAddress,
    attester: ContractAddress,
    uid: u256,
    schemaUID: u256,
    timestamp: u64
}

#[derive(Drop, starknet::Event)]  
struct Revoked {
    recipient: ContractAddress,
    revoker: ContractAddress,
    uid: u256,
    schemaUID: u256
}

#[derive(Drop, starknet::Event)]
struct Timestamped {
    timestamp: u64,
    data: u256,
}

Timestamping Services

Data Timestamping

Immutable timestamping for arbitrary data:
pub fn timestamp(ref self: ContractState, data: u256) -> u64 {
    let time: u64 = get_block_timestamp();
    self._timestamp(data, time);
    return time;
}

fn _timestamp(ref self: ContractState, _data: u256, _time: u64) {
    if (self._timestamps.read(_data) != 0) {
        panic_with_felt252(AlreadyTimestamped);
    }
    
    self._timestamps.write(_data, _time);
    self.emit(Event::Timestamped(Timestamped { 
        data: _data, 
        timestamp: _time 
    }));
}

pub fn multiTimestamp(ref self: ContractState, data: Array<u256>) -> u64 {
    let timestamp = get_block_timestamp();
    
    let mut i = 0;
    loop {
        if i >= data.len() {
            break;
        }
        self._timestamp(*data.at(i), timestamp);
        i += 1;
    };
    
    return timestamp;
}

Error Handling

Comprehensive Error System

Detailed error handling with descriptive error codes:
use starknet_attestso::helpers::common::Errors::{
    AccessDenied,           // Insufficient permissions
    InvalidRegistry,        // Invalid schema registry
    InvalidLength,          // Invalid data length
    NotFound,              // Attestation not found
    InvalidSchema,         // Schema validation failed
    InvalidExpirationTime, // Invalid expiration timestamp
    Irrevocable,           // Attestation not revocable
    NotPayable,            // Payment not accepted
    InsufficientValue,     // Insufficient payment
    InvalidRevocation,     // Revocation validation failed
    InvalidAttestation,    // Attestation validation failed
    AlreadyRevoked,        // Already revoked
    AlreadyTimestamped,    // Data already timestamped
};

// Error handling in contract functions
if (_schemaRecord.uid == EMPTY_UID) {
    panic_with_felt252(InvalidSchema);
}

if (attestation.attester != revoker) {
    panic_with_felt252(AccessDenied);
}

Integration Examples

JavaScript Integration

Using Starknet.js for contract interaction:
import { Contract, Account, ec, json, stark, Provider, hash } from 'starknet';

// Initialize provider and contract
const provider = new Provider({ sequencer: { network: 'goerli-alpha' } });
const contractAddress = '0x...'; // Contract address
const contract = new Contract(abi, contractAddress, provider);

// Create attestation
const attestationRequest = {
    schema: '0x123...', // Schema UID
    data: {
        recipient: '0x456...', // Recipient address
        expirationTime: Math.floor(Date.now() / 1000) + 86400, // 24 hours
        revocable: true,
        refUID: '0x0', // No reference
        data: [1, 2, 3], // Attestation data as ByteArray
        value: 0 // No payment
    }
};

// Send attestation transaction
const attestTx = await contract.attest(attestationRequest);
const result = await provider.waitForTransaction(attestTx.transaction_hash);

// Query attestation
const attestation = await contract.getAttestation(attestationUID);
console.log('Attestation:', attestation);

Cairo Integration

Direct contract-to-contract calls:
use starknet::{ContractAddress, contract_address_const};
use starknet_attestso::SAS::{ISASDispatcher, ISASDispatcherTrait};

#[starknet::contract]
mod ClientContract {
    use super::*;
    
    #[storage]
    struct Storage {
        sas_contract: ContractAddress,
    }
    
    #[external(v0)]
    fn create_attestation(
        ref self: ContractState,
        schema_uid: u256,
        recipient: ContractAddress,
        data: ByteArray
    ) -> u256 {
        let sas = ISASDispatcher { 
            contract_address: self.sas_contract.read() 
        };
        
        let request = AttestationRequest {
            schema: schema_uid,
            data: AttestationRequestData {
                recipient,
                expirationTime: 0, // No expiration
                revocable: true,
                refUID: 0,
                data,
                value: 0
            }
        };
        
        return sas.attest(request);
    }
}

Network Information

Deployment Status

NetworkContract AddressStatus
Goerli Testnet0x123...abcActive
Sepolia Testnet0x456...defActive
MainnetTBDIn Development

Network Configuration

Goerli Testnet:
# RPC endpoint
GOERLI_RPC="https://starknet-goerli.g.alchemy.com/v2/YOUR_API_KEY"

# Chain ID
GOERLI_CHAIN_ID="SN_GOERLI"
Sepolia Testnet:
# RPC endpoint  
SEPOLIA_RPC="https://starknet-sepolia.g.alchemy.com/v2/YOUR_API_KEY"

# Chain ID
SEPOLIA_CHAIN_ID="SN_SEPOLIA"

Development Workflow

Build and Deploy

Complete development setup for Cairo contracts:
# Clone repository
git clone https://github.com/daccred/attest.so.git
cd attest.so/contracts/starknet

# Install dependencies
npm install

# Build contract with Scarb
scarb build

# Run tests
scarb test

# Declare contract
npm run declare

# Deploy contract
npm run deploy

# Verify deployment
starkli call $CONTRACT_ADDRESS get_schema_registry

Testing Framework

Comprehensive test suite using Starknet Foundry:
#[cfg(test)]
mod tests {
    use super::*;
    use starknet::testing::set_contract_address;
    
    #[test]
    fn test_attestation_creation() {
        // Deploy contract
        let contract = deploy_contract();
        
        // Create test attestation
        let schema_uid = 0x123;
        let recipient = contract_address_const::<0x456>();
        
        let request = AttestationRequest {
            schema: schema_uid,
            data: AttestationRequestData {
                recipient,
                expirationTime: 0,
                revocable: true,
                refUID: 0,
                data: "test data",
                value: 0
            }
        };
        
        let uid = contract.attest(request);
        assert(uid != 0, 'Attestation UID should not be zero');
        
        // Verify attestation
        let attestation = contract.getAttestation(uid);
        assert(attestation.schema == schema_uid, 'Schema mismatch');
        assert(attestation.recipient == recipient, 'Recipient mismatch');
    }
    
    #[test]
    fn test_attestation_revocation() {
        // Test complete revocation workflow
        let contract = deploy_contract();
        
        // Create attestation
        let uid = create_test_attestation(contract);
        
        // Revoke attestation
        let revocation_request = RevocationRequest {
            schema: 0x123,
            data: RevocationRequestData {
                uid,
                value: 0
            }
        };
        
        contract.revoke(revocation_request);
        
        // Verify revocation
        let attestation = contract.getAttestation(uid);
        assert(attestation.isRevoked == true, 'Attestation should be revoked');
    }
}

Performance Characteristics

Transaction Costs

Optimized for Starknet’s fee structure:
  • Attestation Creation: ~50,000 steps
  • Attestation Revocation: ~30,000 steps
  • Schema Validation: ~20,000 steps
  • Timestamp Operation: ~15,000 steps

Scalability Features

  • Batch Operations: Multiple attestations in single transaction
  • Efficient Storage: Optimized storage key design
  • Cairo Optimization: Native Cairo performance benefits
  • STARK Proof Aggregation: Efficient zero-knowledge operations

Future Enhancements

Planned Features

Advanced Zero-Knowledge Support:
  • Fully anonymous attestations
  • Selective disclosure protocols
  • Credential aggregation proofs
  • Privacy-preserving queries
Cross-Chain Integration:
  • Starknet to Ethereum bridge attestations
  • Layer 2 attestation synchronization
  • Multi-chain attestation verification
Enhanced Developer Experience:
  • Visual schema builder
  • Attestation analytics dashboard
  • Advanced querying capabilities

Next Steps