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:
Signature Process
- Serialize the payload: Convert the JSON object to a UTF-8 string
- Sign with wallet: Use Sui wallet's
signPersonalMessagemethod to sign the message - 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)
- Serialize Order: Convert order to hex string
- Calculate Hash: Compute SHA-256 hash of the serialized order
- Get Hash Hex: Convert hash bytes to hex string
- Sign Hash Hex: Use wallet's
signPersonalMessageto sign the hash hex string
Method B: SDK Signing (Keypair)
- Serialize Order: Convert order to hex string
- Calculate Hash: Compute SHA-256 hash of the serialized order
- 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:
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
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