Source Code

;; title: connection-v3
;; version:
;; summary:
;; description:

;; traits
;;

;; constants
(define-constant err-not-admin (err u1100))
(define-constant err-duplicate-message (err u1101))
(define-constant err-signatures-less-than-threshold (err u1102))
(define-constant err-already-initialized (err u1103))
(define-constant err-threshold-too-high (err u1104))
(define-constant err-valid-signatures-less-than-threshold (err u1105))
(define-constant err-invalid-chain-id (err u1106))
(define-constant err-threshold-zero (err u1107))
(define-constant err-not-proposed-admin (err u1108))
;;

;; public functions
(define-public (init (chain-id-in uint))
  (begin
    (try! (is-admin))
    (try! (is-not-initialized))
    (asserts! (> chain-id-in u0) err-invalid-chain-id)
    (contract-call? .connection-v3-state set-chain-id chain-id-in)
  ))

(define-public (set-connection-impl (who principal))
  (begin
    (try! (is-admin))
    (contract-call? .connection-v3-state set-connection-impl who)
  ))

(define-public (propose-admin (who principal))
  (begin
    (try! (is-admin))
    (contract-call? .connection-v3-state set-proposed-admin who)
))

(define-public (accept-admin)
  (let ((proposed-admin (unwrap! (contract-call? .connection-v3-state get-proposed-admin) err-not-proposed-admin)))
    (asserts!  (is-eq contract-caller proposed-admin) err-not-proposed-admin)
    (contract-call? .connection-v3-state set-admin proposed-admin)
  ))

(define-public (set-validators (validators-list (list 50 (buff 65))) (threshold uint))
  (begin
    (try! (is-admin))
    (let (
      (unique-valid-validators (fold fetch-unique-validator validators-list (list)))
    )
    (asserts! (<= threshold (len unique-valid-validators)) err-threshold-too-high)
    (asserts! (> threshold u0) err-threshold-zero)

    (unwrap-panic (contract-call? .connection-v3-state set-validators unique-valid-validators))
    (unwrap-panic (contract-call? .connection-v3-state set-validators-threshold threshold))
    )
    (ok true)
  ))

(define-public (set-validators-threshold (threshold uint))
  (begin
    (try! (is-admin))
    (let ((validators (get-validators))
          (validators-length (len validators)))
      (asserts! (>= validators-length threshold) err-threshold-too-high)
      (asserts! (> threshold u0) err-threshold-zero)
    )
    (contract-call? .connection-v3-state set-validators-threshold threshold)
  ))

(define-public (send-message (dst-chain-id uint) (dst-address (buff 256)) (payload (buff 4096)))
  (let ((src-chain-id (contract-call? .connection-v3-state get-chain-id))
        (src-address contract-caller)
        (nonce (unwrap-panic (contract-call? .connection-v3-state increment-connection-sn))))
    (emit-message-event src-chain-id (principal-to-bytes src-address) dst-chain-id dst-address nonce payload)
    (ok true)
  ))

(define-public (verify-message (src-chain-id uint) (src-address (buff 256)) (conn-sn uint) (payload (buff 4096)) (signatures (list 50 (buff 65))))
  (begin 
    (asserts! (> (get-validators-threshold) u0) err-threshold-zero)
    (asserts! (>= (len signatures) (get-validators-threshold)) err-signatures-less-than-threshold)
    (let ((dst-chain-id (contract-call? .connection-v3-state get-chain-id))
        (dst-address (principal-to-bytes contract-caller))
        (nonce conn-sn)
        (messagehash (get-message-hash src-chain-id src-address dst-chain-id dst-address nonce payload)))
    (asserts! (not (receipt-status src-chain-id conn-sn)) err-duplicate-message)
    (unwrap-panic (contract-call? .connection-v3-state set-receipt src-chain-id nonce true))
    (asserts! (verify-signatures messagehash signatures) err-valid-signatures-less-than-threshold)
    (ok true)
  )))

;; read-only functions
(define-read-only (is-admin)
  (ok (asserts! (is-eq contract-caller (contract-call? .connection-v3-state get-admin)) err-not-admin))
)

(define-read-only (is-not-initialized)
  (ok (asserts! (is-eq (contract-call? .connection-v3-state get-chain-id) u0) err-already-initialized))
)

(define-read-only (receipt-status (src-chain-id uint) (conn-sn-in uint))
  (contract-call? .connection-v3-state get-receipt src-chain-id conn-sn-in)
)

;; private functions
(define-private (get-message-hash (src-chain-id uint) (src-address (buff 256)) (dst-chain-id uint) (dst-address (buff 256)) (nonce uint) (payload (buff 4096)))
  (keccak256
    (concat (uint-to-32-bytes nonce)
    (concat (uint-to-32-bytes src-chain-id)
    (concat (uint-to-32-bytes dst-chain-id)
    (concat src-address
    (concat dst-address payload)))))
  )
)

(define-private (uint-to-32-bytes (value uint))
  (let ((encoded-num (unwrap-panic (to-consensus-buff? value)))
        (encoded-u0 (unwrap-panic (to-consensus-buff? u0)))
        (sliced (unwrap-panic (slice? encoded-num u1 (len encoded-num))))
        (sliced-u0 (unwrap-panic (slice? encoded-u0 u1 (len encoded-u0))))
        (bytes32 (concat sliced-u0 sliced)))
    bytes32
  )
)

(define-read-only (recover-public-key (messageHash (buff 32)) (signature (buff 65)))
  (let 
    (
      (v
        (match (element-at? signature u64) last-byte
          (to-be-bytes
            (mod
              (buff-to-uint-be last-byte)
              u27))
          0x00))
      (signature-clarity 
        (unwrap-panic 
          (replace-at? signature u64 (unwrap-panic (element-at? v u0)))))
    )
    (secp256k1-recover? messageHash signature-clarity)
  )
)


(define-private (exists-in-validators (pub-key (buff 65)))
  (let ((validators-list (get-validators)))
    (match (index-of? validators-list pub-key) exists true false)
  ))

(define-private (verify-signature (signature (buff 65)) 
                                  (accumulator {messagehash: (buff 32), 
                                                count: uint, 
                                                unique-validators-list: (list 50 (buff 100))}))
  (let ((messageHash (get messagehash accumulator))
        (recovered-pub-key (recover-public-key messageHash signature)))
    
    (match recovered-pub-key recovered-key
      ;; If the public key is recovered successfully
      (if (exists-in-validators recovered-key)
          (match (index-of? (get unique-validators-list accumulator) recovered-key) 
            ;; If the recovered key exists in the list
            exists accumulator
            ;; Update accumulator with new count and unique validators list
            (merge accumulator 
                   {count: (+ (get count accumulator) u1), 
                    unique-validators-list: (unwrap-panic (as-max-len? 
                      (append (get unique-validators-list accumulator) recovered-key) u50))}))
          accumulator)
      ;; If recovering the public key fails, return the unchanged accumulator
      error 
      accumulator)))


(define-private (verify-signatures (messageHash (buff 32)) (signatures (list 50 (buff 65))))
  (let ((is-verified (fold verify-signature signatures {messagehash: messageHash, count: u0, unique-validators-list: (list)})))
    (if (>= (get count is-verified) (get-validators-threshold))
        true
        false)
  )
)

(define-private (get-validators) (contract-call? .connection-v3-state get-validators))

(define-private (get-validators-threshold) (contract-call? .connection-v3-state get-validators-threshold))

(define-private (principal-to-bytes (p principal))
  (unwrap-panic (to-consensus-buff? p))
)

(define-read-only (compress-public-key (uncompressed-key (buff 65)))
  (let ((x-coord (unwrap-panic (slice? uncompressed-key u1 u33)))
        (last-byte (unwrap-panic (element-at? uncompressed-key u64)))
        (prefix (if (is-eq (mod (buff-to-uint-be last-byte) u2) u0) 0x02 0x03)))
    (concat prefix x-coord)
  )
)

(define-private (fetch-unique-validator (uncompressed-validator (buff 65)) 
                                  (unique-validators-list  (list 50 (buff 100))))
  (let (
      (is-valid-prefix (is-eq (unwrap-panic (element-at? uncompressed-validator u0)) 0x04))
      (compressed-key (compress-public-key uncompressed-validator))
    )
    (if is-valid-prefix
        (match (index-of? unique-validators-list compressed-key) 
            exists unique-validators-list
                    (unwrap-panic (as-max-len? 
                        (append unique-validators-list compressed-key) u50)))
        unique-validators-list)
  ))


(define-private (check-length (data (buff 5000)))
  (unwrap-panic (as-max-len? data u4096))
)

(define-private (rm-lead (num (buff 1)) (buffer (buff 4096)))
  (if (is-eq 0x00 buffer)
      num
      (check-length (concat buffer num))
  )
)

(define-private (to-be-bytes (data uint))
  (if (is-eq data u0)
      0x00
      (let ((encoded (unwrap-panic (to-consensus-buff? data)))
            (sliced (unwrap-panic (slice? encoded u1 (len encoded))))
            (stripped (fold rm-lead sliced 0x00)))
        stripped
      )
  )
)

;; events
(define-private (emit-message-event (src-chain-id uint) (src-address (buff 256)) (dst-chain-id uint) (dst-address (buff 256)) (conn-sn uint) (payload (buff 4096)))
  (print {
    event: "Message",
    src-chain-id: src-chain-id,
    src-address: src-address,
    conn-sn: conn-sn,
    dst-chain-id: dst-chain-id,
    dst-address: dst-address,
    payload: payload
  })
)

Functions (26)

FunctionAccessArgs
initpublicchain-id-in: uint
set-connection-implpublicwho: principal
propose-adminpublicwho: principal
accept-adminpublic
set-validatorspublicvalidators-list: (list 50 (buff 65
set-validators-thresholdpublicthreshold: uint
send-messagepublicdst-chain-id: uint, dst-address: (buff 256
verify-messagepublicsrc-chain-id: uint, src-address: (buff 256
is-adminread-only
is-not-initializedread-only
receipt-statusread-onlysrc-chain-id: uint, conn-sn-in: uint
get-message-hashprivatesrc-chain-id: uint, src-address: (buff 256
uint-to-32-bytesprivatevalue: uint
recover-public-keyread-onlymessageHash: (buff 32
exists-in-validatorsprivatepub-key: (buff 65
verify-signatureprivatesignature: (buff 65
verify-signaturesprivatemessageHash: (buff 32
get-validatorsprivate
get-validators-thresholdprivate
principal-to-bytesprivatep: principal
compress-public-keyread-onlyuncompressed-key: (buff 65
fetch-unique-validatorprivateuncompressed-validator: (buff 65
check-lengthprivatedata: (buff 5000
rm-leadprivatenum: (buff 1
to-be-bytesprivatedata: uint
emit-message-eventprivatesrc-chain-id: uint, src-address: (buff 256