Skip to content

Signature Method

ED25519 Signature Method

Dipcoin Perpetual API uses ED25519 signature algorithm for authentication and order signing. This document describes the signature methods for different scenarios.

About ED25519

ED25519 is a modern elliptic curve signature scheme that provides: - High security with 128-bit security level - Fast signature generation and verification - Small signature size (64 bytes) - Native support in Sui blockchain

Signature Scenarios

1. JWT Authentication (Onboarding)

When authenticating to obtain JWT Token, you need to sign a specific message using your wallet.

Message Payload

The message to sign is a JSON object:

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

Signature Process

  1. Serialize the payload: Convert the JSON object to a UTF-8 string
  2. Sign with wallet: Use Sui wallet's signPersonalMessage method to sign the message
  3. Submit signature: Send the signature along with wallet address to the authentication endpoint

Implementation Example

Using Sui DApp Kit (React/TypeScript)

import { useSignPersonalMessage } from "@mysten/dapp-kit";

const { mutate: signPersonalMessage } = useSignPersonalMessage();

const payload = { "onboarding": "www.dipcoin.io" };

// Encode the JSON payload
const messageToSign = new TextEncoder().encode(JSON.stringify(payload));

signPersonalMessage(
    { message: messageToSign },
    {
        onSuccess: (result) => {
            // result.signature contains the signature
            // Submit to /api/authorize endpoint
            const authRequest = {
                userAddress: walletAddress,
                signature: result.signature,
                isTermAccepted: true
            };
            // ... make API call
        },
        onError: (error) => {
            console.error("Signature failed:", error);
        }
    }
);

Using 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);

// Verify signature
const isValid = await OrderSigner.verifySignature(
    payload, 
    sigPK.signature, 
    sigPK.publicKey
);

Signature Protocol

The authentication signature uses Sui PersonalMessage protocol, which adds intent bytes [3, 0, 0] and BCS-encoded message length before the actual message.

2. Order Signing

When placing an order, you need to sign the order data using ED25519.

Order Data Structure

interface Order {
    market: string;          // Market ID (PerpetualID)
    creator: string;          // Order owner's wallet address
    isLong: boolean;          // Direction: true for long, false for short
    reduceOnly: boolean;      // Reduce-only order
    postOnly: boolean;        // Post-only order (maker only)
    cancelOnRevert?: boolean; // Auto-cancel on revert
    orderbookOnly: boolean;   // Orderbook only
    ioc: boolean;            // Immediate or Cancel
    quantity: BigNumber;       // Order quantity (in base asset)
    price: BigNumber;        // Order price (in quote asset)
    leverage: BigNumber;      // Leverage multiplier
    expiration: BigNumber;   // Expiration timestamp (milliseconds)
    salt: BigNumber;         // Random salt for replay protection
}

Signature Process

The order signing process differs based on the signing method:

Method A: Wallet Client Signing (UI Wallet)

  1. Serialize Order: Convert order to hex string
  2. Calculate Hash: Compute SHA-256 hash of the serialized order
  3. Get Hash Hex: Convert hash bytes to hex string
  4. Sign Hash Hex: Use wallet's signPersonalMessage to sign the hash hex string

Method B: SDK Signing (Keypair)

  1. Serialize Order: Convert order to hex string
  2. Calculate Hash: Compute SHA-256 hash of the serialized order
  3. Sign Hash: Use ED25519 to sign the hash bytes directly

Implementation Example

Using Wallet Client (UI Wallet)

import { useSignPersonalMessage } from "@mysten/dapp-kit";
import { OrderSigner } from '@dipcoin/perp-library';

const { mutate: signPersonalMessage } = useSignPersonalMessage();

// 1. Build order object
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. Get order hash hex for wallet signing
const orderHashHex = OrderSigner.getOrderMessageForUIWallet(order);

// 3. Sign with wallet
signPersonalMessage(
    { message: new TextEncoder().encode(orderHashHex) },
    {
        onSuccess: (result) => {
            // Parse signature
            const sigPk = OrderSigner.parseFromUIWalletSignature(result.signature);

            // Verify signature
            OrderSigner.verifySignatureUsingOrder(order, sigPk.signature, sigPk.publicKey)
                .then(isValid => {
                    if (isValid) {
                        // Submit order with signature
                        submitOrder(order, sigPk.signature, sigPk.publicKey);
                    }
                });
        }
    }
);

Using SDK (Keypair)

import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import { OrderSigner } from '@dipcoin/perp-library';

const keypair = new Ed25519Keypair();
const order: Order = { /* ... */ };

// Sign order directly
const sigPK = await OrderSigner.signOrder(order, keypair);

// Verify signature
const isValid = await OrderSigner.verifySignatureUsingOrder(
    order,
    sigPK.signature,
    sigPK.publicKey
);

if (isValid) {
    // Submit order
    submitOrder(order, sigPK.signature, sigPK.publicKey);
}

Order Serialization Format

The order is serialized in the following format (all values are hex-encoded, padded to specified lengths):

[price (32 bytes)] + 
[quantity (32 bytes)] + 
[leverage (32 bytes)] + 
[salt (32 bytes)] + 
[expiration (16 bytes)] + 
[order flags (1 byte)] + 
[market (32 bytes)] + 
[creator (32 bytes)]

Order Flags (1 byte, bit flags): - 0th bit: ioc - 1st bit: postOnly - 2nd bit: reduceOnly - 3rd bit: isBuy (isLong) - 4th bit: orderbookOnly

Signature Format

The signature string format is:

{signature_hex}{signature_type}

Where signature_type is: - 0: Secp256k1 (EVM wallets) - 1: ED25519 Keypair - 2: ED25519 UI Wallet (PersonalMessage)

For ED25519, the signature type is appended as the last character.

3. Cancel Order Signing

When canceling orders, sign a JSON payload containing order hashes.

Message Payload

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

Implementation

The signing process is the same as JWT authentication (Onboarding):

const payload = {
    orderHashes: ["0x...", "0x..."]
};

const messageToSign = new TextEncoder().encode(JSON.stringify(payload));

signPersonalMessage(
    { message: messageToSign },
    {
        onSuccess: (result) => {
            // Submit cancel request with signature
        }
    }
);

Key Differences

Wallet Client vs SDK Signing

Aspect Wallet Client (UI) SDK (Keypair)
Protocol Sui PersonalMessage Direct ED25519
Message Hash hex string Hash bytes
Intent Bytes Added automatically Not needed
Use Case User-initiated orders Automated/backend

ED25519 vs Secp256k1

Algorithm Signature Type Message Format Use Case
ED25519 1 or 2 Hash of serialized order Sui native wallets
Secp256k1 0 Serialized order directly EVM-compatible wallets

Note

  • Secp256k1 signs the serialized order directly (the algorithm internally hashes it)
  • ED25519 signs the SHA-256 hash of the serialized order
  • Wallet client (UI) uses PersonalMessage protocol which adds intent bytes
  • SDK signing is more efficient as it directly signs the hash

Verification

All signatures can be verified using the OrderSigner utility:

// Verify authentication signature
const isValid = await OrderSigner.verifySignature(
    payload,
    signature,
    publicKey
);

// Verify order signature
const isValid = await OrderSigner.verifySignatureUsingOrder(
    order,
    signature,
    publicKey
);

Important Notes

Important

  • All numeric values in orders are multiplied by 10^18
  • Salt must be unique for each order to prevent replay attacks
  • Expiration timestamp is in milliseconds
  • Signature type is appended as the last character of the signature string
  • Wallet address must match the public key derived from the signature

Best Practices

  • Always verify signatures before submitting to the API
  • Use secure random number generation for salt
  • Set appropriate expiration times for orders
  • Store and manage keypairs securely
  • Use wallet client signing for user-initiated actions
  • Use SDK signing for automated trading systems