Signature Format Guide

Developer documentation for AEKnow login signature

Overview

AEKnow uses Ed25519 message signature to verify identity. Users sign a challenge message, and the server verifies the signature comes from the private key holder of the corresponding address.

Flow: Request Challenge → Sign Message → Submit Address|Signature → Server Verify
Challenge Message Format
Simple Mode (Recommended)
AEKnow Login
Nonce: {16-byte random hex}
Time: {Unix timestamp}
Standard Mode
AEKnow Login Challenge
Address: {ak_xxx address}
Nonce: {32-byte random hex}
Timestamp: {Unix timestamp}
Address Format

ak_xxx - Base58Check encoded Ed25519 public key

ak_ + Base58Check(public_key_32bytes)
Decode Address to Get Public Key
JavaScript
import { decode } from '@aeternity/aepp-sdk';
const publicKey = decode(address); // Uint8Array(32)
Python
import base58
address = "ak_2swhLkgBPeeADxVTAby..."
public_key = base58.b58decode_check(address[3:])  # 32 bytes
Signature Format
sg_xxx (Recommended)

Aeternity standard format, Base58Check encoded

sg_ + Base58Check(signature_64bytes)
Hex Format

64-byte signature as hexadecimal

{128 hex characters}
Signature Methods

Ed25519 sign on raw message UTF-8 bytes

signature = ed25519_sign(private_key, message.encode('utf-8'))

Aeternity SDK default format with fixed prefix

prefix = "aeternity Signed Message:\n" + uint32_be(message_length)
full_message = prefix + message_bytes
signature = ed25519_sign(private_key, full_message)

Sign on Blake2b hash of the message

hash = blake2b(message.encode('utf-8'), digest_size=32)
signature = ed25519_sign(private_key, hash)
Note: Server will try to verify methods A, B, C in sequence.
Code Examples
import { 
  generateKeyPair, 
  sign, 
  encode, 
  Encoding 
} from '@aeternity/aepp-sdk';
import { mnemonicToSeed } from '@aeternity/bip39';
import { derivePath } from 'ed25519-hd-key';

async function signChallenge(mnemonic, challenge) {
  // Derive key
  const seed = await mnemonicToSeed(mnemonic);
  const { key } = derivePath("m/44'/457'/0'/0'/0'", seed.toString('hex'));
  const keyPair = generateKeyPair(key);
  
  // Sign
  const messageBytes = Buffer.from(challenge, 'utf-8');
  const signature = sign(messageBytes, keyPair.secretKey);
  
  // Encode as sg_ format
  const sgSignature = encode(signature, Encoding.Signature);
  
  return {
    address: keyPair.publicKey,  // ak_xxx
    signature: sgSignature       // sg_xxx
  };
}
import nacl.signing
import base58

def sign_message(private_key_bytes: bytes, message: str) -> tuple[str, str]:
    """
    Sign message
    
    Args:
        private_key_bytes: 32-byte private key
        message: Message to sign
    
    Returns:
        (address, signature) - ak_xxx and sg_xxx format
    """
    # Create signing key
    signing_key = nacl.signing.SigningKey(private_key_bytes)
    verify_key = signing_key.verify_key
    
    # Generate address
    public_key = bytes(verify_key)
    address = "ak_" + base58.b58encode_check(public_key).decode()
    
    # Sign
    message_bytes = message.encode('utf-8')
    signed = signing_key.sign(message_bytes)
    signature_bytes = signed.signature
    
    # Encode signature
    signature = "sg_" + base58.b58encode_check(signature_bytes).decode()
    
    return address, signature

# Usage:
# address, sig = sign_message(private_key, challenge)
# print(f"{address}|{sig}")
# Sign message with aecli
aecli account sign <keystore.json> "AEKnow Login\nNonce: abc123\nTime: 1736265600"

# Output: sg_xxx
API Endpoints
GET/auth/simple-challenge

Get challenge message

Response:
{
  "success": true,
  "data": {
    "challenge_id": "abc123...",
    "challenge": "AEKnow Login\nNonce: xxx\nTime: 123",
    "expires_in": 300
  }
}
POST/auth/simple-login

Submit login

Request:
{
  "challenge_id": "abc123...",
  "address": "ak_xxx",
  "signature": "sg_xxx"
}

Response:
{
  "success": true,
  "data": {
    "address": "ak_xxx",
    "login_method": "simple_sign"
  }
}
Submit Format

Single field format for login:

{address}|{signature}

Example:

ak_2swhLkgBPeeADxVTAbyJUcoF5bXEzQq3tQqyJmKYJ8e9G9DaeL|sg_7YzR3xKPmKvNjBdG5F8hXq...
Dependencies
Python
pip install pynacl base58 mnemonic
JavaScript
npm install @aeternity/aepp-sdk
Security Notes
  • Private Key Safety - Private key and mnemonic should only be used locally, never transmit to server
  • Challenge Expiration - Challenge valid for 5 minutes, need to refresh if expired
  • One-time Use - Each challenge can only be used once
  • Unique Nonce - Prevents replay attacks