身份验证 (Authentication)
私有接口需要使用 Ethereum 钱包私钥进行签名。所有包含 signature 参数的请求都需要遵循以下流程。
签名规则
签名消息由以下部分按顺序拼接而成:
timestamp + method + path + body
timestamp: 毫秒时间戳method: HTTP 方法 (如 POST, GET, DELETE)path: 接口路径 (如 /api/v1/orders)body: 请求体 JSON 字符串 (如无 Body 则为空字符串)
签名算法
使用 secp256k1 曲线进行签名,消息需要使 用 Ethereum 的 Personal Sign 格式。
Python 代码示例
import time
import json
from eth_account import Account
from eth_account.messages import encode_defunct
def sign_request(private_key, method, path, body=""):
timestamp = int(time.time() * 1000)
# 构造消息
message_text = f"{timestamp}{method}{path}{body}"
message = encode_defunct(text=message_text)
# 签名
signed_message = Account.sign_message(message, private_key=private_key)
return {
"signature": signed_message.signature.hex(),
"timestamp": timestamp
}
# 示例:创建订单签名
body = json.dumps({
"symbol": "BTCUSDT",
"side": "buy",
"order_type": "limit",
"amount": "0.1",
"price": "65000"
})
auth_data = sign_request("0x...", "POST", "/api/v1/orders", body)
请求头 (Headers)
虽然签名参数通常放在请求体中,但我们也支持通过 Headers 传递。
| Header | 示例值 |
|---|---|
| X-ZTDX-TIMESTAMP | 1630000000000 |
登录流程 (Login Flow)
对于需要持久化 Session 的应用,可以先进行登录。
1. 获取 Nonce
在使用私钥登录前,必须先获取一个随机的 nonce。
请求
- Method:
GET - Path:
/api/v1/auth/nonce/:address - 特性:
nonce在成功登录后会 自增 1。- 在未登录前多次调用,返回的
nonce保持不变。
路径参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| address | string | 是 | Ethereum 地址(0x开头,42字符) |
响应示例
{
"nonce": 1,
"message": "Sign this message to login to ZTDX.\n\nAddress: 0x742d35cc6634c0532925a3b844bc9e7595f0beb\nNonce: 1"
}
响应字段
| 字段 | 类型 | 描述 |
|---|---|---|
| nonce | number | 当前用户的 nonce 值 |
| message | string | 需要签名的完整消息 |
2. 构建签名消息
您需要对以下格式的字符串进行签名:
Sign this message to login to ZTDX.
Address: {address}
Nonce: {nonce}
[!IMPORTANT]
{address}必须为全小写。- 消息中的换行符和空格由于 Docusaurus 显示原因,请确保与上述模板完全一致。
3. 登录请求
请求
- Method:
POST - Path:
/api/v1/auth/login - Content-Type:
application/json
请求参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| address | string | 是 | Ethereum 地址(小写) |
| signature | string | 是 | 对 nonce 消息的签名(0x开 头) |
| timestamp | number | 是 | Unix 时间戳(秒),需在5分钟内有效 |
请求示例
{
"address": "0x742d35cc6634c0532925a3b844bc9e7595f0beb",
"signature": "0x...",
"timestamp": 1704067200
}
响应示例
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": 1704153600
}
响应字段
| 字段 | 类型 | 描述 |
|---|---|---|
| token | string | JWT 认证令牌 |
| expires_at | number | 令牌过期时间(Unix 时间戳) |
错误响应
| HTTP 状态码 | 错误码 | 描述 |
|---|---|---|
| 400 | TIMESTAMP_EXPIRED | 时间戳已过期 |
| 400 | INVALID_SIGNATURE_FORMAT | 签名格式无效 |
| 401 | SIGNATURE_INVALID | 签名验证失败 |
| 404 | USER_NOT_FOUND | 用户不存在,请先获取 nonce |
| 500 | DATABASE_ERROR | 数据库错误 |
| 500 | JWT_GENERATION_FAILED | JWT 生成失败 |
使用 JWT Token
登录成功后,在所有需要认证的接口中,需要在请求头中携带 JWT Token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...