Source Code

;; multi-sig-health - Clarity 4
;; Multi-signature authorization for sensitive health data operations

(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-TRANSACTION-NOT-FOUND (err u101))
(define-constant ERR-ALREADY-SIGNED (err u102))
(define-constant ERR-ALREADY-EXECUTED (err u103))
(define-constant ERR-THRESHOLD-NOT-MET (err u104))

(define-map multi-sig-wallets uint
  {
    wallet-name: (string-utf8 100),
    signers: (list 10 principal),
    required-signatures: uint,
    created-at: uint,
    is-active: bool
  }
)

(define-map pending-transactions uint
  {
    wallet-id: uint,
    transaction-type: (string-ascii 50),
    data-hash: (buff 64),
    proposed-by: principal,
    proposed-at: uint,
    executed: bool,
    execution-timestamp: (optional uint)
  }
)

(define-map transaction-signatures { transaction-id: uint, signer: principal }
  {
    signed-at: uint,
    signature-hash: (buff 64)
  }
)

(define-map signature-counts uint
  { count: uint }
)

(define-data-var wallet-counter uint u0)
(define-data-var transaction-counter uint u0)

(define-public (create-multi-sig-wallet
    (wallet-name (string-utf8 100))
    (signers (list 10 principal))
    (required-signatures uint))
  (let ((wallet-id (+ (var-get wallet-counter) u1)))
    (asserts! (<= required-signatures (len signers)) ERR-NOT-AUTHORIZED)
    (map-set multi-sig-wallets wallet-id
      {
        wallet-name: wallet-name,
        signers: signers,
        required-signatures: required-signatures,
        created-at: stacks-block-time,
        is-active: true
      })
    (var-set wallet-counter wallet-id)
    (ok wallet-id)))

(define-public (propose-transaction
    (wallet-id uint)
    (transaction-type (string-ascii 50))
    (data-hash (buff 64)))
  (let ((wallet (unwrap! (map-get? multi-sig-wallets wallet-id) ERR-TRANSACTION-NOT-FOUND))
        (transaction-id (+ (var-get transaction-counter) u1)))
    (asserts! (is-some (index-of (get signers wallet) tx-sender)) ERR-NOT-AUTHORIZED)
    (map-set pending-transactions transaction-id
      {
        wallet-id: wallet-id,
        transaction-type: transaction-type,
        data-hash: data-hash,
        proposed-by: tx-sender,
        proposed-at: stacks-block-time,
        executed: false,
        execution-timestamp: none
      })
    (map-set signature-counts transaction-id { count: u0 })
    (var-set transaction-counter transaction-id)
    (ok transaction-id)))

(define-public (sign-transaction (transaction-id uint) (signature-hash (buff 64)))
  (let ((transaction (unwrap! (map-get? pending-transactions transaction-id) ERR-TRANSACTION-NOT-FOUND))
        (wallet (unwrap! (map-get? multi-sig-wallets (get wallet-id transaction)) ERR-TRANSACTION-NOT-FOUND)))
    (asserts! (is-some (index-of (get signers wallet) tx-sender)) ERR-NOT-AUTHORIZED)
    (asserts! (is-none (map-get? transaction-signatures { transaction-id: transaction-id, signer: tx-sender })) ERR-ALREADY-SIGNED)
    (asserts! (not (get executed transaction)) ERR-ALREADY-EXECUTED)
    (map-set transaction-signatures { transaction-id: transaction-id, signer: tx-sender }
      {
        signed-at: stacks-block-time,
        signature-hash: signature-hash
      })
    (let ((sig-count (unwrap! (map-get? signature-counts transaction-id) ERR-TRANSACTION-NOT-FOUND)))
      (map-set signature-counts transaction-id { count: (+ (get count sig-count) u1) }))
    (ok true)))

(define-public (execute-transaction (transaction-id uint))
  (let ((transaction (unwrap! (map-get? pending-transactions transaction-id) ERR-TRANSACTION-NOT-FOUND))
        (wallet (unwrap! (map-get? multi-sig-wallets (get wallet-id transaction)) ERR-TRANSACTION-NOT-FOUND))
        (sig-count (unwrap! (map-get? signature-counts transaction-id) ERR-TRANSACTION-NOT-FOUND)))
    (asserts! (not (get executed transaction)) ERR-ALREADY-EXECUTED)
    (asserts! (>= (get count sig-count) (get required-signatures wallet)) ERR-THRESHOLD-NOT-MET)
    (ok (map-set pending-transactions transaction-id
      (merge transaction {
        executed: true,
        execution-timestamp: (some stacks-block-time)
      })))))

(define-public (revoke-signature (transaction-id uint))
  (let ((transaction (unwrap! (map-get? pending-transactions transaction-id) ERR-TRANSACTION-NOT-FOUND))
        (signature (unwrap! (map-get? transaction-signatures { transaction-id: transaction-id, signer: tx-sender }) ERR-NOT-AUTHORIZED)))
    (asserts! (not (get executed transaction)) ERR-ALREADY-EXECUTED)
    (map-delete transaction-signatures { transaction-id: transaction-id, signer: tx-sender })
    (let ((sig-count (unwrap! (map-get? signature-counts transaction-id) ERR-TRANSACTION-NOT-FOUND)))
      (map-set signature-counts transaction-id { count: (- (get count sig-count) u1) }))
    (ok true)))

(define-read-only (get-wallet (wallet-id uint))
  (ok (map-get? multi-sig-wallets wallet-id)))

(define-read-only (get-transaction (transaction-id uint))
  (ok (map-get? pending-transactions transaction-id)))

(define-read-only (get-signature (transaction-id uint) (signer principal))
  (ok (map-get? transaction-signatures { transaction-id: transaction-id, signer: signer })))

(define-read-only (get-signature-count (transaction-id uint))
  (ok (map-get? signature-counts transaction-id)))

(define-read-only (validate-principal (p principal))
  (principal-destruct? p))

(define-read-only (format-transaction-id (transaction-id uint))
  (ok (int-to-ascii transaction-id)))

(define-read-only (parse-transaction-id (id-str (string-ascii 20)))
  (string-to-uint? id-str))

(define-read-only (get-bitcoin-block)
  (ok burn-block-height))

Functions (13)

FunctionAccessArgs
create-multi-sig-walletpublicwallet-name: (string-utf8 100
propose-transactionpublicwallet-id: uint, transaction-type: (string-ascii 50
sign-transactionpublictransaction-id: uint, signature-hash: (buff 64
execute-transactionpublictransaction-id: uint
revoke-signaturepublictransaction-id: uint
get-walletread-onlywallet-id: uint
get-transactionread-onlytransaction-id: uint
get-signatureread-onlytransaction-id: uint, signer: principal
get-signature-countread-onlytransaction-id: uint
validate-principalread-onlyp: principal
format-transaction-idread-onlytransaction-id: uint
parse-transaction-idread-onlyid-str: (string-ascii 20
get-bitcoin-blockread-only