11. Cosigner & Callback Handler

Stage 24 정식 명세 · 5 auth option · /v2 URL · key model 비대칭

Cosigner 종류

종류위치SGX 강제
Mobile Co-SignerUser mobile device (iOS Keychain / Android TEE)device-level enclave
API Co-SignerCustomer-deployed SGX serverSGX baseline (Stage 8 ANSWERED)
Fireblocks Cloud Co-Signers (2)Azure SGX (3-5 machines, segregated network)YES
Fireblocks Communal Test Co-signerFireblocks 공유 (testnet 전용)

Chain of Trust

Co-Signer Certificate (self-generated, priv → Configuration DB)
       ↓ (CSR via Co-Signer Broker)
Core Services Intermediate Certificate (built into image)
       ↓ (sign)
Co-Signer End Certificate (배포)

Callback Handler — Optional 성격 (★ 보안 신호)

Default = Auto-Sign (★ Risk)

create-api-co-signer-callback-handler.md 직접 인용:
"If a Callback Handler is not configured for an API user, the Co-signer will automatically sign or approve all requests it receives for that API user."
Callback Handler 미설정 = Co-signer 자동 승인/서명. 외부 business / compliance 검증 bypass 가능 — Risk register 등재.

5 Authentication Options (★ Stage 24 ANSWERED)

#명칭MessageTLSVersionSGX only
1Public key authenticationJWT (RSA 2048)HTTPS + trusted CA (prod)all
2Self-Signed Cert pinningJSONTLS cert pinall
3Root-CA CertJSONTLS Root-CA validationv2025.12.11+
4Hybrid — Public key + Cert pinJWTTLS cert pinv2025.12.11+★ SGX only
5Hybrid — Public key + Root-CAJWTTLS Root-CAv2025.12.11+★ SGX only

Hybrid options (4/5) = message-layer (JWT 서명) + TLS-layer (cert) dual security.

Payload / URL Convention

  • Endpoints: tx_sign_request + config_change_sign_request (Co-signer → Callback Handler POST)
  • /v2 URL 분기:
    • JWT-bearing (1, 4, 5): https://<base>/v2/tx_sign_request
    • JSON-bearing (2, 3): https://<base>/tx_sign_request (no /v2 prefix)
  • URL setting 시 base URL + custom relative path 만 입력 — /v2/... 는 자동 추가
  • Production = HTTPS w/ trusted CA cert 강제 권장 / dev = HTTP 허용

Key Model 비대칭 (★ architectural 신호)

범위비고
Co-signer private keyglobal — 해당 Co-signer 에 페어링된 모든 API user 의 request 서명에 재사용"The same Co-signer private key is used to sign request messages... for all API users paired with this Co-signer."
Callback Handler private keyper-API-user — API user 별 별도 public key 를 Co-signer 에 등록RSA 2048 only (response auth)

→ Co-signer 측 single global key compromise = 모든 API user 의 request 서명 위조 가능. Callback Handler 측 key 는 per-user 격리.

Pairing / Re-enroll

  • Pairing token 1시간 유효
  • Callback Handler SSL public key pinning — Co-signer 가 cert 고정 신뢰
  • Re-enroll 요구: (a) auth method 전환, (b) SSL 인증서 변경 또는 만료 — 둘 다 Owner 의 key share 승인 필요

DB Schema

CREATE TABLE cosigner_configs (
  id                BINARY(16) PRIMARY KEY,
  workspace_id      BINARY(16) NOT NULL,
  paired_api_user_id BINARY(16),                       -- API Co-signer 의 경우
  type              ENUM('mobile', 'api-sgx', 'communal-test', 'fb-cloud-sgx') NOT NULL,
  end_cert_pem      TEXT NOT NULL,
  end_cert_fingerprint BINARY(32) NOT NULL,
  intermediate_cert_id  BINARY(16) NOT NULL,           -- chain of trust
  configuration_db_keyref VARCHAR(128),                -- private key reference (HSM)
  registered_at     DATETIME(6) NOT NULL,
  re_enrolled_at    DATETIME(6),
  UNIQUE KEY (end_cert_fingerprint)
);

CREATE TABLE callback_endpoints (
  id                BINARY(16) PRIMARY KEY,
  cosigner_id       BINARY(16) NOT NULL,
  paired_api_user_id BINARY(16) NOT NULL,              -- per-API-user key model
  url_base          VARCHAR(255) NOT NULL,
  custom_path       VARCHAR(128),
  auth_option       TINYINT NOT NULL,                  -- 1 ~ 5
  jwt_or_json       ENUM('jwt', 'json') NOT NULL,
  url_uses_v2       BOOLEAN NOT NULL,
  callback_pubkey   BLOB NOT NULL,                     -- RSA 2048 public
  tls_pin_sha256    BINARY(32),                        -- option 2, 4 의 cert pin
  registered_at     DATETIME(6) NOT NULL,
  last_invoked_at   DATETIME(6)
);

-- Optional 검증 endpoint — 없으면 auto-sign default 적용 (★ Risk)
CREATE TABLE designated_signers (
  id                BINARY(16) PRIMARY KEY,
  policy_rule_id    BINARY(16) NOT NULL,
  signer_user_id    BINARY(16),
  signer_user_group_id BINARY(16),
  internal_exchange_excluded BOOLEAN NOT NULL DEFAULT TRUE,
  KEY (policy_rule_id)
);