;; 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
})
)