Ed25519 Signature

Some endpoints require Ed25519 signatures for security (indicated by the X-Signature header parameter).

Generating Your Ed25519 Keypair

Before you can sign requests, you need to generate an Ed25519 keypair. Here's how to generate one:

import nacl from "tweetnacl";

// Generate a new random Ed25519 keypair
const keyPair = nacl.sign.keyPair();

// Encode keys as base64 strings
const publicKey = Buffer.from(keyPair.publicKey).toString("base64");
const privateKey = Buffer.from(keyPair.secretKey).toString("base64");

// Output as JSON for easier parsing
console.log("publicKey:", publicKey);
console.log("privateKey:", privateKey);

Important: You need to register your public key with the ORO Bank platform before you can use it to sign requests. Keep your private key secure and never share it.

Overview

The signature generation process involves:

  1. Create Hash Payload - Construct a JSON object with specific fields
  2. Stringify with Sorted Keys - JSON.stringify with keys in alphabetical order
  3. SHA-256 Hash - Hash the stringified JSON
  4. Sign - Sign the hash with your Ed25519 private key
  5. Base64 Encode - Encode the signature in base64
  6. Include in Header - Add to the X-Signature header

Hash Payload Structure

For Transfer Orders (POST /accounts/{accountId}/orders)

{
  "method": "POST",
  "path": "/accounts/123/orders",
  "nonce": "1704528000000",
  "amount": "100000",
  "recipientID": 368
}

Important: Only include fields that are in the request body (amount, recipientID) plus method, path, and nonce.

Code Examples

JavaScript

import crypto from "crypto";
import nacl from "tweetnacl";

function generateSignature(payload, privateKeyBase64) {
  // 1. Sort keys and stringify
  const payloadString = JSON.stringify(payload);

  // 2. SHA-256 hash
  const hash = crypto.createHash("sha256").update(payloadString).digest();

  // 3. Sign with Ed25519 private key
  const privateKey = Buffer.from(privateKeyBase64, "base64");
  const signature = nacl.sign.detached(hash, privateKey);

  // 4. Base64 encode
  return Buffer.from(signature).toString("base64");
}

// Usage example
const payload = {
  method: "POST",
  path: "/accounts/123/orders",
  nonce: Date.now().toString(),
  amount: "100000",
  recipientID: 368,
};

const signature = generateSignature(payload, yourPrivateKey);

// Make API call
await fetch("https://bank.bank.place/accounts/123/orders", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + accessToken,
    "Content-Type": "application/json",
    "X-Nonce": payload.nonce,
    "X-Signature": signature,
    "Workspace-ID": "456",
  },
  body: JSON.stringify({
    amount: "100000",
    type: "instant",
    recipientID: 368,
  }),
});

Go

package main

import (
    "crypto/ed25519"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "time"
)

type NewOrderHashPayload struct {
    Method      string `json:"method"`
    Path        string `json:"path"`
    Nonce       string `json:"nonce"`
    Amount      string `json:"amount"`
    RecipientID int    `json:"recipientID"`
}

func generateSignature(payload NewOrderHashPayload, privateKeyBase64 string) (string, error) {
    payloadBytes, err := json.Marshal(payload)
    if err != nil {
        return "", err
    }

    // 2. SHA-256 hash
    hash := sha256.Sum256(payloadBytes)

    // 3. Decode private key
    privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyBase64)
    if err != nil {
        return "", err
    }

    // 4. Sign with Ed25519
    signature := ed25519.Sign(ed25519.PrivateKey(privateKeyBytes), hash[:])

    // 5. Base64 encode
    return base64.StdEncoding.EncodeToString(signature), nil
}

// Usage example
func main() {
    // Create struct instance with the same data as the original map
    payload := NewOrderHashPayload{
        Method:      "POST",
        Path:        "/accounts/123/orders",
        Nonce:       fmt.Sprintf("%d", time.Now().UnixMilli()),
        Amount:      "100000",
        RecipientID: 368,
    }

    // Replace 'yourPrivateKey' with actual base64-encoded private key
    signature, err := generateSignature(payload, "yourPrivateKey")
    if err != nil {
        fmt.Printf("Error generating signature: %v\n", err)
        return
    }

    // Make API call with signature in X-Signature header
    fmt.Printf("Generated signature: %s\n", signature)
}

Important Notes

Warning: Never include your private key in client-side code or commit it to version control.

Info: The nonce must be a Unix timestamp in milliseconds. It's used for deduplication and replay attack prevention.

Info: The hash payload must include ALL request body fields that will be sent, plus method, path, and nonce.

Success: Keys in the JSON payload are automatically sorted alphabetically by most JSON libraries (Go's json.Marshal, Python's json.dumps with sort_keys=True, JavaScript's JSON.stringify with sorted keys).