#!/usr/bin/env python3 """ Vector Zulu — LICK Signing Module CONFIRMED WORKING: raw UTF-8 key bytes, no sort_keys on canonicalize. Verified 2026-07-14 against korechain.korenet.cloud. """ import base64, hashlib, hmac, json, os, time, uuid from typing import Optional SIGNER_ID = 'vector-zulu' def _get_lick_key() -> bytes: key_raw = ( os.environ.get('VECTOR_ZULU_LICK_KEY') or os.environ.get('KORE_LICK_HMAC_KEY') or os.environ.get('LICK_HMAC_SECRET', '') ) if not key_raw: raise ValueError('LICK key not configured — set VECTOR_ZULU_LICK_KEY') # Key is raw string — encode to bytes directly (NOT base64 decoded) return key_raw.encode('utf-8') def canonicalize(payload: dict) -> str: """Compact JSON — NO sort_keys (server does not sort).""" return json.dumps(payload, separators=(',', ':')) def sign_canonical(payload: dict, timestamp: Optional[int] = None) -> str: if timestamp is None: timestamp = int(time.time()) key = _get_lick_key() canonical = canonicalize(payload) msg = f"{canonical}\n{timestamp}" return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).hexdigest() def build_headers(payload: dict, variant: str = 'canonical') -> dict: ts = int(time.time()) nonce = str(uuid.uuid4()) sig = sign_canonical(payload, ts) return { 'x-lick-ts': str(ts), 'x-lick-sig': sig, 'x-lick-nonce': nonce, 'x-lick-signer': SIGNER_ID, 'x-lick-variant': variant, 'x-sentinel-trace': str(uuid.uuid4()), 'Idempotency-Key': str(uuid.uuid4()), 'X-Tenant-Id': 'vector-zulu', 'Content-Type': 'application/json', }