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:
- Create Hash Payload - Construct a JSON object with specific fields
- Stringify with Sorted Keys - JSON.stringify with keys in alphabetical order
- SHA-256 Hash - Hash the stringified JSON
- Sign - Sign the hash with your Ed25519 private key
- Base64 Encode - Encode the signature in base64
- Include in Header - Add to the
X-Signatureheader
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).