签名方法
ED25519 签名方法
Dipcoin Perpetual API 使用 ED25519 签名算法进行身份认证和订单签名。本文档介绍不同场景下的签名方法。
关于 ED25519
ED25519 是一种现代椭圆曲线签名方案,具有以下特点: - 高安全性(128 位安全级别) - 快速的签名生成和验证 - 签名体积小(64 字节) - Sui 区块链原生支持
签名场景
1. JWT 认证(登录)
在获取 JWT Token 进行身份认证时,需要使用钱包对特定消息进行签名。
消息 Payload
待签名的消息是一个 JSON 对象:
签名流程
- 序列化 Payload:将 JSON 对象转换为 UTF-8 字符串
- 钱包签名:使用 Sui 钱包的
signPersonalMessage方法对消息进行签名 - 提交签名:将签名和钱包地址一起提交到认证接口
实现示例
使用 Sui DApp Kit (React/TypeScript)
import { useSignPersonalMessage } from "@mysten/dapp-kit";
const { mutate: signPersonalMessage } = useSignPersonalMessage();
const payload = { "onboarding": "www.dipcoin.io" };
// 编码 JSON payload
const messageToSign = new TextEncoder().encode(JSON.stringify(payload));
signPersonalMessage(
{ message: messageToSign },
{
onSuccess: (result) => {
// result.signature 包含签名
// 提交到 /api/authorize 接口
const authRequest = {
userAddress: walletAddress,
signature: result.signature,
isTermAccepted: true
};
// ... 发起 API 请求
},
onError: (error) => {
console.error("签名失败:", error);
}
}
);
使用 SDK (Keypair)
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import { OrderSigner } from '@dipcoin/perp-library';
const keypair = new Ed25519Keypair();
const payload = { "onboarding": "www.dipcoin.io" };
const sigPK = await OrderSigner.signPayload(payload, keypair);
// 验证签名
const isValid = await OrderSigner.verifySignature(
payload,
sigPK.signature,
sigPK.publicKey
);
签名协议
认证签名使用 Sui PersonalMessage 协议,该协议在实际消息前添加了意图字节 [3, 0, 0] 和 BCS 编码的消息长度。
2. 订单签名
在下单时,需要使用 ED25519 对订单数据进行签名。
订单数据结构
interface Order {
market: string; // 市场 ID (PerpetualID)
creator: string; // 订单所有者的钱包地址
isLong: boolean; // 方向:true 为做多,false 为做空
reduceOnly: boolean; // 只减仓订单
postOnly: boolean; // 只做 Maker
cancelOnRevert?: boolean; // 交易失败时自动取消
orderbookOnly: boolean; // 仅限订单簿
ioc: boolean; // 立即成交或取消
quantity: BigNumber; // 订单数量(基础资产)
price: BigNumber; // 订单价格(报价资产)
leverage: BigNumber; // 杠杆倍数
expiration: BigNumber; // 过期时间戳(毫秒)
salt: BigNumber; // 随机盐值,用于防止重放攻击
}
签名流程
根据签名方式的不同,订单签名流程也有所不同:
方式 A:钱包客户端签名(UI 钱包)
- 序列化订单:将订单转换为十六进制字符串
- 计算哈希:计算序列化订单的 SHA-256 哈希值
- 获取哈希十六进制:将哈希字节转换为十六进制字符串
- 签名哈希十六进制:使用钱包的
signPersonalMessage对哈希十六进制字符串进行签名
方式 B:SDK 签名(Keypair)
- 序列化订单:将订单转换为十六进制字符串
- 计算哈希:计算序列化订单的 SHA-256 哈希值
- 签名哈希:使用 ED25519 直接对哈希字节进行签名
实现示例
使用钱包客户端(UI 钱包)
import { useSignPersonalMessage } from "@mysten/dapp-kit";
import { OrderSigner } from '@dipcoin/perp-library';
const { mutate: signPersonalMessage } = useSignPersonalMessage();
// 1. 构建订单对象
const order: Order = {
market: "0x...",
creator: walletAddress,
isLong: true,
reduceOnly: false,
postOnly: false,
orderbookOnly: false,
ioc: false,
quantity: new BigNumber("1000000000000000000"), // 1.0 * 10^18
price: new BigNumber("50000000000000000000"), // 50.0 * 10^18
leverage: new BigNumber("5000000000000000000"), // 5.0 * 10^18
expiration: new BigNumber(Date.now() + 3600000),
salt: generateRandomSalt()
};
// 2. 获取订单哈希十六进制用于钱包签名
const orderHashHex = OrderSigner.getOrderMessageForUIWallet(order);
// 3. 使用钱包签名
signPersonalMessage(
{ message: new TextEncoder().encode(orderHashHex) },
{
onSuccess: (result) => {
// 解析签名
const sigPk = OrderSigner.parseFromUIWalletSignature(result.signature);
// 验证签名
OrderSigner.verifySignatureUsingOrder(order, sigPk.signature, sigPk.publicKey)
.then(isValid => {
if (isValid) {
// 提交订单和签名
submitOrder(order, sigPk.signature, sigPk.publicKey);
}
});
}
}
);
使用 SDK (Keypair)
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import { OrderSigner } from '@dipcoin/perp-library';
const keypair = new Ed25519Keypair();
const order: Order = { /* ... */ };
// 直接签名订单
const sigPK = await OrderSigner.signOrder(order, keypair);
// 验证签名
const isValid = await OrderSigner.verifySignatureUsingOrder(
order,
sigPK.signature,
sigPK.publicKey
);
if (isValid) {
// 提交订单
submitOrder(order, sigPK.signature, sigPK.publicKey);
}
订单序列化格式
订单按以下格式序列化(所有值均为十六进制编码,填充到指定长度):
[价格 (32 字节)] +
[数量 (32 字节)] +
[杠杆 (32 字节)] +
[盐值 (32 字节)] +
[过期时间 (16 字节)] +
[订单标志位 (1 字节)] +
[市场 (32 字节)] +
[创建者 (32 字节)]
订单标志位(1 字节,位标志):
- 第 0 位:ioc
- 第 1 位:postOnly
- 第 2 位:reduceOnly
- 第 3 位:isBuy (isLong)
- 第 4 位:orderbookOnly
签名格式
签名字符串格式为:
其中 签名类型 为:
- 0:Secp256k1(EVM 钱包)
- 1:ED25519 Keypair
- 2:ED25519 UI 钱包(PersonalMessage)
对于 ED25519,签名类型作为最后一个字符附加。
3. 撤单签名
取消订单时,需要对包含订单哈希的 JSON payload 进行签名。
消息 Payload
实现
签名流程与 JWT 认证(登录)相同:
const payload = {
orderHashes: ["0x...", "0x..."]
};
const messageToSign = new TextEncoder().encode(JSON.stringify(payload));
signPersonalMessage(
{ message: messageToSign },
{
onSuccess: (result) => {
// 提交撤单请求和签名
}
}
);
主要区别
钱包客户端 vs SDK 签名
| 方面 | 钱包客户端(UI) | SDK(Keypair) |
|---|---|---|
| 协议 | Sui PersonalMessage | 直接 ED25519 |
| 消息 | 哈希十六进制字符串 | 哈希字节 |
| 意图字节 | 自动添加 | 不需要 |
| 使用场景 | 用户发起的订单 | 自动化/后端 |
ED25519 vs Secp256k1
| 算法 | 签名类型 | 消息格式 | 使用场景 |
|---|---|---|---|
| ED25519 | 1 或 2 |
序列化订单的哈希 | Sui 原生钱包 |
| Secp256k1 | 0 |
直接序列化订单 | EVM 兼容钱包 |
注意
- Secp256k1 直接对序列化订单进行签名(算法内部会对其进行哈希)
- ED25519 对序列化订单的 SHA-256 哈希进行签名
- 钱包客户端(UI)使用 PersonalMessage 协议,会添加意图字节
- SDK 签名更高效,因为它直接对哈希进行签名
验证
所有签名都可以使用 OrderSigner 工具进行验证:
// 验证认证签名
const isValid = await OrderSigner.verifySignature(
payload,
signature,
publicKey
);
// 验证订单签名
const isValid = await OrderSigner.verifySignatureUsingOrder(
order,
signature,
publicKey
);
重要提示
重要
- 订单中的所有数值都乘以 10^18
- Salt 必须对每个订单唯一,以防止重放攻击
- 过期时间戳以毫秒为单位
- 签名类型作为签名字符串的最后一个字符附加
- 钱包地址必须与从签名派生的公钥匹配
最佳实践
- 在提交到 API 之前始终验证签名
- 使用安全的随机数生成器生成 salt
- 为订单设置适当的过期时间
- 安全地存储和管理密钥对
- 用户发起的操作使用钱包客户端签名
- 自动化交易系统使用 SDK 签名