Callback phê duyệt

Trước khi tham gia bất kỳ phiên nào, cosigner gửi một yêu cầu có chữ ký đến máy chủ của bạn và chờ nhận một approve có chữ ký. Máy chủ của bạn nhận được dữ liệu byte thô cần ký, nhờ đó có thể xác minh giao dịch độc lập với Fystack.

Cosigner gửi yêu cầu có chữ ký đến callback, callback trả về approve hoặc reject
Click to open the full-size diagram

Cách hoạt động

  1. Cosigner xác minh session_start của orchestrator.
  2. Cosigner gửi POST một JWT RS256 đến URL của bạn (Content-Type: application/jwt).
  3. Máy chủ của bạn xác minh JWT, chạy chính sách và trả về 200 kèm một JWT RS256.
  4. Cosigner xác minh phản hồi và request_id, sau đó tham gia phiên.

Yêu cầu được gửi cho KEYGEN, SIGN và RESHARE. Hãy áp dụng chính sách giao dịch cho SIGN.

Thiết lập

Hai cặp khóa RSA-4096. Chỉ khóa công khai được trao đổi.

  • Khóa cosigner: Được tạo bởi cosigner-cli, dùng để ký yêu cầu. Cung cấp cosigner-public.pem cho máy chủ của bạn.
  • Khóa máy chủ: Được tạo trên máy chủ của bạn hoặc trong KMS, dùng để ký phản hồi. Cung cấp server-public.pem cho cosigner.

Cùng host: trỏ đến thư mục khóa của máy chủ. CLI sẽ đọc server-public.pem và ghi cosigner-public.pem. URL mặc định là http://host.docker.internal:8089/cosigner/approve.

cosigner-cli callback add ./node-callback-server/keys

Máy chủ từ xa:

cosigner-cli callback add https://risk.example.com/cosigner/approve \
  --server-public-key ./server-public.pem

CLI in ra đường dẫn và fingerprint của cosigner-public.pem; hãy cài đặt tệp này trên máy chủ của bạn. Thêm --ca ./ca.pem nếu dùng CA riêng.

callback add cập nhật config.yaml, đặt require_callback: true và khởi động lại cosigner (bỏ qua bằng --no-restart). cosigner-cli info hiển thị URL và fingerprint hiện tại.

config.yaml

approval:
  require_callback: true
callback:
  url: https://risk.example.com/cosigner/approve
  timeout: 30s
  retry_count: 1
  private_key:
    type: file
    config:
      path: /run/secrets/cosigner-private.der.hex
  response_public_key_path: /run/secrets/server-public.pem
  # ca_cert_path: /run/secrets/callback-ca.pem
  • Name
    approval.require_callback
    Type
    boolean
    Description

    Từ chối mọi phiên nếu không có callback. Giữ true trên production.

  • Name
    callback.url
    Type
    string
    Description

    Chỉ hỗ trợ HTTPS. Cho phép HTTP thường với host.docker.internal. /cosigner/approve sẽ được nối thêm nếu không có đường dẫn.

  • Name
    callback.timeout
    Type
    duration
    Description

    Tổng thời gian cho phép, bao gồm cả lần thử lại. Đồng thời là thời hạn của JWT yêu cầu. Mặc định 30s.

  • Name
    callback.retry_count
    Type
    integer
    Description

    Số lần thử lại khi gặp lỗi mạng hoặc 5xx, dùng cùng token. Tối đa và mặc định là 1.

  • Name
    callback.private_key
    Type
    secret provider
    Description

    Khóa ký RSA-4096 của cosigner, định dạng hex PKCS#8 DER.

  • Name
    callback.response_public_key_path
    Type
    string
    Description

    Khóa công khai RSA-4096 của máy chủ của bạn (PEM).

  • Name
    callback.ca_cert_path
    Type
    string
    Description

    Chứng chỉ CA cho chứng chỉ máy chủ riêng hoặc tự ký.

Yêu cầu

JWT header {"alg": "RS256", "typ": "JWT"}. Chỉ chấp nhận RS256 và xác minh bằng cosigner-public.pem trước khi đọc các claim.

  • Name
    request_id
    Type
    string (UUID)
    Description

    Duy nhất cho mỗi yêu cầu, đồng thời là jti. Hãy trả lại giá trị này. Dùng làm khóa idempotency.

  • Name
    iat, exp
    Type
    number
    Description

    exp = iat + timeout.

  • Name
    cosigner_id, workspace_id, participant_id
    Type
    string
    Description

    Định danh của cosigner.

  • Name
    session_id, orchestrator_id
    Type
    string
    Description

    Phiên và orchestrator đã khởi tạo phiên đó.

  • Name
    coordinator_sequence, coordinator_signature
    Type
    number, string
    Description

    Chữ ký orchestrator đã được xác minh. Ghi log để truy vết.

  • Name
    protocol
    Type
    string
    Description

    ECDSA hoặc EdDSA.

  • Name
    operation
    Type
    string
    Description

    KEYGEN, SIGN, RESHARE hoặc PING.

  • Name
    threshold, participants
    Type
    number, array
    Description

    participant_id, party_key, identity_public_key của từng thành viên.

  • Name
    key_id
    Type
    string
    Description

    Khóa ví.

  • Name
    signing_input
    Type
    string (base64)
    Description

    Chỉ với SIGN. Chính xác dữ liệu byte cần ký.

  • Name
    signing_input_sha256
    Type
    string (hex)
    Description

    Chỉ với SIGN. SHA-256 của signing_input.

  • Name
    signing_context
    Type
    object
    Description

    Chỉ với SIGN, tùy chọn. Chi tiết giao dịch từ Fystack, tối đa 64 KiB.

  • Name
    derivation
    Type
    object
    Description

    Chỉ với SIGN, tùy chọn. path và delta ECDSA không hardened.

  • Name
    new_threshold, new_participants
    Type
    number, array
    Description

    Chỉ với RESHARE.

Decoded SIGN request

{
  "request_id": "3f1c2a8e-7b0d-4d0c-9a51-2f6c4f0e9b12",
  "operation": "SIGN",
  "protocol": "ECDSA",
  "key_id": "8b0f3c4e-...",
  "signing_input": "q83vEjRWeJA...",
  "signing_input_sha256": "5d41402abc4b2a76...",
  "signing_context": {
    "version": 1,
    "apex_request_id": "wd_01J9...",
    "reference": { "type": "onchain_tx", "id": "..." },
    "requested_at": "2026-10-12T08:30:00Z",
    "chain": { "network": "ethereum", "chain_id": "1", "protocol": "ECDSA" },
    "wallet": { "wallet_id": "8b0f3c4e-...", "derivation_path": [44, 60, 0, 0, 0] },
    "tx": {
      "raw": "0x02f8...",
      "payload_hash": "0x5d41402a...",
      "verified": true,
      "verification_mode": "evm_transaction",
      "decoded": { "to_address": "0xAbC...", "value": "1000000000000000000" }
    }
  },
  "threshold": 1,
  "participants": [ ... ]
}

verification_mode: evm_transaction, solana_message, tron_transaction, bitcoin_transaction hoặc digest_only.

Phản hồi

HTTP 200, Content-Type: application/jwt, ký RS256 bằng khóa máy chủ của bạn. Tối đa 64 KiB. Chuyển hướng sẽ không được theo.

  • Name
    request_id
    Type
    string
    Description

    Phải khớp với yêu cầu.

  • Name
    action
    Type
    string
    Description

    approve hoặc reject. pong cho PING.

  • Name
    reason
    Type
    string
    Description

    Bắt buộc khi reject, tối đa 1024 byte, bỏ qua khi approve. Được ghi vào nhật ký kiểm toán.

  • Name
    exp
    Type
    number
    Required
    required
    Description

    Nên đặt ngắn, ví dụ 60 giây.

Response payload

200
{
  "request_id": "3f1c2a8e-7b0d-4d0c-9a51-2f6c4f0e9b12",
  "action": "reject",
  "reason": "destination_not_allowlisted",
  "iat": 1791794400,
  "exp": 1791794460,
  "jti": "b7e5..."
}

Kiểm tra hoạt động

Cosigner gửi "operation": "PING" khi khởi động và cứ mỗi 30 giây. Hãy trả lời "action": "pong" mà không cần chạy chính sách.

  • Ping khi khởi động thất bại: cosigner dừng lại.
  • Các lần ping sau thất bại: trạng thái sức khỏe hiển thị callback_unavailable và mọi yêu cầu ký bị từ chối cho đến khi callback phục hồi.

Máy chủ mẫu

cosigner-cli đi kèm một ví dụ Node.js trong examples/node-callback-server (express + jose).

server.js

app.post('/cosigner/approve', express.text({ type: 'application/jwt' }), async (req, res) => {
  try {
    // RS256 only, signed by the pinned cosigner key, with exp and request_id.
    const request = await verifier.verify(req.body.trim())

    if (request.operation === 'PING') {
      return res.type('application/jwt').send(await signer.sign(request, { action: 'pong' }))
    }

    let decision = { action: 'approve' }
    if (request.operation === 'SIGN' && !(await policyAllows(request))) {
      decision = { action: 'reject', reason: 'destination_not_allowlisted' }
    }

    res.type('application/jwt').send(await signer.sign(request, decision))
  } catch (err) {
    res.sendStatus(401)
  }
})

Danh sách kiểm tra cho production

  • Giữ require_callback: true. cosigner-cli callback remove sẽ tắt hoàn toàn cơ chế phê duyệt.
  • Mặc định từ chối.
  • Giải mã tx.raw và đối chiếu với signing_input.
  • Áp dụng danh sách cho phép, hạn mức và kiểm tra AML.
  • Đảm bảo idempotent theo request_id; các lần thử lại sẽ gửi lại cùng token.
  • Lưu khóa máy chủ trong KMS hoặc HSM. AWS KMS RSA_4096 với RSASSA_PKCS1_V1_5_SHA_256 hoạt động với RS256.
  • Phản hồi sớm, trong giới hạn thời gian chờ.
  • Thiết lập cảnh báo khi có callback_unavailable và khi có yêu cầu bị từ chối.