Skip to content

签名方法

ED25519 签名方法

Dipcoin Perpetual API 使用 ED25519 签名算法进行身份认证和订单签名。本文档介绍不同场景下的签名方法。

关于 ED25519

ED25519 是一种现代椭圆曲线签名方案,具有以下特点: - 高安全性(128 位安全级别) - 快速的签名生成和验证 - 签名体积小(64 字节) - Sui 区块链原生支持

签名场景

1. JWT 认证(登录)

在获取 JWT Token 进行身份认证时,需要使用钱包对特定消息进行签名。

消息 Payload

待签名的消息是一个 JSON 对象:

{
    "onboarding": "www.dipcoin.io"
}

签名流程

  1. 序列化 Payload:将 JSON 对象转换为 UTF-8 字符串
  2. 钱包签名:使用 Sui 钱包的 signPersonalMessage 方法对消息进行签名
  3. 提交签名:将签名和钱包地址一起提交到认证接口

实现示例

使用 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 钱包)

  1. 序列化订单:将订单转换为十六进制字符串
  2. 计算哈希:计算序列化订单的 SHA-256 哈希值
  3. 获取哈希十六进制:将哈希字节转换为十六进制字符串
  4. 签名哈希十六进制:使用钱包的 signPersonalMessage 对哈希十六进制字符串进行签名

方式 B:SDK 签名(Keypair)

  1. 序列化订单:将订单转换为十六进制字符串
  2. 计算哈希:计算序列化订单的 SHA-256 哈希值
  3. 签名哈希:使用 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

{
    "orderHashes": [
        "0xabc123...",
        "0xdef456...",
        "..."
    ]
}

实现

签名流程与 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 12 序列化订单的哈希 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 签名