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