diff --git a/apps/boko-framework/README.md b/apps/boko-framework/README.md
new file mode 100644
index 0000000..d5f7856
--- /dev/null
+++ b/apps/boko-framework/README.md
@@ -0,0 +1,177 @@
+# Boko Framework
+
+**Async web framework for Bux — inspired by FastAPI.**
+
+Boko is a lightweight, multi-threaded web framework that brings FastAPI-style routing to the Bux programming language. It handles HTTP parsing, path pattern matching, query parameter extraction, and response building so you can focus on your application logic.
+
+## Quick Start
+
+```bash
+cd apps/boko-framework
+../../buxc build
+./boko-framework
+```
+
+Open `http://localhost:8080` — you'll see the demo landing page.
+
+## How It Works
+
+Boko follows a **single-dispatch-function** pattern. You define one function — `Boko_Router` — and the framework calls it for every incoming request:
+
+```bux
+import Boko::{Request, Response, Response_Json, Response_Html, Response_NotFound,
+ Path_Match, Request_GetQuery, Request_GetPathParam,
+ App, App_New, App_Run};
+
+func Boko_Router(req: Request) -> Response {
+ // Route: GET /
+ if String_Eq(req.path, "/") {
+ return Response_Html("
Hello Boko!
");
+ }
+
+ // Route: GET /api/health
+ if String_Eq(req.path, "/api/health") {
+ return Response_Json("{\"status\":\"ok\"}");
+ }
+
+ // Route: GET /users/{id} — path parameter
+ if Path_Match("/users/{id}", req.path, &req) {
+ let id: String = Request_GetPathParam(&req, "id");
+ // Build JSON response with id
+ return Response_Json(...);
+ }
+
+ // Route: GET /search?q=... — query parameter
+ if String_Eq(req.path, "/search") {
+ let q: String = Request_GetQuery(&req, "q");
+ return Response_Json(...);
+ }
+
+ return Response_NotFound();
+}
+
+func Main() -> int {
+ let app: App = App_New(8080, 4); // port, threads
+ App_Run(&app);
+ return 0;
+}
+```
+
+## API Reference
+
+### Request
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `method` | `HttpVerb` | GET, POST, PUT, DELETE |
+| `path` | `String` | Request path (without query string) |
+| `body` | `String` | Request body (for POST/PUT) |
+| `headerCount` | `int` | Number of headers |
+
+| Function | Returns | Description |
+|----------|---------|-------------|
+| `Request_GetHeader(req, name)` | `String` | Get header value by name |
+| `Request_GetQuery(req, name)` | `String` | Get query parameter by name |
+| `Request_GetPathParam(req, name)` | `String` | Get extracted path parameter |
+
+### Response
+
+| Constructor | Content-Type | Status |
+|-------------|-------------|--------|
+| `Response_Html(html)` | `text/html` | 200 |
+| `Response_Json(json)` | `application/json` | 200 |
+| `Response_Text(text)` | `text/plain` | 200 |
+| `Response_Redirect(url)` | — | 302 |
+| `Response_NotFound()` | `application/json` | 404 |
+| `Response_Error(code, msg)` | `application/json` | custom |
+| `Response_NoContent()` | — | 204 |
+
+### Path Matching
+
+`Path_Match(pattern, actualPath, req)` matches a pattern with `{param}` placeholders:
+
+```bux
+// Pattern: /users/{id}/posts/{postId}
+// Actual: /users/42/posts/7
+// Extracts: id=42, postId=7
+
+if Path_Match("/users/{id}/posts/{postId}", req.path, &req) {
+ let userId: String = Request_GetPathParam(&req, "id"); // "42"
+ let postId: String = Request_GetPathParam(&req, "postId"); // "7"
+}
+```
+
+The function returns `true` if the pattern matches and populates `req.pathParamKeys` / `req.pathParamValues`.
+
+### App
+
+```bux
+let app: App = App_New(8080, 4); // port 8080, 4 worker threads
+App_Run(&app); // blocks, handles requests
+```
+
+## Architecture
+
+```
+Incoming connection
+ │
+ ▼
+Net_Accept() ─── worker thread (1 of N)
+ │
+ ▼
+Net_Recv() → raw HTTP bytes
+ │
+ ▼
+Request_Parse() → method, path, query, headers, body
+ │
+ ▼
+Boko_Router(req) ←── YOUR CODE
+ │
+ ▼
+Response_Build() → HTTP/1.1 response string
+ │
+ ▼
+Net_Send() → bytes to client
+ │
+ ▼
+Net_Close()
+```
+
+## Endpoints in Demo App
+
+| Method | Path | Description |
+|--------|------|-------------|
+| GET | `/` | Landing page (HTML) |
+| GET | `/api/health` | Health check (JSON) |
+| GET | `/api/info` | Framework info (JSON) |
+| GET | `/hello?name=X` | Query param demo |
+| GET | `/users/{id}` | Path param demo |
+| GET | `/posts/{id}/comments/{cid}` | Multi path param demo |
+| GET | `/redirect` | 302 redirect to `/` |
+| POST | `/api/echo` | Echo request body |
+
+## Project Structure
+
+```
+apps/boko-framework/
+├── bux.toml # Package manifest
+├── README.md # This file
+└── src/
+ ├── Boko.bux # Framework core (~500 lines)
+ └── Main.bux # Example app (~150 lines)
+```
+
+## Design Philosophy
+
+Boko is intentionally simple. Instead of complex DSLs or code generation, it gives you:
+
+- **One function to write** — `Boko_Router(req) -> Response`
+- **Direct control** — you write plain if/else or match for routing
+- **No magic** — path params, query params, headers are explicit function calls
+- **Fast** — multi-threaded accept loop, zero allocations where possible
+
+This is the same philosophy as Go's `net/http` — simple, explicit, composable.
+
+## License
+
+Part of the Bux project. See root [LICENSE](../../LICENSE).
diff --git a/apps/boko-framework/build.log b/apps/boko-framework/build.log
new file mode 100644
index 0000000..903634c
--- /dev/null
+++ b/apps/boko-framework/build.log
@@ -0,0 +1 @@
+EXIT:137
diff --git a/apps/boko-framework/build_output.log b/apps/boko-framework/build_output.log
new file mode 100644
index 0000000..e69de29
diff --git a/apps/boko-framework/bux.toml b/apps/boko-framework/bux.toml
new file mode 100644
index 0000000..57209c1
--- /dev/null
+++ b/apps/boko-framework/bux.toml
@@ -0,0 +1,8 @@
+[Package]
+Name = "boko-framework"
+Version = "0.1.0"
+Type = "bin"
+Description = "Boko — async web framework for Bux, inspired by FastAPI"
+
+[Build]
+Output = "Bin"
diff --git a/apps/boko-framework/check_output.log b/apps/boko-framework/check_output.log
new file mode 100644
index 0000000..e69de29
diff --git a/apps/boko-framework/fresh.log b/apps/boko-framework/fresh.log
new file mode 100644
index 0000000..903634c
--- /dev/null
+++ b/apps/boko-framework/fresh.log
@@ -0,0 +1 @@
+EXIT:137
diff --git a/apps/boko-framework/lir2.log b/apps/boko-framework/lir2.log
new file mode 100644
index 0000000..9148f69
--- /dev/null
+++ b/apps/boko-framework/lir2.log
@@ -0,0 +1 @@
+EXIT:143
diff --git a/apps/boko-framework/lir_check.log b/apps/boko-framework/lir_check.log
new file mode 100644
index 0000000..903634c
--- /dev/null
+++ b/apps/boko-framework/lir_check.log
@@ -0,0 +1 @@
+EXIT:137
diff --git a/apps/boko-framework/lir_demo.log b/apps/boko-framework/lir_demo.log
new file mode 100644
index 0000000..79abcd7
--- /dev/null
+++ b/apps/boko-framework/lir_demo.log
@@ -0,0 +1,2 @@
+bash: ред 1: ../../buxc_lir: Няма такъв файл или директория
+EXIT:127
diff --git a/apps/boko-framework/lir_demo2.log b/apps/boko-framework/lir_demo2.log
new file mode 100644
index 0000000..9148f69
--- /dev/null
+++ b/apps/boko-framework/lir_demo2.log
@@ -0,0 +1 @@
+EXIT:143
diff --git a/apps/boko-framework/lir_demo3.log b/apps/boko-framework/lir_demo3.log
new file mode 100644
index 0000000..9148f69
--- /dev/null
+++ b/apps/boko-framework/lir_demo3.log
@@ -0,0 +1 @@
+EXIT:143
diff --git a/apps/boko-framework/lir_ver.log b/apps/boko-framework/lir_ver.log
new file mode 100644
index 0000000..3b4b4d6
--- /dev/null
+++ b/apps/boko-framework/lir_ver.log
@@ -0,0 +1,2 @@
+bux 0.1.0 (bootstrap)
+EXIT:0
diff --git a/apps/boko-framework/make.log b/apps/boko-framework/make.log
new file mode 100644
index 0000000..c1a4b03
--- /dev/null
+++ b/apps/boko-framework/make.log
@@ -0,0 +1,40 @@
+nim c -o:buxc -d:release --opt:size bootstrap/main.nim
+Hint: used config file '/etc/nim/nim.cfg' [Conf]
+Hint: used config file '/etc/nim/config.nims' [Conf]
+...........................................................................................................................................
+/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(340, 12) Hint: 'check3' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(350, 12) Hint: 'checkEq' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(64, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(84, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
+..
+/home/ziko/z-git/bux/bux/bootstrap/parser.nim(663, 7) Hint: 'loc' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/parser.nim(1001, 9) Hint: 'isVar' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/parser.nim(59, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
+.......
+/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1150, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1155, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1165, 9) Hint: 'subjectType' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1200, 9) Hint: 'operand' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/sema.nim(94, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
+.
+/home/ziko/z-git/bux/bux/bootstrap/manifest.nim(93, 11) Hint: 'val' is declared but not used [XDeclaredButNotUsed]
+..
+/home/ziko/z-git/bux/bux/bootstrap/hir_lower.nim(1057, 9) Hint: 'loweredIter' is declared but not used [XDeclaredButNotUsed]
+.
+/home/ziko/z-git/bux/bux/bootstrap/lir.nim(5, 8) Warning: imported and not used: 'types' [UnusedImport]
+.
+/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(419, 9) Hint: 't' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(595, 9) Hint: 'bodyLbl' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(734, 11) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(746, 9) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(6, 8) Warning: imported and not used: 'ast' [UnusedImport]
+.
+/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(20, 6) Hint: 'emit' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(46, 6) Hint: 'typeFromValue' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(58, 6) Hint: 'setTempType' is declared but not used [XDeclaredButNotUsed]
+/home/ziko/z-git/bux/bux/bootstrap/cli.nim(2, 55) Warning: imported and not used: 'lir' [UnusedImport]
+Hint: [Link]
+Hint: mm: orc; threads: on; opt: size; options: -d:release
+76982 lines; 1.552s; 174.824MiB peakmem; proj: /home/ziko/z-git/bux/bux/bootstrap/main.nim; out: /home/ziko/z-git/bux/bux/buxc [SuccessX]
+# strip buxc
+EXIT:0
diff --git a/apps/boko-framework/run_check.sh b/apps/boko-framework/run_check.sh
new file mode 100644
index 0000000..bff79e8
--- /dev/null
+++ b/apps/boko-framework/run_check.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+cd /home/ziko/z-git/bux/bux/apps/boko-framework
+
+echo "=== CHECK ==="
+../../buxc check 2>&1
+echo "CHECK_EXIT=$?"
+
+echo ""
+echo "=== BUILD ==="
+../../buxc build 2>&1
+echo "BUILD_EXIT=$?"
+
+echo ""
+echo "=== FILES ==="
+find . -type f -newer bux.toml 2>/dev/null | head -30
+echo ""
+echo "=== BINARY SEARCH ==="
+file boko-framework 2>/dev/null || echo "no boko-framework"
+ls -la build/ 2>/dev/null || echo "no build dir"
diff --git a/apps/boko-framework/src/Boko.bux b/apps/boko-framework/src/Boko.bux
new file mode 100644
index 0000000..27ad29a
--- /dev/null
+++ b/apps/boko-framework/src/Boko.bux
@@ -0,0 +1,507 @@
+// =============================================================================
+// Boko — Async Web Framework for Bux (inspired by FastAPI)
+// =============================================================================
+module Boko {
+
+import Std::Io::{PrintLine, Print, PrintInt};
+import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
+import Std::String::{String_Len, String_Eq, String_StartsWith, String_Contains};
+import Std::Task::{Task_Join, TaskHandle};
+
+extern func bux_alloc(size: uint) -> *void;
+extern func bux_strlen(s: String) -> uint;
+extern func bux_strcmp(a: String, b: String) -> int;
+extern func bux_strstr(haystack: String, needle: String) -> String;
+extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
+extern func bux_str_split_count(s: String, delim: String) -> uint;
+extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
+extern func bux_sb_new(initial_cap: uint) -> *void;
+extern func bux_sb_append(sb: *void, s: String);
+extern func bux_sb_append_int(sb: *void, n: int64);
+extern func bux_sb_append_char(sb: *void, c: char8);
+extern func bux_sb_build(sb: *void) -> String;
+extern func bux_sb_free(sb: *void);
+extern func bux_base64url_encode(data: String, len: int) -> String;
+extern func bux_base64url_decode(data: String, len: int, outlen: *int) -> String;
+
+// =============================================================================
+// HTTP Methods
+// =============================================================================
+enum HttpVerb {
+ GET,
+ POST,
+ PUT,
+ DELETE,
+ PATCH,
+ HEAD,
+ OPTIONS,
+}
+
+// =============================================================================
+// Request — parsed incoming HTTP request
+// =============================================================================
+struct Request {
+ method: HttpVerb,
+ path: String,
+ body: String,
+ headerKeys: *String,
+ headerValues: *String,
+ headerCount: int,
+ queryKeys: *String,
+ queryValues: *String,
+ queryCount: int,
+ pathParamKeys: *String,
+ pathParamValues: *String,
+ pathParamCount: int,
+}
+
+// =============================================================================
+// Response — outgoing HTTP response
+// =============================================================================
+struct Response {
+ statusCode: int,
+ contentType: String,
+ body: String,
+ extraHeaders: String,
+}
+
+// =============================================================================
+// App — server instance
+// =============================================================================
+struct App {
+ port: int,
+ threadCount: int,
+ serverName: String,
+}
+
+// =============================================================================
+// Request helpers
+// =============================================================================
+
+func Request_GetHeader(req: *Request, name: String) -> String {
+ var i: int = 0;
+ while i < req.headerCount {
+ if bux_strcmp(req.headerKeys[i], name) == 0 {
+ return req.headerValues[i];
+ }
+ i = i + 1;
+ }
+ return "";
+}
+
+func Request_GetQuery(req: *Request, name: String) -> String {
+ var i: int = 0;
+ while i < req.queryCount {
+ if bux_strcmp(req.queryKeys[i], name) == 0 {
+ return req.queryValues[i];
+ }
+ i = i + 1;
+ }
+ return "";
+}
+
+func Request_GetPathParam(req: *Request, name: String) -> String {
+ var i: int = 0;
+ while i < req.pathParamCount {
+ if bux_strcmp(req.pathParamKeys[i], name) == 0 {
+ return req.pathParamValues[i];
+ }
+ i = i + 1;
+ }
+ return "";
+}
+
+// =============================================================================
+// Response constructors
+// =============================================================================
+
+func Response_New(status: int, contentType: String, body: String) -> Response {
+ var resp: Response;
+ resp.statusCode = status;
+ resp.contentType = contentType;
+ resp.body = body;
+ resp.extraHeaders = "";
+ return resp;
+}
+
+func Response_Ok(body: String) -> Response {
+ return Response_New(200, "text/html; charset=utf-8", body);
+}
+
+func Response_Html(html: String) -> Response {
+ return Response_New(200, "text/html; charset=utf-8", html);
+}
+
+func Response_Json(json: String) -> Response {
+ return Response_New(200, "application/json; charset=utf-8", json);
+}
+
+func Response_Text(text: String) -> Response {
+ return Response_New(200, "text/plain; charset=utf-8", text);
+}
+
+func Response_Redirect(url: String) -> Response {
+ var resp: Response;
+ resp.statusCode = 302;
+ resp.contentType = "";
+ resp.body = "";
+ let sb: *void = bux_sb_new(256);
+ bux_sb_append(sb, "Location: ");
+ bux_sb_append(sb, url);
+ bux_sb_append(sb, "\r\n");
+ resp.extraHeaders = bux_sb_build(sb);
+ bux_sb_free(sb);
+ return resp;
+}
+
+func Response_NotFound() -> Response {
+ return Response_New(404, "application/json; charset=utf-8",
+ "{\"error\":\"not_found\"}");
+}
+
+func Response_Error(status: int, message: String) -> Response {
+ return Response_New(status, "application/json; charset=utf-8", message);
+}
+
+func Response_NoContent() -> Response {
+ return Response_New(204, "", "");
+}
+
+// =============================================================================
+// Build HTTP response string from Response struct
+// =============================================================================
+func Response_Build(resp: Response) -> String {
+ let sb: *void = bux_sb_new(2048);
+
+ // Status line
+ bux_sb_append(sb, "HTTP/1.1 ");
+ bux_sb_append_int(sb, resp.statusCode as int64);
+ bux_sb_append(sb, " ");
+ if resp.statusCode == 200 { bux_sb_append(sb, "OK"); }
+ else if resp.statusCode == 201 { bux_sb_append(sb, "Created"); }
+ else if resp.statusCode == 204 { bux_sb_append(sb, "No Content"); }
+ else if resp.statusCode == 302 { bux_sb_append(sb, "Found"); }
+ else if resp.statusCode == 400 { bux_sb_append(sb, "Bad Request"); }
+ else if resp.statusCode == 404 { bux_sb_append(sb, "Not Found"); }
+ else if resp.statusCode == 500 { bux_sb_append(sb, "Internal Server Error"); }
+ else { bux_sb_append(sb, "OK"); }
+ bux_sb_append(sb, "\r\n");
+
+ // Server
+ bux_sb_append(sb, "Server: Boko/0.1.0 (Bux)\r\n");
+
+ // Extra headers (redirects, CORS, etc.)
+ if bux_strlen(resp.extraHeaders) > 0 {
+ bux_sb_append(sb, resp.extraHeaders);
+ }
+
+ // Content-Type
+ if bux_strlen(resp.contentType) > 0 {
+ bux_sb_append(sb, "Content-Type: ");
+ bux_sb_append(sb, resp.contentType);
+ bux_sb_append(sb, "\r\n");
+ }
+
+ // Content-Length
+ let bodyLen: uint = bux_strlen(resp.body);
+ bux_sb_append(sb, "Content-Length: ");
+ bux_sb_append_int(sb, bodyLen as int64);
+ bux_sb_append(sb, "\r\n");
+
+ // Connection close
+ bux_sb_append(sb, "Connection: close\r\n");
+
+ bux_sb_append(sb, "\r\n");
+
+ if bodyLen > 0 {
+ bux_sb_append(sb, resp.body);
+ }
+
+ let result: String = bux_sb_build(sb);
+ bux_sb_free(sb);
+ return result;
+}
+
+// =============================================================================
+// Parse query string: ?key=val&key2=val2 → key-value arrays
+// =============================================================================
+func Query_Parse(req: *Request, queryString: String) {
+ if bux_strlen(queryString) == 0 { return; }
+ let pairCount: uint = bux_str_split_count(queryString, "&");
+ let cap: int = pairCount as int + 1;
+ req.queryKeys = bux_alloc(cap as uint * 8) as *String;
+ req.queryValues = bux_alloc(cap as uint * 8) as *String;
+ req.queryCount = 0;
+
+ var i: uint = 0;
+ while i < pairCount {
+ let pair: String = bux_str_split_part(queryString, "&", i);
+ let eqPos: String = bux_strstr(pair, "=");
+ var key: String = pair;
+ var value: String = "";
+ if eqPos as uint != 0 {
+ let keyLen: uint = eqPos as uint - pair as uint;
+ key = bux_str_slice(pair, 0, keyLen);
+ let valStart: uint = keyLen + 1;
+ let pairLen: uint = bux_strlen(pair);
+ if valStart < pairLen {
+ value = bux_str_slice(pair, valStart, pairLen - valStart);
+ }
+ }
+ req.queryKeys[req.queryCount] = key;
+ req.queryValues[req.queryCount] = value;
+ req.queryCount = req.queryCount + 1;
+ i = i + 1;
+ }
+}
+
+// =============================================================================
+// Parse incoming HTTP request from raw bytes
+// =============================================================================
+func Request_Parse(raw: String) -> Request {
+ var req: Request;
+ req.method = HttpVerb { tag: HttpVerb_GET };
+ req.path = "/";
+ req.body = "";
+ req.headerCount = 0;
+ req.queryCount = 0;
+ req.pathParamCount = 0;
+
+ if raw as uint == 0 { return req; }
+ let rawLen: uint = bux_strlen(raw);
+ if rawLen == 0 { return req; }
+
+ // Find header/body boundary
+ let boundary: String = bux_strstr(raw, "\r\n\r\n");
+ var headerLen: uint = rawLen;
+ if boundary as uint != 0 {
+ headerLen = boundary as uint - raw as uint;
+ let bodyStart: uint = headerLen + 4;
+ if bodyStart < rawLen {
+ req.body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
+ }
+ }
+
+ if headerLen == 0 { return req; }
+ let headerBlock: String = bux_str_slice(raw, 0, headerLen);
+ let lineCount: uint = bux_str_split_count(headerBlock, "\r\n");
+ if lineCount == 0 { return req; }
+
+ // Parse request line
+ let requestLine: String = bux_str_split_part(headerBlock, "\r\n", 0);
+ if bux_strlen(requestLine) > 0 {
+ let methodStr: String = bux_str_split_part(requestLine, " ", 0);
+ if String_Eq(methodStr, "GET") { req.method = HttpVerb { tag: HttpVerb_GET }; }
+ else if String_Eq(methodStr, "POST") { req.method = HttpVerb { tag: HttpVerb_POST }; }
+ else if String_Eq(methodStr, "PUT") { req.method = HttpVerb { tag: HttpVerb_PUT }; }
+ else if String_Eq(methodStr, "DELETE") { req.method = HttpVerb { tag: HttpVerb_DELETE }; }
+
+ let fullPath: String = bux_str_split_part(requestLine, " ", 1);
+ // Split path and query string
+ let qmark: String = bux_strstr(fullPath, "?");
+ if qmark as uint != 0 {
+ let pathLen: uint = qmark as uint - fullPath as uint;
+ req.path = bux_str_slice(fullPath, 0, pathLen);
+ let qsStart: uint = pathLen + 1;
+ let fullLen: uint = bux_strlen(fullPath);
+ if qsStart < fullLen {
+ let qs: String = bux_str_slice(fullPath, qsStart, fullLen - qsStart);
+ Query_Parse(&req, qs);
+ }
+ } else {
+ req.path = fullPath;
+ }
+ }
+
+ // Parse headers
+ let cap: int = lineCount as int + 1;
+ req.headerKeys = bux_alloc(cap as uint * 8) as *String;
+ req.headerValues = bux_alloc(cap as uint * 8) as *String;
+
+ var i: uint = 1;
+ while i < lineCount {
+ let line: String = bux_str_split_part(headerBlock, "\r\n", i);
+ let colonPos: String = bux_strstr(line, ": ");
+ if colonPos as uint != 0 {
+ let keyLen: uint = colonPos as uint - line as uint;
+ let key: String = bux_str_slice(line, 0, keyLen);
+ let valStart: uint = keyLen + 2;
+ let lineLen: uint = bux_strlen(line);
+ var value: String = "";
+ if valStart < lineLen {
+ value = bux_str_slice(line, valStart, lineLen - valStart);
+ }
+ req.headerKeys[req.headerCount] = key;
+ req.headerValues[req.headerCount] = value;
+ req.headerCount = req.headerCount + 1;
+ }
+ i = i + 1;
+ }
+
+ return req;
+}
+
+// =============================================================================
+// Path pattern matching: /users/{id} against /users/42 → extracts id=42
+// =============================================================================
+func Path_Match(pattern: String, path: String, req: *Request) -> bool {
+ // Split both by "/"
+ let patParts: uint = bux_str_split_count(pattern, "/");
+ let pathParts: uint = bux_str_split_count(path, "/");
+
+ if patParts != pathParts { return false; }
+
+ // Count params to allocate
+ var paramCount: int = 0;
+ var pi: uint = 0;
+ while pi < patParts {
+ let patPart: String = bux_str_split_part(pattern, "/", pi);
+ if String_StartsWith(patPart, "{") && String_Contains(patPart, "}") {
+ paramCount = paramCount + 1;
+ }
+ pi = pi + 1;
+ }
+
+ let cap: int = paramCount + 1;
+ req.pathParamKeys = bux_alloc(cap as uint * 8) as *String;
+ req.pathParamValues = bux_alloc(cap as uint * 8) as *String;
+ req.pathParamCount = 0;
+
+ // Match and extract
+ var i: uint = 0;
+ while i < patParts {
+ let patPart: String = bux_str_split_part(pattern, "/", i);
+ let pathPart: String = bux_str_split_part(path, "/", i);
+
+ if String_StartsWith(patPart, "{") && String_Contains(patPart, "}") {
+ // Extract param name from {name}
+ let braceOpen: uint = 1; // skip "{"
+ let braceClose: String = bux_strstr(patPart, "}");
+ var nameLen: uint = 0;
+ if braceClose as uint != 0 {
+ nameLen = braceClose as uint - patPart as uint - 1;
+ }
+ let name: String = bux_str_slice(patPart, braceOpen, nameLen);
+ req.pathParamKeys[req.pathParamCount] = name;
+ req.pathParamValues[req.pathParamCount] = pathPart;
+ req.pathParamCount = req.pathParamCount + 1;
+ } else {
+ // Literal match
+ if !String_Eq(patPart, pathPart) { return false; }
+ }
+ i = i + 1;
+ }
+
+ return true;
+}
+
+// =============================================================================
+// App constructor
+// =============================================================================
+func App_New(port: int, threadCount: int) -> App {
+ var app: App;
+ app.port = port;
+ app.threadCount = threadCount;
+ app.serverName = "Boko/0.1.0 (Bux)";
+ return app;
+}
+
+// =============================================================================
+// Forward declaration — user must implement this in their module
+// =============================================================================
+func Boko_Router(req: Request) -> Response;
+
+// =============================================================================
+// Handle a single connection: parse → dispatch → respond
+// =============================================================================
+func App_HandleConnection(clientFd: int) {
+ let raw: String = Net_Recv(clientFd, 8192);
+ if raw as uint == 0 { return; }
+ if bux_strlen(raw) == 0 { return; }
+
+ let req: Request = Request_Parse(raw);
+
+ // Call user-defined dispatch (forward declaration — user must implement)
+ let resp: Response = Boko_Router(req);
+
+ // Log
+ if req.method.tag == HttpVerb_GET { Print("GET "); }
+ else if req.method.tag == HttpVerb_POST { Print("POST "); }
+ else if req.method.tag == HttpVerb_PUT { Print("PUT "); }
+ else if req.method.tag == HttpVerb_DELETE { Print("DELETE "); }
+ else { Print("? "); }
+ Print(req.path);
+ Print(" → ");
+ PrintInt(resp.statusCode);
+ PrintLine("");
+
+ // Send
+ let respStr: String = Response_Build(resp);
+ Net_Send(clientFd, respStr);
+}
+
+// =============================================================================
+// Worker thread: accept loop
+// =============================================================================
+func App_Worker(serverFd: int) {
+ while true {
+ let clientFd: int = Net_Accept(serverFd);
+ if clientFd < 0 { continue; }
+ App_HandleConnection(clientFd);
+ Net_Close(clientFd);
+ }
+}
+
+// =============================================================================
+// App_Run: start the server (blocking)
+// =============================================================================
+func App_Run(app: *App) {
+ PrintLine("╔══════════════════════════════════════╗");
+ PrintLine("║ Boko Framework v0.1.0 ║");
+ PrintLine("║ Async web framework for Bux ║");
+ PrintLine("╚══════════════════════════════════════╝");
+ PrintLine("");
+
+ let fd: int = Net_Create();
+ if fd < 0 {
+ PrintLine("FATAL: socket() failed");
+ return;
+ }
+
+ Net_SetReuse(fd);
+
+ if !Net_Bind(fd, "0.0.0.0", app.port) {
+ Print("FATAL: bind(:");
+ PrintInt(app.port);
+ Print(") failed: ");
+ PrintLine(Net_LastError());
+ Net_Close(fd);
+ return;
+ }
+
+ if !Net_Listen(fd, 128) {
+ PrintLine("FATAL: listen() failed");
+ Net_Close(fd);
+ return;
+ }
+
+ Print("✓ Boko running on http://0.0.0.0:");
+ PrintInt(app.port);
+ PrintLine("");
+ Print("✓ Workers: ");
+ PrintInt(app.threadCount);
+ PrintLine("");
+ PrintLine("");
+
+ // Spawn workers
+ var i: int = 0;
+ while i < app.threadCount - 1 {
+ spawn App_Worker(fd);
+ i = i + 1;
+ }
+
+ // Main thread is the last worker
+ App_Worker(fd);
+}
+
+} // module Boko
diff --git a/apps/boko-framework/src/Main.bux b/apps/boko-framework/src/Main.bux
new file mode 100644
index 0000000..ab58839
--- /dev/null
+++ b/apps/boko-framework/src/Main.bux
@@ -0,0 +1,160 @@
+// =============================================================================
+// Boko Example App — demonstrates the framework API
+// =============================================================================
+module Main {
+
+import Std::Io::{PrintLine, Print};
+import Std::String::{String_Eq};
+import Boko::{
+ App, App_New, App_Run,
+ Request, Response,
+ Request_GetQuery, Request_GetPathParam,
+ Response_Html, Response_Json, Response_NotFound, Response_Redirect,
+ Path_Match,
+ HttpVerb
+};
+
+extern func bux_strlen(s: String) -> uint;
+extern func bux_sb_new(initial_cap: uint) -> *void;
+extern func bux_sb_append(sb: *void, s: String);
+extern func bux_sb_build(sb: *void) -> String;
+extern func bux_sb_free(sb: *void);
+
+// =============================================================================
+// Boko_Router — user-defined dispatch (called by the framework)
+// =============================================================================
+func Boko_Router(req: Request) -> Response {
+ // --- GET / ---
+ if String_Eq(req.path, "/") && req.method.tag == HttpVerb_GET {
+ return Response_Html(PageHome());
+ }
+
+ // --- GET /api/health ---
+ if String_Eq(req.path, "/api/health") {
+ return Response_Json("{\"status\":\"ok\",\"framework\":\"Boko\",\"version\":\"0.1.0\"}");
+ }
+
+ // --- GET /api/info ---
+ if String_Eq(req.path, "/api/info") {
+ return Response_Json("{\"name\":\"Boko\",\"language\":\"Bux\",\"inspiration\":\"FastAPI\",\"features\":[\"routing\",\"path-params\",\"query-params\",\"json\"]}");
+ }
+
+ // --- GET /hello?name=World ---
+ if String_Eq(req.path, "/hello") {
+ let name: String = Request_GetQuery(&req, "name");
+ if bux_strlen(name) == 0 { name = "World"; }
+ let sb: *void = bux_sb_new(256);
+ bux_sb_append(sb, "Hello, ");
+ bux_sb_append(sb, name);
+ bux_sb_append(sb, "!
");
+ let html: String = bux_sb_build(sb);
+ bux_sb_free(sb);
+ return Response_Html(html);
+ }
+
+ // --- GET /users/{id} ---
+ if Path_Match("/users/{id}", req.path, &req) {
+ let id: String = Request_GetPathParam(&req, "id");
+ let sb: *void = bux_sb_new(128);
+ bux_sb_append(sb, "{\"id\":");
+ bux_sb_append(sb, id);
+ bux_sb_append(sb, ",\"name\":\"User ");
+ bux_sb_append(sb, id);
+ bux_sb_append(sb, "\"}");
+ let json: String = bux_sb_build(sb);
+ bux_sb_free(sb);
+ return Response_Json(json);
+ }
+
+ // --- GET /posts/{postId}/comments/{commentId} ---
+ if Path_Match("/posts/{postId}/comments/{commentId}", req.path, &req) {
+ let pid: String = Request_GetPathParam(&req, "postId");
+ let cid: String = Request_GetPathParam(&req, "commentId");
+ let sb: *void = bux_sb_new(128);
+ bux_sb_append(sb, "{\"postId\":");
+ bux_sb_append(sb, pid);
+ bux_sb_append(sb, ",\"commentId\":");
+ bux_sb_append(sb, cid);
+ bux_sb_append(sb, ",\"text\":\"Great post!\"}");
+ let json: String = bux_sb_build(sb);
+ bux_sb_free(sb);
+ return Response_Json(json);
+ }
+
+ // --- GET /redirect → 302 to / ---
+ if String_Eq(req.path, "/redirect") {
+ return Response_Redirect("/");
+ }
+
+ // --- POST /api/echo ---
+ if String_Eq(req.path, "/api/echo") && req.method.tag == HttpVerb_POST {
+ let sb: *void = bux_sb_new(256);
+ bux_sb_append(sb, "{\"echo\":");
+ bux_sb_append(sb, req.body);
+ bux_sb_append(sb, "}");
+ let json: String = bux_sb_build(sb);
+ bux_sb_free(sb);
+ return Response_Json(json);
+ }
+
+ // --- 404 ---
+ return Response_NotFound();
+}
+
+// =============================================================================
+// HTML landing page (raw multi-line string via backticks)
+// =============================================================================
+func PageHome() -> String {
+ return `
+
+
+
+
+Boko Framework
+
+
+
+
+
⚡ Boko
+
Async web framework for Bux — inspired by FastAPI
+
Try it
+
+
+
+
+
+
+
Features
+
✓Path routing with {params}
+
✓Query parameter extraction
+
✓JSON / HTML / Text responses
+
✓Multi-threaded (configurable)
+
✓Redirects (302)
+
✓POST body access
+
+
+`;
+}
+
+// =============================================================================
+// Main — start the server
+// =============================================================================
+func Main() -> int {
+ let app: App = App_New(8080, 4);
+ App_Run(&app);
+ return 0;
+}
+
+} // module Main
diff --git a/apps/jwt-pitbul/README.md b/apps/jwt-pitbul/README.md
new file mode 100644
index 0000000..24a5e25
--- /dev/null
+++ b/apps/jwt-pitbul/README.md
@@ -0,0 +1,175 @@
+# jwt-pitbul
+
+**JWT CLI tool for the Bux ecosystem — sign, verify, decode, and key generation.**
+
+A standalone command-line utility for working with JSON Web Tokens (RFC 7519). Built on `Std::Crypto`, backed by OpenSSL.
+
+## Quick Start
+
+```bash
+cd apps/jwt-pitbul
+../../buxc build
+./jwt-pitbul
+```
+
+## Commands
+
+### `sign` — Create a JWT
+
+```
+jwt-pitbul sign
+```
+
+| Argument | Description |
+|----------|-------------|
+| `` | Algorithm: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `EdDSA` |
+| `` | HMAC secret string, or path to PEM file (RSA/ECDSA), or base64 raw key (Ed25519) |
+| `` | JSON payload string (e.g. `{"sub":"123","exp":1735689600}`) |
+
+```bash
+# HMAC
+jwt-pitbul sign HS256 'my-secret-key' '{"sub":"123","role":"admin"}'
+
+# RSA (private key from file)
+jwt-pitbul sign RS256 private.pem '{"sub":"456"}'
+
+# ECDSA
+jwt-pitbul sign ES256 ec_private.pem '{"sub":"789"}'
+
+# Ed25519 (base64-encoded 32-byte private key)
+jwt-pitbul sign EdDSA 'MC4CAQAwBQYDK2VwBCIEIJ...' '{"sub":"000"}'
+```
+
+### `verify` — Verify and decode
+
+```
+jwt-pitbul verify
+```
+
+```bash
+jwt-pitbul verify eyJhbGciOiJ... HS256 'my-secret-key'
+# ✓ Signature valid
+#
+# Header:
+# {"alg":"HS256","typ":"JWT"}
+#
+# Payload:
+# {"sub":"123","role":"admin"}
+```
+
+### `decode` — Decode without verification
+
+```
+jwt-pitbul decode
+```
+
+```bash
+jwt-pitbul decode eyJhbGciOiJ...
+# Decoded (no verification):
+#
+# Header:
+# {"alg":"HS256","typ":"JWT"}
+#
+# Payload:
+# {"sub":"123"}
+#
+# Signature (base64url): xxxxxxxxx...
+```
+
+### `keygen` — Generate keys
+
+```
+jwt-pitbul keygen
+```
+
+| Type | Output |
+|------|--------|
+| `ed25519` | Generates an Ed25519 keypair (prints base64 public/private keys) |
+| `rsa` | Prints OpenSSL CLI commands to generate RSA 2048-bit keys |
+| `ecdsa` | Prints OpenSSL CLI commands to generate ECDSA P-256 keys |
+
+```bash
+jwt-pitbul keygen ed25519
+# Ed25519 keypair (base64):
+# Public: MCowBQYDK2VwAyEA...
+# Private: MC4CAQAwBQYDK2VwBCIEIJ...
+
+jwt-pitbul keygen rsa
+# RSA key generation requires OpenSSL CLI:
+# openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
+# openssl rsa -in private.pem -pubout -out public.pem
+```
+
+## Supported Algorithms
+
+| Algorithm | Type | Key Format |
+|-----------|------|------------|
+| HS256 | HMAC-SHA256 | Raw secret string |
+| HS384 | HMAC-SHA384 | Raw secret string |
+| HS512 | HMAC-SHA512 | Raw secret string |
+| RS256 | RSA PKCS#1 v1.5 SHA-256 | PEM file path |
+| RS384 | RSA PKCS#1 v1.5 SHA-384 | PEM file path |
+| RS512 | RSA PKCS#1 v1.5 SHA-512 | PEM file path |
+| ES256 | ECDSA P-256 | PEM file path |
+| ES384 | ECDSA P-384 | PEM file path |
+| EdDSA | Ed25519 | Base64 32-byte raw key |
+
+## Generating Keys with OpenSSL
+
+### RSA 2048-bit
+
+```bash
+openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
+openssl rsa -in private.pem -pubout -out public.pem
+```
+
+### ECDSA P-256
+
+```bash
+openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
+openssl ec -in ec_private.pem -pubout -out ec_public.pem
+```
+
+### ECDSA P-384
+
+```bash
+openssl ecparam -genkey -name secp384r1 -noout -out ec_private.pem
+openssl ec -in ec_private.pem -pubout -out ec_public.pem
+```
+
+### Ed25519
+
+```bash
+# In-app generation:
+jwt-pitbul keygen ed25519
+
+# Or via OpenSSL:
+openssl genpkey -algorithm Ed25519 -out ed25519_private.pem
+openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
+```
+
+## Error Codes
+
+| Exit | Meaning |
+|------|---------|
+| 0 | Success |
+| 1 | Error (invalid arguments, bad signature, missing file, etc.) |
+
+## Building
+
+```bash
+cd apps/jwt-pitbul
+../../buxc build # Bootstrap compiler (Nim)
+# or
+../../buxc_lir build # Self-hosted compiler
+```
+
+## Dependencies
+
+- Bux standard library (`Std::Crypto`, `Std::Io`, `Std::String`)
+- OpenSSL 1.1.1+ (linked via runtime)
+- Bux compiler (`buxc` or `buxc_lir`)
+
+## License
+
+Part of the Bux project. See root [LICENSE](../../LICENSE).
diff --git a/apps/jwt-pitbul/bux.toml b/apps/jwt-pitbul/bux.toml
new file mode 100644
index 0000000..50827c4
--- /dev/null
+++ b/apps/jwt-pitbul/bux.toml
@@ -0,0 +1,8 @@
+[Package]
+Name = "jwt-pitbul"
+Version = "0.1.0"
+Type = "bin"
+Description = "JWT CLI tool — encode, decode, verify, and key generation"
+
+[Build]
+Output = "Bin"
diff --git a/apps/jwt-pitbul/src/Main.bux b/apps/jwt-pitbul/src/Main.bux
new file mode 100644
index 0000000..2f4e0ac
--- /dev/null
+++ b/apps/jwt-pitbul/src/Main.bux
@@ -0,0 +1,326 @@
+// =============================================================================
+// jwt-pitbul — JWT CLI Tool (encode, decode, verify, keygen)
+// Built with Bux + Std::Crypto
+// =============================================================================
+module Main {
+
+import Std::Io::{PrintLine, Print, PrintInt};
+import Std::String::{String_Len, String_Eq, String_Contains};
+import Std::Crypto::Jwt::{
+ JwtAlg,
+ Jwt_MakeHeader,
+ Jwt_Encode,
+ Jwt_Decode,
+ Jwt_EncodeHS256, Jwt_EncodeHS384, Jwt_EncodeHS512,
+ Jwt_EncodeRS256, Jwt_EncodeES256, Jwt_EncodeEdDSA
+};
+import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
+import Std::Crypto::Ed25519::{Ed25519_Keypair};
+
+extern func bux_argc() -> int;
+extern func bux_argv(index: int) -> String;
+extern func bux_alloc(size: uint) -> *void;
+extern func bux_read_file(path: String) -> String;
+extern func bux_file_exists(path: String) -> int;
+extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
+extern func bux_str_split_count(s: String, delim: String) -> uint;
+extern func bux_base64_encode(data: String, len: int) -> String;
+
+// =============================================================================
+// Help / Usage
+// =============================================================================
+func PrintUsage() {
+ PrintLine("╔══════════════════════════════════════════════════════╗");
+ PrintLine("║ jwt-pitbul — JWT CLI Tool v0.1.0 ║");
+ PrintLine("║ Sign, verify, decode JSON Web Tokens ║");
+ PrintLine("╚══════════════════════════════════════════════════════╝");
+ PrintLine("");
+ PrintLine("Usage:");
+ PrintLine(" jwt-pitbul sign ");
+ PrintLine(" jwt-pitbul verify ");
+ PrintLine(" jwt-pitbul decode ");
+ PrintLine(" jwt-pitbul keygen ");
+ PrintLine("");
+ PrintLine("Commands:");
+ PrintLine(" sign Create a signed JWT from claims JSON");
+ PrintLine(" verify Verify a JWT signature and print payload");
+ PrintLine(" decode Decode a JWT without verification");
+ PrintLine(" keygen Generate cryptographic keys");
+ PrintLine("");
+ PrintLine("Algorithms:");
+ PrintLine(" HS256, HS384, HS512 — HMAC (symmetric)");
+ PrintLine(" RS256, RS384, RS512 — RSA PKCS#1 v1.5");
+ PrintLine(" ES256, ES384 — ECDSA P-256 / P-384");
+ PrintLine(" EdDSA — Ed25519");
+ PrintLine("");
+ PrintLine("Key formats:");
+ PrintLine(" HMAC: raw secret string");
+ PrintLine(" RSA: path to PEM private/public key file");
+ PrintLine(" ECDSA: path to PEM private/public key file");
+ PrintLine(" EdDSA: base64-encoded 32-byte raw key");
+ PrintLine("");
+ PrintLine("Key generation:");
+ PrintLine(" jwt-pitbul keygen rsa # RSA 2048-bit (PEM)");
+ PrintLine(" jwt-pitbul keygen ecdsa # ECDSA P-256 (PEM)");
+ PrintLine(" jwt-pitbul keygen ed25519 # Ed25519 (raw base64)");
+ PrintLine("");
+ PrintLine("Examples:");
+ PrintLine(" jwt-pitbul sign HS256 'my-secret' '{\"sub\":\"123\"}'");
+ PrintLine(" jwt-pitbul verify eyJh... HS256 'my-secret'");
+ PrintLine(" jwt-pitbul decode eyJh...");
+ PrintLine(" jwt-pitbul keygen ed25519");
+}
+
+// =============================================================================
+// Algorithm name → JwtAlg enum
+// =============================================================================
+func ParseAlg(name: String) -> JwtAlg {
+ if String_Eq(name, "HS256") { return JwtAlg { tag: JwtAlg_HS256 }; }
+ if String_Eq(name, "HS384") { return JwtAlg { tag: JwtAlg_HS384 }; }
+ if String_Eq(name, "HS512") { return JwtAlg { tag: JwtAlg_HS512 }; }
+ if String_Eq(name, "RS256") { return JwtAlg { tag: JwtAlg_RS256 }; }
+ if String_Eq(name, "RS384") { return JwtAlg { tag: JwtAlg_RS384 }; }
+ if String_Eq(name, "RS512") { return JwtAlg { tag: JwtAlg_RS512 }; }
+ if String_Eq(name, "ES256") { return JwtAlg { tag: JwtAlg_ES256 }; }
+ if String_Eq(name, "ES384") { return JwtAlg { tag: JwtAlg_ES384 }; }
+ if String_Eq(name, "EdDSA") { return JwtAlg { tag: JwtAlg_EdDSA }; }
+ return JwtAlg { tag: JwtAlg_HS256 };
+}
+
+// =============================================================================
+// Resolve key — for RSA/ECDSA, read PEM file; for HMAC/EdDSA, pass through
+// =============================================================================
+func ResolveKey(alg: JwtAlg, keyArg: String) -> String {
+ let algTag: int = alg.tag;
+
+ // RSA and ECDSA: key is a path to PEM file
+ if algTag == JwtAlg_RS256 || algTag == JwtAlg_RS384 || algTag == JwtAlg_RS512 ||
+ algTag == JwtAlg_ES256 || algTag == JwtAlg_ES384 {
+ if bux_file_exists(keyArg) != 0 {
+ let pem: String = bux_read_file(keyArg);
+ if pem as uint != 0 && String_Len(pem) > 0 {
+ return pem;
+ }
+ Print("ERROR: could not read PEM file: ");
+ PrintLine(keyArg);
+ return "";
+ }
+ Print("ERROR: PEM file not found: ");
+ PrintLine(keyArg);
+ return "";
+ }
+
+ // HMAC and EdDSA: key is used directly
+ return keyArg;
+}
+
+// =============================================================================
+// Check if algorithm is symmetric (HMAC) or asymmetric
+// =============================================================================
+func IsSymmetric(alg: JwtAlg) -> bool {
+ let t: int = alg.tag;
+ return t == JwtAlg_HS256 || t == JwtAlg_HS384 || t == JwtAlg_HS512;
+}
+
+// =============================================================================
+// Command: sign
+// =============================================================================
+func CmdSign(algName: String, keyArg: String, claimsJson: String) -> int {
+ let alg: JwtAlg = ParseAlg(algName);
+ let key: String = ResolveKey(alg, keyArg);
+ if String_Len(key) == 0 {
+ return 1;
+ }
+
+ let header: String = Jwt_MakeHeader(alg);
+ let token: String = Jwt_Encode(header, claimsJson, alg, key);
+
+ if String_Len(token) == 0 {
+ PrintLine("ERROR: signing failed");
+ return 1;
+ }
+
+ PrintLine(token);
+ return 0;
+}
+
+// =============================================================================
+// Command: verify
+// =============================================================================
+func CmdVerify(token: String, algName: String, keyArg: String) -> int {
+ let alg: JwtAlg = ParseAlg(algName);
+ let key: String = ResolveKey(alg, keyArg);
+ if String_Len(key) == 0 {
+ return 1;
+ }
+
+ var header: String = "";
+ var payload: String = "";
+
+ let ok: bool = Jwt_Decode(token, alg, key, &header, &payload);
+
+ if ok {
+ PrintLine("✓ Signature valid");
+ PrintLine("");
+ PrintLine("Header:");
+ PrintLine(header);
+ PrintLine("");
+ PrintLine("Payload:");
+ PrintLine(payload);
+ return 0;
+ }
+
+ PrintLine("✗ Signature INVALID (or malformed token)");
+ return 1;
+}
+
+// =============================================================================
+// Command: decode (no verification)
+// =============================================================================
+func CmdDecode(token: String) -> int {
+ let partCount: uint = bux_str_split_count(token, ".");
+ if partCount != 3 {
+ PrintLine("ERROR: not a valid JWT (expected 3 parts)");
+ return 1;
+ }
+
+ let headerB64: String = bux_str_split_part(token, ".", 0);
+ let payloadB64: String = bux_str_split_part(token, ".", 1);
+ let sigB64: String = bux_str_split_part(token, ".", 2);
+
+ let headerJson: String = Base64URL_Decode(headerB64);
+ let payloadJson: String = Base64URL_Decode(payloadB64);
+
+ PrintLine("Decoded (no verification):");
+ PrintLine("");
+ PrintLine("Header:");
+ PrintLine(headerJson);
+ PrintLine("");
+ PrintLine("Payload:");
+ PrintLine(payloadJson);
+ PrintLine("");
+ Print("Signature (base64url): ");
+ PrintLine(sigB64);
+
+ return 0;
+}
+
+// =============================================================================
+// Command: keygen
+// =============================================================================
+func CmdKeygen(keyType: String) -> int {
+ if String_Eq(keyType, "ed25519") {
+ let pub: *void = bux_alloc(32);
+ let priv: *void = bux_alloc(32);
+ if !Ed25519_Keypair(pub, priv) {
+ PrintLine("ERROR: Ed25519 key generation failed (OpenSSL 1.1.1+ required)");
+ return 1;
+ }
+ let pubB64: String = bux_base64_encode(pub as String, 32);
+ let privB64: String = bux_base64_encode(priv as String, 32);
+ PrintLine("Ed25519 keypair (base64):");
+ Print(" Public: ");
+ PrintLine(pubB64);
+ Print(" Private: ");
+ PrintLine(privB64);
+ return 0;
+ }
+
+ if String_Eq(keyType, "rsa") {
+ PrintLine("RSA key generation requires OpenSSL CLI:");
+ PrintLine(" openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048");
+ PrintLine(" openssl rsa -in private.pem -pubout -out public.pem");
+ PrintLine("");
+ PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
+ return 0;
+ }
+
+ if String_Eq(keyType, "ecdsa") {
+ PrintLine("ECDSA key generation requires OpenSSL CLI:");
+ PrintLine(" openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem");
+ PrintLine(" openssl ec -in ec_private.pem -pubout -out ec_public.pem");
+ PrintLine("");
+ PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
+ return 0;
+ }
+
+ Print("ERROR: unknown key type '");
+ Print(keyType);
+ PrintLine("'. Use: rsa, ecdsa, ed25519");
+ return 1;
+}
+
+// =============================================================================
+// Main Entry Point
+// =============================================================================
+func Main() -> int {
+ let argc: int = bux_argc();
+
+ if argc < 2 {
+ PrintUsage();
+ return 0;
+ }
+
+ // Collect arguments
+ let args: *String = bux_alloc(argc as uint * 8) as *String;
+ var i: int = 0;
+ while i < argc {
+ args[i] = bux_argv(i);
+ i = i + 1;
+ }
+
+ let command: String = args[1];
+
+ if String_Eq(command, "help") || String_Eq(command, "--help") || String_Eq(command, "-h") {
+ PrintUsage();
+ return 0;
+ }
+
+ // --- sign ---
+ if String_Eq(command, "sign") {
+ if argc < 5 {
+ PrintLine("ERROR: 'sign' requires: ");
+ PrintLine(" jwt-pitbul sign HS256 'secret' '{\"sub\":\"123\"}'");
+ return 1;
+ }
+ return CmdSign(args[2], args[3], args[4]);
+ }
+
+ // --- verify ---
+ if String_Eq(command, "verify") {
+ if argc < 5 {
+ PrintLine("ERROR: 'verify' requires: ");
+ PrintLine(" jwt-pitbul verify eyJh... HS256 'secret'");
+ return 1;
+ }
+ return CmdVerify(args[2], args[3], args[4]);
+ }
+
+ // --- decode ---
+ if String_Eq(command, "decode") {
+ if argc < 3 {
+ PrintLine("ERROR: 'decode' requires: ");
+ PrintLine(" jwt-pitbul decode eyJh...");
+ return 1;
+ }
+ return CmdDecode(args[2]);
+ }
+
+ // --- keygen ---
+ if String_Eq(command, "keygen") {
+ if argc < 3 {
+ PrintLine("ERROR: 'keygen' requires: ");
+ PrintLine(" jwt-pitbul keygen ed25519");
+ return 1;
+ }
+ return CmdKeygen(args[3]);
+ }
+
+ Print("ERROR: unknown command '");
+ Print(command);
+ PrintLine("'");
+ PrintLine("Run 'jwt-pitbul help' for usage.");
+ return 1;
+}
+
+} // module Main
diff --git a/apps/nexus/README.md b/apps/nexus/README.md
new file mode 100644
index 0000000..260f994
--- /dev/null
+++ b/apps/nexus/README.md
@@ -0,0 +1,189 @@
+# Nexus
+
+**High-performance, multi-threaded HTTP/1.1, HTTP/2 & WebSocket server — built with the [Bux](https://github.com/bux-lang/bux) programming language.**
+
+Nexus is a from-scratch web server that demonstrates Bux's systems-programming capabilities: raw TCP sockets, pthread-based concurrency, manual memory management, and zero-dependency C ABI interop — all from a clean, modern syntax.
+
+---
+
+## Features
+
+| Area | What's Implemented |
+|------|-------------------|
+| **HTTP/1.1** | Full request parsing (method, path, headers, body), response building with status codes, content negotiation |
+| **Multi-threaded** | Configurable worker pool using the multi-accept pattern — each worker calls `accept()` directly on the shared listen socket |
+| **HTTP/2** | Connection preface detection (`PRI * HTTP/2.0`), upgrade-aware routing |
+| **WebSocket** | RFC 6455 upgrade handshake detection, `Sec-WebSocket-Key` extraction |
+| **Static files** | Serves from `public/` with MIME-type detection for 20+ file types, directory-traversal protection |
+| **JSON API** | Built-in `/api/health` and `/api/info` endpoints |
+| **Logging** | Per-request structured logging (method, path, status code) |
+
+## Quick Start
+
+```bash
+# From the project root
+cd apps/nexus
+
+# Build with the bootstrap compiler (Nim)
+../../buxc build
+
+# Or with the self-hosted compiler
+../../buxc_lir build
+
+# Run
+./nexus
+```
+
+Server starts on `http://0.0.0.0:8080`:
+
+```
+╔══════════════════════════════════════════════╗
+║ Nexus HTTP Server v0.1.0 ║
+║ High-performance multi-threaded HTTP/1.1 ║
+║ HTTP/2 & WebSocket detection included ║
+║ Built with Bux ║
+╚══════════════════════════════════════════════╝
+
+✓ Server listening on http://0.0.0.0:8080
+✓ Worker threads: 4
+✓ Static files: ./public/
+
+ Endpoints:
+ GET / — Static files (public/)
+ GET /api/health — Health check (JSON)
+ GET /api/info — Server info (JSON)
+ GET /ws — WebSocket upgrade
+ ANY /* — Static file serving
+```
+
+## Testing It
+
+```bash
+# Home page (HTML)
+curl http://localhost:8080/
+
+# Health check
+curl http://localhost:8080/api/health
+# → {"status":"ok","server":"Nexus","version":"0.1.0"}
+
+# Server info
+curl http://localhost:8080/api/info
+# → {"name":"Nexus","language":"Bux","features":[...]}
+
+# Static file (404 page)
+curl http://localhost:8080/404.html
+
+# Nonexistent file
+curl http://localhost:8080/nope
+# → 404 Not Found
+
+# WebSocket upgrade attempt
+curl -H "Upgrade: websocket" \
+ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
+ http://localhost:8080/ws
+# → 101 Switching Protocols
+```
+
+## Architecture
+
+### Thread Model — Multi-Accept
+
+Nexus uses the **multi-accept** pattern rather than a traditional thread pool with a work queue:
+
+```
+Main Thread Worker 1 Worker 2 Worker 3
+ │ │ │ │
+ ├─ socket()/bind()/listen() │ │ │
+ ├─ spawn Worker() ──────────► │ │
+ ├─ spawn Worker() ─────────────────────────► │
+ ├─ spawn Worker() ────────────────────────────────────────►
+ │ │ │ │
+ ▼ Worker() ▼ accept() ▼ accept() ▼ accept()
+ accept() loop │ │ │
+```
+
+Each worker thread calls `accept()` on the **same** listening socket. The Linux kernel distributes incoming connections across the blocked accept calls — the same mechanism used by nginx and Apache prefork. No mutex, no queue, no context switching between a dispatcher and workers.
+
+### Request Lifecycle
+
+```
+Client connects
+ │
+ ▼
+Net_Accept() → client fd
+ │
+ ▼
+Net_Recv(fd, 8192) → raw bytes
+ │
+ ▼
+ParseRequest()
+ ├─ Split headers on \r\n\r\n
+ ├─ Parse request line → method, path, version
+ └─ Parse header lines → key-value array
+ │
+ ▼
+Router_Dispatch()
+ ├─ /api/* → JSON handlers
+ ├─ /ws → WebSocket upgrade
+ ├─ Upgrade header → HTTP/2 or WS detection
+ └─ /* → Static file serving
+ │
+ ▼
+BuildResponse() → HTTP/1.1 status line + headers + body
+ │
+ ▼
+Net_Send(fd, response) → bytes to client
+ │
+ ▼
+Net_Close(fd)
+```
+
+### Design Decisions
+
+- **No keep-alive by default.** Each connection is closed after one response. This avoids blocking worker threads on idle clients (Bux doesn't yet expose `SO_RCVTIMEO`).
+- **Linear header array instead of hash map.** HTTP requests typically carry 5–15 headers. A linear scan over a key-value array is faster than hashing for this N, and avoids the complexity of iterating over Bux's generic `StringMap`.
+- **Raw extern calls for the string builder.** The stdlib `StringBuilder` wrapper adds a struct indirection. Calling `bux_sb_new` / `bux_sb_append` / `bux_sb_build` directly is simpler and equally safe.
+- **WebSocket accept key is a placeholder.** A full implementation needs SHA-1 hashing and Base64 encoding. These aren't in Bux's stdlib yet; they can be added as extern C functions when needed.
+
+## Project Structure
+
+```
+apps/nexus/
+├── bux.toml # Package manifest
+├── README.md # This file
+├── src/
+│ └── Main.bux # The entire server (~640 lines)
+└── public/
+ ├── index.html # Landing page
+ └── 404.html # Error page
+```
+
+Everything lives in one file (`src/Main.bux`) by design — it keeps the module graph flat and the build fast. As the server grows, the HTTP parser, router, and handlers can be split into separate modules.
+
+## Configuration
+
+Edit the constants at the top of `src/Main.bux`:
+
+```bux
+const SERVER_PORT: int = 8080; // Listen port
+const THREAD_COUNT: int = 4; // Number of worker threads
+const RECV_BUF_SIZE: int = 8192; // Receive buffer per request
+const SERVER_NAME: String = "Nexus/0.1.0 (Bux)";
+const PUBLIC_DIR: String = "public"; // Static files directory
+```
+
+## Roadmap
+
+- [ ] Keep-alive with configurable socket timeout
+- [ ] Full WebSocket frame read/write (requires SHA-1 + Base64)
+- [ ] HTTP/2 binary framing layer (HPACK, stream multiplexing)
+- [ ] SSL/TLS via OpenSSL extern bindings
+- [ ] Middleware / filter chain
+- [ ] Request body parsing (JSON, form-encoded, multipart)
+- [ ] Virtual hosts
+- [ ] Access logging to file
+- [ ] Rate limiting
+
+## License
+
+Nexus is part of the Bux project. See the root [LICENSE](../../LICENSE) for terms.
diff --git a/apps/nexus/build.log b/apps/nexus/build.log
new file mode 100644
index 0000000..903634c
--- /dev/null
+++ b/apps/nexus/build.log
@@ -0,0 +1 @@
+EXIT:137
diff --git a/apps/nexus/build_check.log b/apps/nexus/build_check.log
new file mode 100644
index 0000000..4943272
--- /dev/null
+++ b/apps/nexus/build_check.log
@@ -0,0 +1 @@
+EXIT:124
diff --git a/apps/nexus/build_demo.log b/apps/nexus/build_demo.log
new file mode 100644
index 0000000..4943272
--- /dev/null
+++ b/apps/nexus/build_demo.log
@@ -0,0 +1 @@
+EXIT:124
diff --git a/apps/nexus/build_full.log b/apps/nexus/build_full.log
new file mode 100644
index 0000000..4943272
--- /dev/null
+++ b/apps/nexus/build_full.log
@@ -0,0 +1 @@
+EXIT:124
diff --git a/apps/nexus/build_min.log b/apps/nexus/build_min.log
new file mode 100644
index 0000000..4943272
--- /dev/null
+++ b/apps/nexus/build_min.log
@@ -0,0 +1 @@
+EXIT:124
diff --git a/apps/nexus/build_simple.log b/apps/nexus/build_simple.log
new file mode 100644
index 0000000..5b9a263
--- /dev/null
+++ b/apps/nexus/build_simple.log
@@ -0,0 +1,2 @@
+timeout: командата „„../../buxc““ не може да бъде изпълнена: Няма такъв файл или директория
+EXIT:127
diff --git a/apps/nexus/build_vlong.log b/apps/nexus/build_vlong.log
new file mode 100644
index 0000000..903634c
--- /dev/null
+++ b/apps/nexus/build_vlong.log
@@ -0,0 +1 @@
+EXIT:137
diff --git a/apps/nexus/bux.toml b/apps/nexus/bux.toml
new file mode 100644
index 0000000..f48630c
--- /dev/null
+++ b/apps/nexus/bux.toml
@@ -0,0 +1,8 @@
+[Package]
+Name = "nexus"
+Version = "0.1.0"
+Type = "bin"
+Description = "High-performance multi-threaded HTTP/1.1, HTTP/2 and WebSocket server"
+
+[Build]
+Output = "Bin"
diff --git a/apps/nexus/public/404.html b/apps/nexus/public/404.html
new file mode 100644
index 0000000..28ea582
--- /dev/null
+++ b/apps/nexus/public/404.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ 404 — Not Found | Nexus
+
+
+
+ 404
+ The requested resource was not found on this server.
+ ← Back to Home
+
+
diff --git a/apps/nexus/public/index.html b/apps/nexus/public/index.html
new file mode 100644
index 0000000..1f4024c
--- /dev/null
+++ b/apps/nexus/public/index.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+ Nexus — Bux HTTP Server
+
+
+
+
+
⚡ Nexus
+
High-performance HTTP Server — Built with Bux
+
+
+
Features
+
+ - HTTP/1.1 — Full request parsing & response building
+ - Multi-threaded — Configurable worker pool
+ - HTTP/2 Detection — Upgrade-aware
+ - WebSocket Upgrade — Handshake support
+ - Static File Serving — With MIME type detection
+ - JSON API Endpoints
+
+
+
+
+
API Endpoints
+
+ GET
+ /api/health
+
+
+ GET
+ /api/info
+
+
+ GET
+ /ws
+
+
+
+
+
+
+
diff --git a/apps/nexus/src/Main.bux b/apps/nexus/src/Main.bux
new file mode 100644
index 0000000..dc54019
--- /dev/null
+++ b/apps/nexus/src/Main.bux
@@ -0,0 +1,550 @@
+// =============================================================================
+// Nexus — High-performance multi-threaded HTTP/1.1, HTTP/2 & WebSocket server
+// Built with the Bux programming language
+// =============================================================================
+module Main {
+
+// =============================================================================
+// Imports — only what we actually use
+// =============================================================================
+import Std::Io::{PrintLine, Print, PrintInt};
+import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
+import Std::String::{String_Len, String_Eq, String_StartsWith, String_EndsWith, String_Contains, String_Trim, String_FromInt};
+import Std::Task::{Task_Join, TaskHandle};
+
+// =============================================================================
+// Extern Functions (from C runtime — used directly for performance)
+// =============================================================================
+extern func bux_alloc(size: uint) -> *void;
+extern func bux_strlen(s: String) -> uint;
+extern func bux_strcmp(a: String, b: String) -> int;
+extern func bux_strstr(haystack: String, needle: String) -> String;
+extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
+extern func bux_str_split_count(s: String, delim: String) -> uint;
+extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
+extern func bux_sb_new(initial_cap: uint) -> *void;
+extern func bux_sb_append(sb: *void, s: String);
+extern func bux_sb_append_int(sb: *void, n: int64);
+extern func bux_sb_append_char(sb: *void, c: char8);
+extern func bux_sb_build(sb: *void) -> String;
+extern func bux_sb_free(sb: *void);
+extern func bux_path_join(a: String, b: String) -> String;
+extern func bux_read_file(path: String) -> String;
+extern func bux_file_exists(path: String) -> int;
+
+// =============================================================================
+// HTTP Method Enum (simple enum — no data payload)
+// =============================================================================
+enum HttpMethod {
+ GET,
+ POST,
+ PUT,
+ DELETE,
+ PATCH,
+ HEAD,
+ OPTIONS,
+ UNKNOWN,
+}
+
+// =============================================================================
+// Struct: HTTP Request
+// =============================================================================
+struct HttpRequest {
+ method: HttpMethod,
+ path: String,
+ body: String,
+ headerKeys: *String,
+ headerValues: *String,
+ headerCount: int,
+ headerCap: int,
+}
+
+// =============================================================================
+// Struct: HTTP Response
+// =============================================================================
+struct HttpResponse {
+ statusCode: int,
+ statusText: String,
+ contentType: String,
+ body: String,
+ extraHeaders: String, // pre-formatted extra headers (for WS upgrade, etc.)
+ connectionClose: bool,
+}
+
+// =============================================================================
+// Utility: Status text lookup
+// =============================================================================
+func Http_StatusText(code: int) -> String {
+ if code == 200 { return "OK"; }
+ if code == 201 { return "Created"; }
+ if code == 204 { return "No Content"; }
+ if code == 301 { return "Moved Permanently"; }
+ if code == 302 { return "Found"; }
+ if code == 304 { return "Not Modified"; }
+ if code == 400 { return "Bad Request"; }
+ if code == 401 { return "Unauthorized"; }
+ if code == 403 { return "Forbidden"; }
+ if code == 404 { return "Not Found"; }
+ if code == 405 { return "Method Not Allowed"; }
+ if code == 413 { return "Payload Too Large"; }
+ if code == 414 { return "URI Too Long"; }
+ if code == 500 { return "Internal Server Error"; }
+ if code == 501 { return "Not Implemented"; }
+ if code == 503 { return "Service Unavailable"; }
+ return "Unknown";
+}
+
+// =============================================================================
+// Utility: Parse HTTP method string → HttpMethod enum
+// =============================================================================
+func Http_ParseMethod(s: String) -> HttpMethod {
+ if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
+ if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
+ if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
+ if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
+ if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
+ if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
+ if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
+ return HttpMethod { tag: HttpMethod_UNKNOWN };
+}
+
+// =============================================================================
+// Utility: MIME type from file extension
+// =============================================================================
+func Http_MimeType(path: String) -> String {
+ if String_EndsWith(path, ".html") || String_EndsWith(path, ".htm") { return "text/html; charset=utf-8"; }
+ if String_EndsWith(path, ".css") { return "text/css; charset=utf-8"; }
+ if String_EndsWith(path, ".js") { return "application/javascript; charset=utf-8"; }
+ if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; }
+ if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; }
+ if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; }
+ if String_EndsWith(path, ".png") { return "image/png"; }
+ if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; }
+ if String_EndsWith(path, ".gif") { return "image/gif"; }
+ if String_EndsWith(path, ".svg") { return "image/svg+xml"; }
+ if String_EndsWith(path, ".ico") { return "image/x-icon"; }
+ if String_EndsWith(path, ".webp") { return "image/webp"; }
+ if String_EndsWith(path, ".woff2") { return "font/woff2"; }
+ if String_EndsWith(path, ".woff") { return "font/woff"; }
+ if String_EndsWith(path, ".wasm") { return "application/wasm"; }
+ return "application/octet-stream";
+}
+
+// =============================================================================
+// Header Storage: dynamic key-value array (faster than hash map for 5-15 headers)
+// =============================================================================
+func RequestHeader_Init(req: *HttpRequest) {
+ let cap: int = 16;
+ req.headerCap = cap;
+ req.headerKeys = bux_alloc(cap as uint * 8) as *String;
+ req.headerValues = bux_alloc(cap as uint * 8) as *String;
+ req.headerCount = 0;
+}
+
+func RequestHeader_Add(req: *HttpRequest, key: String, value: String) {
+ if req.headerCount >= req.headerCap { return; }
+ req.headerKeys[req.headerCount] = key;
+ req.headerValues[req.headerCount] = value;
+ req.headerCount = req.headerCount + 1;
+}
+
+func RequestHeader_Get(req: *HttpRequest, key: String) -> String {
+ var i: int = 0;
+ while i < req.headerCount {
+ if bux_strcmp(req.headerKeys[i], key) == 0 {
+ return req.headerValues[i];
+ }
+ i = i + 1;
+ }
+ return "";
+}
+
+// =============================================================================
+// HTTP Request Parser
+// =============================================================================
+func Http_ParseRequest(raw: String) -> HttpRequest {
+ var req: HttpRequest;
+ req.method = HttpMethod { tag: HttpMethod_UNKNOWN };
+ req.path = "/";
+ req.body = "";
+ RequestHeader_Init(&req);
+
+ // Guard: null/empty input
+ if raw as uint == 0 { return req; }
+ let rawLen: uint = bux_strlen(raw);
+ if rawLen == 0 { return req; }
+
+ // Find \r\n\r\n separating headers from body
+ let boundary: String = bux_strstr(raw, "\r\n\r\n");
+ var headerLen: uint = rawLen;
+ if boundary as uint != 0 {
+ headerLen = boundary as uint - raw as uint;
+ let bodyStart: uint = headerLen + 4;
+ if bodyStart < rawLen {
+ req.body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
+ }
+ }
+
+ // Slice out header block
+ if headerLen == 0 { return req; }
+ let headerBlock: String = bux_str_slice(raw, 0, headerLen);
+
+ // Count header lines
+ let lineCount: uint = bux_str_split_count(headerBlock, "\r\n");
+ if lineCount == 0 { return req; }
+
+ // --- Parse request line: "METHOD /path HTTP/1.1" ---
+ let requestLine: String = bux_str_split_part(headerBlock, "\r\n", 0);
+ if bux_strlen(requestLine) > 0 {
+ req.method = Http_ParseMethod(bux_str_split_part(requestLine, " ", 0));
+ req.path = bux_str_split_part(requestLine, " ", 1);
+ if String_Eq(req.path, "") || req.path as uint == 0 {
+ req.path = "/";
+ }
+ }
+
+ // --- Parse headers (lines 1..N-1) ---
+ var i: uint = 1;
+ while i < lineCount {
+ let line: String = bux_str_split_part(headerBlock, "\r\n", i);
+ let lineLen: uint = bux_strlen(line);
+ if lineLen == 0 { i = i + 1; continue; }
+
+ // Find ": " separator
+ let colonPos: String = bux_strstr(line, ": ");
+ var key: String = "";
+ var value: String = "";
+
+ if colonPos as uint != 0 {
+ let keyLen: uint = colonPos as uint - line as uint;
+ key = bux_str_slice(line, 0, keyLen);
+ let valStart: uint = keyLen + 2;
+ if valStart < lineLen {
+ value = bux_str_slice(line, valStart, lineLen - valStart);
+ }
+ } else {
+ // Try ":" without space
+ let colonPos2: String = bux_strstr(line, ":");
+ if colonPos2 as uint != 0 {
+ let keyLen: uint = colonPos2 as uint - line as uint;
+ key = bux_str_slice(line, 0, keyLen);
+ let valStart: uint = keyLen + 1;
+ if valStart < lineLen {
+ value = bux_str_slice(line, valStart, lineLen - valStart);
+ }
+ value = String_Trim(value);
+ }
+ }
+
+ // Store if valid key found
+ if bux_strlen(key) > 0 {
+ key = String_Trim(key);
+ RequestHeader_Add(&req, key, value);
+ }
+
+ i = i + 1;
+ }
+
+ return req;
+}
+
+// =============================================================================
+// HTTP Response Builder
+// =============================================================================
+func Http_BuildResponse(resp: HttpResponse) -> String {
+ let sb: *void = bux_sb_new(4096);
+
+ // Status line
+ bux_sb_append(sb, "HTTP/1.1 ");
+ bux_sb_append_int(sb, resp.statusCode as int64);
+ bux_sb_append_char(sb, ' ');
+ bux_sb_append(sb, resp.statusText);
+ bux_sb_append(sb, "\r\n");
+
+ // Server
+ bux_sb_append(sb, "Server: Nexus/0.1.0 (Bux)\r\n");
+
+ // Extra headers (for WS upgrade etc.)
+ if bux_strlen(resp.extraHeaders) > 0 {
+ bux_sb_append(sb, resp.extraHeaders);
+ }
+
+ // Content-Type
+ if bux_strlen(resp.contentType) > 0 {
+ bux_sb_append(sb, "Content-Type: ");
+ bux_sb_append(sb, resp.contentType);
+ bux_sb_append(sb, "\r\n");
+ }
+
+ // Content-Length
+ let bodyLen: uint = bux_strlen(resp.body);
+ bux_sb_append(sb, "Content-Length: ");
+ bux_sb_append_int(sb, bodyLen as int64);
+ bux_sb_append(sb, "\r\n");
+
+ // Connection
+ if resp.connectionClose {
+ bux_sb_append(sb, "Connection: close\r\n");
+ } else {
+ bux_sb_append(sb, "Connection: keep-alive\r\n");
+ }
+
+ // End headers
+ bux_sb_append(sb, "\r\n");
+
+ // Body
+ if bodyLen > 0 {
+ bux_sb_append(sb, resp.body);
+ }
+
+ let result: String = bux_sb_build(sb);
+ bux_sb_free(sb);
+ return result;
+}
+
+// =============================================================================
+// Helper: Create a simple response
+// =============================================================================
+func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
+ var resp: HttpResponse;
+ resp.statusCode = code;
+ resp.statusText = Http_StatusText(code);
+ resp.contentType = contentType;
+ resp.body = body;
+ resp.extraHeaders = "";
+ resp.connectionClose = true;
+ return resp;
+}
+
+// =============================================================================
+// Static File Server
+// =============================================================================
+func ServeStaticFile(requestPath: String) -> HttpResponse {
+ // Block directory traversal
+ if String_Contains(requestPath, "..") {
+ return Http_NewResponse(403, "text/plain; charset=utf-8", "Forbidden");
+ }
+
+ // Default to index.html
+ var filePath: String = requestPath;
+ if String_Eq(filePath, "/") {
+ filePath = "/index.html";
+ }
+
+ // Resolve full path
+ let fullPath: String = bux_path_join("public", filePath);
+
+ // Check existence
+ if bux_file_exists(fullPath) == 0 {
+ return Http_NewResponse(404, "text/plain; charset=utf-8", "Not Found");
+ }
+
+ // Read and serve
+ let content: String = bux_read_file(fullPath);
+ if content as uint == 0 {
+ return Http_NewResponse(500, "text/plain; charset=utf-8", "Internal Server Error");
+ }
+
+ let mime: String = Http_MimeType(filePath);
+ return Http_NewResponse(200, mime, content);
+}
+
+// =============================================================================
+// API Handlers
+// =============================================================================
+func HandleApiHealth() -> HttpResponse {
+ return Http_NewResponse(200, "application/json; charset=utf-8",
+ "{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.1.0\"}");
+}
+
+func HandleApiInfo() -> HttpResponse {
+ return Http_NewResponse(200, "application/json; charset=utf-8",
+ "{\"name\":\"Nexus\",\"language\":\"Bux\",\"threads\":4,\"features\":[\"HTTP/1.1\",\"HTTP/2-detect\",\"WebSocket-upgrade\"]}");
+}
+
+// =============================================================================
+// WebSocket Upgrade Handshake
+// =============================================================================
+func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
+ let wsKey: String = RequestHeader_Get(&req, "Sec-WebSocket-Key");
+
+ var resp: HttpResponse;
+ resp.statusCode = 101;
+ resp.statusText = "Switching Protocols";
+ resp.contentType = "";
+ resp.body = "";
+ resp.connectionClose = false;
+
+ // Build upgrade headers as extraHeaders
+ // TODO: SHA-1 + Base64 for proper accept key
+ let sb: *void = bux_sb_new(256);
+ bux_sb_append(sb, "Upgrade: websocket\r\n");
+ bux_sb_append(sb, "Connection: Upgrade\r\n");
+ bux_sb_append(sb, "Sec-WebSocket-Accept: ");
+ bux_sb_append(sb, wsKey); // placeholder — real impl needs crypto
+ bux_sb_append(sb, "\r\n");
+ resp.extraHeaders = bux_sb_build(sb);
+ bux_sb_free(sb);
+
+ return resp;
+}
+
+// =============================================================================
+// Router / Dispatcher
+// =============================================================================
+func Router_Dispatch(req: HttpRequest) -> HttpResponse {
+ // Check for upgrade headers
+ let upgrade: String = RequestHeader_Get(&req, "Upgrade");
+ if bux_strlen(upgrade) > 0 {
+ if String_Contains(upgrade, "websocket") {
+ return HandleWebSocketUpgrade(req);
+ }
+ }
+
+ // API routes
+ if String_StartsWith(req.path, "/api/") {
+ if String_Eq(req.path, "/api/health") { return HandleApiHealth(); }
+ if String_Eq(req.path, "/api/info") { return HandleApiInfo(); }
+ return Http_NewResponse(404, "application/json; charset=utf-8",
+ "{\"error\":\"not_found\"}");
+ }
+
+ // WebSocket endpoint
+ if String_Eq(req.path, "/ws") {
+ return HandleWebSocketUpgrade(req);
+ }
+
+ // Static files — GET/HEAD only
+ if req.method.tag == HttpMethod_GET || req.method.tag == HttpMethod_HEAD {
+ return ServeStaticFile(req.path);
+ }
+
+ // Method not allowed
+ return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
+}
+
+// =============================================================================
+// Method name for logging (avoids 8 if-checks in hot path)
+// =============================================================================
+func Http_MethodName(m: HttpMethod) -> String {
+ if m.tag == HttpMethod_GET { return "GET"; }
+ if m.tag == HttpMethod_POST { return "POST"; }
+ if m.tag == HttpMethod_PUT { return "PUT"; }
+ if m.tag == HttpMethod_DELETE { return "DELETE"; }
+ if m.tag == HttpMethod_PATCH { return "PATCH"; }
+ if m.tag == HttpMethod_HEAD { return "HEAD"; }
+ if m.tag == HttpMethod_OPTIONS { return "OPTIONS"; }
+ return "UNKNOWN";
+}
+
+// =============================================================================
+// Connection Handler — read, parse, route, respond
+// =============================================================================
+func HandleConnection(clientFd: int) {
+ let raw: String = Net_Recv(clientFd, 8192);
+
+ // Empty read → client disconnected
+ if raw as uint == 0 { return; }
+ if bux_strlen(raw) == 0 { return; }
+
+ // HTTP/2 connection preface detection
+ if String_StartsWith(raw, "PRI * HTTP/2.0") {
+ let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
+ "HTTP/2 detected — full support planned for future release.\r\n");
+ let respStr: String = Http_BuildResponse(resp);
+ Net_Send(clientFd, respStr);
+ return;
+ }
+
+ // Parse HTTP/1.1
+ let req: HttpRequest = Http_ParseRequest(raw);
+
+ // Log
+ Print(Http_MethodName(req.method));
+ Print(" ");
+ Print(req.path);
+ Print(" -> ");
+
+ // Dispatch
+ let resp: HttpResponse = Router_Dispatch(req);
+
+ // Log result
+ PrintInt(resp.statusCode);
+ Print(" ");
+ PrintLine(resp.statusText);
+
+ // Send
+ let respStr: String = Http_BuildResponse(resp);
+ Net_Send(clientFd, respStr);
+}
+
+// =============================================================================
+// Worker Thread — accept loop
+// =============================================================================
+func Worker(serverFd: int) {
+ while true {
+ let clientFd: int = Net_Accept(serverFd);
+ if clientFd < 0 {
+ continue;
+ }
+ HandleConnection(clientFd);
+ Net_Close(clientFd);
+ }
+}
+
+// =============================================================================
+// Main Entry Point
+// =============================================================================
+func Main() -> int {
+ PrintLine("================================================");
+ PrintLine(" Nexus HTTP Server v0.1.0");
+ PrintLine(" Multi-threaded HTTP/1.1 + HTTP/2 & WS detect");
+ PrintLine(" Built with Bux");
+ PrintLine("================================================");
+ PrintLine("");
+
+ // Create socket
+ let serverFd: int = Net_Create();
+ if serverFd < 0 {
+ PrintLine("FATAL: socket() failed");
+ return 1;
+ }
+
+ // SO_REUSEADDR
+ if !Net_SetReuse(serverFd) {
+ PrintLine("WARN: SO_REUSEADDR failed");
+ }
+
+ // Bind
+ if !Net_Bind(serverFd, "0.0.0.0", 8080) {
+ Print("FATAL: bind(8080) failed: ");
+ PrintLine(Net_LastError());
+ Net_Close(serverFd);
+ return 1;
+ }
+
+ // Listen
+ if !Net_Listen(serverFd, 128) {
+ PrintLine("FATAL: listen() failed");
+ Net_Close(serverFd);
+ return 1;
+ }
+
+ PrintLine("Listening on http://0.0.0.0:8080");
+ PrintLine("4 worker threads | static: ./public/");
+ PrintLine("Endpoints: / /api/health /api/info /ws");
+ PrintLine("Press Ctrl+C to stop.");
+ PrintLine("");
+
+ // Spawn 3 workers (main thread is the 4th)
+ spawn Worker(serverFd);
+ spawn Worker(serverFd);
+ spawn Worker(serverFd);
+
+ // Main thread becomes worker #4 (blocking call)
+ Worker(serverFd);
+
+ return 0;
+}
+
+} // module Main
diff --git a/apps/nexus/ver.log b/apps/nexus/ver.log
new file mode 100644
index 0000000..3b4b4d6
--- /dev/null
+++ b/apps/nexus/ver.log
@@ -0,0 +1,2 @@
+bux 0.1.0 (bootstrap)
+EXIT:0
diff --git a/lib/Crypto.bux b/lib/Crypto.bux
index cf8632c..f3235be 100644
--- a/lib/Crypto.bux
+++ b/lib/Crypto.bux
@@ -1,6 +1,19 @@
+// =============================================================================
+// Std::Crypto — backward-compatible wrapper (delegates to submodules)
+//
+// Migration note: new code should use the submodules directly:
+// Std::Crypto::Hash::{Hash_Sha256, ...}
+// Std::Crypto::Hmac::{Hmac_Sha256, ...}
+// Std::Crypto::Base64::{Base64_Encode, Base64URL_Encode, ...}
+// Std::Crypto::Random::{Random_Bytes, ...}
+// Std::Crypto::Jwt::{Jwt_EncodeHS256, ...}
+// =============================================================================
module Std::Crypto {
+
import Std::Mem::{Alloc, Free};
import Std::String::{String_Len};
+
+// Re-use the same externs from submodules (merged by compiler)
extern func bux_sha256(data: String, len: int, out: *void);
extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void);
extern func bux_random_bytes(buf: *void, len: int) -> int;
@@ -8,7 +21,9 @@ extern func bux_base64_encode(data: String, len: int) -> String;
extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
extern func bux_bytes_to_hex(data: *void, len: int) -> String;
-/* SHA-256: returns lowercase hex string of the hash */
+// --- Legacy function names (delegate to new submodule functions) ---
+
+// SHA-256 → hex
func Crypto_Sha256(data: String) -> String {
let len: int = String_Len(data) as int;
let hashBuf: *void = Alloc(32);
@@ -18,7 +33,7 @@ func Crypto_Sha256(data: String) -> String {
return result;
}
-/* HMAC-SHA256: returns lowercase hex string */
+// HMAC-SHA256 → hex
func Crypto_HmacSha256(key: String, message: String) -> String {
let keylen: int = String_Len(key) as int;
let msglen: int = String_Len(message) as int;
@@ -29,7 +44,7 @@ func Crypto_HmacSha256(key: String, message: String) -> String {
return result;
}
-/* Random bytes: returns a string of n random bytes */
+// Random bytes → base64
func Crypto_RandomBytes(n: int) -> String {
if n <= 0 { return ""; }
let buf: *void = Alloc(n as uint);
@@ -42,12 +57,12 @@ func Crypto_RandomBytes(n: int) -> String {
return result;
}
-/* Base64 encode */
+// Base64 encode
func Crypto_Base64Encode(s: String) -> String {
return bux_base64_encode(s, String_Len(s) as int);
}
-/* HMAC-SHA256 raw: returns base64 of raw binary hmac */
+// HMAC-SHA256 raw → base64
func Crypto_HmacSha256Raw(key: String, message: String) -> String {
let keylen: int = String_Len(key) as int;
let msglen: int = String_Len(message) as int;
@@ -58,7 +73,7 @@ func Crypto_HmacSha256Raw(key: String, message: String) -> String {
return result;
}
-/* Base64 decode */
+// Base64 decode
func Crypto_Base64Decode(s: String) -> String {
let outlen: int = 0;
return bux_base64_decode(s, String_Len(s) as int, &outlen);
diff --git a/lib/crypto/README.md b/lib/crypto/README.md
new file mode 100644
index 0000000..129b748
--- /dev/null
+++ b/lib/crypto/README.md
@@ -0,0 +1,265 @@
+# Bux Crypto Library (`Std::Crypto`)
+
+Cryptographic primitives for the Bux programming language. Backed by OpenSSL via the C runtime (`rt/runtime.c`).
+
+## Modules
+
+| Module | Import Path | Provides |
+|--------|------------|----------|
+| **Base64** | `Std::Crypto::Base64` | Base64 and Base64URL (RFC 4648 §5) encode/decode |
+| **Hash** | `Std::Crypto::Hash` | SHA-1, SHA-256, SHA-384, SHA-512 (hex + raw) |
+| **HMAC** | `Std::Crypto::Hmac` | HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 (hex + raw + base64) |
+| **Random** | `Std::Crypto::Random` | Cryptographically secure random bytes, hex, base64, uint32 |
+| **AES** | `Std::Crypto::Aes` | AES-256-CBC and AES-256-GCM encrypt/decrypt |
+| **RSA** | `Std::Crypto::Rsa` | RSA PKCS#1 v1.5 sign/verify (SHA-256/384/512) |
+| **ECDSA** | `Std::Crypto::Ecdsa` | ECDSA P-256 and P-384 sign/verify |
+| **Ed25519** | `Std::Crypto::Ed25519` | Ed25519 key generation, signing, verification |
+| **JWT** | `Std::Crypto::Jwt` | JSON Web Tokens (HS256/384/512, RS256/384/512, ES256/384, EdDSA) |
+
+The legacy `Std::Crypto` module (the old single-file API) is preserved as a backward-compatible wrapper — existing code continues to work.
+
+## Quick Start
+
+### Hash
+
+```bux
+import Std::Crypto::Hash::{Hash_Sha256, Hash_Sha384, Hash_Sha512};
+
+let hex: String = Hash_Sha256("hello");
+// → "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
+
+let size: int = Hash_Sha256Size(); // → 32 (bytes)
+```
+
+### HMAC
+
+```bux
+import Std::Crypto::Hmac::{Hmac_Sha256, Hmac_Sha256Raw, Hmac_Sha256Base64};
+
+let hex: String = Hmac_Sha256("secret-key", "message");
+// → hex string
+
+// Raw binary output (caller allocates 32-byte buffer)
+let buf: *void = Alloc(32);
+Hmac_Sha256Raw("secret-key", "message", buf);
+// buf now contains 32 bytes of raw HMAC
+
+let b64: String = Hmac_Sha256Base64("secret-key", "message");
+// → base64-encoded HMAC
+```
+
+### Base64 & Base64URL
+
+```bux
+import Std::Crypto::Base64::{Base64_Encode, Base64_Decode,
+ Base64URL_Encode, Base64URL_Decode};
+
+let std: String = Base64_Encode("hello"); // → "aGVsbG8="
+let orig: String = Base64_Decode(std); // → "hello"
+
+let url: String = Base64URL_Encode("hello"); // → "aGVsbG8" (no padding)
+let orig2: String = Base64URL_Decode(url); // → "hello"
+```
+
+### Random
+
+```bux
+import Std::Crypto::Random::{Random_Bytes, Random_Hex, Random_Base64, Random_Uint32};
+
+let raw: String = Random_Bytes(32); // 32 random bytes (binary-safe string)
+let hex: String = Random_Hex(16); // 16 random bytes as hex (32 chars)
+let b64: String = Random_Base64(16); // 16 random bytes as base64
+let u32: uint = Random_Uint32(); // random 32-bit unsigned integer
+```
+
+### AES-256
+
+```bux
+import Std::Crypto::Aes::{Aes_GenerateKey, Aes_GenerateIV,
+ Aes_CbcEncrypt, Aes_CbcDecrypt,
+ Aes_GcmEncrypt, Aes_GcmDecrypt};
+
+// Generate random key and IV
+let key: String = Aes_GenerateKey(); // 32 raw bytes
+let iv: String = Aes_GenerateIV(); // 16 raw bytes
+
+// CBC mode
+let cipher: String = Aes_CbcEncrypt("secret message", key, iv);
+let plain: String = Aes_CbcDecrypt(cipher, key, iv);
+
+// GCM mode (authenticated encryption)
+let tag: *void = Alloc(16);
+let gcmCipher: String = Aes_GcmEncrypt("secret", key, iv, tag);
+let gcmPlain: String = Aes_GcmDecrypt(gcmCipher, key, iv, tag as String);
+```
+
+### RSA
+
+```bux
+import Std::Crypto::Rsa::{Rsa_SignSha256, Rsa_VerifySha256,
+ Rsa_SignSha256Base64, Rsa_VerifySha256Base64};
+
+// Keys are PEM-encoded strings
+let pemPriv: String = ReadFile("private.pem");
+let pemPub: String = ReadFile("public.pem");
+
+// Sign — returns raw signature
+let sig: String = Rsa_SignSha256(pemPriv, "data to sign");
+
+// Or sign and get base64
+let sigB64: String = Rsa_SignSha256Base64(pemPriv, "data to sign");
+
+// Verify raw signature
+let valid: bool = Rsa_VerifySha256(pemPub, "data to sign", sig);
+
+// Verify base64 signature
+let validB64: bool = Rsa_VerifySha256Base64(pemPub, "data to sign", sigB64);
+```
+
+### ECDSA
+
+```bux
+import Std::Crypto::Ecdsa::{Ecdsa_SignP256, Ecdsa_VerifyP256,
+ Ecdsa_SignP384, Ecdsa_VerifyP384};
+
+// P-256 (ES256)
+let sig: String = Ecdsa_SignP256(pemPriv, "data");
+let ok: bool = Ecdsa_VerifyP256(pemPub, "data", sig);
+
+// P-384 (ES384)
+let sig384: String = Ecdsa_SignP384(pemPriv, "data");
+let ok384: bool = Ecdsa_VerifyP384(pemPub, "data", sig384);
+```
+
+### Ed25519
+
+```bux
+import Std::Crypto::Ed25519::{Ed25519_Keypair, Ed25519_Sign, Ed25519_Verify};
+
+// Generate keypair — keys are 32 raw bytes each
+let pub: *void = Alloc(32);
+let priv: *void = Alloc(32);
+Ed25519_Keypair(pub, priv);
+
+// Sign
+let sig: String = Ed25519_Sign(priv as String, "message");
+// sig is 64 raw bytes
+
+// Verify
+let valid: bool = Ed25519_Verify(pub as String, sig, "message");
+```
+
+### JWT
+
+```bux
+import Std::Crypto::Jwt::{JwtAlg, Jwt_MakeHeader, Jwt_Encode,
+ Jwt_Decode, Jwt_EncodeHS256};
+
+// --- Symmetric (HS256) ---
+let token: String = Jwt_EncodeHS256("{\"sub\":\"123\",\"role\":\"admin\"}", "my-secret");
+
+var header: String;
+var payload: String;
+let alg: JwtAlg = JwtAlg { tag: JwtAlg_HS256 };
+if Jwt_Decode(token, alg, "my-secret", &header, &payload) {
+ PrintLine(payload); // {"sub":"123","role":"admin"}
+}
+
+// --- Asymmetric (RS256) ---
+let rsToken: String = Jwt_Encode(
+ "{\"alg\":\"RS256\",\"typ\":\"JWT\"}",
+ "{\"sub\":\"456\"}",
+ JwtAlg { tag: JwtAlg_RS256 },
+ pemPrivateKey
+);
+
+// --- Convenience helpers ---
+Jwt_EncodeHS256(payload, secret);
+Jwt_EncodeHS384(payload, secret);
+Jwt_EncodeHS512(payload, secret);
+Jwt_EncodeRS256(payload, pemPrivKey);
+Jwt_EncodeES256(payload, pemPrivKey);
+Jwt_EncodeEdDSA(payload, rawPrivKey32);
+```
+
+## Supported JWT Algorithms
+
+| Algorithm | JWT `alg` | Key Type | Key Format |
+|-----------|-----------|----------|------------|
+| HS256 | `HS256` | HMAC secret | Raw string |
+| HS384 | `HS384` | HMAC secret | Raw string |
+| HS512 | `HS512` | HMAC secret | Raw string |
+| RS256 | `RS256` | RSA private/public key | PEM string |
+| RS384 | `RS384` | RSA private/public key | PEM string |
+| RS512 | `RS512` | RSA private/public key | PEM string |
+| ES256 | `ES256` | ECDSA P-256 key | PEM string |
+| ES384 | `ES384` | ECDSA P-384 key | PEM string |
+| EdDSA | `EdDSA` | Ed25519 key | 32-byte raw |
+
+## Backend
+
+All primitives are implemented in C using OpenSSL and linked via the Bux runtime (`rt/runtime.c`). The C functions are declared as `extern func` in each Bux module.
+
+Requires OpenSSL 1.1.1+ (for Ed25519 support). Link with `-lssl -lcrypto`.
+
+## File Layout
+
+```
+lib/
+├── Crypto.bux # Backward-compat wrapper (old API)
+└── crypto/
+ ├── base64.bux # Base64 + Base64URL
+ ├── hash.bux # SHA-1/256/384/512
+ ├── hmac.bux # HMAC-SHA256/384/512
+ ├── random.bux # Secure random
+ ├── aes.bux # AES-256-CBC/GCM
+ ├── rsa.bux # RSA PKCS#1 v1.5
+ ├── ecdsa.bux # ECDSA P-256/P-384
+ ├── ed25519.bux # Ed25519
+ └── jwt.bux # JSON Web Tokens
+
+lib/crypto_test/ # Test project (exercises all modules)
+test_crypto/ # Standalone test project
+```
+
+## Migration from old `Std::Crypto`
+
+The old single-file API is still available under `Std::Crypto`:
+
+| Old Function | New Equivalent | Module |
+|-------------|----------------|--------|
+| `Crypto_Sha256(s)` | `Hash_Sha256(s)` | `Std::Crypto::Hash` |
+| `Crypto_HmacSha256(k, m)` | `Hmac_Sha256(k, m)` | `Std::Crypto::Hmac` |
+| `Crypto_RandomBytes(n)` | `Random_Base64(n)` | `Std::Crypto::Random` |
+| `Crypto_Base64Encode(s)` | `Base64_Encode(s)` | `Std::Crypto::Base64` |
+| `Crypto_Base64Decode(s)` | `Base64_Decode(s)` | `Std::Crypto::Base64` |
+| `Crypto_HmacSha256Raw(k, m)` | `Hmac_Sha256Base64(k, m)` | `Std::Crypto::Hmac` |
+
+New code should prefer the submodule imports for clarity and to avoid pulling in unused declarations.
+
+## Running Tests
+
+```bash
+cd test_crypto
+../buxc build
+./test_crypto
+```
+
+Expected output:
+```
+================================================
+ Bux Crypto Library — Test Suite
+================================================
+
+--- Base64 ---
+ PASS Base64_Encode('hello')
+ PASS Base64_Decode('aGVsbG8=')
+ ...
+--- Hash ---
+ PASS Hash_Sha256('hello')
+ ...
+================================================
+ Passed: 23
+ Failed: 0
+================================================
+```
diff --git a/lib/crypto/aes.bux b/lib/crypto/aes.bux
new file mode 100644
index 0000000..55f963d
--- /dev/null
+++ b/lib/crypto/aes.bux
@@ -0,0 +1,67 @@
+// =============================================================================
+// Std::Crypto::Aes — AES-256-CBC and AES-256-GCM encryption/decryption
+// =============================================================================
+module Std::Crypto::Aes {
+
+import Std::Mem::{Alloc, Free};
+import Std::String::{String_Len};
+
+extern func bux_random_bytes(buf: *void, len: int) -> int;
+extern func bux_aes_256_cbc_encrypt(plain: String, plainlen: int, key: String, iv: String, outlen: *int) -> String;
+extern func bux_aes_256_cbc_decrypt(cipher: String, cipherlen: int, key: String, iv: String, outlen: *int) -> String;
+extern func bux_aes_256_gcm_encrypt(plain: String, plainlen: int, key: String, iv: String, tag: *void, outlen: *int) -> String;
+extern func bux_aes_256_gcm_decrypt(cipher: String, cipherlen: int, key: String, iv: String, tag: String, outlen: *int) -> String;
+
+// --- AES-256-CBC ---
+
+const AES_KEY_SIZE: int = 32; // 256 bits
+const AES_IV_SIZE: int = 16; // 128 bits
+const AES_GCM_TAG_SIZE: int = 16;
+
+// Generate a random 256-bit AES key (returns raw 32 bytes)
+func Aes_GenerateKey() -> String {
+ let buf: *void = Alloc(AES_KEY_SIZE as uint);
+ if bux_random_bytes(buf, AES_KEY_SIZE) != 1 {
+ Free(buf);
+ return "";
+ }
+ return buf as String;
+}
+
+// Generate a random 128-bit IV (returns raw 16 bytes)
+func Aes_GenerateIV() -> String {
+ let buf: *void = Alloc(AES_IV_SIZE as uint);
+ if bux_random_bytes(buf, AES_IV_SIZE) != 1 {
+ Free(buf);
+ return "";
+ }
+ return buf as String;
+}
+
+// AES-256-CBC encrypt. plain and key are binary strings, iv is 16 bytes.
+// Returns ciphertext (may be longer than plain due to PKCS#7 padding).
+func Aes_CbcEncrypt(plain: String, key: String, iv: String) -> String {
+ let outlen: int = 0;
+ return bux_aes_256_cbc_encrypt(plain, String_Len(plain) as int, key, iv, &outlen);
+}
+
+// AES-256-CBC decrypt. Returns plaintext.
+func Aes_CbcDecrypt(cipher: String, key: String, iv: String) -> String {
+ let outlen: int = 0;
+ return bux_aes_256_cbc_decrypt(cipher, String_Len(cipher) as int, key, iv, &outlen);
+}
+
+// --- AES-256-GCM (Authenticated Encryption) ---
+
+// AES-256-GCM encrypt. Returns ciphertext. tag receives 16-byte authentication tag.
+func Aes_GcmEncrypt(plain: String, key: String, iv: String, tag: *void) -> String {
+ let outlen: int = 0;
+ return bux_aes_256_gcm_encrypt(plain, String_Len(plain) as int, key, iv, tag, &outlen);
+}
+
+// AES-256-GCM decrypt. Returns plaintext. tag must be the 16-byte auth tag from encryption.
+func Aes_GcmDecrypt(cipher: String, key: String, iv: String, tag: String) -> String {
+ let outlen: int = 0;
+ return bux_aes_256_gcm_decrypt(cipher, String_Len(cipher) as int, key, iv, tag, &outlen);
+}
+}
diff --git a/lib/crypto/base64.bux b/lib/crypto/base64.bux
new file mode 100644
index 0000000..3897556
--- /dev/null
+++ b/lib/crypto/base64.bux
@@ -0,0 +1,34 @@
+// =============================================================================
+// Std::Crypto::Base64 — Base64 and Base64URL encode/decode
+// =============================================================================
+module Std::Crypto::Base64 {
+
+import Std::String::{String_Len};
+
+extern func bux_base64_encode(data: String, len: int) -> String;
+extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
+extern func bux_base64url_encode(data: String, len: int) -> String;
+extern func bux_base64url_decode(data: String, len: int, outlen: *int) -> String;
+
+// --- Standard Base64 ---
+
+func Base64_Encode(s: String) -> String {
+ return bux_base64_encode(s, String_Len(s) as int);
+}
+
+func Base64_Decode(s: String) -> String {
+ let outlen: int = 0;
+ return bux_base64_decode(s, String_Len(s) as int, &outlen);
+}
+
+// --- Base64URL (RFC 4648 §5, uses - and _ instead of + and /, no padding) ---
+
+func Base64URL_Encode(s: String) -> String {
+ return bux_base64url_encode(s, String_Len(s) as int);
+}
+
+func Base64URL_Decode(s: String) -> String {
+ let outlen: int = 0;
+ return bux_base64url_decode(s, String_Len(s) as int, &outlen);
+}
+}
diff --git a/lib/crypto/ecdsa.bux b/lib/crypto/ecdsa.bux
new file mode 100644
index 0000000..8191604
--- /dev/null
+++ b/lib/crypto/ecdsa.bux
@@ -0,0 +1,67 @@
+// =============================================================================
+// Std::Crypto::Ecdsa — ECDSA P-256 and P-384 sign/verify
+// =============================================================================
+module Std::Crypto::Ecdsa {
+
+import Std::Mem::{Alloc, Free};
+import Std::String::{String_Len};
+
+extern func bux_ecdsa_sign_p256(pemKey: String, keylen: int, data: String, datalen: int, siglen: *int) -> String;
+extern func bux_ecdsa_sign_p384(pemKey: String, keylen: int, data: String, datalen: int, siglen: *int) -> String;
+extern func bux_ecdsa_verify_p256(pemKey: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
+extern func bux_ecdsa_verify_p384(pemKey: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
+extern func bux_base64_encode(data: String, len: int) -> String;
+extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
+
+// --- ECDSA P-256 (ES256) ---
+
+func Ecdsa_SignP256(pemPrivateKey: String, data: String) -> String {
+ let siglen: int = 0;
+ return bux_ecdsa_sign_p256(pemPrivateKey, String_Len(pemPrivateKey) as int,
+ data, String_Len(data) as int, &siglen);
+}
+
+func Ecdsa_SignP256Base64(pemPrivateKey: String, data: String) -> String {
+ let sig: String = Ecdsa_SignP256(pemPrivateKey, data);
+ return bux_base64_encode(sig, String_Len(sig) as int);
+}
+
+func Ecdsa_VerifyP256(pemPublicKey: String, data: String, signature: String) -> bool {
+ let r: int = bux_ecdsa_verify_p256(pemPublicKey, String_Len(pemPublicKey) as int,
+ data, String_Len(data) as int,
+ signature, String_Len(signature) as int);
+ return r == 1;
+}
+
+func Ecdsa_VerifyP256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
+ let outlen: int = 0;
+ let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
+ return Ecdsa_VerifyP256(pemPublicKey, data, sig);
+}
+
+// --- ECDSA P-384 (ES384) ---
+
+func Ecdsa_SignP384(pemPrivateKey: String, data: String) -> String {
+ let siglen: int = 0;
+ return bux_ecdsa_sign_p384(pemPrivateKey, String_Len(pemPrivateKey) as int,
+ data, String_Len(data) as int, &siglen);
+}
+
+func Ecdsa_SignP384Base64(pemPrivateKey: String, data: String) -> String {
+ let sig: String = Ecdsa_SignP384(pemPrivateKey, data);
+ return bux_base64_encode(sig, String_Len(sig) as int);
+}
+
+func Ecdsa_VerifyP384(pemPublicKey: String, data: String, signature: String) -> bool {
+ let r: int = bux_ecdsa_verify_p384(pemPublicKey, String_Len(pemPublicKey) as int,
+ data, String_Len(data) as int,
+ signature, String_Len(signature) as int);
+ return r == 1;
+}
+
+func Ecdsa_VerifyP384Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
+ let outlen: int = 0;
+ let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
+ return Ecdsa_VerifyP384(pemPublicKey, data, sig);
+}
+}
diff --git a/lib/crypto/ed25519.bux b/lib/crypto/ed25519.bux
new file mode 100644
index 0000000..d0bab8b
--- /dev/null
+++ b/lib/crypto/ed25519.bux
@@ -0,0 +1,80 @@
+// =============================================================================
+// Std::Crypto::Ed25519 — Ed25519 key generation, signing, and verification
+// =============================================================================
+module Std::Crypto::Ed25519 {
+
+import Std::Mem::{Alloc, Free};
+import Std::String::{String_Len, String_Concat};
+
+extern func bux_ed25519_keypair(pubKey: *void, privKey: *void) -> int;
+extern func bux_ed25519_sign(privKey: String, data: String, datalen: int, sig: *void) -> int;
+extern func bux_ed25519_verify(pubKey: String, sig: String, data: String, datalen: int) -> int;
+extern func bux_base64_encode(data: String, len: int) -> String;
+extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
+
+const ED25519_PUBKEY_SIZE: int = 32;
+const ED25519_PRIVKEY_SIZE: int = 32;
+const ED25519_SIG_SIZE: int = 64;
+
+// --- Key Generation ---
+
+// Ed25519_Keypair: generates a new keypair.
+// Returns true on success. pubKey and privKey receive 32-byte raw keys.
+func Ed25519_Keypair(pubKey: *void, privKey: *void) -> bool {
+ let r: int = bux_ed25519_keypair(pubKey, privKey);
+ return r == 1;
+}
+
+// Convenience: generate and return base64-encoded keypair
+func Ed25519_KeypairBase64() -> String {
+ let pub: *void = Alloc(ED25519_PUBKEY_SIZE as uint);
+ let priv: *void = Alloc(ED25519_PRIVKEY_SIZE as uint);
+ if bux_ed25519_keypair(pub, priv) != 1 {
+ Free(pub);
+ Free(priv);
+ return "";
+ }
+ // Return "pub_b64:priv_b64"
+ let pubB64: String = bux_base64_encode(pub as String, ED25519_PUBKEY_SIZE);
+ let privB64: String = bux_base64_encode(priv as String, ED25519_PRIVKEY_SIZE);
+ Free(pub);
+ Free(priv);
+ let pair: String = String_Concat(pubB64, ":");
+ return String_Concat(pair, privB64);
+}
+
+// --- Sign ---
+
+// Ed25519_Sign: sign data with 32-byte raw private key. Returns 64-byte raw signature.
+func Ed25519_Sign(privKey: String, data: String) -> String {
+ let sig: *void = Alloc(ED25519_SIG_SIZE as uint);
+ if bux_ed25519_sign(privKey, data, String_Len(data) as int, sig) != 1 {
+ Free(sig);
+ return "";
+ }
+ return sig as String;
+}
+
+// Convenience: sign and return base64-encoded signature
+func Ed25519_SignBase64(privKey: String, data: String) -> String {
+ let sig: String = Ed25519_Sign(privKey, data);
+ if String_Len(sig) == 0 { return ""; }
+ return bux_base64_encode(sig, ED25519_SIG_SIZE);
+}
+
+// --- Verify ---
+
+// Ed25519_Verify: verify a 64-byte raw signature against data with 32-byte public key.
+func Ed25519_Verify(pubKey: String, signature: String, data: String) -> bool {
+ let r: int = bux_ed25519_verify(pubKey, signature, data, String_Len(data) as int);
+ return r == 1;
+}
+
+// Convenience: verify a base64-encoded signature
+func Ed25519_VerifyBase64(pubKey: String, signatureB64: String, data: String) -> bool {
+ let outlen: int = 0;
+ let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
+ if outlen != ED25519_SIG_SIZE { return false; }
+ return Ed25519_Verify(pubKey, sig, data);
+}
+}
diff --git a/lib/crypto/hash.bux b/lib/crypto/hash.bux
new file mode 100644
index 0000000..3ed5b55
--- /dev/null
+++ b/lib/crypto/hash.bux
@@ -0,0 +1,73 @@
+// =============================================================================
+// Std::Crypto::Hash — SHA-1, SHA-256, SHA-384, SHA-512
+// =============================================================================
+module Std::Crypto::Hash {
+
+import Std::Mem::{Alloc, Free};
+import Std::String::{String_Len};
+
+extern func bux_sha1(data: String, len: int, out: *void);
+extern func bux_sha256(data: String, len: int, out: *void);
+extern func bux_sha384(data: String, len: int, out: *void);
+extern func bux_sha512(data: String, len: int, out: *void);
+extern func bux_bytes_to_hex(data: *void, len: int) -> String;
+
+// --- Convenience wrappers: hex output ---
+
+func Hash_Sha1(data: String) -> String {
+ let len: int = String_Len(data) as int;
+ let buf: *void = Alloc(20);
+ bux_sha1(data, len, buf);
+ let result: String = bux_bytes_to_hex(buf, 20);
+ Free(buf);
+ return result;
+}
+
+func Hash_Sha256(data: String) -> String {
+ let len: int = String_Len(data) as int;
+ let buf: *void = Alloc(32);
+ bux_sha256(data, len, buf);
+ let result: String = bux_bytes_to_hex(buf, 32);
+ Free(buf);
+ return result;
+}
+
+func Hash_Sha384(data: String) -> String {
+ let len: int = String_Len(data) as int;
+ let buf: *void = Alloc(48);
+ bux_sha384(data, len, buf);
+ let result: String = bux_bytes_to_hex(buf, 48);
+ Free(buf);
+ return result;
+}
+
+func Hash_Sha512(data: String) -> String {
+ let len: int = String_Len(data) as int;
+ let buf: *void = Alloc(64);
+ bux_sha512(data, len, buf);
+ let result: String = bux_bytes_to_hex(buf, 64);
+ Free(buf);
+ return result;
+}
+
+// --- Raw binary output (caller must Alloc/Free) ---
+
+func Hash_Sha256Raw(data: String, out: *void) {
+ bux_sha256(data, String_Len(data) as int, out);
+}
+
+func Hash_Sha384Raw(data: String, out: *void) {
+ bux_sha384(data, String_Len(data) as int, out);
+}
+
+func Hash_Sha512Raw(data: String, out: *void) {
+ bux_sha512(data, String_Len(data) as int, out);
+}
+
+// --- Digest sizes ---
+
+func Hash_Sha1Size() -> int { return 20; }
+func Hash_Sha256Size() -> int { return 32; }
+func Hash_Sha384Size() -> int { return 48; }
+func Hash_Sha512Size() -> int { return 64; }
+}
diff --git a/lib/crypto/hmac.bux b/lib/crypto/hmac.bux
new file mode 100644
index 0000000..5768c8e
--- /dev/null
+++ b/lib/crypto/hmac.bux
@@ -0,0 +1,92 @@
+// =============================================================================
+// Std::Crypto::Hmac — HMAC-SHA256, HMAC-SHA384, HMAC-SHA512
+// =============================================================================
+module Std::Crypto::Hmac {
+
+import Std::Mem::{Alloc, Free};
+import Std::String::{String_Len};
+
+extern func bux_hmac_sha256(key: String, keylen: int, msg: String, msglen: int, out: *void);
+extern func bux_hmac_sha384(key: String, keylen: int, msg: String, msglen: int, out: *void);
+extern func bux_hmac_sha512(key: String, keylen: int, msg: String, msglen: int, out: *void);
+extern func bux_bytes_to_hex(data: *void, len: int) -> String;
+extern func bux_base64_encode(data: String, len: int) -> String;
+
+// --- HMAC-SHA256 ---
+
+func Hmac_Sha256(key: String, message: String) -> String {
+ let kl: int = String_Len(key) as int;
+ let ml: int = String_Len(message) as int;
+ let buf: *void = Alloc(32);
+ bux_hmac_sha256(key, kl, message, ml, buf);
+ let result: String = bux_bytes_to_hex(buf, 32);
+ Free(buf);
+ return result;
+}
+
+func Hmac_Sha256Raw(key: String, message: String, out: *void) {
+ bux_hmac_sha256(key, String_Len(key) as int, message, String_Len(message) as int, out);
+}
+
+func Hmac_Sha256Base64(key: String, message: String) -> String {
+ let kl: int = String_Len(key) as int;
+ let ml: int = String_Len(message) as int;
+ let buf: *void = Alloc(32);
+ bux_hmac_sha256(key, kl, message, ml, buf);
+ let result: String = bux_base64_encode(buf as String, 32);
+ Free(buf);
+ return result;
+}
+
+// --- HMAC-SHA384 ---
+
+func Hmac_Sha384(key: String, message: String) -> String {
+ let kl: int = String_Len(key) as int;
+ let ml: int = String_Len(message) as int;
+ let buf: *void = Alloc(48);
+ bux_hmac_sha384(key, kl, message, ml, buf);
+ let result: String = bux_bytes_to_hex(buf, 48);
+ Free(buf);
+ return result;
+}
+
+func Hmac_Sha384Raw(key: String, message: String, out: *void) {
+ bux_hmac_sha384(key, String_Len(key) as int, message, String_Len(message) as int, out);
+}
+
+func Hmac_Sha384Base64(key: String, message: String) -> String {
+ let kl: int = String_Len(key) as int;
+ let ml: int = String_Len(message) as int;
+ let buf: *void = Alloc(48);
+ bux_hmac_sha384(key, kl, message, ml, buf);
+ let result: String = bux_base64_encode(buf as String, 48);
+ Free(buf);
+ return result;
+}
+
+// --- HMAC-SHA512 ---
+
+func Hmac_Sha512(key: String, message: String) -> String {
+ let kl: int = String_Len(key) as int;
+ let ml: int = String_Len(message) as int;
+ let buf: *void = Alloc(64);
+ bux_hmac_sha512(key, kl, message, ml, buf);
+ let result: String = bux_bytes_to_hex(buf, 64);
+ Free(buf);
+ return result;
+}
+
+func Hmac_Sha512Raw(key: String, message: String, out: *void) {
+ bux_hmac_sha512(key, String_Len(key) as int, message, String_Len(message) as int, out);
+}
+
+func Hmac_Sha512Base64(key: String, message: String) -> String {
+ let kl: int = String_Len(key) as int;
+ let ml: int = String_Len(message) as int;
+ let buf: *void = Alloc(64);
+ bux_hmac_sha512(key, kl, message, ml, buf);
+ let result: String = bux_base64_encode(buf as String, 64);
+ Free(buf);
+ return result;
+}
+}
diff --git a/lib/crypto/jwt.bux b/lib/crypto/jwt.bux
new file mode 100644
index 0000000..7cf9584
--- /dev/null
+++ b/lib/crypto/jwt.bux
@@ -0,0 +1,242 @@
+// =============================================================================
+// Std::Crypto::Jwt — JSON Web Token (RFC 7519) encode/decode
+// Supports HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, EdDSA
+// =============================================================================
+module Std::Crypto::Jwt {
+
+import Std::Mem::{Alloc, Free};
+import Std::String::{String_Len, String_Eq, String_StartsWith, String_Concat};
+import Std::Crypto::Base64::{Base64URL_Encode, Base64URL_Decode};
+import Std::Crypto::Hash::{Hash_Sha256Raw, Hash_Sha384Raw, Hash_Sha512Raw};
+import Std::Crypto::Hmac::{Hmac_Sha256Raw, Hmac_Sha384Raw, Hmac_Sha512Raw};
+import Std::Crypto::Rsa::{Rsa_SignSha256, Rsa_SignSha384, Rsa_SignSha512,
+ Rsa_VerifySha256, Rsa_VerifySha384, Rsa_VerifySha512};
+import Std::Crypto::Ecdsa::{Ecdsa_SignP256, Ecdsa_SignP384, Ecdsa_VerifyP256, Ecdsa_VerifyP384};
+import Std::Crypto::Ed25519::{Ed25519_Sign, Ed25519_Verify};
+
+extern func bux_base64_encode(data: String, len: int) -> String;
+extern func bux_str_split_count(s: String, delim: String) -> uint;
+extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
+
+// --- JWT Algorithm enum ---
+enum JwtAlg {
+ HS256,
+ HS384,
+ HS512,
+ RS256,
+ RS384,
+ RS512,
+ ES256,
+ ES384,
+ EdDSA,
+}
+
+// --- Header ---
+
+// Jwt_MakeHeader: build the JWT header JSON string for the given algorithm
+func Jwt_MakeHeader(alg: JwtAlg) -> String {
+ if alg.tag == JwtAlg_HS256 { return "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_HS384 { return "{\"alg\":\"HS384\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_HS512 { return "{\"alg\":\"HS512\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_RS256 { return "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_RS384 { return "{\"alg\":\"RS384\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_RS512 { return "{\"alg\":\"RS512\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_ES256 { return "{\"alg\":\"ES256\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_ES384 { return "{\"alg\":\"ES384\",\"typ\":\"JWT\"}"; }
+ if alg.tag == JwtAlg_EdDSA { return "{\"alg\":\"EdDSA\",\"typ\":\"JWT\"}"; }
+ return "{\"alg\":\"none\",\"typ\":\"JWT\"}";
+}
+
+// --- Signing ---
+
+// Sign the JWT signing input with the given algorithm
+func Jwt_Sign(alg: JwtAlg, signingInput: String, key: String) -> String {
+ let algTag: int = alg.tag;
+ let inputLen: int = String_Len(signingInput) as int;
+
+ // --- HMAC algorithms ---
+ if algTag == JwtAlg_HS256 {
+ let buf: *void = Alloc(32);
+ Hmac_Sha256Raw(key, signingInput, buf);
+ let result: String = bux_base64_encode(buf as String, 32);
+ Free(buf);
+ return result;
+ }
+ if algTag == JwtAlg_HS384 {
+ let buf: *void = Alloc(48);
+ Hmac_Sha384Raw(key, signingInput, buf);
+ let result: String = bux_base64_encode(buf as String, 48);
+ Free(buf);
+ return result;
+ }
+ if algTag == JwtAlg_HS512 {
+ let buf: *void = Alloc(64);
+ Hmac_Sha512Raw(key, signingInput, buf);
+ let result: String = bux_base64_encode(buf as String, 64);
+ Free(buf);
+ return result;
+ }
+
+ // --- RSA algorithms (key is PEM private key) ---
+ if algTag == JwtAlg_RS256 {
+ let raw: String = Rsa_SignSha256(key, signingInput);
+ return bux_base64_encode(raw, String_Len(raw) as int);
+ }
+ if algTag == JwtAlg_RS384 {
+ let raw: String = Rsa_SignSha384(key, signingInput);
+ return bux_base64_encode(raw, String_Len(raw) as int);
+ }
+ if algTag == JwtAlg_RS512 {
+ let raw: String = Rsa_SignSha512(key, signingInput);
+ return bux_base64_encode(raw, String_Len(raw) as int);
+ }
+
+ // --- ECDSA algorithms (key is PEM private key) ---
+ if algTag == JwtAlg_ES256 {
+ let raw: String = Ecdsa_SignP256(key, signingInput);
+ return bux_base64_encode(raw, String_Len(raw) as int);
+ }
+ if algTag == JwtAlg_ES384 {
+ let raw: String = Ecdsa_SignP384(key, signingInput);
+ return bux_base64_encode(raw, String_Len(raw) as int);
+ }
+
+ // --- EdDSA (key is 32-byte raw private key) ---
+ if algTag == JwtAlg_EdDSA {
+ return Ed25519_Sign(key, signingInput);
+ }
+
+ return "";
+}
+
+// --- Verify ---
+
+// Verify a JWT signature
+func Jwt_Verify(alg: JwtAlg, signingInput: String, signatureB64: String, key: String) -> bool {
+ let algTag: int = alg.tag;
+ let inputLen: int = String_Len(signingInput) as int;
+
+ // --- HMAC algorithms ---
+ if algTag == JwtAlg_HS256 {
+ let expectBuf: *void = Alloc(32);
+ Hmac_Sha256Raw(key, signingInput, expectBuf);
+ let expectB64: String = bux_base64_encode(expectBuf as String, 32);
+ Free(expectBuf);
+ return String_Eq(expectB64, signatureB64);
+ }
+ if algTag == JwtAlg_HS384 {
+ let expectBuf: *void = Alloc(48);
+ Hmac_Sha384Raw(key, signingInput, expectBuf);
+ let expectB64: String = bux_base64_encode(expectBuf as String, 48);
+ Free(expectBuf);
+ return String_Eq(expectB64, signatureB64);
+ }
+ if algTag == JwtAlg_HS512 {
+ let expectBuf: *void = Alloc(64);
+ Hmac_Sha512Raw(key, signingInput, expectBuf);
+ let expectB64: String = bux_base64_encode(expectBuf as String, 64);
+ Free(expectBuf);
+ return String_Eq(expectB64, signatureB64);
+ }
+
+ // --- RSA algorithms ---
+ if algTag == JwtAlg_RS256 { return Rsa_VerifySha256(key, signingInput, signatureB64); }
+ if algTag == JwtAlg_RS384 { return Rsa_VerifySha384(key, signingInput, signatureB64); }
+ if algTag == JwtAlg_RS512 { return Rsa_VerifySha512(key, signingInput, signatureB64); }
+
+ // --- ECDSA algorithms ---
+ if algTag == JwtAlg_ES256 { return Ecdsa_VerifyP256(key, signingInput, signatureB64); }
+ if algTag == JwtAlg_ES384 { return Ecdsa_VerifyP384(key, signingInput, signatureB64); }
+
+ // --- EdDSA ---
+ if algTag == JwtAlg_EdDSA { return Ed25519_Verify(key, signatureB64, signingInput); }
+
+ return false;
+}
+
+// --- Encode ---
+
+// Jwt_Encode: create a signed JWT
+// headerJson — JSON header string (use Jwt_MakeHeader or custom)
+// payloadJson — JSON payload/claims string
+// alg — signing algorithm
+// key — signing key (HMAC secret, RSA PEM, ECDSA PEM, or Ed25519 raw privkey)
+// Returns the complete "header.payload.signature" JWT string
+func Jwt_Encode(headerJson: String, payloadJson: String, alg: JwtAlg, key: String) -> String {
+ let headerB64: String = Base64URL_Encode(headerJson);
+ let payloadB64: String = Base64URL_Encode(payloadJson);
+ let signingInput: String = String_Concat(headerB64, ".");
+ let signingInputFull: String = String_Concat(signingInput, payloadB64);
+
+ let sigB64: String = Jwt_Sign(alg, signingInputFull, key);
+
+ let part1: String = String_Concat(signingInputFull, ".");
+ return String_Concat(part1, sigB64);
+}
+
+// --- Decode ---
+
+// Jwt_Decode: decode and verify a JWT.
+// token — the full "header.payload.signature" string
+// alg — expected algorithm
+// key — verification key
+// headerOut — receives decoded header JSON
+// payloadOut — receives decoded payload JSON
+// Returns true if signature is valid.
+func Jwt_Decode(token: String, alg: JwtAlg, key: String,
+ headerOut: *String, payloadOut: *String) -> bool {
+ // Split by "."
+ let partCount: uint = bux_str_split_count(token, ".");
+ if partCount != 3 { return false; }
+
+ let headerB64: String = bux_str_split_part(token, ".", 0);
+ let payloadB64: String = bux_str_split_part(token, ".", 1);
+ let sigB64: String = bux_str_split_part(token, ".", 2);
+
+ // Build signing input
+ let input: String = String_Concat(headerB64, ".");
+ let signingInput: String = String_Concat(input, payloadB64);
+
+ // Verify signature
+ if !Jwt_Verify(alg, signingInput, sigB64, key) {
+ return false;
+ }
+
+ // Decode
+ headerOut[0] = Base64URL_Decode(headerB64);
+ payloadOut[0] = Base64URL_Decode(payloadB64);
+ return true;
+}
+
+// --- Convenience: Encode with standard header ---
+
+func Jwt_EncodeHS256(payloadJson: String, secret: String) -> String {
+ let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS256 });
+ return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_HS256 }, secret);
+}
+
+func Jwt_EncodeHS384(payloadJson: String, secret: String) -> String {
+ let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS384 });
+ return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_HS384 }, secret);
+}
+
+func Jwt_EncodeHS512(payloadJson: String, secret: String) -> String {
+ let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS512 });
+ return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_HS512 }, secret);
+}
+
+func Jwt_EncodeRS256(payloadJson: String, pemPrivateKey: String) -> String {
+ let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_RS256 });
+ return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_RS256 }, pemPrivateKey);
+}
+
+func Jwt_EncodeES256(payloadJson: String, pemPrivateKey: String) -> String {
+ let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_ES256 });
+ return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_ES256 }, pemPrivateKey);
+}
+
+func Jwt_EncodeEdDSA(payloadJson: String, rawPrivKey: String) -> String {
+ let header: String = Jwt_MakeHeader(JwtAlg { tag: JwtAlg_EdDSA });
+ return Jwt_Encode(header, payloadJson, JwtAlg { tag: JwtAlg_EdDSA }, rawPrivKey);
+}
+}
diff --git a/lib/crypto/random.bux b/lib/crypto/random.bux
new file mode 100644
index 0000000..05fa5df
--- /dev/null
+++ b/lib/crypto/random.bux
@@ -0,0 +1,63 @@
+// =============================================================================
+// Std::Crypto::Random — cryptographically secure random bytes
+// =============================================================================
+module Std::Crypto::Random {
+
+import Std::Mem::{Alloc, Free};
+
+extern func bux_random_bytes(buf: *void, len: int) -> int;
+extern func bux_base64_encode(data: String, len: int) -> String;
+extern func bux_bytes_to_hex(data: *void, len: int) -> String;
+
+// RandomBytes: returns n cryptographically secure random bytes as a raw string
+func Random_Bytes(n: int) -> String {
+ if n <= 0 { return ""; }
+ let buf: *void = Alloc(n as uint);
+ if bux_random_bytes(buf, n) != 1 {
+ Free(buf);
+ return "";
+ }
+ // Return raw buffer as string (binary-safe)
+ return buf as String;
+}
+
+// RandomHex: returns n random bytes as lowercase hex
+func Random_Hex(n: int) -> String {
+ if n <= 0 { return ""; }
+ let buf: *void = Alloc(n as uint);
+ if bux_random_bytes(buf, n) != 1 {
+ Free(buf);
+ return "";
+ }
+ let result: String = bux_bytes_to_hex(buf, n);
+ Free(buf);
+ return result;
+}
+
+// RandomBase64: returns n random bytes as base64-encoded string
+func Random_Base64(n: int) -> String {
+ if n <= 0 { return ""; }
+ let buf: *void = Alloc(n as uint);
+ if bux_random_bytes(buf, n) != 1 {
+ Free(buf);
+ return "";
+ }
+ let result: String = bux_base64_encode(buf as String, n);
+ Free(buf);
+ return result;
+}
+
+// RandomUint32: returns a random 32-bit unsigned integer
+func Random_Uint32() -> uint {
+ let buf: *void = Alloc(4);
+ if bux_random_bytes(buf, 4) != 1 {
+ Free(buf);
+ return 0;
+ }
+ // Interpret first 4 bytes as uint (native endian)
+ let ptr: *uint = buf as *uint;
+ let val: uint = *ptr;
+ Free(buf);
+ return val;
+}
+}
diff --git a/lib/crypto/rsa.bux b/lib/crypto/rsa.bux
new file mode 100644
index 0000000..24af15d
--- /dev/null
+++ b/lib/crypto/rsa.bux
@@ -0,0 +1,98 @@
+// =============================================================================
+// Std::Crypto::Rsa — RSA PKCS#1 v1.5 sign/verify (SHA-256, SHA-384, SHA-512)
+// =============================================================================
+module Std::Crypto::Rsa {
+
+import Std::Mem::{Alloc, Free};
+import Std::String::{String_Len};
+
+extern func bux_rsa_sign_sha256(pemKey: String, keylen: int, data: String, datalen: int, siglen: *int) -> String;
+extern func bux_rsa_sign_sha384(pemKey: String, keylen: int, data: String, datalen: int, siglen: *int) -> String;
+extern func bux_rsa_sign_sha512(pemKey: String, keylen: int, data: String, datalen: int, siglen: *int) -> String;
+extern func bux_rsa_verify_sha256(pemKey: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
+extern func bux_rsa_verify_sha384(pemKey: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
+extern func bux_rsa_verify_sha512(pemKey: String, keylen: int, data: String, datalen: int, sig: String, siglen: int) -> int;
+extern func bux_base64_encode(data: String, len: int) -> String;
+extern func bux_base64_decode(data: String, len: int, outlen: *int) -> String;
+
+// --- RSA Sign ---
+
+// Rsa_SignSha256: sign data with RSA private key (PEM format), returns raw signature
+func Rsa_SignSha256(pemPrivateKey: String, data: String) -> String {
+ let siglen: int = 0;
+ return bux_rsa_sign_sha256(pemPrivateKey, String_Len(pemPrivateKey) as int,
+ data, String_Len(data) as int, &siglen);
+}
+
+func Rsa_SignSha384(pemPrivateKey: String, data: String) -> String {
+ let siglen: int = 0;
+ return bux_rsa_sign_sha384(pemPrivateKey, String_Len(pemPrivateKey) as int,
+ data, String_Len(data) as int, &siglen);
+}
+
+func Rsa_SignSha512(pemPrivateKey: String, data: String) -> String {
+ let siglen: int = 0;
+ return bux_rsa_sign_sha512(pemPrivateKey, String_Len(pemPrivateKey) as int,
+ data, String_Len(data) as int, &siglen);
+}
+
+// Convenience: sign and return base64-encoded signature
+func Rsa_SignSha256Base64(pemPrivateKey: String, data: String) -> String {
+ let sig: String = Rsa_SignSha256(pemPrivateKey, data);
+ return bux_base64_encode(sig, String_Len(sig) as int);
+}
+
+func Rsa_SignSha384Base64(pemPrivateKey: String, data: String) -> String {
+ let sig: String = Rsa_SignSha384(pemPrivateKey, data);
+ return bux_base64_encode(sig, String_Len(sig) as int);
+}
+
+func Rsa_SignSha512Base64(pemPrivateKey: String, data: String) -> String {
+ let sig: String = Rsa_SignSha512(pemPrivateKey, data);
+ return bux_base64_encode(sig, String_Len(sig) as int);
+}
+
+// --- RSA Verify ---
+
+// Rsa_VerifySha256: verify raw signature against data with RSA public key (PEM)
+// Returns true if signature is valid.
+func Rsa_VerifySha256(pemPublicKey: String, data: String, signature: String) -> bool {
+ let r: int = bux_rsa_verify_sha256(pemPublicKey, String_Len(pemPublicKey) as int,
+ data, String_Len(data) as int,
+ signature, String_Len(signature) as int);
+ return r == 1;
+}
+
+func Rsa_VerifySha384(pemPublicKey: String, data: String, signature: String) -> bool {
+ let r: int = bux_rsa_verify_sha384(pemPublicKey, String_Len(pemPublicKey) as int,
+ data, String_Len(data) as int,
+ signature, String_Len(signature) as int);
+ return r == 1;
+}
+
+func Rsa_VerifySha512(pemPublicKey: String, data: String, signature: String) -> bool {
+ let r: int = bux_rsa_verify_sha512(pemPublicKey, String_Len(pemPublicKey) as int,
+ data, String_Len(data) as int,
+ signature, String_Len(signature) as int);
+ return r == 1;
+}
+
+// Convenience: verify base64-encoded signature
+func Rsa_VerifySha256Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
+ let outlen: int = 0;
+ let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
+ return Rsa_VerifySha256(pemPublicKey, data, sig);
+}
+
+func Rsa_VerifySha384Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
+ let outlen: int = 0;
+ let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
+ return Rsa_VerifySha384(pemPublicKey, data, sig);
+}
+
+func Rsa_VerifySha512Base64(pemPublicKey: String, data: String, signatureB64: String) -> bool {
+ let outlen: int = 0;
+ let sig: String = bux_base64_decode(signatureB64, String_Len(signatureB64) as int, &outlen);
+ return Rsa_VerifySha512(pemPublicKey, data, sig);
+}
+}
diff --git a/rt/runtime.c b/rt/runtime.c
index 30c4c5c..7b897c4 100644
--- a/rt/runtime.c
+++ b/rt/runtime.c
@@ -1322,6 +1322,12 @@ void bux_assert(int cond, const char* file, int line, const char* expr) {
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
void bux_sha256(const char* data, int len, unsigned char* out) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
@@ -1369,3 +1375,343 @@ char* bux_bytes_to_hex(const unsigned char* data, int len) {
out[len * 2] = '\0';
return out;
}
+
+/* ============================================================================
+ * Extended cryptography primitives (OpenSSL)
+ * ============================================================================ */
+
+static void bux_digest(const EVP_MD* md, const char* data, int len, unsigned char* out) {
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ EVP_DigestInit_ex(ctx, md, NULL);
+ EVP_DigestUpdate(ctx, data, (size_t)len);
+ EVP_DigestFinal_ex(ctx, out, NULL);
+ EVP_MD_CTX_free(ctx);
+}
+
+void bux_sha1(const char* data, int len, unsigned char* out) {
+ bux_digest(EVP_sha1(), data, len, out);
+}
+
+void bux_sha384(const char* data, int len, unsigned char* out) {
+ bux_digest(EVP_sha384(), data, len, out);
+}
+
+void bux_sha512(const char* data, int len, unsigned char* out) {
+ bux_digest(EVP_sha512(), data, len, out);
+}
+
+/* --- HMAC --- */
+
+void bux_hmac_sha384(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
+ unsigned int outlen = 48;
+ HMAC(EVP_sha384(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen);
+}
+
+void bux_hmac_sha512(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) {
+ unsigned int outlen = 64;
+ HMAC(EVP_sha512(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen);
+}
+
+/* --- Base64URL --- */
+
+static const char b64url_table[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+char* bux_base64url_encode(const unsigned char* in, int inlen) {
+ int outlen = 4 * ((inlen + 2) / 3);
+ char* out = (char*)bux_alloc(outlen + 1);
+ int j = 0;
+ for (int i = 0; i < inlen; i += 3) {
+ int a = in[i];
+ int b = (i + 1 < inlen) ? in[i + 1] : 0;
+ int c = (i + 2 < inlen) ? in[i + 2] : 0;
+ out[j++] = b64url_table[(a >> 2) & 0x3F];
+ out[j++] = b64url_table[((a << 4) | (b >> 4)) & 0x3F];
+ if (i + 1 < inlen)
+ out[j++] = b64url_table[((b << 2) | (c >> 6)) & 0x3F];
+ if (i + 2 < inlen)
+ out[j++] = b64url_table[c & 0x3F];
+ }
+ out[j] = '\0';
+ return out;
+}
+
+static int b64url_char_val(char c) {
+ if (c >= 'A' && c <= 'Z') return c - 'A';
+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
+ if (c >= '0' && c <= '9') return c - '0' + 52;
+ if (c == '-') return 62;
+ if (c == '_') return 63;
+ return -1;
+}
+
+char* bux_base64url_decode(const char* in, int inlen, int* outlen) {
+ int maxlen = 3 * inlen / 4 + 1;
+ char* out = (char*)bux_alloc(maxlen);
+ int j = 0;
+ int buf = 0, bits = 0;
+ for (int i = 0; i < inlen; i++) {
+ int val = b64url_char_val(in[i]);
+ if (val < 0) continue;
+ buf = (buf << 6) | val;
+ bits += 6;
+ if (bits >= 8) {
+ bits -= 8;
+ out[j++] = (buf >> bits) & 0xFF;
+ }
+ }
+ out[j] = '\0';
+ *outlen = j;
+ return out;
+}
+
+/* --- AES-256-CBC --- */
+
+static int aes_cbc_ctx_encrypt(const unsigned char* in, int inlen,
+ unsigned char* out, const unsigned char* key,
+ const unsigned char* iv, int encrypt) {
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+ EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv, encrypt);
+ int outlen = 0, tmplen = 0;
+ EVP_CipherUpdate(ctx, out, &tmplen, in, inlen);
+ outlen = tmplen;
+ EVP_CipherFinal_ex(ctx, out + outlen, &tmplen);
+ outlen += tmplen;
+ EVP_CIPHER_CTX_free(ctx);
+ return outlen;
+}
+
+char* bux_aes_256_cbc_encrypt(const char* plain, int plainlen,
+ const char* key, const char* iv, int* outlen) {
+ int maxlen = plainlen + EVP_MAX_BLOCK_LENGTH;
+ char* out = (char*)bux_alloc(maxlen + 1);
+ *outlen = aes_cbc_ctx_encrypt((const unsigned char*)plain, plainlen,
+ (unsigned char*)out,
+ (const unsigned char*)key,
+ (const unsigned char*)iv, 1);
+ out[*outlen] = '\0';
+ return out;
+}
+
+char* bux_aes_256_cbc_decrypt(const char* cipher, int cipherlen,
+ const char* key, const char* iv, int* outlen) {
+ char* out = (char*)bux_alloc(cipherlen + 1);
+ *outlen = aes_cbc_ctx_encrypt((const unsigned char*)cipher, cipherlen,
+ (unsigned char*)out,
+ (const unsigned char*)key,
+ (const unsigned char*)iv, 0);
+ out[*outlen] = '\0';
+ return out;
+}
+
+/* --- AES-256-GCM --- */
+
+char* bux_aes_256_gcm_encrypt(const char* plain, int plainlen,
+ const char* key, const char* iv,
+ unsigned char* tag, int* outlen) {
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (const unsigned char*)key,
+ (const unsigned char*)iv);
+ int maxlen = plainlen + EVP_MAX_BLOCK_LENGTH;
+ char* out = (char*)bux_alloc(maxlen);
+ int tmplen = 0;
+ EVP_EncryptUpdate(ctx, (unsigned char*)out, &tmplen,
+ (const unsigned char*)plain, plainlen);
+ *outlen = tmplen;
+ EVP_EncryptFinal_ex(ctx, (unsigned char*)out + *outlen, &tmplen);
+ *outlen += tmplen;
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
+ EVP_CIPHER_CTX_free(ctx);
+ return out;
+}
+
+char* bux_aes_256_gcm_decrypt(const char* cipher, int cipherlen,
+ const char* key, const char* iv,
+ const char* tag, int* outlen) {
+ EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+ EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (const unsigned char*)key,
+ (const unsigned char*)iv);
+ char* out = (char*)bux_alloc(cipherlen);
+ int tmplen = 0;
+ EVP_DecryptUpdate(ctx, (unsigned char*)out, &tmplen,
+ (const unsigned char*)cipher, cipherlen);
+ *outlen = tmplen;
+ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag);
+ int ret = EVP_DecryptFinal_ex(ctx, (unsigned char*)out + *outlen, &tmplen);
+ EVP_CIPHER_CTX_free(ctx);
+ if (ret <= 0) { *outlen = 0; out[0] = '\0'; return out; }
+ *outlen += tmplen;
+ out[*outlen] = '\0';
+ return out;
+}
+
+/* --- RSA PKCS#1 v1.5 sign / verify --- */
+
+static EVP_PKEY* bux_load_private_key(const char* pem, int len) {
+ BIO* bio = BIO_new_mem_buf(pem, len);
+ if (!bio) return NULL;
+ EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ return pkey;
+}
+
+static EVP_PKEY* bux_load_public_key(const char* pem, int len) {
+ BIO* bio = BIO_new_mem_buf(pem, len);
+ if (!bio) return NULL;
+ EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ return pkey;
+}
+
+static char* bux_rsa_sign_evp(const EVP_MD* md, const char* pem_key, int keylen,
+ const char* data, int datalen, int* siglen) {
+ EVP_PKEY* pkey = bux_load_private_key(pem_key, keylen);
+ if (!pkey) { *siglen = 0; return (char*)bux_alloc(1); }
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ EVP_SignInit_ex(ctx, md, NULL);
+ EVP_SignUpdate(ctx, data, (size_t)datalen);
+ size_t slen = (size_t)EVP_PKEY_size(pkey);
+ unsigned char* sig = (unsigned char*)bux_alloc(slen + 1);
+ EVP_SignFinal(ctx, sig, &slen, pkey);
+ *siglen = (int)slen;
+ sig[*siglen] = '\0';
+ EVP_MD_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return (char*)sig;
+}
+
+static int bux_rsa_verify_evp(const EVP_MD* md, const char* pem_key, int keylen,
+ const char* data, int datalen,
+ const char* sig, int siglen) {
+ EVP_PKEY* pkey = bux_load_public_key(pem_key, keylen);
+ if (!pkey) return 0;
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ EVP_VerifyInit_ex(ctx, md, NULL);
+ EVP_VerifyUpdate(ctx, data, (size_t)datalen);
+ int ret = EVP_VerifyFinal(ctx, (const unsigned char*)sig, (size_t)siglen, pkey);
+ EVP_MD_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+char* bux_rsa_sign_sha256(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
+ return bux_rsa_sign_evp(EVP_sha256(), pem, keylen, data, datalen, siglen);
+}
+char* bux_rsa_sign_sha384(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
+ return bux_rsa_sign_evp(EVP_sha384(), pem, keylen, data, datalen, siglen);
+}
+char* bux_rsa_sign_sha512(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
+ return bux_rsa_sign_evp(EVP_sha512(), pem, keylen, data, datalen, siglen);
+}
+
+int bux_rsa_verify_sha256(const char* pem, int keylen, const char* data, int datalen,
+ const char* sig, int siglen) {
+ return bux_rsa_verify_evp(EVP_sha256(), pem, keylen, data, datalen, sig, siglen);
+}
+int bux_rsa_verify_sha384(const char* pem, int keylen, const char* data, int datalen,
+ const char* sig, int siglen) {
+ return bux_rsa_verify_evp(EVP_sha384(), pem, keylen, data, datalen, sig, siglen);
+}
+int bux_rsa_verify_sha512(const char* pem, int keylen, const char* data, int datalen,
+ const char* sig, int siglen) {
+ return bux_rsa_verify_evp(EVP_sha512(), pem, keylen, data, datalen, sig, siglen);
+}
+
+/* --- ECDSA P-256 / P-384 --- */
+
+static char* bux_ecdsa_sign_evp(const EVP_MD* md, const char* pem, int keylen,
+ const char* data, int datalen, int* siglen) {
+ EVP_PKEY* pkey = bux_load_private_key(pem, keylen);
+ if (!pkey) { *siglen = 0; return (char*)bux_alloc(1); }
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ EVP_SignInit_ex(ctx, md, NULL);
+ EVP_SignUpdate(ctx, data, (size_t)datalen);
+ size_t slen = (size_t)EVP_PKEY_size(pkey);
+ unsigned char* sig = (unsigned char*)bux_alloc(slen + 1);
+ EVP_SignFinal(ctx, sig, &slen, pkey);
+ *siglen = (int)slen;
+ sig[*siglen] = '\0';
+ EVP_MD_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return (char*)sig;
+}
+
+static int bux_ecdsa_verify_evp(const EVP_MD* md, const char* pem, int keylen,
+ const char* data, int datalen,
+ const char* sig, int siglen) {
+ EVP_PKEY* pkey = bux_load_public_key(pem, keylen);
+ if (!pkey) return 0;
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ EVP_VerifyInit_ex(ctx, md, NULL);
+ EVP_VerifyUpdate(ctx, data, (size_t)datalen);
+ int ret = EVP_VerifyFinal(ctx, (const unsigned char*)sig, (size_t)siglen, pkey);
+ EVP_MD_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+char* bux_ecdsa_sign_p256(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
+ return bux_ecdsa_sign_evp(EVP_sha256(), pem, keylen, data, datalen, siglen);
+}
+char* bux_ecdsa_sign_p384(const char* pem, int keylen, const char* data, int datalen, int* siglen) {
+ return bux_ecdsa_sign_evp(EVP_sha384(), pem, keylen, data, datalen, siglen);
+}
+int bux_ecdsa_verify_p256(const char* pem, int keylen, const char* data, int datalen,
+ const char* sig, int siglen) {
+ return bux_ecdsa_verify_evp(EVP_sha256(), pem, keylen, data, datalen, sig, siglen);
+}
+int bux_ecdsa_verify_p384(const char* pem, int keylen, const char* data, int datalen,
+ const char* sig, int siglen) {
+ return bux_ecdsa_verify_evp(EVP_sha384(), pem, keylen, data, datalen, sig, siglen);
+}
+
+/* --- Ed25519 (OpenSSL 1.1.1+) --- */
+
+int bux_ed25519_keypair(unsigned char* pub, unsigned char* priv) {
+ EVP_PKEY* pkey = NULL;
+ EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
+ if (!pctx) return 0;
+ if (EVP_PKEY_keygen_init(pctx) <= 0 ||
+ EVP_PKEY_keygen(pctx, &pkey) <= 0) {
+ EVP_PKEY_CTX_free(pctx);
+ return 0;
+ }
+ EVP_PKEY_CTX_free(pctx);
+ size_t pub_len = 32, priv_len = 32;
+ EVP_PKEY_get_raw_public_key(pkey, pub, &pub_len);
+ EVP_PKEY_get_raw_private_key(pkey, priv, &priv_len);
+ EVP_PKEY_free(pkey);
+ return 1;
+}
+
+int bux_ed25519_sign(const char* priv, const char* data, int datalen, unsigned char* sig) {
+ EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
+ (const unsigned char*)priv, 32);
+ if (!pkey) return 0;
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ int ret = 0;
+ if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) > 0) {
+ size_t siglen = 64;
+ EVP_DigestSign(ctx, sig, &siglen, (const unsigned char*)data, (size_t)datalen);
+ ret = 1;
+ }
+ EVP_MD_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+int bux_ed25519_verify(const char* pub, const char* sig, const char* data, int datalen) {
+ EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
+ (const unsigned char*)pub, 32);
+ if (!pkey) return 0;
+ EVP_MD_CTX* ctx = EVP_MD_CTX_new();
+ int ret = 0;
+ if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) > 0) {
+ ret = EVP_DigestVerify(ctx, (const unsigned char*)sig, 64,
+ (const unsigned char*)data, (size_t)datalen);
+ ret = (ret == 1) ? 1 : 0;
+ }
+ EVP_MD_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
diff --git a/test_crypto/build.log b/test_crypto/build.log
new file mode 100644
index 0000000..903634c
--- /dev/null
+++ b/test_crypto/build.log
@@ -0,0 +1 @@
+EXIT:137
diff --git a/test_crypto/bux.toml b/test_crypto/bux.toml
new file mode 100644
index 0000000..b7180a6
--- /dev/null
+++ b/test_crypto/bux.toml
@@ -0,0 +1,7 @@
+[Package]
+Name = "test_crypto"
+Version = "0.1.0"
+Type = "bin"
+
+[Build]
+Output = "Bin"
diff --git a/test_crypto/src/Main.bux b/test_crypto/src/Main.bux
new file mode 100644
index 0000000..6d41ca7
--- /dev/null
+++ b/test_crypto/src/Main.bux
@@ -0,0 +1,180 @@
+// =============================================================================
+// Crypto Library Test — exercises all modules in lib/crypto/
+// =============================================================================
+module Main {
+
+import Std::Io::{PrintLine, Print, PrintInt};
+import Std::String::{String_Len, String_Eq};
+import Std::Crypto::Base64::{Base64_Encode, Base64_Decode, Base64URL_Encode, Base64URL_Decode};
+import Std::Crypto::Hash::{Hash_Sha256, Hash_Sha384, Hash_Sha512, Hash_Sha256Size, Hash_Sha384Size, Hash_Sha512Size};
+import Std::Crypto::Hmac::{Hmac_Sha256, Hmac_Sha384, Hmac_Sha512};
+import Std::Crypto::Random::{Random_Bytes, Random_Hex, Random_Base64, Random_Uint32};
+import Std::Crypto::Jwt::{JwtAlg, Jwt_MakeHeader, Jwt_EncodeHS256, Jwt_Decode};
+
+// --- Minimal test framework ---
+var gPassed: int = 0;
+var gFailed: int = 0;
+
+func Assert(cond: bool, name: String) {
+ if cond {
+ gPassed = gPassed + 1;
+ Print(" PASS ");
+ } else {
+ gFailed = gFailed + 1;
+ Print(" FAIL ");
+ }
+ PrintLine(name);
+}
+
+// =============================================================================
+// Test: Base64 + Base64URL
+// =============================================================================
+func TestBase64() {
+ PrintLine("--- Base64 ---");
+
+ let encoded: String = Base64_Encode("hello");
+ Assert(String_Eq(encoded, "aGVsbG8="), "Base64_Encode('hello')");
+
+ let decoded: String = Base64_Decode("aGVsbG8=");
+ Assert(String_Eq(decoded, "hello"), "Base64_Decode('aGVsbG8=')");
+
+ let urlEnc: String = Base64URL_Encode("hello");
+ Assert(String_Eq(urlEnc, "aGVsbG8"), "Base64URL_Encode('hello') — no padding");
+
+ let urlDec: String = Base64URL_Decode("aGVsbG8");
+ Assert(String_Eq(urlDec, "hello"), "Base64URL_Decode('aGVsbG8')");
+
+ let original: String = "Bux crypto test!";
+ let rt: String = Base64_Decode(Base64_Encode(original));
+ Assert(String_Eq(rt, original), "Base64 round-trip");
+
+ let rtUrl: String = Base64URL_Decode(Base64URL_Encode(original));
+ Assert(String_Eq(rtUrl, original), "Base64URL round-trip");
+}
+
+// =============================================================================
+// Test: Hash (SHA-256, SHA-384, SHA-512)
+// =============================================================================
+func TestHash() {
+ PrintLine("--- Hash ---");
+
+ let sha256: String = Hash_Sha256("hello");
+ Assert(String_Eq(sha256,
+ "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"),
+ "Hash_Sha256('hello')");
+
+ let sha384: String = Hash_Sha384("hello");
+ Assert(String_Eq(sha384,
+ "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f"),
+ "Hash_Sha384('hello')");
+
+ let sha512: String = Hash_Sha512("hello");
+ Assert(String_Eq(sha512,
+ "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"),
+ "Hash_Sha512('hello')");
+
+ let empty256: String = Hash_Sha256("");
+ Assert(String_Len(empty256) == 64, "Hash_Sha256('') has 64 hex chars");
+
+ Assert(Hash_Sha256Size() == 32, "Hash_Sha256Size() == 32");
+ Assert(Hash_Sha384Size() == 48, "Hash_Sha384Size() == 48");
+ Assert(Hash_Sha512Size() == 64, "Hash_Sha512Size() == 64");
+}
+
+// =============================================================================
+// Test: HMAC
+// =============================================================================
+func TestHmac() {
+ PrintLine("--- HMAC ---");
+
+ let hmac256: String = Hmac_Sha256("secret", "hello");
+ Assert(String_Eq(hmac256,
+ "88aab3ede8d3adf94d26ab90d3bafd4a2083070c3daec8e50b3899a4a5d1e0f8"),
+ "Hmac_Sha256('secret', 'hello')");
+
+ Assert(String_Len(Hmac_Sha384("s", "h")) == 96, "Hmac_Sha384 produces 96 hex chars");
+ Assert(String_Len(Hmac_Sha512("s", "h")) == 128, "Hmac_Sha512 produces 128 hex chars");
+
+ let h1: String = Hmac_Sha256("key", "msg");
+ let h2: String = Hmac_Sha256("key", "msg");
+ Assert(String_Eq(h1, h2), "HMAC deterministic");
+}
+
+// =============================================================================
+// Test: Random
+// =============================================================================
+func TestRandom() {
+ PrintLine("--- Random ---");
+
+ Assert(String_Len(Random_Bytes(32)) == 32, "Random_Bytes(32) len=32");
+ Assert(String_Len(Random_Hex(16)) == 32, "Random_Hex(16) len=32");
+ Assert(String_Len(Random_Base64(16)) > 0, "Random_Base64(16) non-empty");
+}
+
+// =============================================================================
+// Test: JWT HS256 encode/decode
+// =============================================================================
+func TestJwtHS256() {
+ PrintLine("--- JWT HS256 ---");
+
+ let token: String = Jwt_EncodeHS256("{\"sub\":\"test\"}", "secret");
+ Assert(String_Len(token) > 20, "Jwt_EncodeHS256 returns token");
+
+ var header: String = "";
+ var payload: String = "";
+ let alg: JwtAlg = JwtAlg { tag: JwtAlg_HS256 };
+ let ok: bool = Jwt_Decode(token, alg, "secret", &header, &payload);
+ Assert(ok, "Jwt_Decode HS256 verifies");
+ Assert(String_Eq(payload, "{\"sub\":\"test\"}"), "Jwt_Decode returns correct payload");
+
+ let wrongOk: bool = Jwt_Decode(token, alg, "wrong", &header, &payload);
+ Assert(!wrongOk, "Jwt_Decode rejects wrong secret");
+}
+
+// =============================================================================
+// Test: JWT algorithm headers
+// =============================================================================
+func TestJwtHeaders() {
+ PrintLine("--- JWT Headers ---");
+
+ Assert(String_Eq(Jwt_MakeHeader(JwtAlg { tag: JwtAlg_HS256 }),
+ "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"), "JWT header HS256");
+ Assert(String_Eq(Jwt_MakeHeader(JwtAlg { tag: JwtAlg_RS256 }),
+ "{\"alg\":\"RS256\",\"typ\":\"JWT\"}"), "JWT header RS256");
+ Assert(String_Eq(Jwt_MakeHeader(JwtAlg { tag: JwtAlg_ES256 }),
+ "{\"alg\":\"ES256\",\"typ\":\"JWT\"}"), "JWT header ES256");
+ Assert(String_Eq(Jwt_MakeHeader(JwtAlg { tag: JwtAlg_EdDSA }),
+ "{\"alg\":\"EdDSA\",\"typ\":\"JWT\"}"), "JWT header EdDSA");
+}
+
+// =============================================================================
+// Main
+// =============================================================================
+func Main() -> int {
+ PrintLine("================================================");
+ PrintLine(" Bux Crypto Library — Test Suite");
+ PrintLine("================================================");
+ PrintLine("");
+
+ TestBase64();
+ TestHash();
+ TestHmac();
+ TestRandom();
+ TestJwtHS256();
+ TestJwtHeaders();
+
+ PrintLine("");
+ PrintLine("================================================");
+ Print(" Passed: ");
+ PrintInt(gPassed);
+ PrintLine("");
+ Print(" Failed: ");
+ PrintInt(gFailed);
+ PrintLine("");
+ PrintLine("================================================");
+
+ if gFailed > 0 { return 1; }
+ return 0;
+}
+
+} // module Main