Source Code

;; Identity Registry Contract - Inspired by ERC-7812: ZK Identity Registry
;; A singleton registry system for storing abstract private provable statements on Stacks.
;; 
;; This contract implements an on-chain registry system for storing and proving abstract
;; statements. Users can store commitments to their private data and later prove its 
;; validity and authenticity via zero knowledge, without disclosing the data itself.
;;
;; Reference: https://eips.ethereum.org/EIPS/eip-7812
;; 
;; Clarity v4 Functions Used:
;; - contract-hash?: Get the hash of a contract for verification
;; - restrict-assets?: Check asset restriction status
;; - to-ascii?: Convert UTF-8 strings to ASCII
;; - stacks-block-time: Get current Stacks block timestamp
;; - secp256r1-verify: Verify secp256r1 (P-256) signatures for identity attestations

;; ==============================
;; Constants
;; ==============================

;; Contract owner for administrative functions
(define-constant CONTRACT_OWNER tx-sender)

;; Error codes following ERC-7812 patterns
(define-constant ERR_UNAUTHORIZED (err u1001))
(define-constant ERR_KEY_ALREADY_EXISTS (err u1002))
(define-constant ERR_KEY_DOES_NOT_EXIST (err u1003))
(define-constant ERR_INVALID_KEY (err u1004))
(define-constant ERR_INVALID_VALUE (err u1005))
(define-constant ERR_INVALID_SIGNATURE (err u1006))
(define-constant ERR_ROOT_NOT_FOUND (err u1007))
(define-constant ERR_ASSET_RESTRICTED (err u1008))
(define-constant ERR_CONVERSION_FAILED (err u1009))
(define-constant ERR_REGISTRAR_NOT_AUTHORIZED (err u1010))
(define-constant ERR_STATEMENT_EXPIRED (err u1011))
(define-constant ERR_INVALID_PROOF (err u1012))

;; Statement expiration period (approximately 1 year in seconds)
(define-constant STATEMENT_EXPIRATION_PERIOD u31536000)

;; Maximum size for Merkle proof siblings (analogous to SMT height of 80 in ERC-7812)
(define-constant MAX_PROOF_HEIGHT u80)

;; ==============================
;; Data Variables
;; ==============================

;; Current Merkle root of the evidence database
(define-data-var current-root (buff 32) 0x0000000000000000000000000000000000000000000000000000000000000000)

;; Root version counter
(define-data-var root-version uint u0)

;; Total number of statements in the registry
(define-data-var statement-count uint u0)

;; Asset restriction flag (using Clarity v4's restrict-assets? concept)
(define-data-var assets-restricted bool false)

;; ==============================
;; Data Maps
;; ==============================

;; Evidence Database: Stores statements (key -> value) with isolated namespacing per registrar
;; Following ERC-7812's EvidenceDB pattern
(define-map evidence-db
  { isolated-key: (buff 32) }
  {
    value: (buff 32),
    registrar: principal,
    created-at: uint,
    updated-at: uint,
    exists: bool,
  }
)

;; Root history: Maps historical roots to their timestamps
;; As per ERC-7812: "The EvidenceRegistry MUST maintain the linear history of EvidenceDB roots"
(define-map root-timestamps
  { root: (buff 32) }
  { timestamp: uint, version: uint }
)

;; Authorized registrars: Contracts authorized to add/update/remove statements
;; Following ERC-7812's Registrar pattern
(define-map authorized-registrars
  principal
  {
    name: (string-utf8 64),
    authorized-at: uint,
    statement-count: uint,
    active: bool,
  }
)

;; Identity commitments: Links principals to their identity commitments
;; Implements the "commitment" concept from ERC-7812
(define-map identity-commitments
  principal
  {
    commitment: (buff 32),
    created-at: uint,
    signature-verified: bool,
    public-key: (buff 33),
  }
)

;; Statement metadata: Additional metadata for statements
(define-map statement-metadata
  { isolated-key: (buff 32) }
  {
    statement-type: (string-utf8 32),
    description: (string-utf8 256),
    ascii-description: (optional (string-ascii 256)),
  }
)

;; Merkle proofs: Store inclusion/exclusion proof data
;; Following ERC-7812's Proof struct pattern
(define-map merkle-proofs
  { key: (buff 32) }
  {
    root: (buff 32),
    existence: bool,
    aux-key: (optional (buff 32)),
    aux-value: (optional (buff 32)),
    verified-at: uint,
  }
)

;; ==============================
;; Clarity v4 Functions - Contract Info
;; ==============================

;; Get the hash of this contract using Clarity v4's contract-hash?
;; This allows verification of contract integrity
;; Note: contract-hash? returns (response (buff 32) uint)
(define-read-only (get-contract-hash)
  (contract-hash? tx-sender)
)

;; Get the hash of a specific contract for cross-contract verification
(define-read-only (get-registrar-contract-hash (registrar principal))
  (contract-hash? registrar)
)

;; ==============================
;; Clarity v4 Functions - Time
;; ==============================

;; Get current Stacks block time using Clarity v4's stacks-block-time
(define-read-only (get-current-block-time)
  stacks-block-time
)

;; Check if a statement has expired based on stacks-block-time
(define-read-only (is-statement-expired (created-at uint))
  (> stacks-block-time (+ created-at STATEMENT_EXPIRATION_PERIOD))
)

;; ==============================
;; Clarity v4 Functions - ASCII Conversion
;; ==============================

;; Convert a UTF-8 statement type to ASCII using to-ascii?
(define-read-only (statement-type-to-ascii (statement-type (string-utf8 32)))
  (to-ascii? statement-type)
)

;; Get statement description as ASCII if possible
(define-read-only (get-statement-ascii-description (isolated-key (buff 32)))
  (match (map-get? statement-metadata { isolated-key: isolated-key })
    metadata (get ascii-description metadata)
    none
  )
)

;; ==============================
;; Clarity v4 Functions - Signature Verification
;; ==============================

;; Verify a secp256r1 signature for identity attestation
;; This uses Clarity v4's secp256r1-verify for WebAuthn/passkey compatibility
(define-read-only (verify-identity-signature 
    (message-hash (buff 32))
    (signature (buff 64))
    (public-key (buff 33))
  )
  (secp256r1-verify message-hash signature public-key)
)

;; ==============================
;; Asset Restriction (Clarity v4)
;; ==============================

;; Check if operations are currently restricted
(define-read-only (are-operations-restricted)
  (var-get assets-restricted)
)

;; Toggle asset/operation restrictions (owner only)
(define-public (set-asset-restrictions (restricted bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
    (var-set assets-restricted restricted)
    (print {
      event: "AssetRestrictionsUpdated",
      restricted: restricted,
      updated-by: tx-sender,
      timestamp: stacks-block-time,
    })
    (ok restricted)
  )
)

;; ==============================
;; Key Isolation (ERC-7812 Core Function)
;; ==============================

;; Build isolated key from source (registrar) and original key
;; Following ERC-7812: "isolatedKey = hash(msg.sender, key)"
(define-read-only (get-isolated-key (source principal) (key (buff 32)))
  (keccak256 (concat (unwrap-panic (to-consensus-buff? source)) key))
)

;; ==============================
;; Registrar Management
;; ==============================

;; Authorize a new registrar
(define-public (authorize-registrar (registrar principal) (name (string-utf8 64)))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
    (asserts! (not (var-get assets-restricted)) ERR_ASSET_RESTRICTED)
    (map-set authorized-registrars registrar {
      name: name,
      authorized-at: stacks-block-time,
      statement-count: u0,
      active: true,
    })
    (print {
      event: "RegistrarAuthorized",
      registrar: registrar,
      name: name,
      authorized-at: stacks-block-time,
      contract-hash: (contract-hash? registrar),
    })
    (ok true)
  )
)

;; Revoke a registrar's authorization
(define-public (revoke-registrar (registrar principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
    (match (map-get? authorized-registrars registrar)
      registrar-data
        (begin
          (map-set authorized-registrars registrar (merge registrar-data { active: false }))
          (print {
            event: "RegistrarRevoked",
            registrar: registrar,
            revoked-at: stacks-block-time,
          })
          (ok true)
        )
      ERR_REGISTRAR_NOT_AUTHORIZED
    )
  )
)

;; Check if a principal is an authorized registrar
(define-read-only (is-authorized-registrar (registrar principal))
  (match (map-get? authorized-registrars registrar)
    data (get active data)
    false
  )
)

;; Get registrar information
(define-read-only (get-registrar-info (registrar principal))
  (map-get? authorized-registrars registrar)
)

;; ==============================
;; Evidence Registry Core Functions (ERC-7812)
;; ==============================

;; Private function to update root and emit event
(define-private (update-root (new-root (buff 32)))
  (let (
    (prev-root (var-get current-root))
    (new-version (+ (var-get root-version) u1))
  )
    ;; Store previous root timestamp
    (map-set root-timestamps 
      { root: prev-root }
      { timestamp: stacks-block-time, version: (var-get root-version) }
    )
    ;; Update current root
    (var-set current-root new-root)
    (var-set root-version new-version)
    ;; Emit RootUpdated event (following ERC-7812)
    (print {
      event: "RootUpdated",
      prev-root: prev-root,
      curr-root: new-root,
      version: new-version,
      timestamp: stacks-block-time,
    })
    new-root
  )
)

;; Add a new statement to the Evidence DB
;; Following ERC-7812's addStatement function
(define-public (add-statement (key (buff 32)) (value (buff 32)))
  (let (
    (isolated-key (get-isolated-key tx-sender key))
  )
    ;; Check restrictions
    (asserts! (not (var-get assets-restricted)) ERR_ASSET_RESTRICTED)
    ;; Check if key already exists (ERC-7812 requirement)
    (asserts! (is-none (map-get? evidence-db { isolated-key: isolated-key })) ERR_KEY_ALREADY_EXISTS)
    
    ;; Store the statement
    (map-set evidence-db { isolated-key: isolated-key } {
      value: value,
      registrar: tx-sender,
      created-at: stacks-block-time,
      updated-at: stacks-block-time,
      exists: true,
    })
    
    ;; Update statement count
    (var-set statement-count (+ (var-get statement-count) u1))
    
    ;; Update registrar statement count if authorized
    (match (map-get? authorized-registrars tx-sender)
      registrar-data 
        (map-set authorized-registrars tx-sender 
          (merge registrar-data { statement-count: (+ (get statement-count registrar-data) u1) }))
      true
    )
    
    ;; Compute new root (simplified - in production would use proper Merkle tree)
    (let ((new-root (keccak256 (concat (var-get current-root) (concat isolated-key value)))))
      (update-root new-root)
      
      (print {
        event: "StatementAdded",
        key: key,
        isolated-key: isolated-key,
        registrar: tx-sender,
        timestamp: stacks-block-time,
      })
      
      (ok isolated-key)
    )
  )
)

;; Remove a statement from the Evidence DB
;; Following ERC-7812's removeStatement function
(define-public (remove-statement (key (buff 32)))
  (let (
    (isolated-key (get-isolated-key tx-sender key))
  )
    ;; Check restrictions
    (asserts! (not (var-get assets-restricted)) ERR_ASSET_RESTRICTED)
    
    ;; Check if key exists and belongs to caller
    (match (map-get? evidence-db { isolated-key: isolated-key })
      statement-data
        (begin
          ;; Verify ownership
          (asserts! (is-eq (get registrar statement-data) tx-sender) ERR_UNAUTHORIZED)
          
          ;; Mark as deleted (soft delete for auditability)
          (map-set evidence-db { isolated-key: isolated-key }
            (merge statement-data { exists: false, updated-at: stacks-block-time }))
          
          ;; Update statement count
          (var-set statement-count (- (var-get statement-count) u1))
          
          ;; Compute new root
          (let ((new-root (keccak256 (concat (var-get current-root) isolated-key))))
            (update-root new-root)
            
            (print {
              event: "StatementRemoved",
              key: key,
              isolated-key: isolated-key,
              registrar: tx-sender,
              timestamp: stacks-block-time,
            })
            
            (ok true)
          )
        )
      ERR_KEY_DOES_NOT_EXIST
    )
  )
)

;; Update a statement in the Evidence DB
;; Following ERC-7812's updateStatement function
(define-public (update-statement (key (buff 32)) (new-value (buff 32)))
  (let (
    (isolated-key (get-isolated-key tx-sender key))
  )
    ;; Check restrictions
    (asserts! (not (var-get assets-restricted)) ERR_ASSET_RESTRICTED)
    
    ;; Check if key exists and belongs to caller
    (match (map-get? evidence-db { isolated-key: isolated-key })
      statement-data
        (begin
          ;; Verify ownership and existence
          (asserts! (is-eq (get registrar statement-data) tx-sender) ERR_UNAUTHORIZED)
          (asserts! (get exists statement-data) ERR_KEY_DOES_NOT_EXIST)
          
          ;; Check expiration
          (asserts! (not (is-statement-expired (get created-at statement-data))) ERR_STATEMENT_EXPIRED)
          
          ;; Update the statement
          (map-set evidence-db { isolated-key: isolated-key }
            (merge statement-data { value: new-value, updated-at: stacks-block-time }))
          
          ;; Compute new root
          (let ((new-root (keccak256 (concat (var-get current-root) (concat isolated-key new-value)))))
            (update-root new-root)
            
            (print {
              event: "StatementUpdated",
              key: key,
              isolated-key: isolated-key,
              registrar: tx-sender,
              timestamp: stacks-block-time,
            })
            
            (ok true)
          )
        )
      ERR_KEY_DOES_NOT_EXIST
    )
  )
)

;; ==============================
;; Identity Commitment Functions
;; ==============================

;; Register an identity commitment with secp256r1 signature verification
;; This enables WebAuthn/passkey-based identity registration
(define-public (register-identity-commitment
    (commitment (buff 32))
    (signature (buff 64))
    (public-key (buff 33))
    (commitment-hash (buff 32))
  )
  (begin
    ;; Check restrictions
    (asserts! (not (var-get assets-restricted)) ERR_ASSET_RESTRICTED)
    
    ;; Verify secp256r1 signature using Clarity v4
    (asserts! (secp256r1-verify commitment-hash signature public-key) ERR_INVALID_SIGNATURE)
    
    ;; Store identity commitment
    (map-set identity-commitments tx-sender {
      commitment: commitment,
      created-at: stacks-block-time,
      signature-verified: true,
      public-key: public-key,
    })
    
    ;; Also add as a statement in the evidence db
    (let ((isolated-key (get-isolated-key tx-sender commitment)))
      (map-set evidence-db { isolated-key: isolated-key } {
        value: commitment,
        registrar: tx-sender,
        created-at: stacks-block-time,
        updated-at: stacks-block-time,
        exists: true,
      })
      
      ;; Update root
      (let ((new-root (keccak256 (concat (var-get current-root) (concat isolated-key commitment)))))
        (update-root new-root)
        
        (print {
          event: "IdentityCommitmentRegistered",
          principal: tx-sender,
          commitment: commitment,
          signature-verified: true,
          timestamp: stacks-block-time,
          contract-hash: (get-contract-hash),
        })
        
        (ok isolated-key)
      )
    )
  )
)

;; Get identity commitment for a principal
(define-read-only (get-identity-commitment (user principal))
  (map-get? identity-commitments user)
)

;; Verify identity using stored commitment and signature
(define-read-only (verify-identity 
    (user principal)
    (message-hash (buff 32))
    (signature (buff 64))
  )
  (match (map-get? identity-commitments user)
    commitment-data
      (if (secp256r1-verify message-hash signature (get public-key commitment-data))
        (ok { verified: true, commitment: (get commitment commitment-data) })
        (ok { verified: false, commitment: (get commitment commitment-data) })
      )
    ERR_KEY_DOES_NOT_EXIST
  )
)

;; ==============================
;; Statement with Metadata
;; ==============================

;; Add a statement with metadata including ASCII description
(define-public (add-statement-with-metadata
    (key (buff 32))
    (value (buff 32))
    (statement-type (string-utf8 32))
    (description (string-utf8 256))
  )
  (let (
    (isolated-key (get-isolated-key tx-sender key))
    ;; to-ascii? returns (response string-ascii uint), convert to optional
    (ascii-desc (match (to-ascii? description)
                  success (some success)
                  error none))
  )
    ;; First add the statement
    (try! (add-statement key value))
    
    ;; Then add metadata
    (map-set statement-metadata { isolated-key: isolated-key } {
      statement-type: statement-type,
      description: description,
      ascii-description: ascii-desc,
    })
    
    (print {
      event: "StatementMetadataAdded",
      isolated-key: isolated-key,
      statement-type: statement-type,
      ascii-description: ascii-desc,
      timestamp: stacks-block-time,
    })
    
    (ok isolated-key)
  )
)

;; ==============================
;; Proof Functions (ERC-7812 Pattern)
;; ==============================

;; Store a Merkle proof for a key
(define-public (store-proof
    (key (buff 32))
    (root (buff 32))
    (existence bool)
    (aux-key (optional (buff 32)))
    (aux-value (optional (buff 32)))
  )
  (begin
    ;; Verify the root exists in history or is current
    (asserts! 
      (or 
        (is-eq root (var-get current-root))
        (is-some (map-get? root-timestamps { root: root }))
      )
      ERR_ROOT_NOT_FOUND
    )
    
    (map-set merkle-proofs { key: key } {
      root: root,
      existence: existence,
      aux-key: aux-key,
      aux-value: aux-value,
      verified-at: stacks-block-time,
    })
    
    (print {
      event: "ProofStored",
      key: key,
      root: root,
      existence: existence,
      timestamp: stacks-block-time,
    })
    
    (ok true)
  )
)

;; Get stored proof for a key
(define-read-only (get-proof (key (buff 32)))
  (map-get? merkle-proofs { key: key })
)

;; ==============================
;; Read Functions (ERC-7812 Pattern)
;; ==============================

;; Get current root
(define-read-only (get-root)
  (var-get current-root)
)

;; Get root version
(define-read-only (get-root-version)
  (var-get root-version)
)

;; Get root timestamp - returns 0 for non-existent roots, current time for latest root
;; Following ERC-7812's getRootTimestamp specification
(define-read-only (get-root-timestamp (root (buff 32)))
  (if (is-eq root (var-get current-root))
    stacks-block-time
    (match (map-get? root-timestamps { root: root })
      data (get timestamp data)
      u0
    )
  )
)

;; Get total statement count
(define-read-only (get-statement-count)
  (var-get statement-count)
)

;; Get a statement value by its isolated key
(define-read-only (get-value (isolated-key (buff 32)))
  (match (map-get? evidence-db { isolated-key: isolated-key })
    data 
      (if (get exists data)
        (some (get value data))
        none
      )
    none
  )
)

;; Get full statement data
(define-read-only (get-statement (isolated-key (buff 32)))
  (map-get? evidence-db { isolated-key: isolated-key })
)

;; Get statement by original key and registrar
(define-read-only (get-statement-by-key (registrar principal) (key (buff 32)))
  (get-statement (get-isolated-key registrar key))
)

;; Get statement metadata
(define-read-only (get-metadata (isolated-key (buff 32)))
  (map-get? statement-metadata { isolated-key: isolated-key })
)

;; ==============================
;; Contract Information
;; ==============================

;; Get comprehensive contract information using Clarity v4 features
(define-read-only (get-registry-info)
  {
    contract-hash: (get-contract-hash),
    current-root: (var-get current-root),
    root-version: (var-get root-version),
    statement-count: (var-get statement-count),
    current-block-time: stacks-block-time,
    assets-restricted: (var-get assets-restricted),
    owner: CONTRACT_OWNER,
    expiration-period: STATEMENT_EXPIRATION_PERIOD,
    max-proof-height: MAX_PROOF_HEIGHT,
  }
)

;; Verify contract integrity by checking hash
;; Returns true if expected-hash matches the actual contract hash
(define-read-only (verify-contract-integrity (expected-hash (buff 32)))
  (match (get-contract-hash)
    actual-hash (is-eq expected-hash actual-hash)
    err-code false
  )
)

Functions (34)

FunctionAccessArgs
get-contract-hashread-only
get-registrar-contract-hashread-onlyregistrar: principal
get-current-block-timeread-only
is-statement-expiredread-onlycreated-at: uint
statement-type-to-asciiread-onlystatement-type: (string-utf8 32
get-statement-ascii-descriptionread-onlyisolated-key: (buff 32
verify-identity-signatureread-onlymessage-hash: (buff 32
are-operations-restrictedread-only
set-asset-restrictionspublicrestricted: bool
get-isolated-keyread-onlysource: principal, key: (buff 32
authorize-registrarpublicregistrar: principal, name: (string-utf8 64
revoke-registrarpublicregistrar: principal
is-authorized-registrarread-onlyregistrar: principal
get-registrar-inforead-onlyregistrar: principal
update-rootprivatenew-root: (buff 32
add-statementpublickey: (buff 32
remove-statementpublickey: (buff 32
update-statementpublickey: (buff 32
register-identity-commitmentpubliccommitment: (buff 32
get-identity-commitmentread-onlyuser: principal
verify-identityread-onlyuser: principal, message-hash: (buff 32
add-statement-with-metadatapublickey: (buff 32
store-proofpublickey: (buff 32
get-proofread-onlykey: (buff 32
get-rootread-only
get-root-versionread-only
get-root-timestampread-onlyroot: (buff 32
get-statement-countread-only
get-valueread-onlyisolated-key: (buff 32
get-statementread-onlyisolated-key: (buff 32
get-statement-by-keyread-onlyregistrar: principal, key: (buff 32
get-metadataread-onlyisolated-key: (buff 32
get-registry-inforead-only
verify-contract-integrityread-onlyexpected-hash: (buff 32