Key API
The executor validates a key with one request. Every response is signed with Ed25519, so a fake server or a replayed "valid" reply is rejected on the client.
Validate a key
POST https://vyrex.win/api/v1/validate with a JSON body:
{
"key": "VYX-XXXXX-XXXXX-XXXXX-XXXXX",
"hwid": "<stable hardware id, 8-256 chars>",
"nonce": "<fresh random string, 16-128 chars of A-Z a-z 0-9 _ ->"
}
The first successful call locks the key to that hwid. The response always echoes your nonce:
{ "ok": true, "code": "OK", "nonce": "…", "expires_at": 1792000000000,
"tier": "free", "hwid": "3f9a1c…", "ts": 1791913600000, "sig": "<base64url>" }
Error codes (with "ok": false): INVALID_KEY, EXPIRED, REVOKED, BANNED, HWID_MISMATCH, BAD_HWID, RATE_LIMITED.
Verify the signature
Rebuild this exact string from the response and verify sig against it with the public key. Reject the reply if the signature fails, the nonce isn't the one you sent, or ts is more than two minutes from your clock.
vyrex-v1|{nonce}|{ok ? 1 : 0}|{code}|{expires_at or 0}|{ts}|{hwid or ""}
Public key (raw Ed25519, base64). Embed it in the executor; it's also served at /api/v1/pubkey:
ndNhPPdl0H/CmCLWMUF7Fr1LPB0X4BYfvm6cbyOVr2A=
Example (Rust, for the Tauri app)
// Cargo: reqwest = { version = "0.12", features = ["json", "blocking"] }, serde_json = "1",
// ed25519-dalek = "2", base64 = "0.22", rand = "0.8"
use base64::{engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}, Engine};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
const PUBKEY: &str = "ndNhPPdl0H/CmCLWMUF7Fr1LPB0X4BYfvm6cbyOVr2A=";
pub fn validate(key: &str, hwid: &str) -> Result<serde_json::Value, String> {
let nonce: String = URL_SAFE_NO_PAD.encode(rand::random::<[u8; 24]>());
let r: serde_json::Value = reqwest::blocking::Client::new()
.post("https://vyrex.win/api/v1/validate")
.json(&serde_json::json!({ "key": key, "hwid": hwid, "nonce": nonce }))
.send().map_err(|e| e.to_string())?
.json().map_err(|e| e.to_string())?;
let s = |k: &str| r[k].as_str().unwrap_or("").to_string();
let n = |k: &str| r[k].as_i64().unwrap_or(0);
let msg = format!("vyrex-v1|{}|{}|{}|{}|{}|{}",
s("nonce"), if r["ok"].as_bool() == Some(true) { 1 } else { 0 },
s("code"), n("expires_at"), n("ts"), s("hwid"));
let pk: [u8; 32] = STANDARD.decode(PUBKEY).unwrap().try_into().unwrap();
let sig: [u8; 64] = URL_SAFE_NO_PAD.decode(s("sig")).map_err(|_| "bad sig")?
.try_into().map_err(|_| "bad sig")?;
VerifyingKey::from_bytes(&pk).unwrap()
.verify(msg.as_bytes(), &Signature::from_bytes(&sig))
.map_err(|_| "signature check failed")?;
if s("nonce") != nonce { return Err("nonce mismatch".into()); }
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis() as i64;
if (now - n("ts")).abs() > 120_000 { return Err("stale response".into()); }
Ok(r)
}
Desktop sign-in
The app's Discord sign-in posts to POST https://vyrex.win/auth/discord with { code, redirectUri, mode, licenseKey } and gets back { username, avatar, discordId, licensed, plan, expiresAt, token }. Use the token later with GET /api/v1/session and Authorization: Bearer <token> to refresh the license state.