Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49ffc3d99e | ||
|
|
34bb1c34a2 | ||
|
|
b27a56a296 | ||
|
|
ecd7095123 | ||
|
|
d449513861 | ||
|
|
6709e229b3 | ||
|
|
00a8f16262 | ||
|
|
00d16f8615 | ||
|
|
c90676d2b2 | ||
|
|
b89c011ad5 | ||
|
|
c3de31aa88 | ||
|
|
b6df0be865 | ||
|
|
a89f84f76e | ||
|
|
5a916cc221 | ||
|
|
dcf421ff93 | ||
|
|
d655258311 | ||
|
|
f6d13d4318 | ||
|
|
d5350d7c30 | ||
|
|
c63fd04c92 | ||
|
|
64b418a551 | ||
|
|
36c9c84047 | ||
|
|
88b4aaa301 | ||
|
|
eac4c227c9 | ||
|
|
d5eb7d4a55 | ||
|
|
80b15e16e2 | ||
|
|
cfacedbb1a | ||
|
|
c3bab033ed | ||
|
|
524f9bd84f | ||
|
|
4658ede9d6 | ||
|
|
f7b463aca1 | ||
|
|
c1a6e92b1d | ||
|
|
eefb0e427e | ||
|
|
c23d81b740 | ||
|
|
6dac231040 | ||
|
|
6fd3e531c3 | ||
|
|
c1c05991cf | ||
|
|
ab378e14d1 | ||
|
|
c0f9bf9ef5 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@ dev-config/
|
||||
db/
|
||||
copy_executable_local.sh
|
||||
nostr_login_lite/
|
||||
style_guide/
|
||||
nostr-tools
|
||||
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
description: "Brief description of what this command does"
|
||||
---
|
||||
|
||||
Run build_and_push.sh, and supply a good git commit message.
|
||||
Run build_and_push.sh, and supply a good git commit message. For example:
|
||||
|
||||
./build_and_push.sh "Fixed the bug with nip05 implementation"
|
||||
1
.rooignore
Normal file
1
.rooignore
Normal file
@@ -0,0 +1 @@
|
||||
src/embedded_web_content.c
|
||||
32
07.md
32
07.md
@@ -1,32 +0,0 @@
|
||||
NIP-07
|
||||
======
|
||||
|
||||
`window.nostr` capability for web browsers
|
||||
------------------------------------------
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
The `window.nostr` object may be made available by web browsers or extensions and websites or web-apps may make use of it after checking its availability.
|
||||
|
||||
That object must define the following methods:
|
||||
|
||||
```
|
||||
async window.nostr.getPublicKey(): string // returns a public key as hex
|
||||
async window.nostr.signEvent(event: { created_at: number, kind: number, tags: string[][], content: string }): Event // takes an event object, adds `id`, `pubkey` and `sig` and returns it
|
||||
```
|
||||
|
||||
Aside from these two basic above, the following functions can also be implemented optionally:
|
||||
```
|
||||
async window.nostr.nip04.encrypt(pubkey, plaintext): string // returns ciphertext and iv as specified in nip-04 (deprecated)
|
||||
async window.nostr.nip04.decrypt(pubkey, ciphertext): string // takes ciphertext and iv as specified in nip-04 (deprecated)
|
||||
async window.nostr.nip44.encrypt(pubkey, plaintext): string // returns ciphertext as specified in nip-44
|
||||
async window.nostr.nip44.decrypt(pubkey, ciphertext): string // takes ciphertext as specified in nip-44
|
||||
```
|
||||
|
||||
### Recommendation to Extension Authors
|
||||
To make sure that the `window.nostr` is available to nostr clients on page load, the authors who create Chromium and Firefox extensions should load their scripts by specifying `"run_at": "document_end"` in the extension's manifest.
|
||||
|
||||
|
||||
### Implementation
|
||||
|
||||
See https://github.com/aljazceru/awesome-nostr#nip-07-browser-extensions.
|
||||
28
AGENTS.md
28
AGENTS.md
@@ -27,7 +27,7 @@
|
||||
## Critical Integration Issues
|
||||
|
||||
### Event-Based Configuration System
|
||||
- **No traditional config files** - all configuration stored as kind 33334 Nostr events
|
||||
- **No traditional config files** - all configuration stored in config table
|
||||
- Admin private key shown **only once** on first startup
|
||||
- Configuration changes require cryptographically signed events
|
||||
- Database path determined by generated relay pubkey
|
||||
@@ -35,7 +35,7 @@
|
||||
### First-Time Startup Sequence
|
||||
1. Relay generates admin keypair and relay keypair
|
||||
2. Creates database file with relay pubkey as filename
|
||||
3. Stores default configuration as kind 33334 event
|
||||
3. Stores default configuration in config table
|
||||
4. **CRITICAL**: Admin private key displayed once and never stored on disk
|
||||
|
||||
### Port Management
|
||||
@@ -48,20 +48,30 @@
|
||||
- Schema version 4 with JSON tag storage
|
||||
- **Critical**: Event expiration filtering done at application level, not SQL level
|
||||
|
||||
### Configuration Event Structure
|
||||
### Admin API Event Structure
|
||||
```json
|
||||
{
|
||||
"kind": 33334,
|
||||
"content": "C Nostr Relay Configuration",
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [
|
||||
["d", "<relay_pubkey>"],
|
||||
["relay_description", "value"],
|
||||
["max_subscriptions_per_client", "25"],
|
||||
["pow_min_difficulty", "16"]
|
||||
["p", "<relay_pubkey>"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Configuration Commands** (encrypted in content):
|
||||
- `["relay_description", "My Relay"]`
|
||||
- `["max_subscriptions_per_client", "25"]`
|
||||
- `["pow_min_difficulty", "16"]`
|
||||
|
||||
**Auth Rule Commands** (encrypted in content):
|
||||
- `["blacklist", "pubkey", "hex_pubkey_value"]`
|
||||
- `["whitelist", "pubkey", "hex_pubkey_value"]`
|
||||
|
||||
**Query Commands** (encrypted in content):
|
||||
- `["auth_query", "all"]`
|
||||
- `["system_command", "system_status"]`
|
||||
|
||||
### Process Management
|
||||
```bash
|
||||
# Kill existing relay processes
|
||||
|
||||
119
Dockerfile.alpine-musl
Normal file
119
Dockerfile.alpine-musl
Normal file
@@ -0,0 +1,119 @@
|
||||
# Alpine-based MUSL static binary builder for C-Relay
|
||||
# Produces truly portable binaries with zero runtime dependencies
|
||||
|
||||
FROM alpine:3.19 AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
musl-dev \
|
||||
git \
|
||||
cmake \
|
||||
pkgconfig \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
openssl-dev \
|
||||
openssl-libs-static \
|
||||
zlib-dev \
|
||||
zlib-static \
|
||||
curl-dev \
|
||||
curl-static \
|
||||
sqlite-dev \
|
||||
sqlite-static \
|
||||
linux-headers \
|
||||
wget \
|
||||
bash
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /build
|
||||
|
||||
# Build libsecp256k1 static (cached layer - only rebuilds if Alpine version changes)
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/bitcoin-core/secp256k1.git && \
|
||||
cd secp256k1 && \
|
||||
./autogen.sh && \
|
||||
./configure --enable-static --disable-shared --prefix=/usr \
|
||||
CFLAGS="-fPIC" && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /tmp/secp256k1
|
||||
|
||||
# Build libwebsockets static with minimal features (cached layer)
|
||||
RUN cd /tmp && \
|
||||
git clone --depth 1 --branch v4.3.3 https://github.com/warmcat/libwebsockets.git && \
|
||||
cd libwebsockets && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. \
|
||||
-DLWS_WITH_STATIC=ON \
|
||||
-DLWS_WITH_SHARED=OFF \
|
||||
-DLWS_WITH_SSL=ON \
|
||||
-DLWS_WITHOUT_TESTAPPS=ON \
|
||||
-DLWS_WITHOUT_TEST_SERVER=ON \
|
||||
-DLWS_WITHOUT_TEST_CLIENT=ON \
|
||||
-DLWS_WITHOUT_TEST_PING=ON \
|
||||
-DLWS_WITH_HTTP2=OFF \
|
||||
-DLWS_WITH_LIBUV=OFF \
|
||||
-DLWS_WITH_LIBEVENT=OFF \
|
||||
-DLWS_IPV6=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DCMAKE_C_FLAGS="-fPIC" && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /tmp/libwebsockets
|
||||
|
||||
# Copy only submodule configuration and git directory
|
||||
COPY .gitmodules /build/.gitmodules
|
||||
COPY .git /build/.git
|
||||
|
||||
# Clean up any stale submodule references (nips directory is not a submodule)
|
||||
RUN git rm --cached nips 2>/dev/null || true
|
||||
|
||||
# Initialize submodules (cached unless .gitmodules changes)
|
||||
RUN git submodule update --init --recursive
|
||||
|
||||
# Copy nostr_core_lib source files (cached unless nostr_core_lib changes)
|
||||
COPY nostr_core_lib /build/nostr_core_lib/
|
||||
|
||||
# Build nostr_core_lib with required NIPs (cached unless nostr_core_lib changes)
|
||||
# Disable fortification in build.sh to prevent __*_chk symbol issues
|
||||
# NIPs: 001(Basic), 006(Keys), 013(PoW), 017(DMs), 019(Bech32), 044(Encryption), 059(Gift Wrap - required by NIP-17)
|
||||
RUN cd nostr_core_lib && \
|
||||
chmod +x build.sh && \
|
||||
sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \
|
||||
rm -f *.o *.a 2>/dev/null || true && \
|
||||
./build.sh --nips=1,6,13,17,19,44,59
|
||||
|
||||
# Copy c-relay source files LAST (only this layer rebuilds on source changes)
|
||||
COPY src/ /build/src/
|
||||
COPY Makefile /build/Makefile
|
||||
|
||||
# Build c-relay with full static linking (only rebuilds when src/ changes)
|
||||
# Disable fortification to avoid __*_chk symbols that don't exist in MUSL
|
||||
RUN gcc -static -O2 -Wall -Wextra -std=c99 \
|
||||
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
|
||||
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core \
|
||||
-Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
|
||||
src/main.c src/config.c src/dm_admin.c src/request_validator.c \
|
||||
src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c \
|
||||
src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \
|
||||
-o /build/c_relay_static \
|
||||
nostr_core_lib/libnostr_core_x64.a \
|
||||
-lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \
|
||||
-lcurl -lz -lpthread -lm -ldl
|
||||
|
||||
# Strip binary to reduce size
|
||||
RUN strip /build/c_relay_static
|
||||
|
||||
# Verify it's truly static
|
||||
RUN echo "=== Binary Information ===" && \
|
||||
file /build/c_relay_static && \
|
||||
ls -lh /build/c_relay_static && \
|
||||
echo "=== Checking for dynamic dependencies ===" && \
|
||||
(ldd /build/c_relay_static 2>&1 || echo "Binary is static") && \
|
||||
echo "=== Build complete ==="
|
||||
|
||||
# Output stage - just the binary
|
||||
FROM scratch AS output
|
||||
COPY --from=builder /build/c_relay_static /c_relay_static
|
||||
513
IMPLEMENT_API.md
513
IMPLEMENT_API.md
@@ -1,513 +0,0 @@
|
||||
# Implementation Plan: Enhanced Admin Event API Structure
|
||||
|
||||
## Current Issue
|
||||
|
||||
The current admin event routing at [`main.c:3248-3268`](src/main.c:3248) has a security vulnerability:
|
||||
|
||||
```c
|
||||
if (event_kind == 23455 || event_kind == 23456) {
|
||||
// Admin event processing
|
||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
||||
} else {
|
||||
// Regular event storage and broadcasting
|
||||
}
|
||||
```
|
||||
|
||||
**Problem**: Any event with these kinds gets routed to admin processing, regardless of authorization. This allows unauthorized users to send admin events that could be processed as legitimate admin commands.
|
||||
|
||||
**Note**: Event kinds 33334 and 33335 are no longer used and have been removed from the admin event routing.
|
||||
|
||||
## Required Security Enhancement
|
||||
|
||||
Admin events must be validated for proper authorization BEFORE routing to admin processing:
|
||||
|
||||
1. **Relay Public Key Check**: Event must have a `p` tag equal to the relay's public key
|
||||
2. **Admin Signature Check**: Event must be signed by an authorized admin private key
|
||||
3. **Fallback to Regular Processing**: If authorization fails, treat as regular event (not admin event)
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Add Admin Authorization Validation
|
||||
|
||||
#### 1.1 Create Consolidated Admin Authorization Function
|
||||
**Location**: [`src/main.c`](src/main.c) or [`src/config.c`](src/config.c)
|
||||
|
||||
```c
|
||||
/**
|
||||
* Consolidated admin event authorization validator
|
||||
* Implements defense-in-depth security for admin events
|
||||
*
|
||||
* @param event - The event to validate for admin authorization
|
||||
* @param error_message - Buffer for detailed error messages
|
||||
* @param error_size - Size of error message buffer
|
||||
* @return 0 if authorized, -1 if unauthorized, -2 if validation error
|
||||
*/
|
||||
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!event) {
|
||||
snprintf(error_message, error_size, "admin_auth: null event");
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Extract event components
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
|
||||
if (!kind_obj || !pubkey_obj || !tags_obj) {
|
||||
snprintf(error_message, error_size, "admin_auth: missing required fields");
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Validation Layer 1: Kind Check
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
if (event_kind != 23455 && event_kind != 23456) {
|
||||
snprintf(error_message, error_size, "admin_auth: not an admin event kind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validation Layer 2: Relay Targeting Check
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
if (!relay_pubkey) {
|
||||
snprintf(error_message, error_size, "admin_auth: relay pubkey not configured");
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Check for 'p' tag targeting this relay
|
||||
int has_relay_target = 0;
|
||||
if (cJSON_IsArray(tags_obj)) {
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_obj) {
|
||||
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
|
||||
const char* name = cJSON_GetStringValue(tag_name);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
if (strcmp(name, "p") == 0 && strcmp(value, relay_pubkey) == 0) {
|
||||
has_relay_target = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_relay_target) {
|
||||
// Admin event for different relay - not unauthorized, just not for us
|
||||
snprintf(error_message, error_size, "admin_auth: admin event for different relay");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validation Layer 3: Admin Signature Check (only if targeting this relay)
|
||||
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
if (!event_pubkey) {
|
||||
snprintf(error_message, error_size, "admin_auth: invalid pubkey format");
|
||||
return -2;
|
||||
}
|
||||
|
||||
const char* admin_pubkey = get_config_value("admin_pubkey");
|
||||
if (!admin_pubkey || strcmp(event_pubkey, admin_pubkey) != 0) {
|
||||
// This is the ONLY case where we log as "Unauthorized admin event attempt"
|
||||
// because it's targeting THIS relay but from wrong admin
|
||||
snprintf(error_message, error_size, "admin_auth: unauthorized admin for this relay");
|
||||
log_warning("SECURITY: Unauthorized admin event attempt for this relay");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// All validation layers passed
|
||||
log_info("ADMIN: Admin event authorized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 1.2 Update Event Routing Logic
|
||||
**Location**: [`main.c:3248`](src/main.c:3248)
|
||||
|
||||
```c
|
||||
// Current problematic code:
|
||||
if (event_kind == 23455 || event_kind == 23456) {
|
||||
// Admin event processing
|
||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
||||
} else {
|
||||
// Regular event storage and broadcasting
|
||||
}
|
||||
|
||||
// Enhanced secure code with consolidated authorization:
|
||||
if (result == 0) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
|
||||
// Check if this is an admin event
|
||||
if (event_kind == 23455 || event_kind == 23456) {
|
||||
// Use consolidated authorization check
|
||||
char auth_error[512] = {0};
|
||||
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
||||
|
||||
if (auth_result == 0) {
|
||||
// Authorized admin event - process through admin API
|
||||
char admin_error[512] = {0};
|
||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
||||
|
||||
if (admin_result != 0) {
|
||||
result = -1;
|
||||
strncpy(error_message, admin_error, sizeof(error_message) - 1);
|
||||
}
|
||||
// Admin events are NOT broadcast to subscriptions
|
||||
} else {
|
||||
// Unauthorized admin event - treat as regular event
|
||||
log_warning("Unauthorized admin event treated as regular event");
|
||||
if (store_event(event) != 0) {
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular event - normal processing
|
||||
if (store_event(event) != 0) {
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Enhanced Admin Event Processing
|
||||
|
||||
#### 2.1 Admin Event Validation in Config System
|
||||
**Location**: [`src/config.c`](src/config.c) - [`process_admin_event_in_config()`](src/config.c:2065)
|
||||
|
||||
Add additional validation within the admin processing function:
|
||||
|
||||
```c
|
||||
int process_admin_event_in_config(cJSON* event, char* error_buffer, size_t error_buffer_size, struct lws* wsi) {
|
||||
// Double-check authorization (defense in depth)
|
||||
if (!is_authorized_admin_event(event)) {
|
||||
snprintf(error_buffer, error_buffer_size, "unauthorized: not a valid admin event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Continue with existing admin event processing...
|
||||
// ... rest of function unchanged
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 Logging and Monitoring
|
||||
Add comprehensive logging for admin event attempts:
|
||||
|
||||
```c
|
||||
// In the routing logic - enhanced logging
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
int event_kind = kind_obj ? cJSON_GetNumberValue(kind_obj) : -1;
|
||||
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
||||
|
||||
if (is_authorized_admin_event(event)) {
|
||||
char log_msg[256];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"ADMIN EVENT: Authorized admin event (kind=%d) from pubkey=%.16s...",
|
||||
event_kind, event_pubkey);
|
||||
log_info(log_msg);
|
||||
} else if (event_kind == 23455 || event_kind == 23456) {
|
||||
// This catches unauthorized admin event attempts
|
||||
char log_msg[256];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"SECURITY: Unauthorized admin event attempt (kind=%d) from pubkey=%.16s...",
|
||||
event_kind, event_pubkey);
|
||||
log_warning(log_msg);
|
||||
}
|
||||
```
|
||||
|
||||
## Phase 3: Unified Output Flow Architecture
|
||||
|
||||
### 3.1 Current Output Flow Analysis
|
||||
|
||||
After analyzing both [`main.c`](src/main.c) and [`config.c`](src/config.c), the **admin event responses already flow through the standard WebSocket output pipeline**. This is the correct architecture and requires no changes.
|
||||
|
||||
#### Standard WebSocket Output Pipeline
|
||||
|
||||
**Regular Events** ([`main.c:2978-2996`](src/main.c:2978)):
|
||||
```c
|
||||
// Database query responses
|
||||
unsigned char* buf = malloc(LWS_PRE + msg_len);
|
||||
memcpy(buf + LWS_PRE, msg_str, msg_len);
|
||||
lws_write(wsi, buf + LWS_PRE, msg_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
```
|
||||
|
||||
**OK Responses** ([`main.c:3342-3375`](src/main.c:3342)):
|
||||
```c
|
||||
// Event processing results: ["OK", event_id, success_boolean, message]
|
||||
unsigned char *buf = malloc(LWS_PRE + response_len);
|
||||
memcpy(buf + LWS_PRE, response_str, response_len);
|
||||
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
```
|
||||
|
||||
#### Admin Event Output Pipeline (Already Unified)
|
||||
|
||||
**Admin Responses** ([`config.c:2363-2414`](src/config.c:2363)):
|
||||
```c
|
||||
// Admin query responses use IDENTICAL pattern
|
||||
int send_websocket_response_data(struct lws* wsi, cJSON* response_data) {
|
||||
unsigned char* buf = malloc(LWS_PRE + response_len);
|
||||
memcpy(buf + LWS_PRE, response_str, response_len);
|
||||
|
||||
// Same lws_write() call as regular events
|
||||
int result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Unified Output Flow Confirmation
|
||||
|
||||
✅ **Admin responses already use the same WebSocket transmission mechanism as regular events**
|
||||
|
||||
✅ **Both admin and regular events use identical buffer allocation patterns**
|
||||
|
||||
✅ **Both admin and regular events use the same [`lws_write()`](src/config.c:2393) function**
|
||||
|
||||
✅ **Both admin and regular events follow the same cleanup patterns**
|
||||
|
||||
### 3.3 Output Flow Integration Points
|
||||
|
||||
The admin event processing in [`config.c:2436`](src/config.c:2436) already integrates correctly with the unified output system:
|
||||
|
||||
1. **Admin Query Processing** ([`config.c:2568-2583`](src/config.c:2568)):
|
||||
- Auth queries return structured JSON via [`send_websocket_response_data()`](src/config.c:2571)
|
||||
- System commands return status data via [`send_websocket_response_data()`](src/config.c:2631)
|
||||
|
||||
2. **Response Format Consistency**:
|
||||
- Admin responses use standard JSON format
|
||||
- Regular events use standard Nostr event format
|
||||
- Both transmitted through same WebSocket pipeline
|
||||
|
||||
3. **Error Handling Consistency**:
|
||||
- Admin errors returned via same WebSocket connection
|
||||
- Regular event errors returned via OK messages
|
||||
- Both use identical transmission mechanism
|
||||
|
||||
### 3.4 Key Architectural Benefits
|
||||
|
||||
**No Changes Required**: The output flow is already unified and correctly implemented.
|
||||
|
||||
**Security Separation**: Admin events are processed separately but responses flow through the same secure WebSocket channel.
|
||||
|
||||
**Performance Consistency**: Both admin and regular responses use the same optimized transmission path.
|
||||
|
||||
**Maintenance Simplicity**: Single WebSocket output pipeline reduces complexity and potential bugs.
|
||||
|
||||
### 3.5 Admin Event Flow Summary
|
||||
|
||||
```
|
||||
Admin Event Input → Authorization Check → Admin Processing → Unified WebSocket Output
|
||||
Regular Event Input → Validation → Storage + Broadcast → Unified WebSocket Output
|
||||
```
|
||||
|
||||
Both flows converge at the **Unified WebSocket Output** stage, which is already correctly implemented.
|
||||
|
||||
## Phase 4: Integration Points for Secure Admin Event Routing
|
||||
|
||||
### 4.1 Configuration System Integration
|
||||
|
||||
**Required Configuration Values**:
|
||||
- `admin_pubkey` - Public key of authorized administrator
|
||||
- `relay_pubkey` - Public key of this relay instance
|
||||
|
||||
**Integration Points**:
|
||||
1. [`get_config_value()`](src/config.c) - Used by authorization function
|
||||
2. [`get_relay_pubkey_cached()`](src/config.c) - Used for relay targeting validation
|
||||
3. Configuration loading during startup - Must ensure admin/relay pubkeys are available
|
||||
|
||||
### 4.3 Forward Declarations Required
|
||||
|
||||
**Location**: [`src/main.c`](src/main.c) - Add near other forward declarations (around line 230)
|
||||
|
||||
```c
|
||||
// Forward declarations for enhanced admin event authorization
|
||||
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
|
||||
```
|
||||
|
||||
### 4.4 Error Handling Integration
|
||||
|
||||
**Enhanced Error Response System**:
|
||||
|
||||
```c
|
||||
// In main.c event processing - enhanced error handling for admin events
|
||||
if (auth_result != 0) {
|
||||
// Admin authorization failed - send detailed OK response
|
||||
cJSON* event_id = cJSON_GetObjectItem(event, "id");
|
||||
if (event_id && cJSON_IsString(event_id)) {
|
||||
cJSON* response = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id)));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // Failed
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(auth_error));
|
||||
|
||||
// Send via standard WebSocket output pipeline
|
||||
char *response_str = cJSON_Print(response);
|
||||
if (response_str) {
|
||||
size_t response_len = strlen(response_str);
|
||||
unsigned char *buf = malloc(LWS_PRE + response_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, response_str, response_len);
|
||||
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
}
|
||||
free(response_str);
|
||||
}
|
||||
cJSON_Delete(response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 Logging Integration Points
|
||||
|
||||
**Console Logging**: Uses existing [`log_warning()`](src/main.c:993), [`log_info()`](src/main.c:972) functions
|
||||
|
||||
**Security Event Categories**:
|
||||
- Admin authorization success logged via `log_info()`
|
||||
- Admin authorization failures logged via `log_warning()`
|
||||
- Admin event processing logged via existing admin logging
|
||||
|
||||
## Phase 5: Detailed Function Specifications
|
||||
|
||||
### 5.1 Core Authorization Function
|
||||
|
||||
**Function**: `is_authorized_admin_event()`
|
||||
**Location**: [`src/main.c`](src/main.c) or [`src/config.c`](src/config.c)
|
||||
**Dependencies**:
|
||||
- `get_config_value()` for admin/relay pubkeys
|
||||
- `log_warning()` and `log_info()` for logging
|
||||
- `cJSON` library for event parsing
|
||||
|
||||
**Return Values**:
|
||||
- `0` - Event is authorized for admin processing
|
||||
- `-1` - Event is unauthorized (treat as regular event)
|
||||
- `-2` - Validation error (malformed event)
|
||||
|
||||
**Error Handling**: Detailed error messages in provided buffer for client feedback
|
||||
|
||||
### 5.2 Enhanced Event Routing
|
||||
|
||||
**Location**: [`main.c:3248-3340`](src/main.c:3248)
|
||||
**Integration**: Replaces existing admin event routing logic
|
||||
**Dependencies**:
|
||||
- `is_authorized_admin_event()` for authorization
|
||||
- `process_admin_event_in_config()` for admin processing
|
||||
- `store_event()` and `broadcast_event_to_subscriptions()` for regular events
|
||||
|
||||
**Security Features**:
|
||||
- Graceful degradation for unauthorized admin events
|
||||
- Comprehensive logging of authorization attempts
|
||||
- No broadcast of admin events to subscriptions
|
||||
- Detailed error responses for failed authorization
|
||||
|
||||
### 5.4 Defense-in-Depth Validation
|
||||
|
||||
**Primary Validation**: In main event routing logic
|
||||
**Secondary Validation**: In `process_admin_event_in_config()` function
|
||||
**Tertiary Validation**: In individual admin command handlers
|
||||
|
||||
**Validation Layers**:
|
||||
1. **Kind Check** - Must be admin event kind (23455/23456)
|
||||
2. **Relay Targeting Check** - Must have 'p' tag with this relay's pubkey
|
||||
3. **Admin Signature Check** - Must be signed by authorized admin (only if targeting this relay)
|
||||
4. **Processing Check** - Additional validation in admin handlers
|
||||
|
||||
**Security Logic**:
|
||||
- If no 'p' tag for this relay → Admin event for different relay (not unauthorized)
|
||||
- If 'p' tag for this relay + wrong admin signature → "Unauthorized admin event attempt"
|
||||
|
||||
## Phase 6: Event Flow Documentation
|
||||
|
||||
### 6.1 Complete Event Processing Flow
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ WebSocket Input │
|
||||
└─────────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Unified │
|
||||
│ Validation │ ← nostr_validate_unified_request()
|
||||
└─────────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Kind-Based │
|
||||
│ Routing Check │ ← Check if kind 23455/23456
|
||||
└─────────┬───────┘
|
||||
│
|
||||
┌────▼────┐
|
||||
│ Admin? │
|
||||
└────┬────┘
|
||||
│
|
||||
┌─────▼─────┐ ┌─────────────┐
|
||||
│ YES │ │ NO │
|
||||
│ │ │ │
|
||||
▼ │ ▼ │
|
||||
┌─────────────┐ │ ┌─────────────┐ │
|
||||
│ Admin │ │ │ Regular │ │
|
||||
│ Authorization│ │ │ Event │ │
|
||||
│ Check │ │ │ Processing │ │
|
||||
└─────┬───────┘ │ └─────┬───────┘ │
|
||||
│ │ │ │
|
||||
┌────▼────┐ │ ▼ │
|
||||
│Authorized?│ │ ┌─────────────┐ │
|
||||
└────┬────┘ │ │ store_event()│ │
|
||||
│ │ │ + │ │
|
||||
┌─────▼─────┐ │ │ broadcast() │ │
|
||||
│ YES NO │ │ └─────┬───────┘ │
|
||||
│ │ │ │ │ │ │
|
||||
│ ▼ ▼ │ │ ▼ │
|
||||
│┌─────┐┌───┴┐ │ ┌─────────────┐ │
|
||||
││Admin││Treat│ │ │ WebSocket │ │
|
||||
││API ││as │ │ │ OK Response │ │
|
||||
││ ││Reg │ │ └─────────────┘ │
|
||||
│└──┬──┘└───┬┘ │ │
|
||||
│ │ │ │ │
|
||||
│ ▼ │ │ │
|
||||
│┌─────────┐│ │ │
|
||||
││WebSocket││ │ │
|
||||
││Response ││ │ │
|
||||
│└─────────┘│ │ │
|
||||
└───────────┴───┘ │
|
||||
│ │
|
||||
└───────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Unified │
|
||||
│ WebSocket │
|
||||
│ Output │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### 6.2 Security Decision Points
|
||||
|
||||
1. **Event Kind Check** - Identifies potential admin events
|
||||
2. **Authorization Validation** - Three-layer security check
|
||||
3. **Routing Decision** - Admin API vs Regular processing
|
||||
4. **Response Generation** - Unified output pipeline
|
||||
5. **Audit Logging** - Security event tracking
|
||||
|
||||
### 6.3 Error Handling Paths
|
||||
|
||||
**Validation Errors**: Return detailed error messages via OK response
|
||||
**Authorization Failures**: Log security event + treat as regular event
|
||||
**Processing Errors**: Return admin-specific error responses
|
||||
**System Errors**: Fallback to standard error handling
|
||||
|
||||
This completes the comprehensive implementation plan for the enhanced admin event API structure with unified output flow architecture.
|
||||
88
Makefile
88
Makefile
@@ -9,7 +9,7 @@ LIBS = -lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k
|
||||
BUILD_DIR = build
|
||||
|
||||
# Source files
|
||||
MAIN_SRC = src/main.c src/config.c src/request_validator.c
|
||||
MAIN_SRC = src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c
|
||||
NOSTR_CORE_LIB = nostr_core_lib/libnostr_core_x64.a
|
||||
|
||||
# Architecture detection
|
||||
@@ -32,14 +32,22 @@ $(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
# Check if nostr_core_lib is built
|
||||
# Explicitly specify NIPs to ensure NIP-44 (encryption) is included
|
||||
# NIPs: 1 (basic), 6 (keys), 13 (PoW), 17 (DMs), 19 (bech32), 44 (encryption), 59 (gift wrap)
|
||||
$(NOSTR_CORE_LIB):
|
||||
@echo "Building nostr_core_lib..."
|
||||
cd nostr_core_lib && ./build.sh
|
||||
@echo "Building nostr_core_lib with required NIPs (including NIP-44 for encryption)..."
|
||||
cd nostr_core_lib && ./build.sh --nips=1,6,13,17,19,44,59
|
||||
|
||||
# Generate version.h from git tags
|
||||
src/version.h:
|
||||
@if [ -d .git ]; then \
|
||||
echo "Generating version.h from git tags..."; \
|
||||
# Update main.h version information (requires main.h to exist)
|
||||
src/main.h:
|
||||
@if [ ! -f src/main.h ]; then \
|
||||
echo "ERROR: src/main.h not found!"; \
|
||||
echo "Please ensure src/main.h exists with relay metadata."; \
|
||||
echo "Copy from a backup or create manually with proper relay configuration."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
if [ -d .git ]; then \
|
||||
echo "Updating main.h version information from git tags..."; \
|
||||
RAW_VERSION=$$(git describe --tags --always 2>/dev/null || echo "unknown"); \
|
||||
if echo "$$RAW_VERSION" | grep -q "^v[0-9]"; then \
|
||||
CLEAN_VERSION=$$(echo "$$RAW_VERSION" | sed 's/^v//' | cut -d- -f1); \
|
||||
@@ -51,54 +59,34 @@ src/version.h:
|
||||
VERSION="v0.0.0"; \
|
||||
MAJOR=0; MINOR=0; PATCH=0; \
|
||||
fi; \
|
||||
echo "/* Auto-generated version information */" > src/version.h; \
|
||||
echo "#ifndef VERSION_H" >> src/version.h; \
|
||||
echo "#define VERSION_H" >> src/version.h; \
|
||||
echo "" >> src/version.h; \
|
||||
echo "#define VERSION \"$$VERSION\"" >> src/version.h; \
|
||||
echo "#define VERSION_MAJOR $$MAJOR" >> src/version.h; \
|
||||
echo "#define VERSION_MINOR $$MINOR" >> src/version.h; \
|
||||
echo "#define VERSION_PATCH $$PATCH" >> src/version.h; \
|
||||
echo "" >> src/version.h; \
|
||||
echo "#endif /* VERSION_H */" >> src/version.h; \
|
||||
echo "Generated version.h with clean version: $$VERSION"; \
|
||||
elif [ ! -f src/version.h ]; then \
|
||||
echo "Git not available and version.h missing, creating fallback version.h..."; \
|
||||
VERSION="v0.0.0"; \
|
||||
echo "/* Auto-generated version information */" > src/version.h; \
|
||||
echo "#ifndef VERSION_H" >> src/version.h; \
|
||||
echo "#define VERSION_H" >> src/version.h; \
|
||||
echo "" >> src/version.h; \
|
||||
echo "#define VERSION \"$$VERSION\"" >> src/version.h; \
|
||||
echo "#define VERSION_MAJOR 0" >> src/version.h; \
|
||||
echo "#define VERSION_MINOR 0" >> src/version.h; \
|
||||
echo "#define VERSION_PATCH 0" >> src/version.h; \
|
||||
echo "" >> src/version.h; \
|
||||
echo "#endif /* VERSION_H */" >> src/version.h; \
|
||||
echo "Created fallback version.h with version: $$VERSION"; \
|
||||
echo "Updating version information in existing main.h..."; \
|
||||
sed -i "s/#define VERSION \".*\"/#define VERSION \"$$VERSION\"/g" src/main.h; \
|
||||
sed -i "s/#define VERSION_MAJOR [0-9]*/#define VERSION_MAJOR $$MAJOR/g" src/main.h; \
|
||||
sed -i "s/#define VERSION_MINOR [0-9]*/#define VERSION_MINOR $$MINOR/g" src/main.h; \
|
||||
sed -i "s/#define VERSION_PATCH [0-9]*/#define VERSION_PATCH $$PATCH/g" src/main.h; \
|
||||
echo "Updated main.h version to: $$VERSION"; \
|
||||
else \
|
||||
echo "Git not available, preserving existing version.h"; \
|
||||
echo "Git not available, preserving existing main.h version information"; \
|
||||
fi
|
||||
|
||||
# Force version.h regeneration (useful for development)
|
||||
# Update main.h version information (requires existing main.h)
|
||||
force-version:
|
||||
@echo "Force regenerating version.h..."
|
||||
@rm -f src/version.h
|
||||
@$(MAKE) src/version.h
|
||||
@echo "Force updating main.h version information..."
|
||||
@$(MAKE) src/main.h
|
||||
|
||||
# Build the relay
|
||||
$(TARGET): $(BUILD_DIR) src/version.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
|
||||
$(TARGET): $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
|
||||
@echo "Compiling C-Relay for architecture: $(ARCH)"
|
||||
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(TARGET) $(NOSTR_CORE_LIB) $(LIBS)
|
||||
@echo "Build complete: $(TARGET)"
|
||||
|
||||
# Build for specific architectures
|
||||
x86: $(BUILD_DIR) src/version.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
|
||||
x86: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
|
||||
@echo "Building C-Relay for x86_64..."
|
||||
$(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(BUILD_DIR)/c_relay_x86 $(NOSTR_CORE_LIB) $(LIBS)
|
||||
@echo "Build complete: $(BUILD_DIR)/c_relay_x86"
|
||||
|
||||
arm64: $(BUILD_DIR) src/version.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
|
||||
arm64: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB)
|
||||
@echo "Cross-compiling C-Relay for ARM64..."
|
||||
@if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \
|
||||
echo "ERROR: ARM64 cross-compiler not found."; \
|
||||
@@ -171,7 +159,6 @@ init-db:
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
||||
rm -f src/version.h
|
||||
@echo "Clean complete"
|
||||
|
||||
# Clean everything including nostr_core_lib
|
||||
@@ -210,6 +197,23 @@ help:
|
||||
@echo " make check-toolchain # Check what compilers are available"
|
||||
@echo " make test # Run tests"
|
||||
@echo " make init-db # Set up database"
|
||||
@echo " make force-version # Force regenerate version.h from git"
|
||||
@echo " make force-version # Force regenerate main.h from git"
|
||||
|
||||
# Build fully static MUSL binaries using Docker
|
||||
static-musl-x86_64:
|
||||
@echo "Building fully static MUSL binary for x86_64..."
|
||||
docker buildx build --platform linux/amd64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-x86_64 --load .
|
||||
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-x86_64 sh -c "cp /c_relay_static_musl_x86_64 /output/"
|
||||
@echo "Static binary created: build/c_relay_static_musl_x86_64"
|
||||
|
||||
static-musl-arm64:
|
||||
@echo "Building fully static MUSL binary for ARM64..."
|
||||
docker buildx build --platform linux/arm64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-arm64 --load .
|
||||
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-arm64 sh -c "cp /c_relay_static_musl_x86_64 /output/c_relay_static_musl_arm64"
|
||||
@echo "Static binary created: build/c_relay_static_musl_arm64"
|
||||
|
||||
static-musl: static-musl-x86_64 static-musl-arm64
|
||||
@echo "Built static MUSL binaries for both architectures"
|
||||
|
||||
.PHONY: static-musl-x86_64 static-musl-arm64 static-musl
|
||||
.PHONY: all x86 arm64 test init-db clean clean-all install-deps install-cross-tools install-arm64-deps check-toolchain help force-version
|
||||
218
README.md
218
README.md
@@ -1,8 +1,8 @@
|
||||
# C Nostr Relay - Event-Based Configuration System
|
||||
# C-Nostr Relay
|
||||
|
||||
A high-performance Nostr relay implemented in C with SQLite backend, featuring a revolutionary **zero-configuration** approach using event-based configuration management.
|
||||
A high-performance Nostr relay implemented in C with SQLite backend, featuring nostr event-based management.
|
||||
|
||||
## 📜 Supported NIPs
|
||||
## Supported NIPs
|
||||
|
||||
<!--
|
||||
NOTE FOR ASSISTANTS: When updating the NIPs checklist below, ONLY change [ ] to [x] to mark as complete.
|
||||
@@ -18,13 +18,89 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
||||
- [x] NIP-33: Parameterized Replaceable Events
|
||||
- [x] NIP-40: Expiration Timestamp
|
||||
- [x] NIP-42: Authentication of clients to relays
|
||||
- [ ] NIP-45: Counting results
|
||||
- [ ] NIP-50: Keywords filter
|
||||
- [ ] NIP-70: Protected Events
|
||||
- [x] NIP-45: Counting results
|
||||
- [x] NIP-50: Keywords filter
|
||||
- [x] NIP-70: Protected Events
|
||||
|
||||
## 🔧 Administrator API
|
||||
## Quick Start
|
||||
|
||||
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. All admin commands use **tag-based parameters** for simplicity and compatibility.
|
||||
Get your C-Relay up and running in minutes with a static binary (no dependencies required):
|
||||
|
||||
### 1. Download Static Binary
|
||||
|
||||
Download the latest static release from the [releases page](https://git.laantungir.net/laantungir/c-relay/releases):
|
||||
|
||||
```bash
|
||||
# Static binary - works on all Linux distributions (no dependencies)
|
||||
wget https://git.laantungir.net/laantungir/c-relay/releases/download/v0.6.0/c-relay-v0.6.0-linux-x86_64-static
|
||||
chmod +x c-relay-v0.6.0-linux-x86_64-static
|
||||
mv c-relay-v0.6.0-linux-x86_64-static c-relay
|
||||
```
|
||||
|
||||
### 2. Start the Relay
|
||||
|
||||
Simply run the binary - no configuration files needed:
|
||||
|
||||
```bash
|
||||
./c-relay
|
||||
```
|
||||
|
||||
On first startup, you'll see:
|
||||
- **Admin Private Key**: Save this securely! You'll need it for administration
|
||||
- **Relay Public Key**: Your relay's identity on the Nostr network
|
||||
- **Port Information**: Default is 8888, or the next available port
|
||||
|
||||
### 3. Access the Web Interface
|
||||
|
||||
Open your browser and navigate to:
|
||||
```
|
||||
http://localhost:8888/api/
|
||||
```
|
||||
|
||||
The web interface provides:
|
||||
- Real-time configuration management
|
||||
- Database statistics dashboard
|
||||
- Auth rules management
|
||||
- Secure admin authentication with your Nostr identity
|
||||
|
||||
### 4. Test Your Relay
|
||||
|
||||
Test basic connectivity:
|
||||
```bash
|
||||
# Test WebSocket connection
|
||||
curl -H "Accept: application/nostr+json" http://localhost:8888
|
||||
|
||||
# Test with a Nostr client
|
||||
# Add ws://localhost:8888 to your client's relay list
|
||||
```
|
||||
|
||||
### 5. Configure Your Relay (Optional)
|
||||
|
||||
Use the web interface or send admin commands to customize:
|
||||
- Relay name and description
|
||||
- Authentication rules (whitelist/blacklist)
|
||||
- Connection limits
|
||||
- Proof-of-work requirements
|
||||
|
||||
**That's it!** Your relay is now running with zero configuration required. The event-based configuration system means you can adjust all settings through the web interface or admin API without editing config files.
|
||||
|
||||
|
||||
## Web Admin Interface
|
||||
|
||||
C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides:
|
||||
|
||||
- **Real-time Configuration Management**: View and edit all relay settings through a web UI
|
||||
- **Database Statistics Dashboard**: Monitor event counts, storage usage, and performance metrics
|
||||
- **Auth Rules Management**: Configure whitelist/blacklist rules for pubkeys
|
||||
- **NIP-42 Authentication**: Secure access using your Nostr identity
|
||||
- **Event-Based Updates**: All changes are applied as cryptographically signed Nostr events
|
||||
|
||||
The web interface serves embedded static files with no external dependencies and includes proper CORS headers for browser compatibility.
|
||||
|
||||
|
||||
## Administrator API
|
||||
|
||||
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. All admin commands use **NIP-44 encrypted command arrays** for security and compatibility.
|
||||
|
||||
### Authentication
|
||||
|
||||
@@ -32,7 +108,7 @@ All admin commands require signing with the admin private key displayed during f
|
||||
|
||||
### Event Structure
|
||||
|
||||
All admin commands use the same unified event structure with tag-based parameters:
|
||||
All admin commands use the same unified event structure with NIP-44 encrypted content:
|
||||
|
||||
**Admin Command Event:**
|
||||
```json
|
||||
@@ -41,14 +117,16 @@ All admin commands use the same unified event structure with tag-based parameter
|
||||
"pubkey": "admin_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23456,
|
||||
"content": "<nip44 encrypted command>",
|
||||
"content": "AqHBUgcM7dXFYLQuDVzGwMST1G8jtWYyVvYxXhVGEu4nAb4LVw...",
|
||||
"tags": [
|
||||
["p", "relay_public_key"],
|
||||
["p", "relay_public_key"]
|
||||
],
|
||||
"sig": "event_signature"
|
||||
}
|
||||
```
|
||||
|
||||
The `content` field contains a NIP-44 encrypted JSON array representing the command.
|
||||
|
||||
**Admin Response Event:**
|
||||
```json
|
||||
["EVENT", "temp_sub_id", {
|
||||
@@ -56,7 +134,7 @@ All admin commands use the same unified event structure with tag-based parameter
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "<nip44 encrypted response>",
|
||||
"content": "BpKCVhfN8eYtRmPqSvWxZnMkL2gHjUiOp3rTyEwQaS5dFg...",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
@@ -64,30 +142,42 @@ All admin commands use the same unified event structure with tag-based parameter
|
||||
}]
|
||||
```
|
||||
|
||||
The `content` field contains a NIP-44 encrypted JSON response object.
|
||||
|
||||
### Admin Commands
|
||||
|
||||
All commands are sent as nip44 encrypted content. The following table lists all available commands:
|
||||
All commands are sent as NIP-44 encrypted JSON arrays in the event content. The following table lists all available commands:
|
||||
|
||||
| Command Type | Tag Format | Description |
|
||||
|--------------|------------|-------------|
|
||||
| Command Type | Command Format | Description |
|
||||
|--------------|----------------|-------------|
|
||||
| **Configuration Management** |
|
||||
| `config_update` | `["relay_description", "My Relay"]` | Update relay configuration parameters |
|
||||
| `config_query` | `["config_query", "list_all_keys"]` | List all available configuration keys |
|
||||
| `config_update` | `["config_update", [{"key": "auth_enabled", "value": "true", "data_type": "boolean", "category": "auth"}, {"key": "relay_description", "value": "My Relay", "data_type": "string", "category": "relay"}, ...]]` | Update relay configuration parameters (supports multiple updates) |
|
||||
| `config_query` | `["config_query", "all"]` | Query all configuration parameters |
|
||||
| **Auth Rules Management** |
|
||||
| `auth_add_blacklist` | `["blacklist", "pubkey", "abc123..."]` | Add pubkey to blacklist |
|
||||
| `auth_add_whitelist` | `["whitelist", "pubkey", "def456..."]` | Add pubkey to whitelist |
|
||||
| `auth_delete_rule` | `["delete_auth_rule", "blacklist", "pubkey", "abc123..."]` | Delete specific auth rule |
|
||||
| `auth_query_all` | `["auth_query", "all"]` | Query all auth rules |
|
||||
| `auth_query_type` | `["auth_query", "whitelist"]` | Query specific rule type |
|
||||
| `auth_query_pattern` | `["auth_query", "pattern", "abc123..."]` | Query specific pattern |
|
||||
| **System Commands** |
|
||||
| `system_clear_auth` | `["system_command", "clear_all_auth_rules"]` | Clear all auth rules |
|
||||
| `system_status` | `["system_command", "system_status"]` | Get system status |
|
||||
| `stats_query` | `["stats_query"]` | Get comprehensive database statistics |
|
||||
|
||||
### Available Configuration Keys
|
||||
|
||||
**Basic Relay Settings:**
|
||||
- `relay_name`: Relay name (displayed in NIP-11)
|
||||
- `relay_description`: Relay description text
|
||||
- `relay_contact`: Contact information
|
||||
- `relay_software`: Software URL
|
||||
- `relay_version`: Software version
|
||||
- `supported_nips`: Comma-separated list of supported NIP numbers (e.g., "1,2,4,9,11,12,13,15,16,20,22,33,40,42")
|
||||
- `language_tags`: Comma-separated list of supported language tags (e.g., "en,es,fr" or "*" for all)
|
||||
- `relay_countries`: Comma-separated list of supported country codes (e.g., "US,CA,MX" or "*" for all)
|
||||
- `posting_policy`: Posting policy URL or text
|
||||
- `payments_url`: Payment URL for premium features
|
||||
- `max_connections`: Maximum concurrent connections
|
||||
- `max_subscriptions_per_client`: Max subscriptions per client
|
||||
- `max_event_tags`: Maximum tags per event
|
||||
@@ -103,6 +193,24 @@ All commands are sent as nip44 encrypted content. The following table lists all
|
||||
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
||||
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
|
||||
|
||||
### Dynamic Configuration Updates
|
||||
|
||||
C-Relay supports **dynamic configuration updates** without requiring a restart for most settings. Configuration parameters are categorized as either **dynamic** (can be updated immediately) or **restart-required** (require relay restart to take effect).
|
||||
|
||||
**Dynamic Configuration Parameters (No Restart Required):**
|
||||
- All relay information (NIP-11) settings: `relay_name`, `relay_description`, `relay_contact`, `relay_software`, `relay_version`, `supported_nips`, `language_tags`, `relay_countries`, `posting_policy`, `payments_url`
|
||||
- Authentication settings: `auth_enabled`, `nip42_auth_required`, `nip42_auth_required_kinds`, `nip42_challenge_timeout`
|
||||
- Subscription limits: `max_subscriptions_per_client`, `max_total_subscriptions`
|
||||
- Event validation limits: `max_event_tags`, `max_content_length`, `max_message_length`
|
||||
- Proof of Work settings: `pow_min_difficulty`, `pow_mode`
|
||||
- Event expiration settings: `nip40_expiration_enabled`, `nip40_expiration_strict`, `nip40_expiration_filter`, `nip40_expiration_grace_period`
|
||||
|
||||
**Restart-Required Configuration Parameters:**
|
||||
- Connection settings: `max_connections`, `relay_port`
|
||||
- Database and core system settings
|
||||
|
||||
When updating configuration, the admin API response will indicate whether a restart is required for each parameter. Dynamic updates take effect immediately and are reflected in NIP-11 relay information documents without restart.
|
||||
|
||||
### Response Format
|
||||
|
||||
All admin commands return **signed EVENT responses** via WebSocket following standard Nostr protocol. Responses use JSON content with structured data.
|
||||
@@ -116,7 +224,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"status\": \"success\", \"message\": \"Operation completed successfully\"}",
|
||||
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"success\", \"message\": \"Operation completed successfully\", \"timestamp\": 1234567890}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
@@ -131,7 +239,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"status\": \"error\", \"message\": \"Error: invalid configuration value\"}",
|
||||
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"error\", \"error\": \"invalid configuration value\", \"timestamp\": 1234567890}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
@@ -146,7 +254,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"query_type\": \"auth_rules\", \"total_results\": 2, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"deny\"}]}",
|
||||
"content": "nip44 encrypted:{\"query_type\": \"auth_rules_all\", \"total_results\": 2, \"timestamp\": 1234567890, \"data\": [{\"rule_type\": \"blacklist\", \"pattern_type\": \"pubkey\", \"pattern_value\": \"abc123...\", \"action\": \"allow\"}]}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
@@ -161,7 +269,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"query_type\": \"config_keys\", \"config_keys\": [\"auth_enabled\", \"max_connections\"], \"descriptions\": {\"auth_enabled\": \"Enable whitelist/blacklist rules\"}}",
|
||||
"content": "nip44 encrypted:{\"query_type\": \"config_all\", \"total_results\": 27, \"timestamp\": 1234567890, \"data\": [{\"key\": \"auth_enabled\", \"value\": \"false\", \"data_type\": \"boolean\", \"category\": \"auth\", \"description\": \"Enable NIP-42 authentication\"}, {\"key\": \"relay_description\", \"value\": \"My Relay\", \"data_type\": \"string\", \"category\": \"relay\", \"description\": \"Relay description text\"}]}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
@@ -169,3 +277,71 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
||||
}]
|
||||
```
|
||||
|
||||
**Configuration Update Success Response:**
|
||||
```json
|
||||
["EVENT", "temp_sub_id", {
|
||||
"id": "response_event_id",
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"total_results\": 2, \"timestamp\": 1234567890, \"status\": \"success\", \"data\": [{\"key\": \"auth_enabled\", \"value\": \"true\", \"status\": \"updated\"}, {\"key\": \"relay_description\", \"value\": \"My Updated Relay\", \"status\": \"updated\"}]}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
```
|
||||
|
||||
**Configuration Update Error Response:**
|
||||
```json
|
||||
["EVENT", "temp_sub_id", {
|
||||
"id": "response_event_id",
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"query_type\": \"config_update\", \"status\": \"error\", \"error\": \"field validation failed: invalid port number '99999' (must be 1-65535)\", \"timestamp\": 1234567890}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
```
|
||||
|
||||
**Database Statistics Query Response:**
|
||||
```json
|
||||
["EVENT", "temp_sub_id", {
|
||||
"id": "response_event_id",
|
||||
"pubkey": "relay_public_key",
|
||||
"created_at": 1234567890,
|
||||
"kind": 23457,
|
||||
"content": "nip44 encrypted:{\"query_type\": \"stats_query\", \"timestamp\": 1234567890, \"database_size_bytes\": 1048576, \"total_events\": 15432, \"database_created_at\": 1234567800, \"latest_event_at\": 1234567890, \"event_kinds\": [{\"kind\": 1, \"count\": 12000, \"percentage\": 77.8}, {\"kind\": 0, \"count\": 2500, \"percentage\": 16.2}], \"time_stats\": {\"total\": 15432, \"last_24h\": 234, \"last_7d\": 1456, \"last_30d\": 5432}, \"top_pubkeys\": [{\"pubkey\": \"abc123...\", \"event_count\": 1234, \"percentage\": 8.0}, {\"pubkey\": \"def456...\", \"event_count\": 987, \"percentage\": 6.4}]}",
|
||||
"tags": [
|
||||
["p", "admin_public_key"]
|
||||
],
|
||||
"sig": "response_event_signature"
|
||||
}]
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Direct Messaging Admin System
|
||||
|
||||
In addition to the above admin API, c-relay allows the administrator to direct message the relay to get information or control some settings. As long as the administrator is signed in with any nostr client that allows sending nip-17 direct messages (DMs), they can control the relay.
|
||||
|
||||
The is possible because the relay is a full nostr citizen with it's own private and public key, and it knows the administrator's public key.
|
||||
|
||||
|
||||
|
||||
**Available DM commands**
|
||||
|
||||
The intent is not to be strict in the formatting of the DM. So for example if the relay receives any DM from the administrator with the words "stats" or "statistics" in it, it will respond to the administrator with a reply DM with the current relay statistics.
|
||||
|
||||
- `stats`|`statistics`: Relay statistics
|
||||
- `config`|`configuration`: Relay configuration
|
||||
|
||||
|
||||
|
||||
490
api/index.css
Normal file
490
api/index.css
Normal file
@@ -0,0 +1,490 @@
|
||||
:root {
|
||||
/* Core Variables (7) */
|
||||
--primary-color: #000000;
|
||||
--secondary-color: #ffffff;
|
||||
--accent-color: #ff0000;
|
||||
--muted-color: #dddddd;
|
||||
--border-color: var(--muted-color);
|
||||
--font-family: "Courier New", Courier, monospace;
|
||||
--border-radius: 15px;
|
||||
--border-width: 1px;
|
||||
|
||||
/* Floating Tab Variables (8) */
|
||||
--tab-bg-logged-out: #ffffff;
|
||||
--tab-bg-logged-in: #ffffff;
|
||||
--tab-bg-opacity-logged-out: 0.9;
|
||||
--tab-bg-opacity-logged-in: 0.2;
|
||||
--tab-color-logged-out: #000000;
|
||||
--tab-color-logged-in: #ffffff;
|
||||
--tab-border-logged-out: #000000;
|
||||
--tab-border-logged-in: #ff0000;
|
||||
--tab-border-opacity-logged-out: 1.0;
|
||||
--tab-border-opacity-logged-in: 0.1;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
/* line-height: 1.4; */
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
border-bottom: var(--border-width) solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 30px;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
padding-left: 10px;
|
||||
font-size: 16px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.section {
|
||||
background: var(--secondary-color);
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
font-family: var(--font-family);
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
border-color: var(--accent-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
font-family: var(--font-family);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin: 5px 0;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
button:active {
|
||||
background: var(--accent-color);
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #ccc;
|
||||
color: var(--muted-color);
|
||||
cursor: not-allowed;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
font-weight: bold;
|
||||
font-family: var(--font-family);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.status.connected {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.status.disconnected {
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.status.authenticated {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.config-table {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin: 10px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.config-table th,
|
||||
.config-table td {
|
||||
border: 0.1px solid var(--muted-color);
|
||||
padding: 4px;
|
||||
text-align: left;
|
||||
font-family: var(--font-family);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.config-table-container {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.config-table th {
|
||||
font-weight: bold;
|
||||
height: 40px; /* Double the default height */
|
||||
line-height: 40px; /* Center text vertically */
|
||||
}
|
||||
|
||||
.config-table tr:hover {
|
||||
background-color: var(--muted-color);
|
||||
}
|
||||
|
||||
/* Inline config value inputs - remove borders and padding to fit seamlessly in table cells */
|
||||
.config-value-input {
|
||||
border: none;
|
||||
padding: 2px 4px;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
min-height: auto;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.config-value-input:focus {
|
||||
border: 1px solid var(--accent-color);
|
||||
background: var(--secondary-color);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Config actions cell - clickable for saving */
|
||||
.config-actions-cell {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.config-actions-cell:hover {
|
||||
border: 1px solid var(--accent-color);
|
||||
background-color: var(--muted-color);
|
||||
}
|
||||
|
||||
.json-display {
|
||||
background-color: var(--secondary-color);
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 10px;
|
||||
font-family: var(--font-family);
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.log-panel {
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
background-color: var(--secondary-color);
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid var(--muted-color);
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.log-timestamp {
|
||||
font-weight: bold;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.inline-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.inline-buttons button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
padding: 10px;
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
margin: 10px 0;
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.user-info-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.user-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.login-logout-btn {
|
||||
width: auto;
|
||||
min-width: 120px;
|
||||
padding: 12px 16px;
|
||||
background: var(--secondary-color);
|
||||
color: var(--primary-color);
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
font-family: var(--font-family);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.login-logout-btn:hover {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.login-logout-btn:active {
|
||||
background: var(--accent-color);
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.login-logout-btn.logout-state {
|
||||
background: var(--accent-color);
|
||||
color: var(--secondary-color);
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.login-logout-btn.logout-state:hover {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.user-pubkey {
|
||||
font-family: var(--font-family);
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: var(--border-width) solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.auth-rules-controls {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-header .status {
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
min-width: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Auth Rule Input Sections Styling */
|
||||
.auth-rule-section {
|
||||
border: var(--border-width) solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.auth-rule-section h3 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
border-left: 4px solid var(--border-color);
|
||||
padding-left: 8px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.auth-rule-section p {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 13px;
|
||||
color: var(--muted-color);
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.rule-status {
|
||||
margin-top: 10px;
|
||||
padding: 8px;
|
||||
border: var(--border-width) solid var(--muted-color);
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 12px;
|
||||
min-height: 20px;
|
||||
background-color: var(--secondary-color);
|
||||
font-family: var(--font-family);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.rule-status.success {
|
||||
border-color: #4CAF50;
|
||||
background-color: #E8F5E8;
|
||||
color: #2E7D32;
|
||||
}
|
||||
|
||||
.rule-status.error {
|
||||
border-color: var(--accent-color);
|
||||
background-color: #FFEBEE;
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
.rule-status.warning {
|
||||
border-color: #FF9800;
|
||||
background-color: #FFF3E0;
|
||||
color: #E65100;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
border: var(--border-width) solid #FF9800;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: #FFF3E0;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
font-size: 13px;
|
||||
color: #E65100;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.warning-box strong {
|
||||
color: #D84315;
|
||||
}
|
||||
|
||||
#login-section {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Floating tab styles */
|
||||
.floating-tab {
|
||||
font-family: var(--font-family);
|
||||
border-radius: var(--border-radius);
|
||||
border: var(--border-width) solid;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.floating-tab--logged-out {
|
||||
background: rgba(255, 255, 255, var(--tab-bg-opacity-logged-out));
|
||||
color: var(--tab-color-logged-out);
|
||||
border-color: rgba(0, 0, 0, var(--tab-border-opacity-logged-out));
|
||||
}
|
||||
|
||||
.floating-tab--logged-in {
|
||||
background: rgba(0, 0, 0, var(--tab-bg-opacity-logged-in));
|
||||
color: var(--tab-color-logged-in);
|
||||
border-color: rgba(255, 0, 0, var(--tab-border-opacity-logged-in));
|
||||
}
|
||||
|
||||
.transition {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Main Sections Wrapper */
|
||||
.main-sections-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--border-width);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.flex-section {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.inline-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
2295
api/index.html
2295
api/index.html
File diff suppressed because it is too large
Load Diff
3512
api/index.js
Normal file
3512
api/index.js
Normal file
File diff suppressed because it is too large
Load Diff
1784
api/nostr-lite.js
1784
api/nostr-lite.js
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,33 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
COMMIT_MESSAGE=""
|
||||
RELEASE_MODE=false
|
||||
|
||||
show_usage() {
|
||||
echo "C-Relay Build and Push Script"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 \"commit message\" - Default: compile, increment patch, commit & push"
|
||||
echo " $0 -r \"commit message\" - Release: compile x86+arm64, increment minor, create release"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 \"Fixed event validation bug\""
|
||||
echo " $0 --release \"Major release with new features\""
|
||||
echo ""
|
||||
echo "Default Mode (patch increment):"
|
||||
echo " - Compile C-Relay"
|
||||
echo " - Increment patch version (v1.2.3 → v1.2.4)"
|
||||
echo " - Git add, commit with message, and push"
|
||||
echo ""
|
||||
echo "Release Mode (-r flag):"
|
||||
echo " - Compile C-Relay for x86_64 and arm64 (dynamic and static versions)"
|
||||
echo " - Increment minor version, zero patch (v1.2.3 → v1.3.0)"
|
||||
echo " - Git add, commit, push, and create Gitea release"
|
||||
echo ""
|
||||
echo "Requirements for Release Mode:"
|
||||
echo " - For ARM64 builds: make install-arm64-deps (optional - will build x86_64 only if missing)"
|
||||
echo " - For static builds: sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev"
|
||||
echo " - Gitea token in ~/.gitea_token for release uploads"
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
@@ -38,32 +65,6 @@ while [[ $# -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
show_usage() {
|
||||
echo "C-Relay Build and Push Script"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 \"commit message\" - Default: compile, increment patch, commit & push"
|
||||
echo " $0 -r \"commit message\" - Release: compile x86+arm64, increment minor, create release"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 \"Fixed event validation bug\""
|
||||
echo " $0 --release \"Major release with new features\""
|
||||
echo ""
|
||||
echo "Default Mode (patch increment):"
|
||||
echo " - Compile C-Relay"
|
||||
echo " - Increment patch version (v1.2.3 → v1.2.4)"
|
||||
echo " - Git add, commit with message, and push"
|
||||
echo ""
|
||||
echo "Release Mode (-r flag):"
|
||||
echo " - Compile C-Relay for x86_64 and arm64"
|
||||
echo " - Increment minor version, zero patch (v1.2.3 → v1.3.0)"
|
||||
echo " - Git add, commit, push, and create Gitea release"
|
||||
echo ""
|
||||
echo "Requirements for Release Mode:"
|
||||
echo " - For ARM64 builds: make install-arm64-deps (optional - will build x86_64 only if missing)"
|
||||
echo " - Gitea token in ~/.gitea_token for release uploads"
|
||||
}
|
||||
|
||||
# Validate inputs
|
||||
if [[ -z "$COMMIT_MESSAGE" ]]; then
|
||||
print_error "Commit message is required"
|
||||
@@ -139,11 +140,11 @@ compile_project() {
|
||||
print_warning "Clean failed or no Makefile found"
|
||||
fi
|
||||
|
||||
# Force regenerate version.h to pick up new tags
|
||||
# Force regenerate main.h to pick up new tags
|
||||
if make force-version > /dev/null 2>&1; then
|
||||
print_success "Regenerated version.h"
|
||||
print_success "Regenerated main.h"
|
||||
else
|
||||
print_warning "Failed to regenerate version.h"
|
||||
print_warning "Failed to regenerate main.h"
|
||||
fi
|
||||
|
||||
# Compile the project
|
||||
@@ -190,6 +191,35 @@ build_release_binaries() {
|
||||
print_status "Only x86_64 binary will be included in release"
|
||||
fi
|
||||
|
||||
# Build static x86_64 version
|
||||
print_status "Building static x86_64 version..."
|
||||
make clean > /dev/null 2>&1
|
||||
if make static-musl-x86_64 > /dev/null 2>&1; then
|
||||
if [[ -f "build/c_relay_static_musl_x86_64" ]]; then
|
||||
cp build/c_relay_static_musl_x86_64 c-relay-static-x86_64
|
||||
print_success "Static x86_64 binary created: c-relay-static-x86_64"
|
||||
else
|
||||
print_warning "Static x86_64 binary not found after compilation"
|
||||
fi
|
||||
else
|
||||
print_warning "Static x86_64 build failed - MUSL development packages may not be installed"
|
||||
print_status "Run 'sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev' to enable static builds"
|
||||
fi
|
||||
|
||||
# Try to build static ARM64 version
|
||||
print_status "Attempting static ARM64 build..."
|
||||
make clean > /dev/null 2>&1
|
||||
if make static-musl-arm64 > /dev/null 2>&1; then
|
||||
if [[ -f "build/c_relay_static_musl_arm64" ]]; then
|
||||
cp build/c_relay_static_musl_arm64 c-relay-static-arm64
|
||||
print_success "Static ARM64 binary created: c-relay-static-arm64"
|
||||
else
|
||||
print_warning "Static ARM64 binary not found after compilation"
|
||||
fi
|
||||
else
|
||||
print_warning "Static ARM64 build failed - ARM64 cross-compilation or MUSL ARM64 packages not set up"
|
||||
fi
|
||||
|
||||
# Restore normal build
|
||||
make clean > /dev/null 2>&1
|
||||
make > /dev/null 2>&1
|
||||
@@ -319,12 +349,18 @@ create_gitea_release() {
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
|
||||
|
||||
local upload_result=false
|
||||
|
||||
if echo "$response" | grep -q '"id"'; then
|
||||
print_success "Created release $NEW_VERSION"
|
||||
upload_release_binaries "$api_url" "$token"
|
||||
if upload_release_binaries "$api_url" "$token"; then
|
||||
upload_result=true
|
||||
fi
|
||||
elif echo "$response" | grep -q "already exists"; then
|
||||
print_warning "Release $NEW_VERSION already exists"
|
||||
upload_release_binaries "$api_url" "$token"
|
||||
if upload_release_binaries "$api_url" "$token"; then
|
||||
upload_result=true
|
||||
fi
|
||||
else
|
||||
print_error "Failed to create release $NEW_VERSION"
|
||||
print_error "Response: $response"
|
||||
@@ -334,18 +370,29 @@ create_gitea_release() {
|
||||
local check_response=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
|
||||
if echo "$check_response" | grep -q '"id"'; then
|
||||
print_warning "Release exists but creation response was unexpected"
|
||||
upload_release_binaries "$api_url" "$token"
|
||||
if upload_release_binaries "$api_url" "$token"; then
|
||||
upload_result=true
|
||||
fi
|
||||
else
|
||||
print_error "Release does not exist and creation failed"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Return based on upload success
|
||||
if [[ "$upload_result" == true ]]; then
|
||||
return 0
|
||||
else
|
||||
print_error "Binary upload failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to upload release binaries
|
||||
upload_release_binaries() {
|
||||
local api_url="$1"
|
||||
local token="$2"
|
||||
local upload_success=true
|
||||
|
||||
# Get release ID with more robust parsing
|
||||
print_status "Getting release ID for $NEW_VERSION..."
|
||||
@@ -367,30 +414,100 @@ upload_release_binaries() {
|
||||
# Upload x86_64 binary
|
||||
if [[ -f "c-relay-x86_64" ]]; then
|
||||
print_status "Uploading x86_64 binary..."
|
||||
if curl -s -X POST "$api_url/releases/$release_id/assets" \
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64" > /dev/null; then
|
||||
print_success "Uploaded x86_64 binary"
|
||||
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded x86_64 binary successfully"
|
||||
else
|
||||
print_warning "Failed to upload x86_64 binary"
|
||||
print_error "Failed to upload x86_64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "x86_64 binary not found: c-relay-x86_64"
|
||||
fi
|
||||
|
||||
# Upload ARM64 binary
|
||||
if [[ -f "c-relay-arm64" ]]; then
|
||||
print_status "Uploading ARM64 binary..."
|
||||
if curl -s -X POST "$api_url/releases/$release_id/assets" \
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64" > /dev/null; then
|
||||
print_success "Uploaded ARM64 binary"
|
||||
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded ARM64 binary successfully"
|
||||
else
|
||||
print_warning "Failed to upload ARM64 binary"
|
||||
print_error "Failed to upload ARM64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "ARM64 binary not found: c-relay-arm64"
|
||||
fi
|
||||
|
||||
# Upload static x86_64 binary
|
||||
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||
print_status "Uploading static x86_64 binary..."
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-static-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64-static")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded static x86_64 binary successfully"
|
||||
else
|
||||
print_error "Failed to upload static x86_64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "Static x86_64 binary not found: c-relay-static-x86_64"
|
||||
fi
|
||||
|
||||
# Upload static ARM64 binary
|
||||
if [[ -f "c-relay-static-arm64" ]]; then
|
||||
print_status "Uploading static ARM64 binary..."
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-static-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64-static")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded static ARM64 binary successfully"
|
||||
else
|
||||
print_error "Failed to upload static ARM64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "Static ARM64 binary not found: c-relay-static-arm64"
|
||||
fi
|
||||
|
||||
# Return success/failure status
|
||||
if [[ "$upload_success" == true ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to clean up release binaries
|
||||
cleanup_release_binaries() {
|
||||
local force_cleanup="$1" # Optional parameter to force cleanup even on failure
|
||||
|
||||
if [[ "$force_cleanup" == "force" ]] || [[ "$upload_success" == true ]]; then
|
||||
if [[ -f "c-relay-x86_64" ]]; then
|
||||
rm -f c-relay-x86_64
|
||||
print_status "Cleaned up x86_64 binary"
|
||||
@@ -399,6 +516,30 @@ cleanup_release_binaries() {
|
||||
rm -f c-relay-arm64
|
||||
print_status "Cleaned up ARM64 binary"
|
||||
fi
|
||||
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||
rm -f c-relay-static-x86_64
|
||||
print_status "Cleaned up static x86_64 binary"
|
||||
fi
|
||||
if [[ -f "c-relay-static-arm64" ]]; then
|
||||
rm -f c-relay-static-arm64
|
||||
print_status "Cleaned up static ARM64 binary"
|
||||
fi
|
||||
else
|
||||
print_warning "Keeping binary files due to upload failures"
|
||||
print_status "Files available for manual upload:"
|
||||
if [[ -f "c-relay-x86_64" ]]; then
|
||||
print_status " - c-relay-x86_64"
|
||||
fi
|
||||
if [[ -f "c-relay-arm64" ]]; then
|
||||
print_status " - c-relay-arm64"
|
||||
fi
|
||||
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||
print_status " - c-relay-static-x86_64"
|
||||
fi
|
||||
if [[ -f "c-relay-static-arm64" ]]; then
|
||||
print_status " - c-relay-static-arm64"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
@@ -433,13 +574,17 @@ main() {
|
||||
git_commit_and_push_no_tag
|
||||
|
||||
# Create Gitea release with binaries
|
||||
create_gitea_release
|
||||
|
||||
# Cleanup
|
||||
cleanup_release_binaries
|
||||
|
||||
if create_gitea_release; then
|
||||
print_success "Release $NEW_VERSION completed successfully!"
|
||||
print_status "Binaries uploaded to Gitea release"
|
||||
upload_success=true
|
||||
else
|
||||
print_error "Release creation or binary upload failed"
|
||||
upload_success=false
|
||||
fi
|
||||
|
||||
# Cleanup (only if upload was successful)
|
||||
cleanup_release_binaries
|
||||
|
||||
else
|
||||
print_status "=== DEFAULT MODE ==="
|
||||
|
||||
190
build_static.sh
Executable file
190
build_static.sh
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build fully static MUSL binaries for C-Relay using Alpine Docker
|
||||
# Produces truly portable binaries with zero runtime dependencies
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BUILD_DIR="$SCRIPT_DIR/build"
|
||||
DOCKERFILE="$SCRIPT_DIR/Dockerfile.alpine-musl"
|
||||
|
||||
echo "=========================================="
|
||||
echo "C-Relay MUSL Static Binary Builder"
|
||||
echo "=========================================="
|
||||
echo "Project directory: $SCRIPT_DIR"
|
||||
echo "Build directory: $BUILD_DIR"
|
||||
echo ""
|
||||
|
||||
# Create build directory
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Check if Docker is available
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "ERROR: Docker is not installed or not in PATH"
|
||||
echo ""
|
||||
echo "Docker is required to build MUSL static binaries."
|
||||
echo "Please install Docker:"
|
||||
echo " - Ubuntu/Debian: sudo apt install docker.io"
|
||||
echo " - Or visit: https://docs.docker.com/engine/install/"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Docker daemon is running
|
||||
if ! docker info &> /dev/null; then
|
||||
echo "ERROR: Docker daemon is not running or user not in docker group"
|
||||
echo ""
|
||||
echo "Please start Docker and ensure you're in the docker group:"
|
||||
echo " - sudo systemctl start docker"
|
||||
echo " - sudo usermod -aG docker $USER && newgrp docker"
|
||||
echo " - Or start Docker Desktop"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DOCKER_CMD="docker"
|
||||
|
||||
echo "✓ Docker is available and running"
|
||||
echo ""
|
||||
|
||||
# Detect architecture
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64)
|
||||
PLATFORM="linux/amd64"
|
||||
OUTPUT_NAME="c_relay_static_x86_64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
PLATFORM="linux/arm64"
|
||||
OUTPUT_NAME="c_relay_static_arm64"
|
||||
;;
|
||||
*)
|
||||
echo "WARNING: Unknown architecture: $ARCH"
|
||||
echo "Defaulting to linux/amd64"
|
||||
PLATFORM="linux/amd64"
|
||||
OUTPUT_NAME="c_relay_static_${ARCH}"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Building for platform: $PLATFORM"
|
||||
echo "Output binary: $OUTPUT_NAME"
|
||||
echo ""
|
||||
|
||||
# Build the Docker image
|
||||
echo "=========================================="
|
||||
echo "Step 1: Building Alpine Docker image"
|
||||
echo "=========================================="
|
||||
echo "This will:"
|
||||
echo " - Use Alpine Linux (native MUSL)"
|
||||
echo " - Build all dependencies statically"
|
||||
echo " - Compile c-relay with full static linking"
|
||||
echo ""
|
||||
|
||||
$DOCKER_CMD build \
|
||||
--platform "$PLATFORM" \
|
||||
-f "$DOCKERFILE" \
|
||||
-t c-relay-musl-builder:latest \
|
||||
--progress=plain \
|
||||
. || {
|
||||
echo ""
|
||||
echo "ERROR: Docker build failed"
|
||||
echo "Check the output above for details"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "✓ Docker image built successfully"
|
||||
echo ""
|
||||
|
||||
# Extract the binary from the container
|
||||
echo "=========================================="
|
||||
echo "Step 2: Extracting static binary"
|
||||
echo "=========================================="
|
||||
|
||||
# Build the builder stage to extract the binary
|
||||
$DOCKER_CMD build \
|
||||
--platform "$PLATFORM" \
|
||||
--target builder \
|
||||
-f "$DOCKERFILE" \
|
||||
-t c-relay-static-builder-stage:latest \
|
||||
. > /dev/null 2>&1
|
||||
|
||||
# Create a temporary container to copy the binary
|
||||
CONTAINER_ID=$($DOCKER_CMD create c-relay-static-builder-stage:latest)
|
||||
|
||||
# Copy binary from container
|
||||
$DOCKER_CMD cp "$CONTAINER_ID:/build/c_relay_static" "$BUILD_DIR/$OUTPUT_NAME" || {
|
||||
echo "ERROR: Failed to extract binary from container"
|
||||
$DOCKER_CMD rm "$CONTAINER_ID" 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Clean up container
|
||||
$DOCKER_CMD rm "$CONTAINER_ID" > /dev/null
|
||||
|
||||
echo "✓ Binary extracted to: $BUILD_DIR/$OUTPUT_NAME"
|
||||
echo ""
|
||||
|
||||
# Make binary executable
|
||||
chmod +x "$BUILD_DIR/$OUTPUT_NAME"
|
||||
|
||||
# Verify the binary
|
||||
echo "=========================================="
|
||||
echo "Step 3: Verifying static binary"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
echo "Checking for dynamic dependencies:"
|
||||
if LDD_OUTPUT=$(timeout 5 ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1); then
|
||||
if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable"; then
|
||||
echo "✓ Binary is fully static (no dynamic dependencies)"
|
||||
TRULY_STATIC=true
|
||||
elif echo "$LDD_OUTPUT" | grep -q "statically linked"; then
|
||||
echo "✓ Binary is statically linked"
|
||||
TRULY_STATIC=true
|
||||
else
|
||||
echo "⚠ WARNING: Binary may have dynamic dependencies:"
|
||||
echo "$LDD_OUTPUT"
|
||||
TRULY_STATIC=false
|
||||
fi
|
||||
else
|
||||
# ldd failed or timed out - check with file command instead
|
||||
if file "$BUILD_DIR/$OUTPUT_NAME" | grep -q "statically linked"; then
|
||||
echo "✓ Binary is statically linked (verified with file command)"
|
||||
TRULY_STATIC=true
|
||||
else
|
||||
echo "⚠ Could not verify static linking (ldd check failed)"
|
||||
TRULY_STATIC=false
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "File size: $(ls -lh "$BUILD_DIR/$OUTPUT_NAME" | awk '{print $5}')"
|
||||
echo ""
|
||||
|
||||
# Test if binary runs
|
||||
echo "Testing binary execution:"
|
||||
if "$BUILD_DIR/$OUTPUT_NAME" --version 2>&1 | head -5; then
|
||||
echo "✓ Binary executes successfully"
|
||||
else
|
||||
echo "⚠ Binary execution test failed (this may be normal if --version is not supported)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo "=========================================="
|
||||
echo "Build Summary"
|
||||
echo "=========================================="
|
||||
echo "Binary: $BUILD_DIR/$OUTPUT_NAME"
|
||||
echo "Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)"
|
||||
echo "Platform: $PLATFORM"
|
||||
if [ "$TRULY_STATIC" = true ]; then
|
||||
echo "Type: Fully static binary (Alpine MUSL-based)"
|
||||
echo "Portability: Works on ANY Linux distribution"
|
||||
else
|
||||
echo "Type: Static binary (may have minimal dependencies)"
|
||||
fi
|
||||
echo ""
|
||||
echo "✓ Build complete!"
|
||||
echo ""
|
||||
8
c-relay.code-workspace
Normal file
8
c-relay.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
3
deploy_local.sh
Executable file
3
deploy_local.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
cp build/c_relay_x86 ~/Storage/c_relay/crelay
|
||||
27
deploy_static.sh
Executable file
27
deploy_static.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# C-Relay Static Binary Deployment Script
|
||||
# Deploys build/c_relay_static_x86_64 to server via sshlt
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
LOCAL_BINARY="build/c_relay_static_x86_64"
|
||||
REMOTE_BINARY_PATH="/usr/local/bin/c_relay/c_relay"
|
||||
SERVICE_NAME="c-relay"
|
||||
|
||||
# Create backup
|
||||
ssh ubuntu@laantungir.com "sudo cp '$REMOTE_BINARY_PATH' '${REMOTE_BINARY_PATH}.backup.$(date +%Y%m%d_%H%M%S)'" 2>/dev/null || true
|
||||
|
||||
# Upload binary to temp location
|
||||
scp "$LOCAL_BINARY" "ubuntu@laantungir.com:/tmp/c_relay.tmp"
|
||||
|
||||
# Install binary
|
||||
ssh ubuntu@laantungir.com "sudo mv '/tmp/c_relay.tmp' '$REMOTE_BINARY_PATH'"
|
||||
ssh ubuntu@laantungir.com "sudo chown c-relay:c-relay '$REMOTE_BINARY_PATH'"
|
||||
ssh ubuntu@laantungir.com "sudo chmod +x '$REMOTE_BINARY_PATH'"
|
||||
|
||||
# Restart service
|
||||
ssh ubuntu@laantungir.com "sudo systemctl restart '$SERVICE_NAME'"
|
||||
|
||||
echo "Deployment complete!"
|
||||
@@ -36,122 +36,70 @@ CREATE TABLE auth_rules (
|
||||
|
||||
#### Admin API Commands (via WebSocket with admin private key)
|
||||
|
||||
**Kind 23455: Configuration Management (Ephemeral)**
|
||||
- Update relay settings, limits, authentication policies
|
||||
- **Standard Mode**: Commands in tags `["config_key", "config_value"]`
|
||||
- **Encrypted Mode**: Commands NIP-44 encrypted in content `{"encrypted_tags": "..."}`
|
||||
- Content: Descriptive text or encrypted payload
|
||||
- Security: Optional NIP-44 encryption for sensitive operations
|
||||
|
||||
**Kind 23456: Auth Rules & System Management (Ephemeral)**
|
||||
**Kind 23456: Unified Admin API (Ephemeral)**
|
||||
- Configuration management: Update relay settings, limits, authentication policies
|
||||
- Auth rules: Add/remove/query whitelist/blacklist rules
|
||||
- System commands: clear rules, status, cache management
|
||||
- **Standard Mode**: Commands in tags
|
||||
- Rule format: `["rule_type", "pattern_type", "pattern_value"]`
|
||||
- Query format: `["auth_query", "filter"]`
|
||||
- System format: `["system_command", "command_name"]`
|
||||
- **Encrypted Mode**: Commands NIP-44 encrypted in content `{"encrypted_tags": "..."}`
|
||||
- Content: Action description + optional encrypted payload
|
||||
- Security: Optional NIP-44 encryption for sensitive operations
|
||||
- **Unified Format**: All commands use NIP-44 encrypted content with `["p", "relay_pubkey"]` tags
|
||||
- **Command Types**:
|
||||
- Configuration: `["config_key", "config_value"]`
|
||||
- Auth rules: `["rule_type", "pattern_type", "pattern_value"]`
|
||||
- Queries: `["auth_query", "filter"]` or `["system_command", "command_name"]`
|
||||
- **Security**: All admin commands use NIP-44 encryption for privacy and security
|
||||
|
||||
#### Configuration Query Commands (using Kind 23455)
|
||||
#### Configuration Commands (using Kind 23456)
|
||||
|
||||
1. **List All Configuration Keys (Standard)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "Discovery query",
|
||||
"tags": [["config_query", "list_all_keys"]]
|
||||
}
|
||||
```
|
||||
|
||||
2. **List All Configuration Keys (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"list_config_keys\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["config_query", "list_all_keys"]]`
|
||||
|
||||
3. **Get Current Configuration (Standard)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "Config query",
|
||||
"tags": [["config_query", "get_current_config"]]
|
||||
}
|
||||
```
|
||||
|
||||
4. **Get Current Configuration (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"get_config\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["config_query", "get_current_config"]]`
|
||||
|
||||
#### System Management Commands (using Kind 23456)
|
||||
|
||||
1. **Clear All Auth Rules (Standard)**:
|
||||
1. **Update Configuration**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"clear_all\"}",
|
||||
"tags": [["system_command", "clear_all_auth_rules"]]
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["relay_description", "My Relay"]`
|
||||
|
||||
2. **Clear All Auth Rules (Encrypted)**:
|
||||
2. **Query System Status**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"clear_all\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["system_command", "clear_all_auth_rules"]]`
|
||||
*Encrypted content contains:* `["system_command", "system_status"]`
|
||||
|
||||
3. **Query All Auth Rules (Standard)**:
|
||||
#### Auth Rules and System Commands (using Kind 23456)
|
||||
|
||||
1. **Clear All Auth Rules**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\"}",
|
||||
"tags": [["auth_query", "all"]]
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["system_command", "clear_all_auth_rules"]`
|
||||
|
||||
4. **Query All Auth Rules (Encrypted)**:
|
||||
2. **Query All Auth Rules**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"query\":\"list_auth_rules\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["auth_query", "all"]]`
|
||||
*Encrypted content contains:* `["auth_query", "all"]`
|
||||
|
||||
5. **Add Blacklist Rule (Standard)**:
|
||||
3. **Add Blacklist Rule**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"add\"}",
|
||||
"tags": [["blacklist", "pubkey", "deadbeef1234abcd..."]]
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
|
||||
6. **Add Blacklist Rule (Encrypted)**:
|
||||
```json
|
||||
{
|
||||
"kind": 23456,
|
||||
"content": "{\"action\":\"add\",\"encrypted_tags\":\"nip44_encrypted_payload\"}",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
*Encrypted payload contains:* `[["blacklist", "pubkey", "deadbeef1234abcd..."]]`
|
||||
*Encrypted content contains:* `["blacklist", "pubkey", "deadbeef1234abcd..."]`
|
||||
|
||||
### Phase 2: Auth Rules Schema Alignment
|
||||
|
||||
@@ -181,12 +129,12 @@ Would require changing schema, migration scripts, and storage logic.
|
||||
#### High Priority (Critical for blacklist functionality):
|
||||
1. Fix request_validator.c schema mismatch
|
||||
2. Ensure auth_required configuration is enabled
|
||||
3. Update tests to use ephemeral event kinds (23455/23456)
|
||||
3. Update tests to use unified ephemeral event kind (23456)
|
||||
4. Test blacklist enforcement
|
||||
|
||||
#### Medium Priority (Enhanced Admin Features):
|
||||
1. **Implement NIP-44 Encryption Support**:
|
||||
- Detect empty tags array for Kind 23455/23456 events
|
||||
- Detect NIP-44 encrypted content for Kind 23456 events
|
||||
- Parse `encrypted_tags` field from content JSON
|
||||
- Decrypt using admin privkey and relay pubkey
|
||||
- Process decrypted tags as normal commands
|
||||
@@ -218,45 +166,20 @@ Would require changing schema, migration scripts, and storage logic.
|
||||
## Authentication
|
||||
All admin commands require signing with the admin private key generated during first startup.
|
||||
|
||||
## Configuration Management (Kind 23455 - Ephemeral)
|
||||
## Unified Admin API (Kind 23456 - Ephemeral)
|
||||
Update relay configuration parameters or query available settings.
|
||||
|
||||
**Configuration Update Event:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "Configuration update",
|
||||
"tags": [
|
||||
["config_key1", "config_value1"],
|
||||
["config_key2", "config_value2"]
|
||||
]
|
||||
"kind": 23456,
|
||||
"content": "base64_nip44_encrypted_command_array",
|
||||
"tags": [["p", "relay_pubkey"]]
|
||||
}
|
||||
```
|
||||
*Encrypted content contains:* `["relay_description", "My Relay Description"]`
|
||||
|
||||
**List Available Config Keys:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"list_config_keys\",\"description\":\"Get editable config keys\"}",
|
||||
"tags": [
|
||||
["config_query", "list_all_keys"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Get Current Configuration:**
|
||||
```json
|
||||
{
|
||||
"kind": 23455,
|
||||
"content": "{\"query\":\"get_config\",\"description\":\"Get current config values\"}",
|
||||
"tags": [
|
||||
["config_query", "get_current_config"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Auth Rules Management (Kind 23456 - Ephemeral)
|
||||
Manage whitelist and blacklist rules.
|
||||
**Auth Rules Management:**
|
||||
|
||||
**Add Rule Event:**
|
||||
```json
|
||||
@@ -364,7 +287,7 @@ All admin commands return JSON responses via WebSocket:
|
||||
### Enable Authentication & Add Blacklist
|
||||
```bash
|
||||
# 1. Enable auth system
|
||||
nak event -k 23455 --content "Enable authentication" \
|
||||
nak event -k 23456 --content "base64_nip44_encrypted_command" \
|
||||
-t "auth_enabled=true" \
|
||||
--sec $ADMIN_PRIVKEY | nak event ws://localhost:8888
|
||||
|
||||
@@ -389,18 +312,18 @@ nak event -k 23456 --content '{"action":"clear_all","description":"Clear all rul
|
||||
### Configuration Query Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23455,
|
||||
"content": "{\"config_keys\": [\"auth_enabled\", \"max_connections\"], \"descriptions\": {\"auth_enabled\": \"Enable whitelist/blacklist rules\"}}",
|
||||
"tags": [["response_type", "config_keys_list"]]
|
||||
"kind": 23457,
|
||||
"content": "base64_nip44_encrypted_response",
|
||||
"tags": [["p", "admin_pubkey"]]
|
||||
}]
|
||||
```
|
||||
|
||||
### Current Config Response
|
||||
```json
|
||||
["EVENT", "subscription_id", {
|
||||
"kind": 23455,
|
||||
"content": "{\"current_config\": {\"auth_enabled\": \"true\", \"max_connections\": \"1000\"}}",
|
||||
"tags": [["response_type", "current_config"]]
|
||||
"kind": 23457,
|
||||
"content": "base64_nip44_encrypted_response",
|
||||
"tags": [["p", "admin_pubkey"]]
|
||||
}]
|
||||
```
|
||||
|
||||
@@ -427,7 +350,7 @@ nak event -k 23456 --content '{"action":"clear_all","description":"Clear all rul
|
||||
1. **Document API** (this file) ✅
|
||||
2. **Update to ephemeral event kinds** ✅
|
||||
3. **Fix request_validator.c** schema mismatch
|
||||
4. **Update tests** to use Kind 23455/23456
|
||||
4. **Update tests** to use unified Kind 23456
|
||||
5. **Add auth rule query functionality**
|
||||
6. **Add configuration discovery feature**
|
||||
7. **Test blacklist functionality**
|
||||
@@ -449,8 +372,8 @@ This plan addresses the immediate blacklist issue while establishing a comprehen
|
||||
```c
|
||||
// In admin event processing function
|
||||
bool is_encrypted_command(struct nostr_event *event) {
|
||||
// Check if Kind 23455 or 23456 with empty tags
|
||||
if ((event->kind == 23455 || event->kind == 23456) &&
|
||||
// Check if Kind 23456 with NIP-44 encrypted content
|
||||
if (event->kind == 23456 &&
|
||||
event->tags_count == 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -483,7 +406,7 @@ cJSON *decrypt_admin_tags(struct nostr_event *event) {
|
||||
```
|
||||
|
||||
### Admin Event Processing Flow
|
||||
1. **Receive Event**: Kind 23455/23456 with admin signature
|
||||
1. **Receive Event**: Kind 23456 with admin signature
|
||||
2. **Check Mode**: Empty tags = encrypted, populated tags = standard
|
||||
3. **Decrypt if Needed**: Extract and decrypt `encrypted_tags` from content
|
||||
4. **Process Commands**: Use decrypted/standard tags for command processing
|
||||
@@ -510,7 +433,7 @@ char* nip44_decrypt(const char* ciphertext, const char* recipient_privkey, const
|
||||
|
||||
#### Phase 1: Core Infrastructure (Complete)
|
||||
- [x] Event-based admin authentication system
|
||||
- [x] Kind 23455/23456 (Configuration/Auth Rules) processing
|
||||
- [x] Kind 23456 (Unified Admin API) processing
|
||||
- [x] Basic configuration parameter updates
|
||||
- [x] Auth rule add/remove/clear functionality
|
||||
- [x] Updated to ephemeral event kinds
|
||||
|
||||
275
docs/musl_static_build.md
Normal file
275
docs/musl_static_build.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# MUSL Static Binary Build Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide explains how to build truly portable MUSL-based static binaries of c-relay using Alpine Linux Docker containers. These binaries have **zero runtime dependencies** and work on any Linux distribution.
|
||||
|
||||
## Why MUSL?
|
||||
|
||||
### MUSL vs glibc Static Binaries
|
||||
|
||||
**MUSL Advantages:**
|
||||
- **Truly Static**: No hidden dependencies on system libraries
|
||||
- **Smaller Size**: ~7.6MB vs ~12MB+ for glibc static builds
|
||||
- **Better Portability**: Works on ANY Linux distribution without modification
|
||||
- **Cleaner Linking**: No glibc-specific extensions or fortified functions
|
||||
- **Simpler Deployment**: Single binary, no library compatibility issues
|
||||
|
||||
**glibc Limitations:**
|
||||
- Static builds still require dynamic loading for NSS (Name Service Switch)
|
||||
- Fortified functions (`__*_chk`) don't exist in MUSL
|
||||
- Larger binary size due to glibc's complexity
|
||||
- May have compatibility issues across different glibc versions
|
||||
|
||||
## Build Process
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker installed and running
|
||||
- Sufficient disk space (~2GB for Docker layers)
|
||||
- Internet connection (for downloading dependencies)
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Build MUSL static binary
|
||||
./build_static.sh
|
||||
|
||||
# The binary will be created at:
|
||||
# build/c_relay_static_musl_x86_64 (on x86_64)
|
||||
# build/c_relay_static_musl_arm64 (on ARM64)
|
||||
```
|
||||
|
||||
### What Happens During Build
|
||||
|
||||
1. **Alpine Linux Base**: Uses Alpine 3.19 with native MUSL support
|
||||
2. **Static Dependencies**: Builds all dependencies with static linking:
|
||||
- libsecp256k1 (Bitcoin cryptography)
|
||||
- libwebsockets (WebSocket server)
|
||||
- OpenSSL (TLS/crypto)
|
||||
- SQLite (database)
|
||||
- curl (HTTP client)
|
||||
- zlib (compression)
|
||||
|
||||
3. **nostr_core_lib**: Builds with MUSL-compatible flags:
|
||||
- Disables glibc fortification (`-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0`)
|
||||
- Includes required NIPs: 001, 006, 013, 017, 019, 044, 059
|
||||
- Produces static library (~316KB)
|
||||
|
||||
4. **c-relay Compilation**: Links everything statically:
|
||||
- All source files compiled with `-static` flag
|
||||
- Fortification disabled to avoid `__*_chk` symbols
|
||||
- Results in ~7.6MB stripped binary
|
||||
|
||||
5. **Verification**: Confirms binary is truly static:
|
||||
- `ldd` shows "not a dynamic executable"
|
||||
- `file` shows "statically linked"
|
||||
- Binary executes successfully
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Dockerfile Structure
|
||||
|
||||
The build uses a multi-stage Dockerfile (`Dockerfile.alpine-musl`):
|
||||
|
||||
```dockerfile
|
||||
# Stage 1: Builder (Alpine Linux)
|
||||
FROM alpine:3.19 AS builder
|
||||
- Install build tools and static libraries
|
||||
- Build dependencies from source
|
||||
- Compile nostr_core_lib with MUSL flags
|
||||
- Compile c-relay with full static linking
|
||||
- Strip binary to reduce size
|
||||
|
||||
# Stage 2: Output (scratch)
|
||||
FROM scratch AS output
|
||||
- Contains only the final binary
|
||||
```
|
||||
|
||||
### Key Compilation Flags
|
||||
|
||||
**For nostr_core_lib:**
|
||||
```bash
|
||||
CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"
|
||||
```
|
||||
|
||||
**For c-relay:**
|
||||
```bash
|
||||
gcc -static -O2 -Wall -Wextra -std=c99 \
|
||||
-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \
|
||||
[source files] \
|
||||
-lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \
|
||||
-lcurl -lz -lpthread -lm -ldl
|
||||
```
|
||||
|
||||
### Fortification Issue
|
||||
|
||||
**Problem**: GCC's `-O2` optimization enables fortification by default, replacing standard functions with `__*_chk` variants (e.g., `__snprintf_chk`, `__fprintf_chk`). These are glibc-specific and don't exist in MUSL.
|
||||
|
||||
**Solution**: Explicitly disable fortification with:
|
||||
- `-U_FORTIFY_SOURCE` (undefine any existing definition)
|
||||
- `-D_FORTIFY_SOURCE=0` (set to 0)
|
||||
|
||||
This must be applied to **both** nostr_core_lib and c-relay compilation.
|
||||
|
||||
### NIP Dependencies
|
||||
|
||||
The build includes these NIPs in nostr_core_lib:
|
||||
- **NIP-001**: Basic protocol (event creation, signing)
|
||||
- **NIP-006**: Key derivation from mnemonic
|
||||
- **NIP-013**: Proof of Work validation
|
||||
- **NIP-017**: Private Direct Messages
|
||||
- **NIP-019**: Bech32 encoding (nsec/npub)
|
||||
- **NIP-044**: Modern encryption
|
||||
- **NIP-059**: Gift Wrap (required by NIP-017)
|
||||
|
||||
## Verification
|
||||
|
||||
### Check Binary Type
|
||||
|
||||
```bash
|
||||
# Should show "statically linked"
|
||||
file build/c_relay_static_musl_x86_64
|
||||
|
||||
# Should show "not a dynamic executable"
|
||||
ldd build/c_relay_static_musl_x86_64
|
||||
|
||||
# Check size (should be ~7.6MB)
|
||||
ls -lh build/c_relay_static_musl_x86_64
|
||||
```
|
||||
|
||||
### Test Execution
|
||||
|
||||
```bash
|
||||
# Show help
|
||||
./build/c_relay_static_musl_x86_64 --help
|
||||
|
||||
# Show version
|
||||
./build/c_relay_static_musl_x86_64 --version
|
||||
|
||||
# Run relay
|
||||
./build/c_relay_static_musl_x86_64 --port 8888
|
||||
```
|
||||
|
||||
### Cross-Distribution Testing
|
||||
|
||||
Test the binary on different distributions to verify portability:
|
||||
|
||||
```bash
|
||||
# Alpine Linux
|
||||
docker run --rm -v $(pwd)/build:/app alpine:latest /app/c_relay_static_musl_x86_64 --version
|
||||
|
||||
# Ubuntu
|
||||
docker run --rm -v $(pwd)/build:/app ubuntu:latest /app/c_relay_static_musl_x86_64 --version
|
||||
|
||||
# Debian
|
||||
docker run --rm -v $(pwd)/build:/app debian:latest /app/c_relay_static_musl_x86_64 --version
|
||||
|
||||
# CentOS
|
||||
docker run --rm -v $(pwd)/build:/app centos:latest /app/c_relay_static_musl_x86_64 --version
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Docker Permission Denied
|
||||
|
||||
**Problem**: `permission denied while trying to connect to the Docker daemon socket`
|
||||
|
||||
**Solution**: Add user to docker group:
|
||||
```bash
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker # Or logout and login again
|
||||
```
|
||||
|
||||
### Build Fails with Fortification Errors
|
||||
|
||||
**Problem**: `undefined reference to '__snprintf_chk'` or `'__fprintf_chk'`
|
||||
|
||||
**Solution**: Ensure fortification is disabled in both:
|
||||
1. nostr_core_lib build.sh (line 534)
|
||||
2. c-relay compilation flags in Dockerfile
|
||||
|
||||
### Binary Won't Execute
|
||||
|
||||
**Problem**: Binary fails to run on target system
|
||||
|
||||
**Checks**:
|
||||
1. Verify it's truly static: `ldd binary` should show "not a dynamic executable"
|
||||
2. Check architecture matches: `file binary` should show correct arch
|
||||
3. Ensure execute permissions: `chmod +x binary`
|
||||
|
||||
### Missing NIP Functions
|
||||
|
||||
**Problem**: `undefined reference to 'nostr_nip*'` during linking
|
||||
|
||||
**Solution**: Add missing NIPs to the build command:
|
||||
```bash
|
||||
./build.sh --nips=1,6,13,17,19,44,59
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Single Binary Deployment
|
||||
|
||||
```bash
|
||||
# Copy binary to server
|
||||
scp build/c_relay_static_musl_x86_64 user@server:/opt/c-relay/
|
||||
|
||||
# Run on server (no dependencies needed!)
|
||||
ssh user@server
|
||||
cd /opt/c-relay
|
||||
./c_relay_static_musl_x86_64 --port 8888
|
||||
```
|
||||
|
||||
### SystemD Service
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=C-Relay Nostr Relay (MUSL Static)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=c-relay
|
||||
WorkingDirectory=/opt/c-relay
|
||||
ExecStart=/opt/c-relay/c_relay_static_musl_x86_64
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## Performance Comparison
|
||||
|
||||
| Metric | MUSL Static | glibc Static | glibc Dynamic |
|
||||
|--------|-------------|--------------|---------------|
|
||||
| Binary Size | 7.6 MB | 12+ MB | 2-3 MB |
|
||||
| Startup Time | ~50ms | ~60ms | ~40ms |
|
||||
| Memory Usage | Similar | Similar | Similar |
|
||||
| Portability | ✓ Any Linux | ⚠ glibc only | ✗ Requires libs |
|
||||
| Dependencies | None | NSS libs | Many libs |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always verify** the binary is truly static before deployment
|
||||
2. **Test on multiple distributions** to ensure portability
|
||||
3. **Keep Docker images updated** for security patches
|
||||
4. **Document the build date** and commit hash for reproducibility
|
||||
5. **Store binaries** with architecture in filename (e.g., `_x86_64`, `_arm64`)
|
||||
|
||||
## References
|
||||
|
||||
- [MUSL libc](https://musl.libc.org/)
|
||||
- [Alpine Linux](https://alpinelinux.org/)
|
||||
- [Static Linking Best Practices](https://www.musl-libc.org/faq.html)
|
||||
- [GCC Fortification](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2025-10-11
|
||||
- Initial MUSL build system implementation
|
||||
- Alpine Docker-based build process
|
||||
- Fortification fix for nostr_core_lib
|
||||
- Complete NIP dependency resolution
|
||||
- Documentation created
|
||||
147
docs/static_build_improvements.md
Normal file
147
docs/static_build_improvements.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Static Build Improvements
|
||||
|
||||
## Overview
|
||||
|
||||
The `build_static.sh` script has been updated to properly support MUSL static compilation and includes several optimizations.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. True MUSL Static Binary Support
|
||||
|
||||
The script now attempts to build with `musl-gcc` for truly portable static binaries:
|
||||
|
||||
- **MUSL binaries** have zero runtime dependencies and work across all Linux distributions
|
||||
- **Automatic fallback** to glibc static linking if MUSL compilation fails (e.g., missing MUSL-compiled libraries)
|
||||
- Clear messaging about which type of binary was created
|
||||
|
||||
### 2. SQLite Build Caching
|
||||
|
||||
SQLite is now built once and cached for future builds:
|
||||
|
||||
- **Cache location**: `~/.cache/c-relay-sqlite/`
|
||||
- **Version-specific**: Each SQLite version gets its own cache directory
|
||||
- **Significant speedup**: Subsequent builds skip the SQLite compilation step
|
||||
- **Manual cleanup**: `rm -rf ~/.cache/c-relay-sqlite` to clear cache
|
||||
|
||||
### 3. Smart Package Installation
|
||||
|
||||
The script now checks for required packages before installing:
|
||||
|
||||
- Only installs missing packages
|
||||
- Reduces unnecessary `apt` operations
|
||||
- Faster builds when dependencies are already present
|
||||
|
||||
### 4. Bug Fixes
|
||||
|
||||
- Fixed format warning in `src/subscriptions.c` line 1067 (changed `%zu` to `%d` with cast for `MAX_SEARCH_TERM_LENGTH`)
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./build_static.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. Check for and install `musl-gcc` if needed
|
||||
2. Build or use cached SQLite with JSON1 support
|
||||
3. Attempt MUSL static compilation
|
||||
4. Fall back to glibc static compilation if MUSL fails
|
||||
5. Verify the resulting binary
|
||||
|
||||
## Binary Types
|
||||
|
||||
### MUSL Static Binary (Ideal - Currently Not Achievable)
|
||||
- **Filename**: `build/c_relay_static_musl_x86_64`
|
||||
- **Dependencies**: None (truly static)
|
||||
- **Portability**: Works on any Linux distribution
|
||||
- **Status**: Requires MUSL-compiled libwebsockets and other dependencies (not available by default)
|
||||
|
||||
### Glibc Static Binary (Current Output)
|
||||
- **Filename**: `build/c_relay_static_x86_64` or `build/c_relay_static_glibc_x86_64`
|
||||
- **Dependencies**: None - fully statically linked with glibc
|
||||
- **Portability**: Works on most Linux distributions (glibc is statically included)
|
||||
- **Note**: Despite using glibc, this is a **fully static binary** with no runtime dependencies
|
||||
|
||||
## Verification
|
||||
|
||||
The script automatically verifies binaries using `ldd` and `file`:
|
||||
|
||||
```bash
|
||||
# For MUSL binary
|
||||
ldd build/c_relay_static_musl_x86_64
|
||||
# Output: "not a dynamic executable" (good!)
|
||||
|
||||
# For glibc binary
|
||||
ldd build/c_relay_static_glibc_x86_64
|
||||
# Output: Shows glibc dependencies
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### MUSL Compilation Currently Fails Because:
|
||||
|
||||
1. **libwebsockets not available as MUSL static library**
|
||||
- System libwebsockets is compiled with glibc, not MUSL
|
||||
- MUSL cannot link against glibc-compiled libraries
|
||||
- Solution: Build libwebsockets from source with musl-gcc (future enhancement)
|
||||
|
||||
2. **Other dependencies not MUSL-compatible**
|
||||
- libssl, libcrypto, libsecp256k1, libcurl must be available as MUSL static libraries
|
||||
- Most systems only provide glibc versions
|
||||
- Solution: Build entire dependency chain with musl-gcc (complex, future enhancement)
|
||||
|
||||
### Current Behavior
|
||||
|
||||
The script attempts MUSL compilation but falls back to glibc:
|
||||
1. Tries to compile with `musl-gcc -static` (fails due to missing MUSL libraries)
|
||||
2. Logs the error to `/tmp/musl_build.log`
|
||||
3. Displays a clear warning message
|
||||
4. Automatically falls back to `gcc -static` with glibc
|
||||
5. Produces a **fully static binary** with glibc statically linked (no runtime dependencies)
|
||||
|
||||
**Important**: The glibc static binary is still fully portable across most Linux distributions because glibc is statically included in the binary. It's not as universally portable as MUSL would be, but it works on virtually all modern Linux systems.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Full MUSL dependency chain**: Build all dependencies (libwebsockets, OpenSSL, etc.) with musl-gcc
|
||||
2. **Multi-architecture support**: Add ARM64 MUSL builds
|
||||
3. **Docker-based builds**: Use Alpine Linux containers for guaranteed MUSL environment
|
||||
4. **Dependency vendoring**: Include pre-built MUSL libraries in the repository
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Clear SQLite Cache
|
||||
```bash
|
||||
rm -rf ~/.cache/c-relay-sqlite
|
||||
```
|
||||
|
||||
### Force Package Reinstall
|
||||
```bash
|
||||
sudo apt install --reinstall musl-dev musl-tools libssl-dev libcurl4-openssl-dev libsecp256k1-dev
|
||||
```
|
||||
|
||||
### Check Build Logs
|
||||
```bash
|
||||
cat /tmp/musl_build.log
|
||||
```
|
||||
|
||||
### Verify Binary Type
|
||||
```bash
|
||||
file build/c_relay_static_*
|
||||
ldd build/c_relay_static_* 2>&1
|
||||
```
|
||||
|
||||
## Performance Impact
|
||||
|
||||
- **First build**: ~2-3 minutes (includes SQLite compilation)
|
||||
- **Subsequent builds**: ~30-60 seconds (uses cached SQLite)
|
||||
- **Cache size**: ~10-15 MB per SQLite version
|
||||
|
||||
## Compatibility
|
||||
|
||||
The updated script is compatible with:
|
||||
- Ubuntu 20.04+
|
||||
- Debian 10+
|
||||
- Other Debian-based distributions with `apt` package manager
|
||||
|
||||
For other distributions, adjust package installation commands accordingly.
|
||||
@@ -6,6 +6,7 @@ Complete guide for deploying, configuring, and managing the C Nostr Relay with e
|
||||
|
||||
- [Quick Start](#quick-start)
|
||||
- [Installation](#installation)
|
||||
- [Web Admin Interface](#web-admin-interface)
|
||||
- [Configuration Management](#configuration-management)
|
||||
- [Administration](#administration)
|
||||
- [Monitoring](#monitoring)
|
||||
@@ -43,7 +44,8 @@ Admin Public Key: 68394d08ab87f936a42ff2deb15a84fbdfbe0996ee0eb20cda064aae67328
|
||||
### 3. Connect Clients
|
||||
Your relay is now available at:
|
||||
- **WebSocket**: `ws://localhost:8888`
|
||||
- **NIP-11 Info**: `http://localhost:8888`
|
||||
- **NIP-11 Info**: `http://localhost:8888` (with `Accept: application/nostr+json` header)
|
||||
- **Web Admin Interface**: `http://localhost:8888/api/` (serves embedded admin interface)
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -211,6 +213,38 @@ Send this to your relay via WebSocket, and changes are applied immediately.
|
||||
| `nip40_expiration_filter` | Filter expired events | "true" | "true", "false" |
|
||||
| `nip40_expiration_grace_period` | Grace period (seconds) | "300" | 0-86400 |
|
||||
|
||||
## Web Admin Interface
|
||||
|
||||
The relay includes a built-in web-based administration interface that provides a user-friendly way to manage your relay without command-line tools.
|
||||
|
||||
### Accessing the Interface
|
||||
|
||||
1. **Open your browser** and navigate to: `http://localhost:8888/api/`
|
||||
2. **Authenticate** using your Nostr identity (the admin interface uses NIP-42 authentication)
|
||||
3. **Manage configuration** through the web interface
|
||||
|
||||
### Features
|
||||
|
||||
- **Real-time Configuration**: View and edit all relay settings
|
||||
- **Database Statistics**: Monitor event counts, storage usage, and performance metrics
|
||||
- **Auth Rules Management**: Configure whitelist/blacklist rules for pubkeys
|
||||
- **Relay Connection Testing**: Verify WebSocket connectivity and NIP-11 information
|
||||
- **Event-Based Updates**: All changes are applied as signed Nostr events
|
||||
|
||||
### Security Notes
|
||||
|
||||
- The web interface requires NIP-42 authentication with your admin pubkey
|
||||
- All configuration changes are cryptographically signed
|
||||
- The interface serves embedded static files (no external dependencies)
|
||||
- CORS headers are included for proper browser operation
|
||||
|
||||
### Browser Compatibility
|
||||
|
||||
The admin interface works with modern browsers that support:
|
||||
- WebSocket connections
|
||||
- ES6 JavaScript features
|
||||
- Modern CSS Grid and Flexbox layouts
|
||||
|
||||
## Administration
|
||||
|
||||
### Viewing Current Configuration
|
||||
|
||||
140
docs/why_musl_fails.md
Normal file
140
docs/why_musl_fails.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Why MUSL Compilation Fails: Technical Explanation
|
||||
|
||||
## The Core Problem
|
||||
|
||||
**You cannot mix glibc headers/libraries with MUSL's C library.** They are fundamentally incompatible at the ABI (Application Binary Interface) level.
|
||||
|
||||
## What Happens When We Try
|
||||
|
||||
```bash
|
||||
musl-gcc -I/usr/include src/main.c -lwebsockets
|
||||
```
|
||||
|
||||
### Step-by-Step Breakdown:
|
||||
|
||||
1. **musl-gcc includes `<libwebsockets.h>`** from `/usr/include/libwebsockets.h`
|
||||
|
||||
2. **libwebsockets.h includes standard C headers:**
|
||||
```c
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
```
|
||||
|
||||
3. **The system provides glibc's version of these headers** (from `/usr/include/`)
|
||||
|
||||
4. **glibc's `<string.h>` includes glibc-specific internal headers:**
|
||||
```c
|
||||
#include <bits/libc-header-start.h>
|
||||
#include <bits/types.h>
|
||||
```
|
||||
|
||||
5. **MUSL doesn't have these `bits/` headers** - it has a completely different structure:
|
||||
- MUSL uses `/usr/include/x86_64-linux-musl/` for its headers
|
||||
- MUSL's headers are simpler and don't use the `bits/` subdirectory structure
|
||||
|
||||
6. **Compilation fails** with:
|
||||
```
|
||||
fatal error: bits/libc-header-start.h: No such file or directory
|
||||
```
|
||||
|
||||
## Why This Is Fundamental
|
||||
|
||||
### Different C Library Implementations
|
||||
|
||||
**glibc (GNU C Library):**
|
||||
- Complex, feature-rich implementation
|
||||
- Uses `bits/` subdirectories for platform-specific code
|
||||
- Larger binary size
|
||||
- More system-specific optimizations
|
||||
|
||||
**MUSL:**
|
||||
- Minimal, clean implementation
|
||||
- Simpler header structure
|
||||
- Smaller binary size
|
||||
- Designed for static linking and portability
|
||||
|
||||
### ABI Incompatibility
|
||||
|
||||
Even if headers compiled, the **Application Binary Interface (ABI)** is different:
|
||||
- Function calling conventions may differ
|
||||
- Structure layouts may differ
|
||||
- System call wrappers are implemented differently
|
||||
- Thread-local storage mechanisms differ
|
||||
|
||||
## The Solution: Build Everything with MUSL
|
||||
|
||||
To create a true MUSL static binary, you must:
|
||||
|
||||
### 1. Build libwebsockets with musl-gcc
|
||||
|
||||
```bash
|
||||
git clone https://github.com/warmcat/libwebsockets.git
|
||||
cd libwebsockets
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_C_COMPILER=musl-gcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLWS_WITH_STATIC=ON \
|
||||
-DLWS_WITH_SHARED=OFF \
|
||||
-DLWS_WITHOUT_TESTAPPS=ON
|
||||
make
|
||||
```
|
||||
|
||||
### 2. Build OpenSSL with MUSL
|
||||
|
||||
```bash
|
||||
wget https://www.openssl.org/source/openssl-3.0.0.tar.gz
|
||||
tar xzf openssl-3.0.0.tar.gz
|
||||
cd openssl-3.0.0
|
||||
CC=musl-gcc ./config no-shared --prefix=/opt/musl-openssl
|
||||
make && make install
|
||||
```
|
||||
|
||||
### 3. Build all other dependencies
|
||||
|
||||
- zlib with musl-gcc
|
||||
- libsecp256k1 with musl-gcc
|
||||
- libcurl with musl-gcc (which itself needs OpenSSL built with MUSL)
|
||||
|
||||
### 4. Build c-relay with all MUSL libraries
|
||||
|
||||
```bash
|
||||
musl-gcc -static \
|
||||
-I/opt/musl-libwebsockets/include \
|
||||
-I/opt/musl-openssl/include \
|
||||
src/*.c \
|
||||
-L/opt/musl-libwebsockets/lib -lwebsockets \
|
||||
-L/opt/musl-openssl/lib -lssl -lcrypto \
|
||||
...
|
||||
```
|
||||
|
||||
## Why We Use glibc Static Instead
|
||||
|
||||
Building the entire dependency chain with MUSL is:
|
||||
- **Time-consuming**: Hours to build all dependencies
|
||||
- **Complex**: Each library has its own build quirks
|
||||
- **Maintenance burden**: Must rebuild when dependencies update
|
||||
- **Unnecessary for most use cases**: glibc static binaries work fine
|
||||
|
||||
### glibc Static Binary Advantages:
|
||||
|
||||
✅ **Still fully static** - no runtime dependencies
|
||||
✅ **Works on virtually all Linux distributions**
|
||||
✅ **Much faster to build** - uses system libraries
|
||||
✅ **Easier to maintain** - no custom dependency builds
|
||||
✅ **Same practical portability** for modern Linux systems
|
||||
|
||||
### glibc Static Binary Limitations:
|
||||
|
||||
⚠️ **Slightly larger** than MUSL (glibc is bigger)
|
||||
⚠️ **May not work on very old systems** (ancient glibc versions)
|
||||
⚠️ **Not as universally portable** as MUSL (but close enough)
|
||||
|
||||
## Conclusion
|
||||
|
||||
**MUSL compilation fails because system libraries are compiled with glibc, and you cannot mix glibc and MUSL.**
|
||||
|
||||
The current approach (glibc static binary) is the pragmatic solution that provides excellent portability without the complexity of building an entire MUSL toolchain.
|
||||
|
||||
If true MUSL binaries are needed in the future, the solution is to use Alpine Linux (which uses MUSL natively) in a Docker container, where all system libraries are already MUSL-compiled.
|
||||
128
embed_web_files.sh
Executable file
128
embed_web_files.sh
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to embed web files into C headers for the C-Relay admin interface
|
||||
# Converts HTML, CSS, and JS files from api/ directory into C byte arrays
|
||||
|
||||
set -e
|
||||
|
||||
echo "Embedding web files into C headers..."
|
||||
|
||||
# Output directory for generated headers
|
||||
OUTPUT_DIR="src"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Function to convert a file to C byte array
|
||||
file_to_c_array() {
|
||||
local input_file="$1"
|
||||
local array_name="$2"
|
||||
local output_file="$3"
|
||||
|
||||
# Get file size
|
||||
local file_size=$(stat -c%s "$input_file" 2>/dev/null || stat -f%z "$input_file" 2>/dev/null || echo "0")
|
||||
|
||||
echo "// Auto-generated from $input_file" >> "$output_file"
|
||||
echo "static const unsigned char ${array_name}_data[] = {" >> "$output_file"
|
||||
|
||||
# Convert file to hex bytes
|
||||
hexdump -v -e '1/1 "0x%02x,"' "$input_file" >> "$output_file"
|
||||
|
||||
echo "};" >> "$output_file"
|
||||
echo "static const size_t ${array_name}_size = $file_size;" >> "$output_file"
|
||||
echo "" >> "$output_file"
|
||||
}
|
||||
|
||||
# Generate the header file
|
||||
HEADER_FILE="$OUTPUT_DIR/embedded_web_content.h"
|
||||
echo "// Auto-generated embedded web content header" > "$HEADER_FILE"
|
||||
echo "// Do not edit manually - generated by embed_web_files.sh" >> "$HEADER_FILE"
|
||||
echo "" >> "$HEADER_FILE"
|
||||
echo "#ifndef EMBEDDED_WEB_CONTENT_H" >> "$HEADER_FILE"
|
||||
echo "#define EMBEDDED_WEB_CONTENT_H" >> "$HEADER_FILE"
|
||||
echo "" >> "$HEADER_FILE"
|
||||
echo "#include <stddef.h>" >> "$HEADER_FILE"
|
||||
echo "" >> "$HEADER_FILE"
|
||||
|
||||
# Generate the C file
|
||||
SOURCE_FILE="$OUTPUT_DIR/embedded_web_content.c"
|
||||
echo "// Auto-generated embedded web content" > "$SOURCE_FILE"
|
||||
echo "// Do not edit manually - generated by embed_web_files.sh" >> "$SOURCE_FILE"
|
||||
echo "" >> "$SOURCE_FILE"
|
||||
echo "#include \"embedded_web_content.h\"" >> "$SOURCE_FILE"
|
||||
echo "#include <string.h>" >> "$SOURCE_FILE"
|
||||
echo "" >> "$SOURCE_FILE"
|
||||
|
||||
# Process each web file
|
||||
declare -A file_map
|
||||
|
||||
# Find all web files
|
||||
for file in api/*.html api/*.css api/*.js; do
|
||||
if [ -f "$file" ]; then
|
||||
# Get filename without path
|
||||
basename=$(basename "$file")
|
||||
|
||||
# Create C identifier from filename
|
||||
c_name=$(echo "$basename" | sed 's/[^a-zA-Z0-9_]/_/g' | sed 's/^_//')
|
||||
|
||||
# Determine content type
|
||||
case "$file" in
|
||||
*.html) content_type="text/html" ;;
|
||||
*.css) content_type="text/css" ;;
|
||||
*.js) content_type="application/javascript" ;;
|
||||
*) content_type="text/plain" ;;
|
||||
esac
|
||||
|
||||
echo "Processing $file -> ${c_name}"
|
||||
|
||||
# No extern declarations needed - data is accessed through get_embedded_file()
|
||||
|
||||
# Add to source
|
||||
file_to_c_array "$file" "$c_name" "$SOURCE_FILE"
|
||||
|
||||
# Store mapping for lookup function
|
||||
file_map["/$basename"]="$c_name:$content_type"
|
||||
if [ "$basename" = "index.html" ]; then
|
||||
file_map["/"]="$c_name:$content_type"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate lookup function
|
||||
echo "// Embedded file lookup function" >> "$HEADER_FILE"
|
||||
echo "typedef struct {" >> "$HEADER_FILE"
|
||||
echo " const char *path;" >> "$HEADER_FILE"
|
||||
echo " const unsigned char *data;" >> "$HEADER_FILE"
|
||||
echo " size_t size;" >> "$HEADER_FILE"
|
||||
echo " const char *content_type;" >> "$HEADER_FILE"
|
||||
echo "} embedded_file_t;" >> "$HEADER_FILE"
|
||||
echo "" >> "$HEADER_FILE"
|
||||
echo "embedded_file_t *get_embedded_file(const char *path);" >> "$HEADER_FILE"
|
||||
echo "" >> "$HEADER_FILE"
|
||||
echo "#endif // EMBEDDED_WEB_CONTENT_H" >> "$HEADER_FILE"
|
||||
|
||||
# Generate lookup function implementation
|
||||
echo "// File mapping" >> "$SOURCE_FILE"
|
||||
echo "static embedded_file_t embedded_files[] = {" >> "$SOURCE_FILE"
|
||||
|
||||
for path in "${!file_map[@]}"; do
|
||||
entry="${file_map[$path]}"
|
||||
c_name="${entry%:*}"
|
||||
content_type="${entry#*:}"
|
||||
echo " {\"$path\", ${c_name}_data, ${c_name}_size, \"$content_type\"}," >> "$SOURCE_FILE"
|
||||
done
|
||||
|
||||
echo " {NULL, NULL, 0, NULL} // Sentinel" >> "$SOURCE_FILE"
|
||||
echo "};" >> "$SOURCE_FILE"
|
||||
echo "" >> "$SOURCE_FILE"
|
||||
|
||||
echo "embedded_file_t *get_embedded_file(const char *path) {" >> "$SOURCE_FILE"
|
||||
echo " for (int i = 0; embedded_files[i].path != NULL; i++) {" >> "$SOURCE_FILE"
|
||||
echo " if (strcmp(path, embedded_files[i].path) == 0) {" >> "$SOURCE_FILE"
|
||||
echo " return &embedded_files[i];" >> "$SOURCE_FILE"
|
||||
echo " }" >> "$SOURCE_FILE"
|
||||
echo " }" >> "$SOURCE_FILE"
|
||||
echo " return NULL;" >> "$SOURCE_FILE"
|
||||
echo "}" >> "$SOURCE_FILE"
|
||||
|
||||
echo "Web file embedding complete. Generated:" >&2
|
||||
echo " $HEADER_FILE" >&2
|
||||
echo " $SOURCE_FILE" >&2
|
||||
150
examples/deployment/static-builder.Dockerfile
Normal file
150
examples/deployment/static-builder.Dockerfile
Normal file
@@ -0,0 +1,150 @@
|
||||
# MUSL-based fully static C-Relay builder
|
||||
# Produces portable binaries with zero runtime dependencies
|
||||
|
||||
FROM alpine:latest AS builder
|
||||
|
||||
# Add alternative mirrors and install build dependencies with retry
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/main" > /etc/apk/repositories && \
|
||||
echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/community" >> /etc/apk/repositories && \
|
||||
echo "http://mirror.leaseweb.com/alpine/v3.22/main" >> /etc/apk/repositories && \
|
||||
echo "http://mirror.leaseweb.com/alpine/v3.22/community" >> /etc/apk/repositories && \
|
||||
apk update --no-cache || (sleep 5 && apk update --no-cache) || (sleep 10 && apk update --no-cache)
|
||||
|
||||
# Install build dependencies with retry logic
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
musl-dev \
|
||||
git \
|
||||
cmake \
|
||||
pkgconfig \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
openssl-dev \
|
||||
openssl-libs-static \
|
||||
zlib-dev \
|
||||
zlib-static \
|
||||
curl-dev \
|
||||
curl-static \
|
||||
sqlite-dev \
|
||||
sqlite-static \
|
||||
linux-headers || \
|
||||
(sleep 10 && apk add --no-cache \
|
||||
build-base \
|
||||
musl-dev \
|
||||
git \
|
||||
cmake \
|
||||
pkgconfig \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
openssl-dev \
|
||||
openssl-libs-static \
|
||||
zlib-dev \
|
||||
zlib-static \
|
||||
curl-dev \
|
||||
curl-static \
|
||||
sqlite-dev \
|
||||
sqlite-static \
|
||||
linux-headers)
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /build
|
||||
|
||||
# Build zlib static (if needed)
|
||||
RUN if [ ! -f /usr/lib/libz.a ]; then \
|
||||
cd /tmp && \
|
||||
wget https://zlib.net/zlib-1.3.1.tar.gz && \
|
||||
tar xzf zlib-1.3.1.tar.gz && \
|
||||
cd zlib-1.3.1 && \
|
||||
./configure --static --prefix=/usr && \
|
||||
make && make install; \
|
||||
fi
|
||||
|
||||
# Build OpenSSL static
|
||||
RUN cd /tmp && \
|
||||
wget https://www.openssl.org/source/openssl-3.0.13.tar.gz && \
|
||||
tar xzf openssl-3.0.13.tar.gz && \
|
||||
cd openssl-3.0.13 && \
|
||||
./Configure linux-x86_64 no-shared --prefix=/usr && \
|
||||
make && make install_sw
|
||||
|
||||
# Build SQLite with JSON1 extension enabled
|
||||
RUN cd /tmp && \
|
||||
wget https://www.sqlite.org/2024/sqlite-autoconf-3460000.tar.gz && \
|
||||
tar xzf sqlite-autoconf-3460000.tar.gz && \
|
||||
cd sqlite-autoconf-3460000 && \
|
||||
./configure \
|
||||
--enable-static \
|
||||
--disable-shared \
|
||||
--enable-json1 \
|
||||
--enable-fts5 \
|
||||
--prefix=/usr \
|
||||
CFLAGS="-DSQLITE_ENABLE_JSON1=1 -DSQLITE_ENABLE_FTS5=1" && \
|
||||
make && make install
|
||||
|
||||
# Build libsecp256k1 static
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/bitcoin-core/secp256k1.git && \
|
||||
cd secp256k1 && \
|
||||
./autogen.sh && \
|
||||
./configure --enable-static --disable-shared --prefix=/usr && \
|
||||
make && make install
|
||||
|
||||
# Build libwebsockets static with OpenSSL
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/warmcat/libwebsockets.git && \
|
||||
cd libwebsockets && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. \
|
||||
-DLWS_WITH_STATIC=ON \
|
||||
-DLWS_WITH_SHARED=OFF \
|
||||
-DLWS_WITH_SSL=ON \
|
||||
-DLWS_OPENSSL_LIBRARIES="/usr/lib/libssl.a;/usr/lib/libcrypto.a" \
|
||||
-DLWS_OPENSSL_INCLUDE_DIRS="/usr/include" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr && \
|
||||
make && make install
|
||||
|
||||
# Build curl static (minimal features)
|
||||
RUN cd /tmp && \
|
||||
wget https://curl.se/download/curl-8.6.0.tar.gz && \
|
||||
tar xzf curl-8.6.0.tar.gz && \
|
||||
cd curl-8.6.0 && \
|
||||
./configure \
|
||||
--disable-shared \
|
||||
--enable-static \
|
||||
--disable-ldap \
|
||||
--without-libidn2 \
|
||||
--without-brotli \
|
||||
--without-zstd \
|
||||
--without-rtmp \
|
||||
--without-libpsl \
|
||||
--without-krb5 \
|
||||
--with-openssl \
|
||||
--prefix=/usr && \
|
||||
make && make install
|
||||
|
||||
# Copy c-relay source
|
||||
COPY . /build/
|
||||
|
||||
# Initialize submodules
|
||||
RUN git submodule update --init --recursive
|
||||
|
||||
# Build nostr_core_lib
|
||||
RUN cd nostr_core_lib && ./build.sh
|
||||
|
||||
# Build c-relay static
|
||||
RUN make clean && \
|
||||
CC="musl-gcc -static" \
|
||||
CFLAGS="-O2 -Wall -Wextra -std=c99 -g" \
|
||||
LDFLAGS="-static -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" \
|
||||
LIBS="-lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -lsecp256k1 -lssl -lcrypto -lcurl" \
|
||||
make
|
||||
|
||||
# Strip binary for size
|
||||
RUN strip build/c_relay_x86
|
||||
|
||||
# Multi-stage build to produce minimal output
|
||||
FROM scratch AS output
|
||||
COPY --from=builder /build/build/c_relay_x86 /c_relay_static_musl_x86_64
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# get_settings.sh - Query relay configuration events using nak
|
||||
# Uses admin test key to query kind 33334 configuration events
|
||||
|
||||
# Test key configuration
|
||||
ADMIN_PRIVATE_KEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
ADMIN_PUBLIC_KEY="6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3"
|
||||
RELAY_PUBLIC_KEY="4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"
|
||||
RELAY_URL="ws://localhost:8888"
|
||||
|
||||
echo "Querying configuration events (kind 33334) from relay at $RELAY_URL"
|
||||
echo "Using admin public key: $ADMIN_PUBLIC_KEY"
|
||||
echo "Looking for relay config: $RELAY_PUBLIC_KEY"
|
||||
echo ""
|
||||
|
||||
# Query for kind 33334 configuration events
|
||||
# These events contain the relay configuration with d-tag matching the relay pubkey
|
||||
nak req -k 33334 "$RELAY_URL" | jq .
|
||||
@@ -63,7 +63,7 @@ while [[ $# -gt 0 ]]; do
|
||||
shift 2
|
||||
fi
|
||||
;;
|
||||
--preserve-database)
|
||||
-d|--preserve-database)
|
||||
PRESERVE_DATABASE=true
|
||||
shift
|
||||
;;
|
||||
@@ -137,38 +137,34 @@ fi
|
||||
|
||||
# Handle database file cleanup for fresh start
|
||||
if [ "$PRESERVE_DATABASE" = false ]; then
|
||||
if ls *.db >/dev/null 2>&1 || ls build/*.db >/dev/null 2>&1; then
|
||||
echo "Removing existing database files to trigger fresh key generation..."
|
||||
rm -f *.db build/*.db
|
||||
if ls *.db* >/dev/null 2>&1 || ls build/*.db* >/dev/null 2>&1; then
|
||||
echo "Removing existing database files (including WAL/SHM) to trigger fresh key generation..."
|
||||
rm -f *.db* build/*.db*
|
||||
echo "✓ Database files removed - will generate new keys and database"
|
||||
else
|
||||
echo "No existing database found - will generate fresh setup"
|
||||
fi
|
||||
else
|
||||
echo "Preserving existing database files as requested"
|
||||
# Back up database files before clean build
|
||||
if ls build/*.db >/dev/null 2>&1; then
|
||||
echo "Backing up existing database files..."
|
||||
mkdir -p /tmp/relay_backup_$$
|
||||
cp build/*.db* /tmp/relay_backup_$$/ 2>/dev/null || true
|
||||
echo "Database files backed up to temporary location"
|
||||
fi
|
||||
echo "Preserving existing database files (build process does not touch database files)"
|
||||
fi
|
||||
|
||||
# Clean up legacy files that are no longer used
|
||||
rm -rf dev-config/ 2>/dev/null
|
||||
rm -f db/c_nostr_relay.db* 2>/dev/null
|
||||
|
||||
# Build the project first
|
||||
echo "Building project..."
|
||||
make clean all
|
||||
# Embed web files into C headers before building
|
||||
echo "Embedding web files..."
|
||||
./embed_web_files.sh
|
||||
|
||||
# Restore database files if preserving
|
||||
if [ "$PRESERVE_DATABASE" = true ] && [ -d "/tmp/relay_backup_$$" ]; then
|
||||
echo "Restoring preserved database files..."
|
||||
cp /tmp/relay_backup_$$/*.db* build/ 2>/dev/null || true
|
||||
rm -rf /tmp/relay_backup_$$
|
||||
echo "Database files restored to build directory"
|
||||
# Build the project - ONLY static build
|
||||
echo "Building project (static binary with SQLite JSON1 extension)..."
|
||||
./build_static.sh
|
||||
|
||||
# Exit if static build fails - no fallback
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: Static build failed. Cannot proceed without static binary."
|
||||
echo "Please fix the build errors and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if build was successful
|
||||
@@ -177,25 +173,32 @@ if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if relay binary exists after build - detect architecture
|
||||
# Check if static relay binary exists after build - ONLY use static binary
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64)
|
||||
BINARY_PATH="./build/c_relay_x86"
|
||||
BINARY_PATH="./build/c_relay_static_x86_64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
BINARY_PATH="./build/c_relay_arm64"
|
||||
BINARY_PATH="./build/c_relay_static_arm64"
|
||||
;;
|
||||
*)
|
||||
BINARY_PATH="./build/c_relay_$ARCH"
|
||||
BINARY_PATH="./build/c_relay_static_$ARCH"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Verify static binary exists - no fallbacks
|
||||
if [ ! -f "$BINARY_PATH" ]; then
|
||||
echo "ERROR: Relay binary not found at $BINARY_PATH after build. Build may have failed."
|
||||
echo "ERROR: Static relay binary not found: $BINARY_PATH"
|
||||
echo ""
|
||||
echo "The relay requires the static binary with JSON1 support."
|
||||
echo "Please run: ./build_static.sh"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using static binary: $BINARY_PATH"
|
||||
|
||||
echo "Build successful. Proceeding with relay restart..."
|
||||
|
||||
# Kill existing relay if running - start aggressive immediately
|
||||
@@ -282,14 +285,14 @@ cd build
|
||||
# Start relay in background and capture its PID
|
||||
if [ "$USE_TEST_KEYS" = true ]; then
|
||||
echo "Using deterministic test keys for development..."
|
||||
./$(basename $BINARY_PATH) -a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -r 1111111111111111111111111111111111111111111111111111111111111111 > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) -a 6a04ab98d9e4774ad806e302dddeb63bea16b5cb5f223ee77478e861bb583eb3 -r 1111111111111111111111111111111111111111111111111111111111111111 --strict-port > ../relay.log 2>&1 &
|
||||
elif [ -n "$RELAY_ARGS" ]; then
|
||||
echo "Starting relay with custom configuration..."
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) $RELAY_ARGS --strict-port > ../relay.log 2>&1 &
|
||||
else
|
||||
# No command line arguments needed for random key generation
|
||||
echo "Starting relay with random key generation..."
|
||||
./$(basename $BINARY_PATH) > ../relay.log 2>&1 &
|
||||
./$(basename $BINARY_PATH) --strict-port > ../relay.log 2>&1 &
|
||||
fi
|
||||
RELAY_PID=$!
|
||||
# Change back to original directory
|
||||
|
||||
3
nip_11_curl.sh
Executable file
3
nip_11_curl.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
curl -H "Accept: application/nostr+json" http://localhost:8888/
|
||||
153
node_modules/.package-lock.json
generated
vendored
Normal file
153
node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"name": "c-relay",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@noble/ciphers": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz",
|
||||
"integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
|
||||
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/curves/node_modules/@noble/hashes": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
|
||||
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
|
||||
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||
"integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@scure/bip32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz",
|
||||
"integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/curves": "~1.1.0",
|
||||
"@noble/hashes": "~1.3.1",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip32/node_modules/@noble/curves": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
|
||||
"integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip39": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz",
|
||||
"integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "~1.3.0",
|
||||
"@scure/base": "~1.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-tools": {
|
||||
"version": "2.17.0",
|
||||
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.17.0.tgz",
|
||||
"integrity": "sha512-lrvHM7cSaGhz7F0YuBvgHMoU2s8/KuThihDoOYk8w5gpVHTy0DeUCAgCN8uLGeuSl5MAWekJr9Dkfo5HClqO9w==",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@noble/ciphers": "^0.5.1",
|
||||
"@noble/curves": "1.2.0",
|
||||
"@noble/hashes": "1.3.1",
|
||||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.3.1",
|
||||
"@scure/bip39": "1.2.1",
|
||||
"nostr-wasm": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nostr-wasm": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz",
|
||||
"integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
node_modules/@noble/ciphers/LICENSE
generated
vendored
Normal file
22
node_modules/@noble/ciphers/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 Paul Miller (https://paulmillr.com)
|
||||
Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
655
node_modules/@noble/ciphers/README.md
generated
vendored
Normal file
655
node_modules/@noble/ciphers/README.md
generated
vendored
Normal file
@@ -0,0 +1,655 @@
|
||||
# noble-ciphers
|
||||
|
||||
Auditable & minimal JS implementation of Salsa20, ChaCha and AES.
|
||||
|
||||
- 🔒 Auditable
|
||||
- 🔻 Tree-shaking-friendly: use only what's necessary, other code won't be included
|
||||
- 🏎 [Ultra-fast](#speed), hand-optimized for caveats of JS engines
|
||||
- 🔍 Unique tests ensure correctness: property-based, cross-library and Wycheproof vectors
|
||||
- 💼 AES: ECB, CBC, CTR, CFB, GCM, SIV (nonce misuse-resistant)
|
||||
- 💃 Salsa20, ChaCha, XSalsa20, XChaCha, Poly1305, ChaCha8, ChaCha12
|
||||
- 🥈 Two AES implementations: choose between friendly webcrypto wrapper and pure JS one
|
||||
- 🪶 45KB (8KB gzipped) for everything, 10KB (3KB gzipped) for ChaCha build
|
||||
|
||||
For discussions, questions and support, visit
|
||||
[GitHub Discussions](https://github.com/paulmillr/noble-ciphers/discussions)
|
||||
section of the repository.
|
||||
|
||||
### This library belongs to _noble_ cryptography
|
||||
|
||||
> **noble cryptography** — high-security, easily auditable set of contained cryptographic libraries and tools.
|
||||
|
||||
- Zero or minimal dependencies
|
||||
- Highly readable TypeScript / JS code
|
||||
- PGP-signed releases and transparent NPM builds
|
||||
- All libraries:
|
||||
[ciphers](https://github.com/paulmillr/noble-ciphers),
|
||||
[curves](https://github.com/paulmillr/noble-curves),
|
||||
[hashes](https://github.com/paulmillr/noble-hashes),
|
||||
[post-quantum](https://github.com/paulmillr/noble-post-quantum),
|
||||
4kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) /
|
||||
[ed25519](https://github.com/paulmillr/noble-ed25519)
|
||||
- [Check out homepage](https://paulmillr.com/noble/)
|
||||
for reading resources, documentation and apps built with noble
|
||||
|
||||
## Usage
|
||||
|
||||
> npm install @noble/ciphers
|
||||
|
||||
We support all major platforms and runtimes.
|
||||
For [Deno](https://deno.land), ensure to use
|
||||
[npm specifier](https://deno.land/manual@v1.28.0/node/npm_specifiers).
|
||||
For React Native, you may need a
|
||||
[polyfill for getRandomValues](https://github.com/LinusU/react-native-get-random-values).
|
||||
A standalone file
|
||||
[noble-ciphers.js](https://github.com/paulmillr/noble-ciphers/releases) is also available.
|
||||
|
||||
```js
|
||||
// import * from '@noble/ciphers'; // Error: use sub-imports, to ensure small app size
|
||||
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
|
||||
// import { xchacha20poly1305 } from 'npm:@noble/ciphers@0.5.0/chacha'; // Deno
|
||||
```
|
||||
|
||||
- [Examples](#examples)
|
||||
- [Encrypt with XChaCha20-Poly1305](#encrypt-with-xchacha20-poly1305)
|
||||
- [Encrypt with AES-256-GCM](#encrypt-with-aes-256-gcm)
|
||||
- [Use existing key instead of a new one](#use-existing-key-instead-of-a-new-one)
|
||||
- [Encrypt without nonce](#encrypt-without-nonce)
|
||||
- [Use same array for input and output](#use-same-array-for-input-and-output)
|
||||
- [All imports](#all-imports)
|
||||
- [Implementations](#implementations)
|
||||
- [Salsa20](#salsa)
|
||||
- [ChaCha](#chacha)
|
||||
- [AES](#aes)
|
||||
- [Webcrypto AES](#webcrypto-aes)
|
||||
- [Poly1305, GHash, Polyval](#poly1305-ghash-polyval)
|
||||
- [FF1 format-preserving encryption](#ff1)
|
||||
- [Managed nonces](#managed-nonces)
|
||||
- [Guidance](#guidance)
|
||||
- [Which cipher should I pick?](#which-cipher-should-i-pick)
|
||||
- [How to encrypt properly](#how-to-encrypt-properly)
|
||||
- [Nonces](#nonces)
|
||||
- [Encryption limits](#encryption-limits)
|
||||
- [AES internals and block modes](#aes-internals-and-block-modes)
|
||||
- [Security](#security)
|
||||
- [Speed](#speed)
|
||||
- [Upgrading](#upgrading)
|
||||
- [Contributing & testing](#contributing--testing)
|
||||
- [Resources](#resources)
|
||||
|
||||
## Examples
|
||||
|
||||
#### Encrypt with XChaCha20-Poly1305
|
||||
|
||||
```js
|
||||
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
|
||||
import { utf8ToBytes } from '@noble/ciphers/utils';
|
||||
import { randomBytes } from '@noble/ciphers/webcrypto';
|
||||
const key = randomBytes(32);
|
||||
const nonce = randomBytes(24);
|
||||
const chacha = xchacha20poly1305(key, nonce);
|
||||
const data = utf8ToBytes('hello, noble');
|
||||
const ciphertext = chacha.encrypt(data);
|
||||
const data_ = chacha.decrypt(ciphertext); // utils.bytesToUtf8(data_) === data
|
||||
```
|
||||
|
||||
#### Encrypt with AES-256-GCM
|
||||
|
||||
```js
|
||||
import { gcm } from '@noble/ciphers/aes';
|
||||
import { utf8ToBytes } from '@noble/ciphers/utils';
|
||||
import { randomBytes } from '@noble/ciphers/webcrypto';
|
||||
const key = randomBytes(32);
|
||||
const nonce = randomBytes(24);
|
||||
const aes = gcm(key, nonce);
|
||||
const data = utf8ToBytes('hello, noble');
|
||||
const ciphertext = aes.encrypt(data);
|
||||
const data_ = aes.decrypt(ciphertext); // utils.bytesToUtf8(data_) === data
|
||||
```
|
||||
|
||||
#### Use existing key instead of a new one
|
||||
|
||||
```js
|
||||
const key = new Uint8Array([
|
||||
169, 88, 160, 139, 168, 29, 147, 196, 14, 88, 237, 76, 243, 177, 109, 140, 195, 140, 80, 10, 216,
|
||||
134, 215, 71, 191, 48, 20, 104, 189, 37, 38, 55,
|
||||
]);
|
||||
const nonce = new Uint8Array([
|
||||
180, 90, 27, 63, 160, 191, 150, 33, 67, 212, 86, 71, 144, 6, 200, 102, 218, 32, 23, 147, 8, 41,
|
||||
147, 11,
|
||||
]);
|
||||
// or, hex:
|
||||
import { hexToBytes } from '@noble/ciphers/utils';
|
||||
const key2 = hexToBytes('4b7f89bac90a1086fef73f5da2cbe93b2fae9dfbf7678ae1f3e75fd118ddf999');
|
||||
const nonce2 = hexToBytes('9610467513de0bbd7c4cc2c3c64069f1802086fbd3232b13');
|
||||
```
|
||||
|
||||
#### Encrypt without nonce
|
||||
|
||||
```js
|
||||
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
|
||||
import { managedNonce } from '@noble/ciphers/webcrypto';
|
||||
import { hexToBytes, utf8ToBytes } from '@noble/ciphers/utils';
|
||||
const key = hexToBytes('fa686bfdffd3758f6377abbc23bf3d9bdc1a0dda4a6e7f8dbdd579fa1ff6d7e1');
|
||||
const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
|
||||
const data = utf8ToBytes('hello, noble');
|
||||
const ciphertext = chacha.encrypt(data);
|
||||
const data_ = chacha.decrypt(ciphertext);
|
||||
```
|
||||
|
||||
#### Use same array for input and output
|
||||
|
||||
```js
|
||||
import { chacha20poly1305 } from '@noble/ciphers/chacha';
|
||||
import { utf8ToBytes } from '@noble/ciphers/utils';
|
||||
import { randomBytes } from '@noble/ciphers/webcrypto';
|
||||
|
||||
const key = randomBytes(32);
|
||||
const nonce = randomBytes(12);
|
||||
const buf = new Uint8Array(12 + 16);
|
||||
const _data = utf8ToBytes('hello, noble');
|
||||
buf.set(_data, 0); // first 12 bytes
|
||||
const _12b = buf.subarray(0, 12);
|
||||
|
||||
const chacha = chacha20poly1305(key, nonce);
|
||||
chacha.encrypt(_12b, buf);
|
||||
chacha.decrypt(buf, _12b); // _12b now same as _data
|
||||
```
|
||||
|
||||
#### All imports
|
||||
|
||||
```js
|
||||
import { gcm, siv } from '@noble/ciphers/aes';
|
||||
import { xsalsa20poly1305 } from '@noble/ciphers/salsa';
|
||||
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
|
||||
|
||||
// Unauthenticated encryption: make sure to use HMAC or similar
|
||||
import { ctr, cfb, cbc, ecb } from '@noble/ciphers/aes';
|
||||
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa';
|
||||
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha';
|
||||
|
||||
// Utilities
|
||||
import { bytesToHex, hexToBytes, bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils';
|
||||
import { managedNonce, randomBytes } from '@noble/ciphers/webcrypto';
|
||||
```
|
||||
|
||||
## Implementations
|
||||
|
||||
### Salsa
|
||||
|
||||
```js
|
||||
import { xsalsa20poly1305 } from '@noble/ciphers/salsa';
|
||||
import { secretbox } from '@noble/ciphers/salsa'; // == xsalsa20poly1305
|
||||
import { salsa20, xsalsa20 } from '@noble/ciphers/salsa';
|
||||
```
|
||||
|
||||
[Salsa20](https://cr.yp.to/snuffle.html) stream cipher was released in 2005.
|
||||
Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
|
||||
which are hard to implement in a constant-time manner.
|
||||
Salsa20 is usually faster than AES, a big deal on slow, budget mobile phones.
|
||||
|
||||
[XSalsa20](https://cr.yp.to/snuffle/xsalsa-20110204.pdf), extended-nonce
|
||||
variant was released in 2008. It switched nonces from 96-bit to 192-bit,
|
||||
and became safe to be picked at random.
|
||||
|
||||
Nacl / Libsodium popularized term "secretbox", a simple black-box
|
||||
authenticated encryption. Secretbox is just xsalsa20-poly1305. We provide the
|
||||
alias and corresponding seal / open methods. We don't provide "box" or "sealedbox".
|
||||
|
||||
Check out [PDF](https://cr.yp.to/snuffle/salsafamily-20071225.pdf) and
|
||||
[wiki](https://en.wikipedia.org/wiki/Salsa20).
|
||||
|
||||
### ChaCha
|
||||
|
||||
```js
|
||||
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
|
||||
import { chacha20, xchacha20, chacha8, chacha12 } from '@noble/ciphers/chacha';
|
||||
```
|
||||
|
||||
[ChaCha20](https://cr.yp.to/chacha.html) stream cipher was released
|
||||
in 2008. ChaCha aims to increase the diffusion per round, but had slightly less
|
||||
cryptanalysis. It was standardized in
|
||||
[RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439) and is now used in TLS 1.3.
|
||||
|
||||
[XChaCha20](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha)
|
||||
extended-nonce variant is also provided. Similar to XSalsa, it's safe to use with
|
||||
randomly-generated nonces.
|
||||
|
||||
Check out [PDF](http://cr.yp.to/chacha/chacha-20080128.pdf) and [wiki](https://en.wikipedia.org/wiki/Salsa20).
|
||||
|
||||
### AES
|
||||
|
||||
```js
|
||||
import { gcm, siv, ctr, cfb, cbc, ecb } from '@noble/ciphers/aes';
|
||||
import { randomBytes } from '@noble/ciphers/webcrypto';
|
||||
const plaintext = new Uint8Array(32).fill(16);
|
||||
const key = randomBytes(32); // 24 for AES-192, 16 for AES-128
|
||||
for (let cipher of [gcm, siv]) {
|
||||
const stream = cipher(key, randomBytes(12));
|
||||
const ciphertext_ = stream.encrypt(plaintext);
|
||||
const plaintext_ = stream.decrypt(ciphertext_);
|
||||
}
|
||||
for (const cipher of [ctr, cbc, cbc]) {
|
||||
const stream = cipher(key, randomBytes(16));
|
||||
const ciphertext_ = stream.encrypt(plaintext);
|
||||
const plaintext_ = stream.decrypt(ciphertext_);
|
||||
}
|
||||
for (const cipher of [ecb]) {
|
||||
const stream = cipher(key);
|
||||
const ciphertext_ = stream.encrypt(plaintext);
|
||||
const plaintext_ = stream.decrypt(ciphertext_);
|
||||
}
|
||||
```
|
||||
|
||||
[AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard)
|
||||
is a variant of Rijndael block cipher, standardized by NIST in 2001.
|
||||
We provide the fastest available pure JS implementation.
|
||||
|
||||
We support AES-128, AES-192 and AES-256: the mode is selected dynamically,
|
||||
based on key length (16, 24, 32).
|
||||
|
||||
[AES-GCM-SIV](https://en.wikipedia.org/wiki/AES-GCM-SIV)
|
||||
nonce-misuse-resistant mode is also provided. It's recommended to use it,
|
||||
to prevent catastrophic consequences of nonce reuse. Our implementation of SIV
|
||||
has the same speed as GCM: there is no performance hit.
|
||||
|
||||
Check out [AES internals and block modes](#aes-internals-and-block-modes).
|
||||
|
||||
### Webcrypto AES
|
||||
|
||||
```js
|
||||
import { gcm, ctr, cbc, randomBytes } from '@noble/ciphers/webcrypto';
|
||||
const plaintext = new Uint8Array(32).fill(16);
|
||||
const key = randomBytes(32);
|
||||
for (const cipher of [gcm]) {
|
||||
const stream = cipher(key, randomBytes(12));
|
||||
const ciphertext_ = await stream.encrypt(plaintext);
|
||||
const plaintext_ = await stream.decrypt(ciphertext_);
|
||||
}
|
||||
for (const cipher of [ctr, cbc]) {
|
||||
const stream = cipher(key, randomBytes(16));
|
||||
const ciphertext_ = await stream.encrypt(plaintext);
|
||||
const plaintext_ = await stream.decrypt(ciphertext_);
|
||||
}
|
||||
```
|
||||
|
||||
We also have a separate wrapper over WebCrypto built-in.
|
||||
|
||||
It's the same as using `crypto.subtle`, but with massively simplified API.
|
||||
|
||||
Unlike pure js version, it's asynchronous.
|
||||
|
||||
### Poly1305, GHash, Polyval
|
||||
|
||||
```js
|
||||
import { poly1305 } from '@noble/ciphers/_poly1305';
|
||||
import { ghash, polyval } from '@noble/ciphers/_polyval';
|
||||
```
|
||||
|
||||
We expose polynomial-evaluation MACs: [Poly1305](https://cr.yp.to/mac.html),
|
||||
AES-GCM's [GHash](https://en.wikipedia.org/wiki/Galois/Counter_Mode) and
|
||||
AES-SIV's [Polyval](https://en.wikipedia.org/wiki/AES-GCM-SIV).
|
||||
|
||||
Poly1305 ([PDF](https://cr.yp.to/mac/poly1305-20050329.pdf),
|
||||
[wiki](https://en.wikipedia.org/wiki/Poly1305))
|
||||
is a fast and parallel secret-key message-authentication code suitable for
|
||||
a wide variety of applications. It was standardized in
|
||||
[RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439) and is now used in TLS 1.3.
|
||||
|
||||
Polynomial MACs are not perfect for every situation:
|
||||
they lack Random Key Robustness: the MAC can be forged, and can't
|
||||
be used in PAKE schemes. See
|
||||
[invisible salamanders attack](https://keymaterial.net/2020/09/07/invisible-salamanders-in-aes-gcm-siv/).
|
||||
To combat invisible salamanders, `hash(key)` can be included in ciphertext,
|
||||
however, this would violate ciphertext indistinguishability:
|
||||
an attacker would know which key was used - so `HKDF(key, i)`
|
||||
could be used instead.
|
||||
|
||||
### FF1
|
||||
|
||||
Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
|
||||
[See more info](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf).
|
||||
|
||||
### Managed nonces
|
||||
|
||||
```js
|
||||
import { managedNonce } from '@noble/ciphers/webcrypto';
|
||||
import { gcm, siv, ctr, cbc, cbc, ecb } from '@noble/ciphers/aes';
|
||||
import { xsalsa20poly1305 } from '@noble/ciphers/salsa';
|
||||
import { chacha20poly1305, xchacha20poly1305 } from '@noble/ciphers/chacha';
|
||||
|
||||
const wgcm = managedNonce(gcm);
|
||||
const wsiv = managedNonce(siv);
|
||||
const wcbc = managedNonce(cbc);
|
||||
const wctr = managedNonce(ctr);
|
||||
const wsalsapoly = managedNonce(xsalsa20poly1305);
|
||||
const wchacha = managedNonce(chacha20poly1305);
|
||||
const wxchacha = managedNonce(xchacha20poly1305);
|
||||
|
||||
// Now:
|
||||
const encrypted = wgcm(key).encrypt(data); // no nonces
|
||||
```
|
||||
|
||||
We provide API that manages nonce internally instead of exposing them to library's user.
|
||||
|
||||
For `encrypt`, a `nonceBytes`-length buffer is fetched from CSPRNG and prenended to encrypted ciphertext.
|
||||
|
||||
For `decrypt`, first `nonceBytes` of ciphertext are treated as nonce.
|
||||
|
||||
## Guidance
|
||||
|
||||
### Which cipher should I pick?
|
||||
|
||||
XChaCha20-Poly1305 is the safest bet these days.
|
||||
AES-GCM-SIV is the second safest.
|
||||
AES-GCM is the third.
|
||||
|
||||
### How to encrypt properly
|
||||
|
||||
- Use unpredictable key with enough entropy
|
||||
- Random key must be using cryptographically secure random number generator (CSPRNG), not `Math.random` etc.
|
||||
- Non-random key generated from KDF is fine
|
||||
- Re-using key is fine, but be aware of rules for cryptographic key wear-out and [encryption limits](#encryption-limits)
|
||||
- Use new nonce every time and [don't repeat it](#nonces)
|
||||
- chacha and salsa20 are fine for sequential counters that _never_ repeat: `01, 02...`
|
||||
- xchacha and xsalsa20 should be used for random nonces instead
|
||||
- Prefer authenticated encryption (AEAD)
|
||||
- HMAC+ChaCha / HMAC+AES / chacha20poly1305 / aes-gcm is good
|
||||
- chacha20 without poly1305 or hmac / aes-ctr / aes-cbc is bad
|
||||
- Flipping bits or ciphertext substitution won't be detected in unauthenticated ciphers
|
||||
- Don't re-use keys between different protocols
|
||||
- For example, using secp256k1 key in AES is bad
|
||||
- Use hkdf or, at least, a hash function to create sub-key instead
|
||||
|
||||
### Nonces
|
||||
|
||||
Most ciphers need a key and a nonce (aka initialization vector / IV) to encrypt a data:
|
||||
|
||||
ciphertext = encrypt(plaintext, key, nonce)
|
||||
|
||||
Repeating (key, nonce) pair with different plaintexts would allow an attacker to decrypt it:
|
||||
|
||||
ciphertext_a = encrypt(plaintext_a, key, nonce)
|
||||
ciphertext_b = encrypt(plaintext_b, key, nonce)
|
||||
stream_diff = xor(ciphertext_a, ciphertext_b) # Break encryption
|
||||
|
||||
So, you can't repeat nonces. One way of doing so is using counters:
|
||||
|
||||
for i in 0..:
|
||||
ciphertext[i] = encrypt(plaintexts[i], key, i)
|
||||
|
||||
Another is generating random nonce every time:
|
||||
|
||||
for i in 0..:
|
||||
rand_nonces[i] = random()
|
||||
ciphertext[i] = encrypt(plaintexts[i], key, rand_nonces[i])
|
||||
|
||||
Counters are OK, but it's not always possible to store current counter value:
|
||||
e.g. in decentralized, unsyncable systems.
|
||||
|
||||
Randomness is OK, but there's a catch:
|
||||
ChaCha20 and AES-GCM use 96-bit / 12-byte nonces, which implies
|
||||
higher chance of collision. In the example above,
|
||||
`random()` can collide and produce repeating nonce.
|
||||
|
||||
To safely use random nonces, utilize XSalsa20 or XChaCha:
|
||||
they increased nonce length to 192-bit, minimizing a chance of collision.
|
||||
AES-SIV is also fine. In situations where you can't use eXtended-nonce
|
||||
algorithms, key rotation is advised. hkdf would work great for this case.
|
||||
|
||||
### Encryption limits
|
||||
|
||||
A "protected message" would mean a probability of `2**-50` that a passive attacker
|
||||
successfully distinguishes the ciphertext outputs of the AEAD scheme from the outputs
|
||||
of a random function. See [draft-irtf-cfrg-aead-limits](https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/) for details.
|
||||
|
||||
- Max message size:
|
||||
- AES-GCM: ~68GB, `2**36-256`
|
||||
- Salsa, ChaCha, XSalsa, XChaCha: ~256GB, `2**38-64`
|
||||
- Max amount of protected messages, under same key:
|
||||
- AES-GCM: `2**32.5`
|
||||
- Salsa, ChaCha: `2**46`, but only integrity is affected, not confidentiality
|
||||
- XSalsa, XChaCha: `2**72`
|
||||
- Max amount of protected messages, across all keys:
|
||||
- AES-GCM: `2**69/B` where B is max blocks encrypted by a key. Meaning
|
||||
`2**59` for 1KB, `2**49` for 1MB, `2**39` for 1GB
|
||||
- Salsa, ChaCha, XSalsa, XChaCha: `2**100`
|
||||
|
||||
##### AES internals and block modes
|
||||
|
||||
`cipher = encrypt(block, key)`. Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round does:
|
||||
|
||||
1. **S-box**, table substitution
|
||||
2. **Shift rows**, cyclic shift left of all rows of data array
|
||||
3. **Mix columns**, multiplying every column by fixed polynomial
|
||||
4. **Add round key**, round_key xor i-th column of array
|
||||
|
||||
For non-deterministic (not ECB) schemes, initialization vector (IV) is mixed to block/key;
|
||||
and each new round either depends on previous block's key, or on some counter.
|
||||
|
||||
- ECB — simple deterministic replacement. Dangerous: always map x to y. See [AES Penguin](https://words.filippo.io/the-ecb-penguin/)
|
||||
- CBC — key is previous round’s block. Hard to use: need proper padding, also needs MAC
|
||||
- CTR — counter, allows to create streaming cipher. Requires good IV. Parallelizable. OK, but no MAC
|
||||
- GCM — modern CTR, parallel, with MAC
|
||||
- SIV — synthetic initialization vector, nonce-misuse-resistant. Guarantees that, when a nonce is repeated,
|
||||
the only security loss is that identical plaintexts will produce identical ciphertexts.
|
||||
- XTS — used in hard drives. Similar to ECB (deterministic), but has `[i][j]`
|
||||
tweak arguments corresponding to sector i and 16-byte block (part of sector) j. Not authenticated!
|
||||
|
||||
GCM / SIV are not ideal:
|
||||
|
||||
- Conservative key wear-out is `2**32` (4B) msgs
|
||||
- MAC can be forged: see Poly1305 section above. Same for SIV
|
||||
|
||||
## Security
|
||||
|
||||
The library has not been independently audited yet.
|
||||
|
||||
It is tested against property-based, cross-library and Wycheproof vectors,
|
||||
and has fuzzing by [Guido Vranken's cryptofuzz](https://github.com/guidovranken/cryptofuzz).
|
||||
|
||||
If you see anything unusual: investigate and report.
|
||||
|
||||
### Constant-timeness
|
||||
|
||||
_JIT-compiler_ and _Garbage Collector_ make "constant time" extremely hard to
|
||||
achieve [timing attack](https://en.wikipedia.org/wiki/Timing_attack) resistance
|
||||
in a scripting language. Which means _any other JS library can't have
|
||||
constant-timeness_. Even statically typed Rust, a language without GC,
|
||||
[makes it harder to achieve constant-time](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)
|
||||
for some cases. If your goal is absolute security, don't use any JS lib — including bindings to native ones.
|
||||
Use low-level libraries & languages. Nonetheless we're targetting algorithmic constant time.
|
||||
|
||||
AES uses T-tables, which means it can't be done in constant-time in JS.
|
||||
|
||||
### Supply chain security
|
||||
|
||||
- **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures.
|
||||
- **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs
|
||||
- **Rare releasing** is followed to ensure less re-audit need for end-users
|
||||
- **Dependencies** are minimized and locked-down:
|
||||
- If your app has 500 dependencies, any dep could get hacked and you'll be downloading
|
||||
malware with every install. We make sure to use as few dependencies as possible
|
||||
- We prevent automatic dependency updates by locking-down version ranges. Every update is checked with `npm-diff`
|
||||
- **Dev Dependencies** are only used if you want to contribute to the repo. They are disabled for end-users:
|
||||
- scure-base, micro-bmark and micro-should are developed by the same author and follow identical security practices
|
||||
- prettier (linter), fast-check (property-based testing) and typescript are used for code quality, vector generation and ts compilation. The packages are big, which makes it hard to audit their source code thoroughly and fully
|
||||
|
||||
### Randomness
|
||||
|
||||
We're deferring to built-in
|
||||
[crypto.getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues)
|
||||
which is considered cryptographically secure (CSPRNG).
|
||||
|
||||
In the past, browsers had bugs that made it weak: it may happen again.
|
||||
Implementing a userspace CSPRNG to get resilient to the weakness
|
||||
is even worse: there is no reliable userspace source of quality entropy.
|
||||
|
||||
## Speed
|
||||
|
||||
To summarize, noble is the fastest JS implementation of Salsa, ChaCha and AES.
|
||||
|
||||
You can gain additional speed-up and
|
||||
avoid memory allocations by passing `output`
|
||||
uint8array into encrypt / decrypt methods.
|
||||
|
||||
Benchmark results on Apple M2 with node v20:
|
||||
|
||||
```
|
||||
encrypt (64B)
|
||||
├─xsalsa20poly1305 x 485,672 ops/sec @ 2μs/op
|
||||
├─chacha20poly1305 x 466,200 ops/sec @ 2μs/op
|
||||
├─xchacha20poly1305 x 312,500 ops/sec @ 3μs/op
|
||||
├─aes-256-gcm x 151,057 ops/sec @ 6μs/op
|
||||
└─aes-256-gcm-siv x 124,984 ops/sec @ 8μs/op
|
||||
encrypt (1KB)
|
||||
├─xsalsa20poly1305 x 146,477 ops/sec @ 6μs/op
|
||||
├─chacha20poly1305 x 145,518 ops/sec @ 6μs/op
|
||||
├─xchacha20poly1305 x 126,119 ops/sec @ 7μs/op
|
||||
├─aes-256-gcm x 43,207 ops/sec @ 23μs/op
|
||||
└─aes-256-gcm-siv x 39,363 ops/sec @ 25μs/op
|
||||
encrypt (8KB)
|
||||
├─xsalsa20poly1305 x 23,773 ops/sec @ 42μs/op
|
||||
├─chacha20poly1305 x 24,134 ops/sec @ 41μs/op
|
||||
├─xchacha20poly1305 x 23,520 ops/sec @ 42μs/op
|
||||
├─aes-256-gcm x 8,420 ops/sec @ 118μs/op
|
||||
└─aes-256-gcm-siv x 8,126 ops/sec @ 123μs/op
|
||||
encrypt (1MB)
|
||||
├─xsalsa20poly1305 x 195 ops/sec @ 5ms/op
|
||||
├─chacha20poly1305 x 199 ops/sec @ 5ms/op
|
||||
├─xchacha20poly1305 x 198 ops/sec @ 5ms/op
|
||||
├─aes-256-gcm x 76 ops/sec @ 13ms/op
|
||||
└─aes-256-gcm-siv x 78 ops/sec @ 12ms/op
|
||||
```
|
||||
|
||||
Unauthenticated encryption:
|
||||
|
||||
```
|
||||
encrypt (64B)
|
||||
├─salsa x 1,287,001 ops/sec @ 777ns/op
|
||||
├─chacha x 1,555,209 ops/sec @ 643ns/op
|
||||
├─xsalsa x 938,086 ops/sec @ 1μs/op
|
||||
└─xchacha x 920,810 ops/sec @ 1μs/op
|
||||
encrypt (1KB)
|
||||
├─salsa x 353,107 ops/sec @ 2μs/op
|
||||
├─chacha x 377,216 ops/sec @ 2μs/op
|
||||
├─xsalsa x 331,674 ops/sec @ 3μs/op
|
||||
└─xchacha x 336,247 ops/sec @ 2μs/op
|
||||
encrypt (8KB)
|
||||
├─salsa x 57,084 ops/sec @ 17μs/op
|
||||
├─chacha x 59,520 ops/sec @ 16μs/op
|
||||
├─xsalsa x 57,097 ops/sec @ 17μs/op
|
||||
└─xchacha x 58,278 ops/sec @ 17μs/op
|
||||
encrypt (1MB)
|
||||
├─salsa x 479 ops/sec @ 2ms/op
|
||||
├─chacha x 491 ops/sec @ 2ms/op
|
||||
├─xsalsa x 483 ops/sec @ 2ms/op
|
||||
└─xchacha x 492 ops/sec @ 2ms/op
|
||||
|
||||
AES
|
||||
encrypt (64B)
|
||||
├─ctr-256 x 689,179 ops/sec @ 1μs/op
|
||||
├─cbc-256 x 639,795 ops/sec @ 1μs/op
|
||||
└─ecb-256 x 668,449 ops/sec @ 1μs/op
|
||||
encrypt (1KB)
|
||||
├─ctr-256 x 93,668 ops/sec @ 10μs/op
|
||||
├─cbc-256 x 94,428 ops/sec @ 10μs/op
|
||||
└─ecb-256 x 151,699 ops/sec @ 6μs/op
|
||||
encrypt (8KB)
|
||||
├─ctr-256 x 13,342 ops/sec @ 74μs/op
|
||||
├─cbc-256 x 13,664 ops/sec @ 73μs/op
|
||||
└─ecb-256 x 22,426 ops/sec @ 44μs/op
|
||||
encrypt (1MB)
|
||||
├─ctr-256 x 106 ops/sec @ 9ms/op
|
||||
├─cbc-256 x 109 ops/sec @ 9ms/op
|
||||
└─ecb-256 x 179 ops/sec @ 5ms/op
|
||||
```
|
||||
|
||||
Compare to other implementations:
|
||||
|
||||
```
|
||||
xsalsa20poly1305 (encrypt, 1MB)
|
||||
├─tweetnacl x 108 ops/sec @ 9ms/op
|
||||
└─noble x 190 ops/sec @ 5ms/op
|
||||
|
||||
chacha20poly1305 (encrypt, 1MB)
|
||||
├─node x 1,360 ops/sec @ 735μs/op
|
||||
├─stablelib x 117 ops/sec @ 8ms/op
|
||||
└─noble x 193 ops/sec @ 5ms/op
|
||||
|
||||
chacha (encrypt, 1MB)
|
||||
├─node x 2,035 ops/sec @ 491μs/op
|
||||
├─stablelib x 206 ops/sec @ 4ms/op
|
||||
└─noble x 474 ops/sec @ 2ms/op
|
||||
|
||||
ctr-256 (encrypt, 1MB)
|
||||
├─node x 3,530 ops/sec @ 283μs/op
|
||||
├─stablelib x 70 ops/sec @ 14ms/op
|
||||
├─aesjs x 31 ops/sec @ 32ms/op
|
||||
├─noble-webcrypto x 4,589 ops/sec @ 217μs/op
|
||||
└─noble x 107 ops/sec @ 9ms/op
|
||||
|
||||
cbc-256 (encrypt, 1MB)
|
||||
├─node x 993 ops/sec @ 1ms/op
|
||||
├─stablelib x 63 ops/sec @ 15ms/op
|
||||
├─aesjs x 29 ops/sec @ 34ms/op
|
||||
├─noble-webcrypto x 1,087 ops/sec @ 919μs/op
|
||||
└─noble x 110 ops/sec @ 9ms/op
|
||||
|
||||
gcm-256 (encrypt, 1MB)
|
||||
├─node x 3,196 ops/sec @ 312μs/op
|
||||
├─stablelib x 27 ops/sec @ 36ms/op
|
||||
├─noble-webcrypto x 4,059 ops/sec @ 246μs/op
|
||||
└─noble x 74 ops/sec @ 13ms/op
|
||||
```
|
||||
|
||||
## Upgrading
|
||||
|
||||
Upgrade from `micro-aes-gcm` package is simple:
|
||||
|
||||
```js
|
||||
// prepare
|
||||
const key = Uint8Array.from([
|
||||
64, 196, 127, 247, 172, 2, 34, 159, 6, 241, 30, 174, 183, 229, 41, 114, 253, 122, 119, 168, 177,
|
||||
243, 155, 236, 164, 159, 98, 72, 162, 243, 224, 195,
|
||||
]);
|
||||
const message = 'Hello world';
|
||||
|
||||
// previous
|
||||
import * as aes from 'micro-aes-gcm';
|
||||
const ciphertext = await aes.encrypt(key, aes.utils.utf8ToBytes(message));
|
||||
const plaintext = await aes.decrypt(key, ciphertext);
|
||||
console.log(aes.utils.bytesToUtf8(plaintext) === message);
|
||||
|
||||
// became =>
|
||||
|
||||
import { gcm } from '@noble/ciphers/aes';
|
||||
import { bytesToUtf8, utf8ToBytes } from '@noble/ciphers/utils';
|
||||
import { managedNonce } from '@noble/ciphers/webcrypto';
|
||||
const aes = managedNonce(gcm)(key);
|
||||
const ciphertext = aes.encrypt(utf8ToBytes(message));
|
||||
const plaintext = aes.decrypt(key, ciphertext);
|
||||
console.log(bytesToUtf8(plaintext) === message);
|
||||
```
|
||||
|
||||
## Contributing & testing
|
||||
|
||||
1. Clone the repository
|
||||
2. `npm install` to install build dependencies like TypeScript
|
||||
3. `npm run build` to compile TypeScript code
|
||||
4. `npm run test` will execute all main tests
|
||||
|
||||
## Resources
|
||||
|
||||
Check out [paulmillr.com/noble](https://paulmillr.com/noble/)
|
||||
for useful resources, articles, documentation and demos
|
||||
related to the library.
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Paul Miller [(https://paulmillr.com)](https://paulmillr.com)
|
||||
Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||
|
||||
See LICENSE file.
|
||||
14
node_modules/@noble/ciphers/_arx.d.ts
generated
vendored
Normal file
14
node_modules/@noble/ciphers/_arx.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import { XorStream } from './utils.js';
|
||||
export declare const sigma: Uint32Array;
|
||||
export declare function rotl(a: number, b: number): number;
|
||||
export type CipherCoreFn = (sigma: Uint32Array, key: Uint32Array, nonce: Uint32Array, output: Uint32Array, counter: number, rounds?: number) => void;
|
||||
export type ExtendNonceFn = (sigma: Uint32Array, key: Uint32Array, input: Uint32Array, output: Uint32Array) => void;
|
||||
export type CipherOpts = {
|
||||
allowShortKeys?: boolean;
|
||||
extendNonceFn?: ExtendNonceFn;
|
||||
counterLength?: number;
|
||||
counterRight?: boolean;
|
||||
rounds?: number;
|
||||
};
|
||||
export declare function createCipher(core: CipherCoreFn, opts: CipherOpts): XorStream;
|
||||
//# sourceMappingURL=_arx.d.ts.map
|
||||
1
node_modules/@noble/ciphers/_arx.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_arx.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"_arx.d.ts","sourceRoot":"","sources":["src/_arx.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AA4CvD,eAAO,MAAM,KAAK,aAAqB,CAAC;AAExC,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,MAAM,MAAM,YAAY,GAAG,CACzB,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,KACZ,IAAI,CAAC;AAEV,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,WAAW,KAChB,IAAI,CAAC;AAEV,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAwDF,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,SAAS,CAsF5E"}
|
||||
175
node_modules/@noble/ciphers/_arx.js
generated
vendored
Normal file
175
node_modules/@noble/ciphers/_arx.js
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createCipher = exports.rotl = exports.sigma = void 0;
|
||||
// Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
|
||||
const _assert_js_1 = require("./_assert.js");
|
||||
const utils_js_1 = require("./utils.js");
|
||||
/*
|
||||
RFC8439 requires multi-step cipher stream, where
|
||||
authKey starts with counter: 0, actual msg with counter: 1.
|
||||
|
||||
For this, we need a way to re-use nonce / counter:
|
||||
|
||||
const counter = new Uint8Array(4);
|
||||
chacha(..., counter, ...); // counter is now 1
|
||||
chacha(..., counter, ...); // counter is now 2
|
||||
|
||||
This is complicated:
|
||||
|
||||
- 32-bit counters are enough, no need for 64-bit: max ArrayBuffer size in JS is 4GB
|
||||
- Original papers don't allow mutating counters
|
||||
- Counter overflow is undefined [^1]
|
||||
- Idea A: allow providing (nonce | counter) instead of just nonce, re-use it
|
||||
- Caveat: Cannot be re-used through all cases:
|
||||
- * chacha has (counter | nonce)
|
||||
- * xchacha has (nonce16 | counter | nonce16)
|
||||
- Idea B: separate nonce / counter and provide separate API for counter re-use
|
||||
- Caveat: there are different counter sizes depending on an algorithm.
|
||||
- salsa & chacha also differ in structures of key & sigma:
|
||||
salsa20: s[0] | k(4) | s[1] | nonce(2) | ctr(2) | s[2] | k(4) | s[3]
|
||||
chacha: s(4) | k(8) | ctr(1) | nonce(3)
|
||||
chacha20orig: s(4) | k(8) | ctr(2) | nonce(2)
|
||||
- Idea C: helper method such as `setSalsaState(key, nonce, sigma, data)`
|
||||
- Caveat: we can't re-use counter array
|
||||
|
||||
xchacha [^2] uses the subkey and remaining 8 byte nonce with ChaCha20 as normal
|
||||
(prefixed by 4 NUL bytes, since [RFC8439] specifies a 12-byte nonce).
|
||||
|
||||
[^1]: https://mailarchive.ietf.org/arch/msg/cfrg/gsOnTJzcbgG6OqD8Sc0GO5aR_tU/
|
||||
[^2]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#appendix-A.2
|
||||
*/
|
||||
// We can't make top-level var depend on utils.utf8ToBytes
|
||||
// because it's not present in all envs. Creating a similar fn here
|
||||
const _utf8ToBytes = (str) => Uint8Array.from(str.split('').map((c) => c.charCodeAt(0)));
|
||||
const sigma16 = _utf8ToBytes('expand 16-byte k');
|
||||
const sigma32 = _utf8ToBytes('expand 32-byte k');
|
||||
const sigma16_32 = (0, utils_js_1.u32)(sigma16);
|
||||
const sigma32_32 = (0, utils_js_1.u32)(sigma32);
|
||||
exports.sigma = sigma32_32.slice();
|
||||
function rotl(a, b) {
|
||||
return (a << b) | (a >>> (32 - b));
|
||||
}
|
||||
exports.rotl = rotl;
|
||||
// Is byte array aligned to 4 byte offset (u32)?
|
||||
function isAligned32(b) {
|
||||
return b.byteOffset % 4 === 0;
|
||||
}
|
||||
// Salsa and Chacha block length is always 512-bit
|
||||
const BLOCK_LEN = 64;
|
||||
const BLOCK_LEN32 = 16;
|
||||
// new Uint32Array([2**32]) // => Uint32Array(1) [ 0 ]
|
||||
// new Uint32Array([2**32-1]) // => Uint32Array(1) [ 4294967295 ]
|
||||
const MAX_COUNTER = 2 ** 32 - 1;
|
||||
const U32_EMPTY = new Uint32Array();
|
||||
function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
|
||||
const len = data.length;
|
||||
const block = new Uint8Array(BLOCK_LEN);
|
||||
const b32 = (0, utils_js_1.u32)(block);
|
||||
// Make sure that buffers aligned to 4 bytes
|
||||
const isAligned = isAligned32(data) && isAligned32(output);
|
||||
const d32 = isAligned ? (0, utils_js_1.u32)(data) : U32_EMPTY;
|
||||
const o32 = isAligned ? (0, utils_js_1.u32)(output) : U32_EMPTY;
|
||||
for (let pos = 0; pos < len; counter++) {
|
||||
core(sigma, key, nonce, b32, counter, rounds);
|
||||
if (counter >= MAX_COUNTER)
|
||||
throw new Error('arx: counter overflow');
|
||||
const take = Math.min(BLOCK_LEN, len - pos);
|
||||
// aligned to 4 bytes
|
||||
if (isAligned && take === BLOCK_LEN) {
|
||||
const pos32 = pos / 4;
|
||||
if (pos % 4 !== 0)
|
||||
throw new Error('arx: invalid block position');
|
||||
for (let j = 0, posj; j < BLOCK_LEN32; j++) {
|
||||
posj = pos32 + j;
|
||||
o32[posj] = d32[posj] ^ b32[j];
|
||||
}
|
||||
pos += BLOCK_LEN;
|
||||
continue;
|
||||
}
|
||||
for (let j = 0, posj; j < take; j++) {
|
||||
posj = pos + j;
|
||||
output[posj] = data[posj] ^ block[j];
|
||||
}
|
||||
pos += take;
|
||||
}
|
||||
}
|
||||
function createCipher(core, opts) {
|
||||
const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = (0, utils_js_1.checkOpts)({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts);
|
||||
if (typeof core !== 'function')
|
||||
throw new Error('core must be a function');
|
||||
(0, _assert_js_1.number)(counterLength);
|
||||
(0, _assert_js_1.number)(rounds);
|
||||
(0, _assert_js_1.bool)(counterRight);
|
||||
(0, _assert_js_1.bool)(allowShortKeys);
|
||||
return (key, nonce, data, output, counter = 0) => {
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
(0, _assert_js_1.bytes)(nonce);
|
||||
(0, _assert_js_1.bytes)(data);
|
||||
const len = data.length;
|
||||
if (!output)
|
||||
output = new Uint8Array(len);
|
||||
(0, _assert_js_1.bytes)(output);
|
||||
(0, _assert_js_1.number)(counter);
|
||||
if (counter < 0 || counter >= MAX_COUNTER)
|
||||
throw new Error('arx: counter overflow');
|
||||
if (output.length < len)
|
||||
throw new Error(`arx: output (${output.length}) is shorter than data (${len})`);
|
||||
const toClean = [];
|
||||
// Key & sigma
|
||||
// key=16 -> sigma16, k=key|key
|
||||
// key=32 -> sigma32, k=key
|
||||
let l = key.length, k, sigma;
|
||||
if (l === 32) {
|
||||
k = key.slice();
|
||||
toClean.push(k);
|
||||
sigma = sigma32_32;
|
||||
}
|
||||
else if (l === 16 && allowShortKeys) {
|
||||
k = new Uint8Array(32);
|
||||
k.set(key);
|
||||
k.set(key, 16);
|
||||
sigma = sigma16_32;
|
||||
toClean.push(k);
|
||||
}
|
||||
else {
|
||||
throw new Error(`arx: invalid 32-byte key, got length=${l}`);
|
||||
}
|
||||
// Nonce
|
||||
// salsa20: 8 (8-byte counter)
|
||||
// chacha20orig: 8 (8-byte counter)
|
||||
// chacha20: 12 (4-byte counter)
|
||||
// xsalsa20: 24 (16 -> hsalsa, 8 -> old nonce)
|
||||
// xchacha20: 24 (16 -> hchacha, 8 -> old nonce)
|
||||
// Align nonce to 4 bytes
|
||||
if (!isAligned32(nonce)) {
|
||||
nonce = nonce.slice();
|
||||
toClean.push(nonce);
|
||||
}
|
||||
const k32 = (0, utils_js_1.u32)(k);
|
||||
// hsalsa & hchacha: handle extended nonce
|
||||
if (extendNonceFn) {
|
||||
if (nonce.length !== 24)
|
||||
throw new Error(`arx: extended nonce must be 24 bytes`);
|
||||
extendNonceFn(sigma, k32, (0, utils_js_1.u32)(nonce.subarray(0, 16)), k32);
|
||||
nonce = nonce.subarray(16);
|
||||
}
|
||||
// Handle nonce counter
|
||||
const nonceNcLen = 16 - counterLength;
|
||||
if (nonceNcLen !== nonce.length)
|
||||
throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`);
|
||||
// Pad counter when nonce is 64 bit
|
||||
if (nonceNcLen !== 12) {
|
||||
const nc = new Uint8Array(12);
|
||||
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
|
||||
nonce = nc;
|
||||
toClean.push(nonce);
|
||||
}
|
||||
const n32 = (0, utils_js_1.u32)(nonce);
|
||||
runCipher(core, sigma, k32, n32, data, output, counter, rounds);
|
||||
while (toClean.length > 0)
|
||||
toClean.pop().fill(0);
|
||||
return output;
|
||||
};
|
||||
}
|
||||
exports.createCipher = createCipher;
|
||||
//# sourceMappingURL=_arx.js.map
|
||||
1
node_modules/@noble/ciphers/_arx.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_arx.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
24
node_modules/@noble/ciphers/_assert.d.ts
generated
vendored
Normal file
24
node_modules/@noble/ciphers/_assert.d.ts
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
declare function number(n: number): void;
|
||||
declare function bool(b: boolean): void;
|
||||
export declare function isBytes(a: unknown): a is Uint8Array;
|
||||
declare function bytes(b: Uint8Array | undefined, ...lengths: number[]): void;
|
||||
export type Hash = {
|
||||
(data: Uint8Array): Uint8Array;
|
||||
blockLen: number;
|
||||
outputLen: number;
|
||||
create: any;
|
||||
};
|
||||
declare function hash(hash: Hash): void;
|
||||
declare function exists(instance: any, checkFinished?: boolean): void;
|
||||
declare function output(out: any, instance: any): void;
|
||||
export { number, bool, bytes, hash, exists, output };
|
||||
declare const assert: {
|
||||
number: typeof number;
|
||||
bool: typeof bool;
|
||||
bytes: typeof bytes;
|
||||
hash: typeof hash;
|
||||
exists: typeof exists;
|
||||
output: typeof output;
|
||||
};
|
||||
export default assert;
|
||||
//# sourceMappingURL=_assert.d.ts.map
|
||||
1
node_modules/@noble/ciphers/_assert.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_assert.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"_assert.d.ts","sourceRoot":"","sources":["src/_assert.ts"],"names":[],"mappings":"AAAA,iBAAS,MAAM,CAAC,CAAC,EAAE,MAAM,QAExB;AAED,iBAAS,IAAI,CAAC,CAAC,EAAE,OAAO,QAEvB;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,UAAU,CAKnD;AAED,iBAAS,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,QAI7D;AAED,MAAM,MAAM,IAAI,GAAG;IACjB,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC;CACb,CAAC;AACF,iBAAS,IAAI,CAAC,IAAI,EAAE,IAAI,QAKvB;AAED,iBAAS,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,aAAa,UAAO,QAGlD;AAED,iBAAS,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,QAMtC;AAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACrD,QAAA,MAAM,MAAM;;;;;;;CAAgD,CAAC;AAC7D,eAAe,MAAM,CAAC"}
|
||||
50
node_modules/@noble/ciphers/_assert.js
generated
vendored
Normal file
50
node_modules/@noble/ciphers/_assert.js
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.output = exports.exists = exports.hash = exports.bytes = exports.bool = exports.number = exports.isBytes = void 0;
|
||||
function number(n) {
|
||||
if (!Number.isSafeInteger(n) || n < 0)
|
||||
throw new Error(`positive integer expected, not ${n}`);
|
||||
}
|
||||
exports.number = number;
|
||||
function bool(b) {
|
||||
if (typeof b !== 'boolean')
|
||||
throw new Error(`boolean expected, not ${b}`);
|
||||
}
|
||||
exports.bool = bool;
|
||||
function isBytes(a) {
|
||||
return (a instanceof Uint8Array ||
|
||||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array'));
|
||||
}
|
||||
exports.isBytes = isBytes;
|
||||
function bytes(b, ...lengths) {
|
||||
if (!isBytes(b))
|
||||
throw new Error('Uint8Array expected');
|
||||
if (lengths.length > 0 && !lengths.includes(b.length))
|
||||
throw new Error(`Uint8Array expected of length ${lengths}, not of length=${b.length}`);
|
||||
}
|
||||
exports.bytes = bytes;
|
||||
function hash(hash) {
|
||||
if (typeof hash !== 'function' || typeof hash.create !== 'function')
|
||||
throw new Error('hash must be wrapped by utils.wrapConstructor');
|
||||
number(hash.outputLen);
|
||||
number(hash.blockLen);
|
||||
}
|
||||
exports.hash = hash;
|
||||
function exists(instance, checkFinished = true) {
|
||||
if (instance.destroyed)
|
||||
throw new Error('Hash instance has been destroyed');
|
||||
if (checkFinished && instance.finished)
|
||||
throw new Error('Hash#digest() has already been called');
|
||||
}
|
||||
exports.exists = exists;
|
||||
function output(out, instance) {
|
||||
bytes(out);
|
||||
const min = instance.outputLen;
|
||||
if (out.length < min) {
|
||||
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
|
||||
}
|
||||
}
|
||||
exports.output = output;
|
||||
const assert = { number, bool, bytes, hash, exists, output };
|
||||
exports.default = assert;
|
||||
//# sourceMappingURL=_assert.js.map
|
||||
1
node_modules/@noble/ciphers/_assert.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_assert.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"_assert.js","sourceRoot":"","sources":["src/_assert.ts"],"names":[],"mappings":";;;AAAA,SAAS,MAAM,CAAC,CAAS;IACvB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,EAAE,CAAC,CAAC;AAChG,CAAC;AA6CQ,wBAAM;AA3Cf,SAAS,IAAI,CAAC,CAAU;IACtB,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAyCgB,oBAAI;AAvCrB,SAAgB,OAAO,CAAC,CAAU;IAChC,OAAO,CACL,CAAC,YAAY,UAAU;QACvB,CAAC,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,CAAC,IAAI,KAAK,YAAY,CAAC,CAC5E,CAAC;AACJ,CAAC;AALD,0BAKC;AAED,SAAS,KAAK,CAAC,CAAyB,EAAE,GAAG,OAAiB;IAC5D,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,mBAAmB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AAC3F,CAAC;AA4BsB,sBAAK;AApB5B,SAAS,IAAI,CAAC,IAAU;IACtB,IAAI,OAAO,IAAI,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU;QACjE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACxB,CAAC;AAe6B,oBAAI;AAblC,SAAS,MAAM,CAAC,QAAa,EAAE,aAAa,GAAG,IAAI;IACjD,IAAI,QAAQ,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC5E,IAAI,aAAa,IAAI,QAAQ,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;AACnG,CAAC;AAUmC,wBAAM;AAR1C,SAAS,MAAM,CAAC,GAAQ,EAAE,QAAa;IACrC,KAAK,CAAC,GAAG,CAAC,CAAC;IACX,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC;IAC/B,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,yDAAyD,GAAG,EAAE,CAAC,CAAC;IAClF,CAAC;AACH,CAAC;AAE2C,wBAAM;AAClD,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC7D,kBAAe,MAAM,CAAC"}
|
||||
70
node_modules/@noble/ciphers/_micro.d.ts
generated
vendored
Normal file
70
node_modules/@noble/ciphers/_micro.d.ts
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
||||
import { Cipher, XorStream } from './utils.js';
|
||||
export declare function hsalsa(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array): void;
|
||||
export declare function hchacha(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array): void;
|
||||
/**
|
||||
* salsa20, 12-byte nonce.
|
||||
*/
|
||||
export declare const salsa20: XorStream;
|
||||
/**
|
||||
* xsalsa20, 24-byte nonce.
|
||||
*/
|
||||
export declare const xsalsa20: XorStream;
|
||||
/**
|
||||
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
|
||||
*/
|
||||
export declare const chacha20orig: XorStream;
|
||||
/**
|
||||
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
|
||||
*/
|
||||
export declare const chacha20: XorStream;
|
||||
/**
|
||||
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
*/
|
||||
export declare const xchacha20: XorStream;
|
||||
/**
|
||||
* 8-round chacha from the original paper.
|
||||
*/
|
||||
export declare const chacha8: XorStream;
|
||||
/**
|
||||
* 12-round chacha from the original paper.
|
||||
*/
|
||||
export declare const chacha12: XorStream;
|
||||
export declare function poly1305(msg: Uint8Array, key: Uint8Array): Uint8Array;
|
||||
/**
|
||||
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
|
||||
*/
|
||||
export declare const xsalsa20poly1305: ((key: Uint8Array, nonce: Uint8Array) => {
|
||||
encrypt: (plaintext: Uint8Array) => Uint8Array;
|
||||
decrypt: (ciphertext: Uint8Array) => Uint8Array;
|
||||
}) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
tagLength: number;
|
||||
};
|
||||
/**
|
||||
* Alias to xsalsa20-poly1305
|
||||
*/
|
||||
export declare function secretbox(key: Uint8Array, nonce: Uint8Array): {
|
||||
seal: (plaintext: Uint8Array) => Uint8Array;
|
||||
open: (ciphertext: Uint8Array) => Uint8Array;
|
||||
};
|
||||
export declare const _poly1305_aead: (fn: XorStream) => (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher;
|
||||
/**
|
||||
* chacha20-poly1305 12-byte-nonce chacha.
|
||||
*/
|
||||
export declare const chacha20poly1305: ((key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
tagLength: number;
|
||||
};
|
||||
/**
|
||||
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
*/
|
||||
export declare const xchacha20poly1305: ((key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
tagLength: number;
|
||||
};
|
||||
//# sourceMappingURL=_micro.d.ts.map
|
||||
1
node_modules/@noble/ciphers/_micro.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_micro.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"_micro.d.ts","sourceRoot":"","sources":["src/_micro.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,OAAO,EACL,MAAM,EAAE,SAAS,EAElB,MAAM,YAAY,CAAC;AA+EpB,wBAAgB,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,QAatF;AAuBD,wBAAgB,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,QAavF;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,WAGlB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,WAGnB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY,WAIvB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,WAGnB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,WAIpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,OAAO,WAIlB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,WAInB,CAAC;AAQH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,GAAG,UAAU,CAcrE;AA4BD;;GAEG;AACH,eAAO,MAAM,gBAAgB,SAEI,UAAU,SAAS,UAAU;yBAInC,UAAU;0BAST,UAAU;;;;;CAWrC,CAAC;AAEF;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU;;;EAG3D;AAED,eAAO,MAAM,cAAc,OACpB,SAAS,WACR,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MAuBvD,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,gBAAgB,SA5BrB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MAAM;;;;CA+B/D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,SArCtB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MAAM;;;;CAwC/D,CAAC"}
|
||||
295
node_modules/@noble/ciphers/_micro.js
generated
vendored
Normal file
295
node_modules/@noble/ciphers/_micro.js
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.xchacha20poly1305 = exports.chacha20poly1305 = exports._poly1305_aead = exports.secretbox = exports.xsalsa20poly1305 = exports.poly1305 = exports.chacha12 = exports.chacha8 = exports.xchacha20 = exports.chacha20 = exports.chacha20orig = exports.xsalsa20 = exports.salsa20 = exports.hchacha = exports.hsalsa = void 0;
|
||||
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
||||
// prettier-ignore
|
||||
const utils_js_1 = require("./utils.js");
|
||||
const _arx_js_1 = require("./_arx.js");
|
||||
const _assert_js_1 = require("./_assert.js");
|
||||
/*
|
||||
noble-ciphers-micro: more auditable, but slower version of salsa20, chacha & poly1305.
|
||||
Implements the same algorithms that are present in other files, but without
|
||||
unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling).
|
||||
*/
|
||||
function bytesToNumberLE(bytes) {
|
||||
return (0, utils_js_1.hexToNumber)((0, utils_js_1.bytesToHex)(Uint8Array.from(bytes).reverse()));
|
||||
}
|
||||
function numberToBytesLE(n, len) {
|
||||
return (0, utils_js_1.numberToBytesBE)(n, len).reverse();
|
||||
}
|
||||
function salsaQR(x, a, b, c, d) {
|
||||
x[b] ^= (0, _arx_js_1.rotl)((x[a] + x[d]) | 0, 7);
|
||||
x[c] ^= (0, _arx_js_1.rotl)((x[b] + x[a]) | 0, 9);
|
||||
x[d] ^= (0, _arx_js_1.rotl)((x[c] + x[b]) | 0, 13);
|
||||
x[a] ^= (0, _arx_js_1.rotl)((x[d] + x[c]) | 0, 18);
|
||||
}
|
||||
// prettier-ignore
|
||||
function chachaQR(x, a, b, c, d) {
|
||||
x[a] = (x[a] + x[b]) | 0;
|
||||
x[d] = (0, _arx_js_1.rotl)(x[d] ^ x[a], 16);
|
||||
x[c] = (x[c] + x[d]) | 0;
|
||||
x[b] = (0, _arx_js_1.rotl)(x[b] ^ x[c], 12);
|
||||
x[a] = (x[a] + x[b]) | 0;
|
||||
x[d] = (0, _arx_js_1.rotl)(x[d] ^ x[a], 8);
|
||||
x[c] = (x[c] + x[d]) | 0;
|
||||
x[b] = (0, _arx_js_1.rotl)(x[b] ^ x[c], 7);
|
||||
}
|
||||
function salsaRound(x, rounds = 20) {
|
||||
for (let r = 0; r < rounds; r += 2) {
|
||||
salsaQR(x, 0, 4, 8, 12);
|
||||
salsaQR(x, 5, 9, 13, 1);
|
||||
salsaQR(x, 10, 14, 2, 6);
|
||||
salsaQR(x, 15, 3, 7, 11);
|
||||
salsaQR(x, 0, 1, 2, 3);
|
||||
salsaQR(x, 5, 6, 7, 4);
|
||||
salsaQR(x, 10, 11, 8, 9);
|
||||
salsaQR(x, 15, 12, 13, 14);
|
||||
}
|
||||
}
|
||||
function chachaRound(x, rounds = 20) {
|
||||
for (let r = 0; r < rounds; r += 2) {
|
||||
chachaQR(x, 0, 4, 8, 12);
|
||||
chachaQR(x, 1, 5, 9, 13);
|
||||
chachaQR(x, 2, 6, 10, 14);
|
||||
chachaQR(x, 3, 7, 11, 15);
|
||||
chachaQR(x, 0, 5, 10, 15);
|
||||
chachaQR(x, 1, 6, 11, 12);
|
||||
chachaQR(x, 2, 7, 8, 13);
|
||||
chachaQR(x, 3, 4, 9, 14);
|
||||
}
|
||||
}
|
||||
function salsaCore(s, k, n, out, cnt, rounds = 20) {
|
||||
// prettier-ignore
|
||||
const y = new Uint32Array([
|
||||
s[0], k[0], k[1], k[2], // "expa" Key Key Key
|
||||
k[3], s[1], n[0], n[1], // Key "nd 3" Nonce Nonce
|
||||
cnt, 0, s[2], k[4], // Pos. Pos. "2-by" Key
|
||||
k[5], k[6], k[7], s[3], // Key Key Key "te k"
|
||||
]);
|
||||
const x = y.slice();
|
||||
salsaRound(x, rounds);
|
||||
for (let i = 0; i < 16; i++)
|
||||
out[i] = (y[i] + x[i]) | 0;
|
||||
}
|
||||
// prettier-ignore
|
||||
function hsalsa(s, k, i, o32) {
|
||||
const x = new Uint32Array([
|
||||
s[0], k[0], k[1], k[2],
|
||||
k[3], s[1], i[0], i[1],
|
||||
i[2], i[3], s[2], k[4],
|
||||
k[5], k[6], k[7], s[3]
|
||||
]);
|
||||
salsaRound(x, 20);
|
||||
let oi = 0;
|
||||
o32[oi++] = x[0];
|
||||
o32[oi++] = x[5];
|
||||
o32[oi++] = x[10];
|
||||
o32[oi++] = x[15];
|
||||
o32[oi++] = x[6];
|
||||
o32[oi++] = x[7];
|
||||
o32[oi++] = x[8];
|
||||
o32[oi++] = x[9];
|
||||
}
|
||||
exports.hsalsa = hsalsa;
|
||||
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
||||
// prettier-ignore
|
||||
const y = new Uint32Array([
|
||||
s[0], s[1], s[2], s[3], // "expa" "nd 3" "2-by" "te k"
|
||||
k[0], k[1], k[2], k[3], // Key Key Key Key
|
||||
k[4], k[5], k[6], k[7], // Key Key Key Key
|
||||
cnt, n[0], n[1], n[2], // Counter Counter Nonce Nonce
|
||||
]);
|
||||
const x = y.slice();
|
||||
chachaRound(x, rounds);
|
||||
for (let i = 0; i < 16; i++)
|
||||
out[i] = (y[i] + x[i]) | 0;
|
||||
}
|
||||
// prettier-ignore
|
||||
function hchacha(s, k, i, o32) {
|
||||
const x = new Uint32Array([
|
||||
s[0], s[1], s[2], s[3],
|
||||
k[0], k[1], k[2], k[3],
|
||||
k[4], k[5], k[6], k[7],
|
||||
i[0], i[1], i[2], i[3],
|
||||
]);
|
||||
chachaRound(x, 20);
|
||||
let oi = 0;
|
||||
o32[oi++] = x[0];
|
||||
o32[oi++] = x[1];
|
||||
o32[oi++] = x[2];
|
||||
o32[oi++] = x[3];
|
||||
o32[oi++] = x[12];
|
||||
o32[oi++] = x[13];
|
||||
o32[oi++] = x[14];
|
||||
o32[oi++] = x[15];
|
||||
}
|
||||
exports.hchacha = hchacha;
|
||||
/**
|
||||
* salsa20, 12-byte nonce.
|
||||
*/
|
||||
exports.salsa20 = (0, _arx_js_1.createCipher)(salsaCore, {
|
||||
allowShortKeys: true,
|
||||
counterRight: true,
|
||||
});
|
||||
/**
|
||||
* xsalsa20, 24-byte nonce.
|
||||
*/
|
||||
exports.xsalsa20 = (0, _arx_js_1.createCipher)(salsaCore, {
|
||||
counterRight: true,
|
||||
extendNonceFn: hsalsa,
|
||||
});
|
||||
/**
|
||||
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
|
||||
*/
|
||||
exports.chacha20orig = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
allowShortKeys: true,
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
});
|
||||
/**
|
||||
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
|
||||
*/
|
||||
exports.chacha20 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
});
|
||||
/**
|
||||
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
*/
|
||||
exports.xchacha20 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
extendNonceFn: hchacha,
|
||||
});
|
||||
/**
|
||||
* 8-round chacha from the original paper.
|
||||
*/
|
||||
exports.chacha8 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 8,
|
||||
});
|
||||
/**
|
||||
* 12-round chacha from the original paper.
|
||||
*/
|
||||
exports.chacha12 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 12,
|
||||
});
|
||||
const POW_2_130_5 = BigInt(2) ** BigInt(130) - BigInt(5);
|
||||
const POW_2_128_1 = BigInt(2) ** BigInt(16 * 8) - BigInt(1);
|
||||
const CLAMP_R = BigInt('0x0ffffffc0ffffffc0ffffffc0fffffff');
|
||||
const _0 = BigInt(0);
|
||||
const _1 = BigInt(1);
|
||||
// Can be speed-up using BigUint64Array, but would be more complicated
|
||||
function poly1305(msg, key) {
|
||||
(0, _assert_js_1.bytes)(msg);
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
let acc = _0;
|
||||
const r = bytesToNumberLE(key.subarray(0, 16)) & CLAMP_R;
|
||||
const s = bytesToNumberLE(key.subarray(16));
|
||||
// Process by 16 byte chunks
|
||||
for (let i = 0; i < msg.length; i += 16) {
|
||||
const m = msg.subarray(i, i + 16);
|
||||
const n = bytesToNumberLE(m) | (_1 << BigInt(8 * m.length));
|
||||
acc = ((acc + n) * r) % POW_2_130_5;
|
||||
}
|
||||
const res = (acc + s) & POW_2_128_1;
|
||||
return numberToBytesLE(res, 16);
|
||||
}
|
||||
exports.poly1305 = poly1305;
|
||||
function computeTag(fn, key, nonce, ciphertext, AAD) {
|
||||
const res = [];
|
||||
if (AAD) {
|
||||
res.push(AAD);
|
||||
const leftover = AAD.length % 16;
|
||||
if (leftover > 0)
|
||||
res.push(new Uint8Array(16 - leftover));
|
||||
}
|
||||
res.push(ciphertext);
|
||||
const leftover = ciphertext.length % 16;
|
||||
if (leftover > 0)
|
||||
res.push(new Uint8Array(16 - leftover));
|
||||
// Lengths
|
||||
const num = new Uint8Array(16);
|
||||
const view = (0, utils_js_1.createView)(num);
|
||||
(0, utils_js_1.setBigUint64)(view, 0, BigInt(AAD ? AAD.length : 0), true);
|
||||
(0, utils_js_1.setBigUint64)(view, 8, BigInt(ciphertext.length), true);
|
||||
res.push(num);
|
||||
const authKey = fn(key, nonce, new Uint8Array(32));
|
||||
return poly1305((0, utils_js_1.concatBytes)(...res), authKey);
|
||||
}
|
||||
/**
|
||||
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
|
||||
*/
|
||||
exports.xsalsa20poly1305 = (0, utils_js_1.wrapCipher)({ blockSize: 64, nonceLength: 24, tagLength: 16 }, function xsalsa20poly1305(key, nonce) {
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
(0, _assert_js_1.bytes)(nonce);
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
(0, _assert_js_1.bytes)(plaintext);
|
||||
const m = (0, utils_js_1.concatBytes)(new Uint8Array(32), plaintext);
|
||||
const c = (0, exports.xsalsa20)(key, nonce, m);
|
||||
const authKey = c.subarray(0, 32);
|
||||
const data = c.subarray(32);
|
||||
const tag = poly1305(data, authKey);
|
||||
return (0, utils_js_1.concatBytes)(tag, data);
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
(0, _assert_js_1.bytes)(ciphertext);
|
||||
if (ciphertext.length < 16)
|
||||
throw new Error('encrypted data must be at least 16 bytes');
|
||||
const c = (0, utils_js_1.concatBytes)(new Uint8Array(16), ciphertext);
|
||||
const authKey = (0, exports.xsalsa20)(key, nonce, new Uint8Array(32));
|
||||
const tag = poly1305(c.subarray(32), authKey);
|
||||
if (!(0, utils_js_1.equalBytes)(c.subarray(16, 32), tag))
|
||||
throw new Error('invalid poly1305 tag');
|
||||
return (0, exports.xsalsa20)(key, nonce, c).subarray(32);
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Alias to xsalsa20-poly1305
|
||||
*/
|
||||
function secretbox(key, nonce) {
|
||||
const xs = (0, exports.xsalsa20poly1305)(key, nonce);
|
||||
return { seal: xs.encrypt, open: xs.decrypt };
|
||||
}
|
||||
exports.secretbox = secretbox;
|
||||
const _poly1305_aead = (fn) => (key, nonce, AAD) => {
|
||||
const tagLength = 16;
|
||||
const keyLength = 32;
|
||||
(0, _assert_js_1.bytes)(key, keyLength);
|
||||
(0, _assert_js_1.bytes)(nonce);
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
(0, _assert_js_1.bytes)(plaintext);
|
||||
const res = fn(key, nonce, plaintext, undefined, 1);
|
||||
const tag = computeTag(fn, key, nonce, res, AAD);
|
||||
return (0, utils_js_1.concatBytes)(res, tag);
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
(0, _assert_js_1.bytes)(ciphertext);
|
||||
if (ciphertext.length < tagLength)
|
||||
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
|
||||
const passedTag = ciphertext.subarray(-tagLength);
|
||||
const data = ciphertext.subarray(0, -tagLength);
|
||||
const tag = computeTag(fn, key, nonce, data, AAD);
|
||||
if (!(0, utils_js_1.equalBytes)(passedTag, tag))
|
||||
throw new Error('invalid poly1305 tag');
|
||||
return fn(key, nonce, data, undefined, 1);
|
||||
},
|
||||
};
|
||||
};
|
||||
exports._poly1305_aead = _poly1305_aead;
|
||||
/**
|
||||
* chacha20-poly1305 12-byte-nonce chacha.
|
||||
*/
|
||||
exports.chacha20poly1305 = (0, utils_js_1.wrapCipher)({ blockSize: 64, nonceLength: 12, tagLength: 16 }, (0, exports._poly1305_aead)(exports.chacha20));
|
||||
/**
|
||||
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
*/
|
||||
exports.xchacha20poly1305 = (0, utils_js_1.wrapCipher)({ blockSize: 64, nonceLength: 24, tagLength: 16 }, (0, exports._poly1305_aead)(exports.xchacha20));
|
||||
//# sourceMappingURL=_micro.js.map
|
||||
1
node_modules/@noble/ciphers/_micro.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_micro.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
15
node_modules/@noble/ciphers/_poly1305.d.ts
generated
vendored
Normal file
15
node_modules/@noble/ciphers/_poly1305.d.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Input, Hash } from './utils.js';
|
||||
export type CHash = ReturnType<typeof wrapConstructorWithKey>;
|
||||
export declare function wrapConstructorWithKey<H extends Hash<H>>(hashCons: (key: Input) => Hash<H>): {
|
||||
(msg: Input, key: Input): Uint8Array;
|
||||
outputLen: number;
|
||||
blockLen: number;
|
||||
create(key: Input): Hash<H>;
|
||||
};
|
||||
export declare const poly1305: {
|
||||
(msg: Input, key: Input): Uint8Array;
|
||||
outputLen: number;
|
||||
blockLen: number;
|
||||
create(key: Input): Hash<Hash<unknown>>;
|
||||
};
|
||||
//# sourceMappingURL=_poly1305.d.ts.map
|
||||
1
node_modules/@noble/ciphers/_poly1305.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_poly1305.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"_poly1305.d.ts","sourceRoot":"","sources":["src/_poly1305.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAW,IAAI,EAAE,MAAM,YAAY,CAAC;AAkRlD,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC9D,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC;UACrE,KAAK,OAAO,KAAK,GAAG,UAAU;;;gBAI7B,KAAK;EAE3B;AAED,eAAO,MAAM,QAAQ;UARC,KAAK,OAAO,KAAK,GAAG,UAAU;;;gBAI7B,KAAK;CAI8C,CAAC"}
|
||||
268
node_modules/@noble/ciphers/_poly1305.js
generated
vendored
Normal file
268
node_modules/@noble/ciphers/_poly1305.js
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.poly1305 = exports.wrapConstructorWithKey = void 0;
|
||||
const _assert_js_1 = require("./_assert.js");
|
||||
const utils_js_1 = require("./utils.js");
|
||||
// Poly1305 is a fast and parallel secret-key message-authentication code.
|
||||
// https://cr.yp.to/mac.html, https://cr.yp.to/mac/poly1305-20050329.pdf
|
||||
// https://datatracker.ietf.org/doc/html/rfc8439
|
||||
// Based on Public Domain poly1305-donna https://github.com/floodyberry/poly1305-donna
|
||||
const u8to16 = (a, i) => (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
|
||||
class Poly1305 {
|
||||
constructor(key) {
|
||||
this.blockLen = 16;
|
||||
this.outputLen = 16;
|
||||
this.buffer = new Uint8Array(16);
|
||||
this.r = new Uint16Array(10);
|
||||
this.h = new Uint16Array(10);
|
||||
this.pad = new Uint16Array(8);
|
||||
this.pos = 0;
|
||||
this.finished = false;
|
||||
key = (0, utils_js_1.toBytes)(key);
|
||||
(0, _assert_js_1.bytes)(key, 32);
|
||||
const t0 = u8to16(key, 0);
|
||||
const t1 = u8to16(key, 2);
|
||||
const t2 = u8to16(key, 4);
|
||||
const t3 = u8to16(key, 6);
|
||||
const t4 = u8to16(key, 8);
|
||||
const t5 = u8to16(key, 10);
|
||||
const t6 = u8to16(key, 12);
|
||||
const t7 = u8to16(key, 14);
|
||||
// https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47
|
||||
this.r[0] = t0 & 0x1fff;
|
||||
this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
|
||||
this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
|
||||
this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
|
||||
this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
|
||||
this.r[5] = (t4 >>> 1) & 0x1ffe;
|
||||
this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
|
||||
this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
|
||||
this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
|
||||
this.r[9] = (t7 >>> 5) & 0x007f;
|
||||
for (let i = 0; i < 8; i++)
|
||||
this.pad[i] = u8to16(key, 16 + 2 * i);
|
||||
}
|
||||
process(data, offset, isLast = false) {
|
||||
const hibit = isLast ? 0 : 1 << 11;
|
||||
const { h, r } = this;
|
||||
const r0 = r[0];
|
||||
const r1 = r[1];
|
||||
const r2 = r[2];
|
||||
const r3 = r[3];
|
||||
const r4 = r[4];
|
||||
const r5 = r[5];
|
||||
const r6 = r[6];
|
||||
const r7 = r[7];
|
||||
const r8 = r[8];
|
||||
const r9 = r[9];
|
||||
const t0 = u8to16(data, offset + 0);
|
||||
const t1 = u8to16(data, offset + 2);
|
||||
const t2 = u8to16(data, offset + 4);
|
||||
const t3 = u8to16(data, offset + 6);
|
||||
const t4 = u8to16(data, offset + 8);
|
||||
const t5 = u8to16(data, offset + 10);
|
||||
const t6 = u8to16(data, offset + 12);
|
||||
const t7 = u8to16(data, offset + 14);
|
||||
let h0 = h[0] + (t0 & 0x1fff);
|
||||
let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
|
||||
let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
|
||||
let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
|
||||
let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
|
||||
let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
|
||||
let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
|
||||
let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
|
||||
let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
|
||||
let h9 = h[9] + ((t7 >>> 5) | hibit);
|
||||
let c = 0;
|
||||
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
|
||||
c = d0 >>> 13;
|
||||
d0 &= 0x1fff;
|
||||
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
|
||||
c += d0 >>> 13;
|
||||
d0 &= 0x1fff;
|
||||
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
|
||||
c = d1 >>> 13;
|
||||
d1 &= 0x1fff;
|
||||
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
|
||||
c += d1 >>> 13;
|
||||
d1 &= 0x1fff;
|
||||
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
|
||||
c = d2 >>> 13;
|
||||
d2 &= 0x1fff;
|
||||
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
|
||||
c += d2 >>> 13;
|
||||
d2 &= 0x1fff;
|
||||
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
|
||||
c = d3 >>> 13;
|
||||
d3 &= 0x1fff;
|
||||
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
|
||||
c += d3 >>> 13;
|
||||
d3 &= 0x1fff;
|
||||
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
|
||||
c = d4 >>> 13;
|
||||
d4 &= 0x1fff;
|
||||
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
|
||||
c += d4 >>> 13;
|
||||
d4 &= 0x1fff;
|
||||
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
|
||||
c = d5 >>> 13;
|
||||
d5 &= 0x1fff;
|
||||
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
|
||||
c += d5 >>> 13;
|
||||
d5 &= 0x1fff;
|
||||
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
|
||||
c = d6 >>> 13;
|
||||
d6 &= 0x1fff;
|
||||
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
|
||||
c += d6 >>> 13;
|
||||
d6 &= 0x1fff;
|
||||
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
|
||||
c = d7 >>> 13;
|
||||
d7 &= 0x1fff;
|
||||
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
|
||||
c += d7 >>> 13;
|
||||
d7 &= 0x1fff;
|
||||
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
|
||||
c = d8 >>> 13;
|
||||
d8 &= 0x1fff;
|
||||
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
|
||||
c += d8 >>> 13;
|
||||
d8 &= 0x1fff;
|
||||
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
|
||||
c = d9 >>> 13;
|
||||
d9 &= 0x1fff;
|
||||
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
|
||||
c += d9 >>> 13;
|
||||
d9 &= 0x1fff;
|
||||
c = ((c << 2) + c) | 0;
|
||||
c = (c + d0) | 0;
|
||||
d0 = c & 0x1fff;
|
||||
c = c >>> 13;
|
||||
d1 += c;
|
||||
h[0] = d0;
|
||||
h[1] = d1;
|
||||
h[2] = d2;
|
||||
h[3] = d3;
|
||||
h[4] = d4;
|
||||
h[5] = d5;
|
||||
h[6] = d6;
|
||||
h[7] = d7;
|
||||
h[8] = d8;
|
||||
h[9] = d9;
|
||||
}
|
||||
finalize() {
|
||||
const { h, pad } = this;
|
||||
const g = new Uint16Array(10);
|
||||
let c = h[1] >>> 13;
|
||||
h[1] &= 0x1fff;
|
||||
for (let i = 2; i < 10; i++) {
|
||||
h[i] += c;
|
||||
c = h[i] >>> 13;
|
||||
h[i] &= 0x1fff;
|
||||
}
|
||||
h[0] += c * 5;
|
||||
c = h[0] >>> 13;
|
||||
h[0] &= 0x1fff;
|
||||
h[1] += c;
|
||||
c = h[1] >>> 13;
|
||||
h[1] &= 0x1fff;
|
||||
h[2] += c;
|
||||
g[0] = h[0] + 5;
|
||||
c = g[0] >>> 13;
|
||||
g[0] &= 0x1fff;
|
||||
for (let i = 1; i < 10; i++) {
|
||||
g[i] = h[i] + c;
|
||||
c = g[i] >>> 13;
|
||||
g[i] &= 0x1fff;
|
||||
}
|
||||
g[9] -= 1 << 13;
|
||||
let mask = (c ^ 1) - 1;
|
||||
for (let i = 0; i < 10; i++)
|
||||
g[i] &= mask;
|
||||
mask = ~mask;
|
||||
for (let i = 0; i < 10; i++)
|
||||
h[i] = (h[i] & mask) | g[i];
|
||||
h[0] = (h[0] | (h[1] << 13)) & 0xffff;
|
||||
h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
|
||||
h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
|
||||
h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
|
||||
h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
|
||||
h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
|
||||
h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
|
||||
h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
|
||||
let f = h[0] + pad[0];
|
||||
h[0] = f & 0xffff;
|
||||
for (let i = 1; i < 8; i++) {
|
||||
f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
|
||||
h[i] = f & 0xffff;
|
||||
}
|
||||
}
|
||||
update(data) {
|
||||
(0, _assert_js_1.exists)(this);
|
||||
const { buffer, blockLen } = this;
|
||||
data = (0, utils_js_1.toBytes)(data);
|
||||
const len = data.length;
|
||||
for (let pos = 0; pos < len;) {
|
||||
const take = Math.min(blockLen - this.pos, len - pos);
|
||||
// Fast path: we have at least one block in input
|
||||
if (take === blockLen) {
|
||||
for (; blockLen <= len - pos; pos += blockLen)
|
||||
this.process(data, pos);
|
||||
continue;
|
||||
}
|
||||
buffer.set(data.subarray(pos, pos + take), this.pos);
|
||||
this.pos += take;
|
||||
pos += take;
|
||||
if (this.pos === blockLen) {
|
||||
this.process(buffer, 0, false);
|
||||
this.pos = 0;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
destroy() {
|
||||
this.h.fill(0);
|
||||
this.r.fill(0);
|
||||
this.buffer.fill(0);
|
||||
this.pad.fill(0);
|
||||
}
|
||||
digestInto(out) {
|
||||
(0, _assert_js_1.exists)(this);
|
||||
(0, _assert_js_1.output)(out, this);
|
||||
this.finished = true;
|
||||
const { buffer, h } = this;
|
||||
let { pos } = this;
|
||||
if (pos) {
|
||||
buffer[pos++] = 1;
|
||||
// buffer.subarray(pos).fill(0);
|
||||
for (; pos < 16; pos++)
|
||||
buffer[pos] = 0;
|
||||
this.process(buffer, 0, true);
|
||||
}
|
||||
this.finalize();
|
||||
let opos = 0;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
out[opos++] = h[i] >>> 0;
|
||||
out[opos++] = h[i] >>> 8;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
digest() {
|
||||
const { buffer, outputLen } = this;
|
||||
this.digestInto(buffer);
|
||||
const res = buffer.slice(0, outputLen);
|
||||
this.destroy();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
function wrapConstructorWithKey(hashCons) {
|
||||
const hashC = (msg, key) => hashCons(key).update((0, utils_js_1.toBytes)(msg)).digest();
|
||||
const tmp = hashCons(new Uint8Array(32));
|
||||
hashC.outputLen = tmp.outputLen;
|
||||
hashC.blockLen = tmp.blockLen;
|
||||
hashC.create = (key) => hashCons(key);
|
||||
return hashC;
|
||||
}
|
||||
exports.wrapConstructorWithKey = wrapConstructorWithKey;
|
||||
exports.poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
|
||||
//# sourceMappingURL=_poly1305.js.map
|
||||
1
node_modules/@noble/ciphers/_poly1305.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_poly1305.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
27
node_modules/@noble/ciphers/_polyval.d.ts
generated
vendored
Normal file
27
node_modules/@noble/ciphers/_polyval.d.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Input, Hash } from './utils.js';
|
||||
/**
|
||||
* `mulX_POLYVAL(ByteReverse(H))` from spec
|
||||
* @param k mutated in place
|
||||
*/
|
||||
export declare function _toGHASHKey(k: Uint8Array): Uint8Array;
|
||||
export type CHash = ReturnType<typeof wrapConstructorWithKey>;
|
||||
declare function wrapConstructorWithKey<H extends Hash<H>>(hashCons: (key: Input, expectedLength?: number) => Hash<H>): {
|
||||
(msg: Input, key: Input): Uint8Array;
|
||||
outputLen: number;
|
||||
blockLen: number;
|
||||
create(key: Input, expectedLength?: number): Hash<H>;
|
||||
};
|
||||
export declare const ghash: {
|
||||
(msg: Input, key: Input): Uint8Array;
|
||||
outputLen: number;
|
||||
blockLen: number;
|
||||
create(key: Input, expectedLength?: number): Hash<Hash<unknown>>;
|
||||
};
|
||||
export declare const polyval: {
|
||||
(msg: Input, key: Input): Uint8Array;
|
||||
outputLen: number;
|
||||
blockLen: number;
|
||||
create(key: Input, expectedLength?: number): Hash<Hash<unknown>>;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=_polyval.d.ts.map
|
||||
1
node_modules/@noble/ciphers/_polyval.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_polyval.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"_polyval.d.ts","sourceRoot":"","sources":["src/_polyval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,EAAE,IAAI,EAAO,MAAM,YAAY,CAAC;AAsCnE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,UAAU,CAYrD;AA+KD,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC9D,iBAAS,sBAAsB,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,EAC/C,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC;UAEtC,KAAK,OAAO,KAAK,GAAG,UAAU;;;gBAK7B,KAAK,mBAAmB,MAAM;EAEpD;AAED,eAAO,MAAM,KAAK;UATI,KAAK,OAAO,KAAK,GAAG,UAAU;;;gBAK7B,KAAK,mBAAmB,MAAM;CAMpD,CAAC;AACF,eAAO,MAAM,OAAO;UAZE,KAAK,OAAO,KAAK,GAAG,UAAU;;;gBAK7B,KAAK,mBAAmB,MAAM;CASpD,CAAC"}
|
||||
221
node_modules/@noble/ciphers/_polyval.js
generated
vendored
Normal file
221
node_modules/@noble/ciphers/_polyval.js
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.polyval = exports.ghash = exports._toGHASHKey = void 0;
|
||||
const utils_js_1 = require("./utils.js");
|
||||
const _assert_js_1 = require("./_assert.js");
|
||||
// GHash from AES-GCM and its little-endian "mirror image" Polyval from AES-SIV.
|
||||
// Implemented in terms of GHash with conversion function for keys
|
||||
// GCM GHASH from NIST SP800-38d, SIV from RFC 8452.
|
||||
// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
// GHASH modulo: x^128 + x^7 + x^2 + x + 1
|
||||
// POLYVAL modulo: x^128 + x^127 + x^126 + x^121 + 1
|
||||
const BLOCK_SIZE = 16;
|
||||
// TODO: rewrite
|
||||
// temporary padding buffer
|
||||
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
||||
const ZEROS32 = (0, utils_js_1.u32)(ZEROS16);
|
||||
const POLY = 0xe1; // v = 2*v % POLY
|
||||
// v = 2*v % POLY
|
||||
// NOTE: because x + x = 0 (add/sub is same), mul2(x) != x+x
|
||||
// We can multiply any number using montgomery ladder and this function (works as double, add is simple xor)
|
||||
const mul2 = (s0, s1, s2, s3) => {
|
||||
const hiBit = s3 & 1;
|
||||
return {
|
||||
s3: (s2 << 31) | (s3 >>> 1),
|
||||
s2: (s1 << 31) | (s2 >>> 1),
|
||||
s1: (s0 << 31) | (s1 >>> 1),
|
||||
s0: (s0 >>> 1) ^ ((POLY << 24) & -(hiBit & 1)), // reduce % poly
|
||||
};
|
||||
};
|
||||
const swapLE = (n) => (((n >>> 0) & 0xff) << 24) |
|
||||
(((n >>> 8) & 0xff) << 16) |
|
||||
(((n >>> 16) & 0xff) << 8) |
|
||||
((n >>> 24) & 0xff) |
|
||||
0;
|
||||
/**
|
||||
* `mulX_POLYVAL(ByteReverse(H))` from spec
|
||||
* @param k mutated in place
|
||||
*/
|
||||
function _toGHASHKey(k) {
|
||||
k.reverse();
|
||||
const hiBit = k[15] & 1;
|
||||
// k >>= 1
|
||||
let carry = 0;
|
||||
for (let i = 0; i < k.length; i++) {
|
||||
const t = k[i];
|
||||
k[i] = (t >>> 1) | carry;
|
||||
carry = (t & 1) << 7;
|
||||
}
|
||||
k[0] ^= -hiBit & 0xe1; // if (hiBit) n ^= 0xe1000000000000000000000000000000;
|
||||
return k;
|
||||
}
|
||||
exports._toGHASHKey = _toGHASHKey;
|
||||
const estimateWindow = (bytes) => {
|
||||
if (bytes > 64 * 1024)
|
||||
return 8;
|
||||
if (bytes > 1024)
|
||||
return 4;
|
||||
return 2;
|
||||
};
|
||||
class GHASH {
|
||||
// We select bits per window adaptively based on expectedLength
|
||||
constructor(key, expectedLength) {
|
||||
this.blockLen = BLOCK_SIZE;
|
||||
this.outputLen = BLOCK_SIZE;
|
||||
this.s0 = 0;
|
||||
this.s1 = 0;
|
||||
this.s2 = 0;
|
||||
this.s3 = 0;
|
||||
this.finished = false;
|
||||
key = (0, utils_js_1.toBytes)(key);
|
||||
(0, _assert_js_1.bytes)(key, 16);
|
||||
const kView = (0, utils_js_1.createView)(key);
|
||||
let k0 = kView.getUint32(0, false);
|
||||
let k1 = kView.getUint32(4, false);
|
||||
let k2 = kView.getUint32(8, false);
|
||||
let k3 = kView.getUint32(12, false);
|
||||
// generate table of doubled keys (half of montgomery ladder)
|
||||
const doubles = [];
|
||||
for (let i = 0; i < 128; i++) {
|
||||
doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
|
||||
({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3));
|
||||
}
|
||||
const W = estimateWindow(expectedLength || 1024);
|
||||
if (![1, 2, 4, 8].includes(W))
|
||||
throw new Error(`ghash: wrong window size=${W}, should be 2, 4 or 8`);
|
||||
this.W = W;
|
||||
const bits = 128; // always 128 bits;
|
||||
const windows = bits / W;
|
||||
const windowSize = (this.windowSize = 2 ** W);
|
||||
const items = [];
|
||||
// Create precompute table for window of W bits
|
||||
for (let w = 0; w < windows; w++) {
|
||||
// truth table: 00, 01, 10, 11
|
||||
for (let byte = 0; byte < windowSize; byte++) {
|
||||
// prettier-ignore
|
||||
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
||||
for (let j = 0; j < W; j++) {
|
||||
const bit = (byte >>> (W - j - 1)) & 1;
|
||||
if (!bit)
|
||||
continue;
|
||||
const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
|
||||
(s0 ^= d0), (s1 ^= d1), (s2 ^= d2), (s3 ^= d3);
|
||||
}
|
||||
items.push({ s0, s1, s2, s3 });
|
||||
}
|
||||
}
|
||||
this.t = items;
|
||||
}
|
||||
_updateBlock(s0, s1, s2, s3) {
|
||||
(s0 ^= this.s0), (s1 ^= this.s1), (s2 ^= this.s2), (s3 ^= this.s3);
|
||||
const { W, t, windowSize } = this;
|
||||
// prettier-ignore
|
||||
let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
|
||||
const mask = (1 << W) - 1; // 2**W will kill performance.
|
||||
let w = 0;
|
||||
for (const num of [s0, s1, s2, s3]) {
|
||||
for (let bytePos = 0; bytePos < 4; bytePos++) {
|
||||
const byte = (num >>> (8 * bytePos)) & 0xff;
|
||||
for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
|
||||
const bit = (byte >>> (W * bitPos)) & mask;
|
||||
const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
|
||||
(o0 ^= e0), (o1 ^= e1), (o2 ^= e2), (o3 ^= e3);
|
||||
w += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.s0 = o0;
|
||||
this.s1 = o1;
|
||||
this.s2 = o2;
|
||||
this.s3 = o3;
|
||||
}
|
||||
update(data) {
|
||||
data = (0, utils_js_1.toBytes)(data);
|
||||
(0, _assert_js_1.exists)(this);
|
||||
const b32 = (0, utils_js_1.u32)(data);
|
||||
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
||||
const left = data.length % BLOCK_SIZE;
|
||||
for (let i = 0; i < blocks; i++) {
|
||||
this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
|
||||
}
|
||||
if (left) {
|
||||
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
||||
this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
|
||||
ZEROS32.fill(0); // clean tmp buffer
|
||||
}
|
||||
return this;
|
||||
}
|
||||
destroy() {
|
||||
const { t } = this;
|
||||
// clean precompute table
|
||||
for (const elm of t) {
|
||||
(elm.s0 = 0), (elm.s1 = 0), (elm.s2 = 0), (elm.s3 = 0);
|
||||
}
|
||||
}
|
||||
digestInto(out) {
|
||||
(0, _assert_js_1.exists)(this);
|
||||
(0, _assert_js_1.output)(out, this);
|
||||
this.finished = true;
|
||||
const { s0, s1, s2, s3 } = this;
|
||||
const o32 = (0, utils_js_1.u32)(out);
|
||||
o32[0] = s0;
|
||||
o32[1] = s1;
|
||||
o32[2] = s2;
|
||||
o32[3] = s3;
|
||||
return out;
|
||||
}
|
||||
digest() {
|
||||
const res = new Uint8Array(BLOCK_SIZE);
|
||||
this.digestInto(res);
|
||||
this.destroy();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
class Polyval extends GHASH {
|
||||
constructor(key, expectedLength) {
|
||||
key = (0, utils_js_1.toBytes)(key);
|
||||
const ghKey = _toGHASHKey(key.slice());
|
||||
super(ghKey, expectedLength);
|
||||
ghKey.fill(0);
|
||||
}
|
||||
update(data) {
|
||||
data = (0, utils_js_1.toBytes)(data);
|
||||
(0, _assert_js_1.exists)(this);
|
||||
const b32 = (0, utils_js_1.u32)(data);
|
||||
const left = data.length % BLOCK_SIZE;
|
||||
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
||||
for (let i = 0; i < blocks; i++) {
|
||||
this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
|
||||
}
|
||||
if (left) {
|
||||
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
||||
this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
|
||||
ZEROS32.fill(0); // clean tmp buffer
|
||||
}
|
||||
return this;
|
||||
}
|
||||
digestInto(out) {
|
||||
(0, _assert_js_1.exists)(this);
|
||||
(0, _assert_js_1.output)(out, this);
|
||||
this.finished = true;
|
||||
// tmp ugly hack
|
||||
const { s0, s1, s2, s3 } = this;
|
||||
const o32 = (0, utils_js_1.u32)(out);
|
||||
o32[0] = s0;
|
||||
o32[1] = s1;
|
||||
o32[2] = s2;
|
||||
o32[3] = s3;
|
||||
return out.reverse();
|
||||
}
|
||||
}
|
||||
function wrapConstructorWithKey(hashCons) {
|
||||
const hashC = (msg, key) => hashCons(key, msg.length).update((0, utils_js_1.toBytes)(msg)).digest();
|
||||
const tmp = hashCons(new Uint8Array(16), 0);
|
||||
hashC.outputLen = tmp.outputLen;
|
||||
hashC.blockLen = tmp.blockLen;
|
||||
hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
|
||||
return hashC;
|
||||
}
|
||||
exports.ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
|
||||
exports.polyval = wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
|
||||
//# sourceMappingURL=_polyval.js.map
|
||||
1
node_modules/@noble/ciphers/_polyval.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/_polyval.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
86
node_modules/@noble/ciphers/aes.d.ts
generated
vendored
Normal file
86
node_modules/@noble/ciphers/aes.d.ts
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Cipher, CipherWithOutput } from './utils.js';
|
||||
export declare function expandKeyLE(key: Uint8Array): Uint32Array;
|
||||
export declare function expandKeyDecLE(key: Uint8Array): Uint32Array;
|
||||
declare function encrypt(xk: Uint32Array, s0: number, s1: number, s2: number, s3: number): {
|
||||
s0: number;
|
||||
s1: number;
|
||||
s2: number;
|
||||
s3: number;
|
||||
};
|
||||
declare function decrypt(xk: Uint32Array, s0: number, s1: number, s2: number, s3: number): {
|
||||
s0: number;
|
||||
s1: number;
|
||||
s2: number;
|
||||
s3: number;
|
||||
};
|
||||
declare function ctrCounter(xk: Uint32Array, nonce: Uint8Array, src: Uint8Array, dst?: Uint8Array): Uint8Array;
|
||||
declare function ctr32(xk: Uint32Array, isLE: boolean, nonce: Uint8Array, src: Uint8Array, dst?: Uint8Array): Uint8Array;
|
||||
/**
|
||||
* CTR: counter mode. Creates stream cipher.
|
||||
* Requires good IV. Parallelizable. OK, but no MAC.
|
||||
*/
|
||||
export declare const ctr: ((key: Uint8Array, nonce: Uint8Array) => CipherWithOutput) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
};
|
||||
export type BlockOpts = {
|
||||
disablePadding?: boolean;
|
||||
};
|
||||
/**
|
||||
* ECB: Electronic CodeBook. Simple deterministic replacement.
|
||||
* Dangerous: always map x to y. See [AES Penguin](https://words.filippo.io/the-ecb-penguin/).
|
||||
*/
|
||||
export declare const ecb: ((key: Uint8Array, opts?: BlockOpts) => CipherWithOutput) & {
|
||||
blockSize: number;
|
||||
};
|
||||
/**
|
||||
* CBC: Cipher-Block-Chaining. Key is previous round’s block.
|
||||
* Fragile: needs proper padding. Unauthenticated: needs MAC.
|
||||
*/
|
||||
export declare const cbc: ((key: Uint8Array, iv: Uint8Array, opts?: BlockOpts) => CipherWithOutput) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
};
|
||||
/**
|
||||
* CFB: Cipher Feedback Mode. The input for the block cipher is the previous cipher output.
|
||||
* Unauthenticated: needs MAC.
|
||||
*/
|
||||
export declare const cfb: ((key: Uint8Array, iv: Uint8Array) => CipherWithOutput) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
};
|
||||
/**
|
||||
* GCM: Galois/Counter Mode.
|
||||
* Good, modern version of CTR, parallel, with MAC.
|
||||
* Be careful: MACs can be forged.
|
||||
*/
|
||||
export declare const gcm: ((key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
tagLength: number;
|
||||
};
|
||||
/**
|
||||
* AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance.
|
||||
* Guarantees that, when a nonce is repeated, the only security loss is that identical
|
||||
* plaintexts will produce identical ciphertexts.
|
||||
* RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452
|
||||
*/
|
||||
export declare const siv: ((key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => Cipher) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
tagLength: number;
|
||||
};
|
||||
declare function encryptBlock(xk: Uint32Array, block: Uint8Array): Uint8Array;
|
||||
declare function decryptBlock(xk: Uint32Array, block: Uint8Array): Uint8Array;
|
||||
export declare const unsafe: {
|
||||
expandKeyLE: typeof expandKeyLE;
|
||||
expandKeyDecLE: typeof expandKeyDecLE;
|
||||
encrypt: typeof encrypt;
|
||||
decrypt: typeof decrypt;
|
||||
encryptBlock: typeof encryptBlock;
|
||||
decryptBlock: typeof decryptBlock;
|
||||
ctrCounter: typeof ctrCounter;
|
||||
ctr32: typeof ctr32;
|
||||
};
|
||||
export {};
|
||||
//# sourceMappingURL=aes.d.ts.map
|
||||
1
node_modules/@noble/ciphers/aes.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/aes.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"aes.d.ts","sourceRoot":"","sources":["src/aes.ts"],"names":[],"mappings":"AACA,OAAO,EACO,MAAM,EAAE,gBAAgB,EAErC,MAAM,YAAY,CAAC;AAmGpB,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,CAmBxD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,CAkB3D;AAwBD,iBAAS,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;EAkB/E;AAED,iBAAS,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;;;;;EAkB/E;AAWD,iBAAS,UAAU,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,UAAU,cAmCxF;AAKD,iBAAS,KAAK,CACZ,EAAE,EAAE,WAAW,EACf,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,UAAU,EACf,GAAG,CAAC,EAAE,UAAU,cAiCjB;AAED;;;GAGG;AACH,eAAO,MAAM,GAAG,SAEI,UAAU,SAAS,UAAU,KAAG,gBAAgB;;;CAgBnE,CAAC;AAgDF,MAAM,MAAM,SAAS,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAErD;;;GAGG;AACH,eAAO,MAAM,GAAG,SAEI,UAAU,SAAQ,SAAS,KAAQ,gBAAgB;;CAoCtE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,GAAG,SAEI,UAAU,MAAM,UAAU,SAAQ,SAAS,KAAQ,gBAAgB;;;CA+CtF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,GAAG,SAEI,UAAU,MAAM,UAAU,KAAG,gBAAgB;;;CAqChE,CAAC;AAqBF;;;;GAIG;AACH,eAAO,MAAM,GAAG,SAEI,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MAAM;;;;CAyD3E,CAAC;AAOF;;;;;GAKG;AACH,eAAO,MAAM,GAAG,SAEI,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,MAAM;;;;CAqF3E,CAAC;AAUF,iBAAS,YAAY,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,cAOvD;AAED,iBAAS,YAAY,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,cAOvD;AAID,eAAO,MAAM,MAAM;;;;;;;;;CASlB,CAAC"}
|
||||
675
node_modules/@noble/ciphers/aes.js
generated
vendored
Normal file
675
node_modules/@noble/ciphers/aes.js
generated
vendored
Normal file
@@ -0,0 +1,675 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.unsafe = exports.siv = exports.gcm = exports.cfb = exports.cbc = exports.ecb = exports.ctr = exports.expandKeyDecLE = exports.expandKeyLE = void 0;
|
||||
// prettier-ignore
|
||||
const utils_js_1 = require("./utils.js");
|
||||
const _polyval_js_1 = require("./_polyval.js");
|
||||
const _assert_js_1 = require("./_assert.js");
|
||||
/*
|
||||
AES (Advanced Encryption Standard) aka Rijndael block cipher.
|
||||
|
||||
Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256 bits). In every round:
|
||||
1. **S-box**, table substitution
|
||||
2. **Shift rows**, cyclic shift left of all rows of data array
|
||||
3. **Mix columns**, multiplying every column by fixed polynomial
|
||||
4. **Add round key**, round_key xor i-th column of array
|
||||
|
||||
Resources:
|
||||
- FIPS-197 https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf
|
||||
- Original proposal: https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf
|
||||
*/
|
||||
const BLOCK_SIZE = 16;
|
||||
const BLOCK_SIZE32 = 4;
|
||||
const EMPTY_BLOCK = new Uint8Array(BLOCK_SIZE);
|
||||
const POLY = 0x11b; // 1 + x + x**3 + x**4 + x**8
|
||||
// TODO: remove multiplication, binary ops only
|
||||
function mul2(n) {
|
||||
return (n << 1) ^ (POLY & -(n >> 7));
|
||||
}
|
||||
function mul(a, b) {
|
||||
let res = 0;
|
||||
for (; b > 0; b >>= 1) {
|
||||
// Montgomery ladder
|
||||
res ^= a & -(b & 1); // if (b&1) res ^=a (but const-time).
|
||||
a = mul2(a); // a = 2*a
|
||||
}
|
||||
return res;
|
||||
}
|
||||
// AES S-box is generated using finite field inversion,
|
||||
// an affine transform, and xor of a constant 0x63.
|
||||
const sbox = /* @__PURE__ */ (() => {
|
||||
let t = new Uint8Array(256);
|
||||
for (let i = 0, x = 1; i < 256; i++, x ^= mul2(x))
|
||||
t[i] = x;
|
||||
const box = new Uint8Array(256);
|
||||
box[0] = 0x63; // first elm
|
||||
for (let i = 0; i < 255; i++) {
|
||||
let x = t[255 - i];
|
||||
x |= x << 8;
|
||||
box[t[i]] = (x ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63) & 0xff;
|
||||
}
|
||||
return box;
|
||||
})();
|
||||
// Inverted S-box
|
||||
const invSbox = /* @__PURE__ */ sbox.map((_, j) => sbox.indexOf(j));
|
||||
// Rotate u32 by 8
|
||||
const rotr32_8 = (n) => (n << 24) | (n >>> 8);
|
||||
const rotl32_8 = (n) => (n << 8) | (n >>> 24);
|
||||
// T-table is optimization suggested in 5.2 of original proposal (missed from FIPS-197). Changes:
|
||||
// - LE instead of BE
|
||||
// - bigger tables: T0 and T1 are merged into T01 table and T2 & T3 into T23;
|
||||
// so index is u16, instead of u8. This speeds up things, unexpectedly
|
||||
function genTtable(sbox, fn) {
|
||||
if (sbox.length !== 256)
|
||||
throw new Error('Wrong sbox length');
|
||||
const T0 = new Uint32Array(256).map((_, j) => fn(sbox[j]));
|
||||
const T1 = T0.map(rotl32_8);
|
||||
const T2 = T1.map(rotl32_8);
|
||||
const T3 = T2.map(rotl32_8);
|
||||
const T01 = new Uint32Array(256 * 256);
|
||||
const T23 = new Uint32Array(256 * 256);
|
||||
const sbox2 = new Uint16Array(256 * 256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
for (let j = 0; j < 256; j++) {
|
||||
const idx = i * 256 + j;
|
||||
T01[idx] = T0[i] ^ T1[j];
|
||||
T23[idx] = T2[i] ^ T3[j];
|
||||
sbox2[idx] = (sbox[i] << 8) | sbox[j];
|
||||
}
|
||||
}
|
||||
return { sbox, sbox2, T0, T1, T2, T3, T01, T23 };
|
||||
}
|
||||
const tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => (mul(s, 3) << 24) | (s << 16) | (s << 8) | mul(s, 2));
|
||||
const tableDecoding = /* @__PURE__ */ genTtable(invSbox, (s) => (mul(s, 11) << 24) | (mul(s, 13) << 16) | (mul(s, 9) << 8) | mul(s, 14));
|
||||
const xPowers = /* @__PURE__ */ (() => {
|
||||
const p = new Uint8Array(16);
|
||||
for (let i = 0, x = 1; i < 16; i++, x = mul2(x))
|
||||
p[i] = x;
|
||||
return p;
|
||||
})();
|
||||
function expandKeyLE(key) {
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
const len = key.length;
|
||||
if (![16, 24, 32].includes(len))
|
||||
throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`);
|
||||
const { sbox2 } = tableEncoding;
|
||||
const k32 = (0, utils_js_1.u32)(key);
|
||||
const Nk = k32.length;
|
||||
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
||||
const xk = new Uint32Array(len + 28); // expanded key
|
||||
xk.set(k32);
|
||||
// 4.3.1 Key expansion
|
||||
for (let i = Nk; i < xk.length; i++) {
|
||||
let t = xk[i - 1];
|
||||
if (i % Nk === 0)
|
||||
t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
|
||||
else if (Nk > 6 && i % Nk === 4)
|
||||
t = subByte(t);
|
||||
xk[i] = xk[i - Nk] ^ t;
|
||||
}
|
||||
return xk;
|
||||
}
|
||||
exports.expandKeyLE = expandKeyLE;
|
||||
function expandKeyDecLE(key) {
|
||||
const encKey = expandKeyLE(key);
|
||||
const xk = encKey.slice();
|
||||
const Nk = encKey.length;
|
||||
const { sbox2 } = tableEncoding;
|
||||
const { T0, T1, T2, T3 } = tableDecoding;
|
||||
// Inverse key by chunks of 4 (rounds)
|
||||
for (let i = 0; i < Nk; i += 4) {
|
||||
for (let j = 0; j < 4; j++)
|
||||
xk[i + j] = encKey[Nk - i - 4 + j];
|
||||
}
|
||||
encKey.fill(0);
|
||||
// apply InvMixColumn except first & last round
|
||||
for (let i = 4; i < Nk - 4; i++) {
|
||||
const x = xk[i];
|
||||
const w = applySbox(sbox2, x, x, x, x);
|
||||
xk[i] = T0[w & 0xff] ^ T1[(w >>> 8) & 0xff] ^ T2[(w >>> 16) & 0xff] ^ T3[w >>> 24];
|
||||
}
|
||||
return xk;
|
||||
}
|
||||
exports.expandKeyDecLE = expandKeyDecLE;
|
||||
// Apply tables
|
||||
function apply0123(T01, T23, s0, s1, s2, s3) {
|
||||
return (T01[((s0 << 8) & 0xff00) | ((s1 >>> 8) & 0xff)] ^
|
||||
T23[((s2 >>> 8) & 0xff00) | ((s3 >>> 24) & 0xff)]);
|
||||
}
|
||||
function applySbox(sbox2, s0, s1, s2, s3) {
|
||||
return (sbox2[(s0 & 0xff) | (s1 & 0xff00)] |
|
||||
(sbox2[((s2 >>> 16) & 0xff) | ((s3 >>> 16) & 0xff00)] << 16));
|
||||
}
|
||||
function encrypt(xk, s0, s1, s2, s3) {
|
||||
const { sbox2, T01, T23 } = tableEncoding;
|
||||
let k = 0;
|
||||
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
|
||||
const rounds = xk.length / 4 - 2;
|
||||
for (let i = 0; i < rounds; i++) {
|
||||
const t0 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
|
||||
const t1 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
|
||||
const t2 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
|
||||
const t3 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
|
||||
(s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
|
||||
}
|
||||
// last round (without mixcolumns, so using SBOX2 table)
|
||||
const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
|
||||
const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
|
||||
const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
|
||||
const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
|
||||
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
||||
}
|
||||
function decrypt(xk, s0, s1, s2, s3) {
|
||||
const { sbox2, T01, T23 } = tableDecoding;
|
||||
let k = 0;
|
||||
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
|
||||
const rounds = xk.length / 4 - 2;
|
||||
for (let i = 0; i < rounds; i++) {
|
||||
const t0 = xk[k++] ^ apply0123(T01, T23, s0, s3, s2, s1);
|
||||
const t1 = xk[k++] ^ apply0123(T01, T23, s1, s0, s3, s2);
|
||||
const t2 = xk[k++] ^ apply0123(T01, T23, s2, s1, s0, s3);
|
||||
const t3 = xk[k++] ^ apply0123(T01, T23, s3, s2, s1, s0);
|
||||
(s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
|
||||
}
|
||||
// Last round
|
||||
const t0 = xk[k++] ^ applySbox(sbox2, s0, s3, s2, s1);
|
||||
const t1 = xk[k++] ^ applySbox(sbox2, s1, s0, s3, s2);
|
||||
const t2 = xk[k++] ^ applySbox(sbox2, s2, s1, s0, s3);
|
||||
const t3 = xk[k++] ^ applySbox(sbox2, s3, s2, s1, s0);
|
||||
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
||||
}
|
||||
function getDst(len, dst) {
|
||||
if (!dst)
|
||||
return new Uint8Array(len);
|
||||
(0, _assert_js_1.bytes)(dst);
|
||||
if (dst.length < len)
|
||||
throw new Error(`aes: wrong destination length, expected at least ${len}, got: ${dst.length}`);
|
||||
return dst;
|
||||
}
|
||||
// TODO: investigate merging with ctr32
|
||||
function ctrCounter(xk, nonce, src, dst) {
|
||||
(0, _assert_js_1.bytes)(nonce, BLOCK_SIZE);
|
||||
(0, _assert_js_1.bytes)(src);
|
||||
const srcLen = src.length;
|
||||
dst = getDst(srcLen, dst);
|
||||
const ctr = nonce;
|
||||
const c32 = (0, utils_js_1.u32)(ctr);
|
||||
// Fill block (empty, ctr=0)
|
||||
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
||||
const src32 = (0, utils_js_1.u32)(src);
|
||||
const dst32 = (0, utils_js_1.u32)(dst);
|
||||
// process blocks
|
||||
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
||||
dst32[i + 0] = src32[i + 0] ^ s0;
|
||||
dst32[i + 1] = src32[i + 1] ^ s1;
|
||||
dst32[i + 2] = src32[i + 2] ^ s2;
|
||||
dst32[i + 3] = src32[i + 3] ^ s3;
|
||||
// Full 128 bit counter with wrap around
|
||||
let carry = 1;
|
||||
for (let i = ctr.length - 1; i >= 0; i--) {
|
||||
carry = (carry + (ctr[i] & 0xff)) | 0;
|
||||
ctr[i] = carry & 0xff;
|
||||
carry >>>= 8;
|
||||
}
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
||||
}
|
||||
// leftovers (less than block)
|
||||
// It's possible to handle > u32 fast, but is it worth it?
|
||||
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
||||
if (start < srcLen) {
|
||||
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
||||
const buf = (0, utils_js_1.u8)(b32);
|
||||
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
||||
dst[i] = src[i] ^ buf[pos];
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
// AES CTR with overflowing 32 bit counter
|
||||
// It's possible to do 32le significantly simpler (and probably faster) by using u32.
|
||||
// But, we need both, and perf bottleneck is in ghash anyway.
|
||||
function ctr32(xk, isLE, nonce, src, dst) {
|
||||
(0, _assert_js_1.bytes)(nonce, BLOCK_SIZE);
|
||||
(0, _assert_js_1.bytes)(src);
|
||||
dst = getDst(src.length, dst);
|
||||
const ctr = nonce; // write new value to nonce, so it can be re-used
|
||||
const c32 = (0, utils_js_1.u32)(ctr);
|
||||
const view = (0, utils_js_1.createView)(ctr);
|
||||
const src32 = (0, utils_js_1.u32)(src);
|
||||
const dst32 = (0, utils_js_1.u32)(dst);
|
||||
const ctrPos = isLE ? 0 : 12;
|
||||
const srcLen = src.length;
|
||||
// Fill block (empty, ctr=0)
|
||||
let ctrNum = view.getUint32(ctrPos, isLE); // read current counter value
|
||||
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
||||
// process blocks
|
||||
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
||||
dst32[i + 0] = src32[i + 0] ^ s0;
|
||||
dst32[i + 1] = src32[i + 1] ^ s1;
|
||||
dst32[i + 2] = src32[i + 2] ^ s2;
|
||||
dst32[i + 3] = src32[i + 3] ^ s3;
|
||||
ctrNum = (ctrNum + 1) >>> 0; // u32 wrap
|
||||
view.setUint32(ctrPos, ctrNum, isLE);
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
||||
}
|
||||
// leftovers (less than a block)
|
||||
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
||||
if (start < srcLen) {
|
||||
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
||||
const buf = (0, utils_js_1.u8)(b32);
|
||||
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
||||
dst[i] = src[i] ^ buf[pos];
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
/**
|
||||
* CTR: counter mode. Creates stream cipher.
|
||||
* Requires good IV. Parallelizable. OK, but no MAC.
|
||||
*/
|
||||
exports.ctr = (0, utils_js_1.wrapCipher)({ blockSize: 16, nonceLength: 16 }, function ctr(key, nonce) {
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
(0, _assert_js_1.bytes)(nonce, BLOCK_SIZE);
|
||||
function processCtr(buf, dst) {
|
||||
const xk = expandKeyLE(key);
|
||||
const n = nonce.slice();
|
||||
const out = ctrCounter(xk, n, buf, dst);
|
||||
xk.fill(0);
|
||||
n.fill(0);
|
||||
return out;
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext, dst) => processCtr(plaintext, dst),
|
||||
decrypt: (ciphertext, dst) => processCtr(ciphertext, dst),
|
||||
};
|
||||
});
|
||||
function validateBlockDecrypt(data) {
|
||||
(0, _assert_js_1.bytes)(data);
|
||||
if (data.length % BLOCK_SIZE !== 0) {
|
||||
throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE}`);
|
||||
}
|
||||
}
|
||||
function validateBlockEncrypt(plaintext, pcks5, dst) {
|
||||
let outLen = plaintext.length;
|
||||
const remaining = outLen % BLOCK_SIZE;
|
||||
if (!pcks5 && remaining !== 0)
|
||||
throw new Error('aec/(cbc-ecb): unpadded plaintext with disabled padding');
|
||||
const b = (0, utils_js_1.u32)(plaintext);
|
||||
if (pcks5) {
|
||||
let left = BLOCK_SIZE - remaining;
|
||||
if (!left)
|
||||
left = BLOCK_SIZE; // if no bytes left, create empty padding block
|
||||
outLen = outLen + left;
|
||||
}
|
||||
const out = getDst(outLen, dst);
|
||||
const o = (0, utils_js_1.u32)(out);
|
||||
return { b, o, out };
|
||||
}
|
||||
function validatePCKS(data, pcks5) {
|
||||
if (!pcks5)
|
||||
return data;
|
||||
const len = data.length;
|
||||
if (!len)
|
||||
throw new Error(`aes/pcks5: empty ciphertext not allowed`);
|
||||
const lastByte = data[len - 1];
|
||||
if (lastByte <= 0 || lastByte > 16)
|
||||
throw new Error(`aes/pcks5: wrong padding byte: ${lastByte}`);
|
||||
const out = data.subarray(0, -lastByte);
|
||||
for (let i = 0; i < lastByte; i++)
|
||||
if (data[len - i - 1] !== lastByte)
|
||||
throw new Error(`aes/pcks5: wrong padding`);
|
||||
return out;
|
||||
}
|
||||
function padPCKS(left) {
|
||||
const tmp = new Uint8Array(16);
|
||||
const tmp32 = (0, utils_js_1.u32)(tmp);
|
||||
tmp.set(left);
|
||||
const paddingByte = BLOCK_SIZE - left.length;
|
||||
for (let i = BLOCK_SIZE - paddingByte; i < BLOCK_SIZE; i++)
|
||||
tmp[i] = paddingByte;
|
||||
return tmp32;
|
||||
}
|
||||
/**
|
||||
* ECB: Electronic CodeBook. Simple deterministic replacement.
|
||||
* Dangerous: always map x to y. See [AES Penguin](https://words.filippo.io/the-ecb-penguin/).
|
||||
*/
|
||||
exports.ecb = (0, utils_js_1.wrapCipher)({ blockSize: 16 }, function ecb(key, opts = {}) {
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
const pcks5 = !opts.disablePadding;
|
||||
return {
|
||||
encrypt: (plaintext, dst) => {
|
||||
(0, _assert_js_1.bytes)(plaintext);
|
||||
const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
|
||||
const xk = expandKeyLE(key);
|
||||
let i = 0;
|
||||
for (; i + 4 <= b.length;) {
|
||||
const { s0, s1, s2, s3 } = encrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
if (pcks5) {
|
||||
const tmp32 = padPCKS(plaintext.subarray(i * 4));
|
||||
const { s0, s1, s2, s3 } = encrypt(xk, tmp32[0], tmp32[1], tmp32[2], tmp32[3]);
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return _out;
|
||||
},
|
||||
decrypt: (ciphertext, dst) => {
|
||||
validateBlockDecrypt(ciphertext);
|
||||
const xk = expandKeyDecLE(key);
|
||||
const out = getDst(ciphertext.length, dst);
|
||||
const b = (0, utils_js_1.u32)(ciphertext);
|
||||
const o = (0, utils_js_1.u32)(out);
|
||||
for (let i = 0; i + 4 <= b.length;) {
|
||||
const { s0, s1, s2, s3 } = decrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return validatePCKS(out, pcks5);
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* CBC: Cipher-Block-Chaining. Key is previous round’s block.
|
||||
* Fragile: needs proper padding. Unauthenticated: needs MAC.
|
||||
*/
|
||||
exports.cbc = (0, utils_js_1.wrapCipher)({ blockSize: 16, nonceLength: 16 }, function cbc(key, iv, opts = {}) {
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
(0, _assert_js_1.bytes)(iv, 16);
|
||||
const pcks5 = !opts.disablePadding;
|
||||
return {
|
||||
encrypt: (plaintext, dst) => {
|
||||
const xk = expandKeyLE(key);
|
||||
const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
|
||||
const n32 = (0, utils_js_1.u32)(iv);
|
||||
// prettier-ignore
|
||||
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
||||
let i = 0;
|
||||
for (; i + 4 <= b.length;) {
|
||||
(s0 ^= b[i + 0]), (s1 ^= b[i + 1]), (s2 ^= b[i + 2]), (s3 ^= b[i + 3]);
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
if (pcks5) {
|
||||
const tmp32 = padPCKS(plaintext.subarray(i * 4));
|
||||
(s0 ^= tmp32[0]), (s1 ^= tmp32[1]), (s2 ^= tmp32[2]), (s3 ^= tmp32[3]);
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return _out;
|
||||
},
|
||||
decrypt: (ciphertext, dst) => {
|
||||
validateBlockDecrypt(ciphertext);
|
||||
const xk = expandKeyDecLE(key);
|
||||
const n32 = (0, utils_js_1.u32)(iv);
|
||||
const out = getDst(ciphertext.length, dst);
|
||||
const b = (0, utils_js_1.u32)(ciphertext);
|
||||
const o = (0, utils_js_1.u32)(out);
|
||||
// prettier-ignore
|
||||
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
||||
for (let i = 0; i + 4 <= b.length;) {
|
||||
// prettier-ignore
|
||||
const ps0 = s0, ps1 = s1, ps2 = s2, ps3 = s3;
|
||||
(s0 = b[i + 0]), (s1 = b[i + 1]), (s2 = b[i + 2]), (s3 = b[i + 3]);
|
||||
const { s0: o0, s1: o1, s2: o2, s3: o3 } = decrypt(xk, s0, s1, s2, s3);
|
||||
(o[i++] = o0 ^ ps0), (o[i++] = o1 ^ ps1), (o[i++] = o2 ^ ps2), (o[i++] = o3 ^ ps3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return validatePCKS(out, pcks5);
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* CFB: Cipher Feedback Mode. The input for the block cipher is the previous cipher output.
|
||||
* Unauthenticated: needs MAC.
|
||||
*/
|
||||
exports.cfb = (0, utils_js_1.wrapCipher)({ blockSize: 16, nonceLength: 16 }, function cfb(key, iv) {
|
||||
(0, _assert_js_1.bytes)(key);
|
||||
(0, _assert_js_1.bytes)(iv, 16);
|
||||
function processCfb(src, isEncrypt, dst) {
|
||||
const xk = expandKeyLE(key);
|
||||
const srcLen = src.length;
|
||||
dst = getDst(srcLen, dst);
|
||||
const src32 = (0, utils_js_1.u32)(src);
|
||||
const dst32 = (0, utils_js_1.u32)(dst);
|
||||
const next32 = isEncrypt ? dst32 : src32;
|
||||
const n32 = (0, utils_js_1.u32)(iv);
|
||||
// prettier-ignore
|
||||
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
||||
for (let i = 0; i + 4 <= src32.length;) {
|
||||
const { s0: e0, s1: e1, s2: e2, s3: e3 } = encrypt(xk, s0, s1, s2, s3);
|
||||
dst32[i + 0] = src32[i + 0] ^ e0;
|
||||
dst32[i + 1] = src32[i + 1] ^ e1;
|
||||
dst32[i + 2] = src32[i + 2] ^ e2;
|
||||
dst32[i + 3] = src32[i + 3] ^ e3;
|
||||
(s0 = next32[i++]), (s1 = next32[i++]), (s2 = next32[i++]), (s3 = next32[i++]);
|
||||
}
|
||||
// leftovers (less than block)
|
||||
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
||||
if (start < srcLen) {
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
||||
const buf = (0, utils_js_1.u8)(new Uint32Array([s0, s1, s2, s3]));
|
||||
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
||||
dst[i] = src[i] ^ buf[pos];
|
||||
buf.fill(0);
|
||||
}
|
||||
xk.fill(0);
|
||||
return dst;
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext, dst) => processCfb(plaintext, true, dst),
|
||||
decrypt: (ciphertext, dst) => processCfb(ciphertext, false, dst),
|
||||
};
|
||||
});
|
||||
// TODO: merge with chacha, however gcm has bitLen while chacha has byteLen
|
||||
function computeTag(fn, isLE, key, data, AAD) {
|
||||
const h = fn.create(key, data.length + (AAD?.length || 0));
|
||||
if (AAD)
|
||||
h.update(AAD);
|
||||
h.update(data);
|
||||
const num = new Uint8Array(16);
|
||||
const view = (0, utils_js_1.createView)(num);
|
||||
if (AAD)
|
||||
(0, utils_js_1.setBigUint64)(view, 0, BigInt(AAD.length * 8), isLE);
|
||||
(0, utils_js_1.setBigUint64)(view, 8, BigInt(data.length * 8), isLE);
|
||||
h.update(num);
|
||||
return h.digest();
|
||||
}
|
||||
/**
|
||||
* GCM: Galois/Counter Mode.
|
||||
* Good, modern version of CTR, parallel, with MAC.
|
||||
* Be careful: MACs can be forged.
|
||||
*/
|
||||
exports.gcm = (0, utils_js_1.wrapCipher)({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function gcm(key, nonce, AAD) {
|
||||
(0, _assert_js_1.bytes)(nonce);
|
||||
// Nonce can be pretty much anything (even 1 byte). But smaller nonces less secure.
|
||||
if (nonce.length === 0)
|
||||
throw new Error('aes/gcm: empty nonce');
|
||||
const tagLength = 16;
|
||||
function _computeTag(authKey, tagMask, data) {
|
||||
const tag = computeTag(_polyval_js_1.ghash, false, authKey, data, AAD);
|
||||
for (let i = 0; i < tagMask.length; i++)
|
||||
tag[i] ^= tagMask[i];
|
||||
return tag;
|
||||
}
|
||||
function deriveKeys() {
|
||||
const xk = expandKeyLE(key);
|
||||
const authKey = EMPTY_BLOCK.slice();
|
||||
const counter = EMPTY_BLOCK.slice();
|
||||
ctr32(xk, false, counter, counter, authKey);
|
||||
if (nonce.length === 12) {
|
||||
counter.set(nonce);
|
||||
}
|
||||
else {
|
||||
// Spec (NIST 800-38d) supports variable size nonce.
|
||||
// Not supported for now, but can be useful.
|
||||
const nonceLen = EMPTY_BLOCK.slice();
|
||||
const view = (0, utils_js_1.createView)(nonceLen);
|
||||
(0, utils_js_1.setBigUint64)(view, 8, BigInt(nonce.length * 8), false);
|
||||
// ghash(nonce || u64be(0) || u64be(nonceLen*8))
|
||||
_polyval_js_1.ghash.create(authKey).update(nonce).update(nonceLen).digestInto(counter);
|
||||
}
|
||||
const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
|
||||
return { xk, authKey, counter, tagMask };
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
(0, _assert_js_1.bytes)(plaintext);
|
||||
const { xk, authKey, counter, tagMask } = deriveKeys();
|
||||
const out = new Uint8Array(plaintext.length + tagLength);
|
||||
ctr32(xk, false, counter, plaintext, out);
|
||||
const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
|
||||
out.set(tag, plaintext.length);
|
||||
xk.fill(0);
|
||||
return out;
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
(0, _assert_js_1.bytes)(ciphertext);
|
||||
if (ciphertext.length < tagLength)
|
||||
throw new Error(`aes/gcm: ciphertext less than tagLen (${tagLength})`);
|
||||
const { xk, authKey, counter, tagMask } = deriveKeys();
|
||||
const data = ciphertext.subarray(0, -tagLength);
|
||||
const passedTag = ciphertext.subarray(-tagLength);
|
||||
const tag = _computeTag(authKey, tagMask, data);
|
||||
if (!(0, utils_js_1.equalBytes)(tag, passedTag))
|
||||
throw new Error('aes/gcm: invalid ghash tag');
|
||||
const out = ctr32(xk, false, counter, data);
|
||||
authKey.fill(0);
|
||||
tagMask.fill(0);
|
||||
xk.fill(0);
|
||||
return out;
|
||||
},
|
||||
};
|
||||
});
|
||||
const limit = (name, min, max) => (value) => {
|
||||
if (!Number.isSafeInteger(value) || min > value || value > max)
|
||||
throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`);
|
||||
};
|
||||
/**
|
||||
* AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance.
|
||||
* Guarantees that, when a nonce is repeated, the only security loss is that identical
|
||||
* plaintexts will produce identical ciphertexts.
|
||||
* RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452
|
||||
*/
|
||||
exports.siv = (0, utils_js_1.wrapCipher)({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function siv(key, nonce, AAD) {
|
||||
const tagLength = 16;
|
||||
// From RFC 8452: Section 6
|
||||
const AAD_LIMIT = limit('AAD', 0, 2 ** 36);
|
||||
const PLAIN_LIMIT = limit('plaintext', 0, 2 ** 36);
|
||||
const NONCE_LIMIT = limit('nonce', 12, 12);
|
||||
const CIPHER_LIMIT = limit('ciphertext', 16, 2 ** 36 + 16);
|
||||
(0, _assert_js_1.bytes)(nonce);
|
||||
NONCE_LIMIT(nonce.length);
|
||||
if (AAD) {
|
||||
(0, _assert_js_1.bytes)(AAD);
|
||||
AAD_LIMIT(AAD.length);
|
||||
}
|
||||
function deriveKeys() {
|
||||
const len = key.length;
|
||||
if (len !== 16 && len !== 24 && len !== 32)
|
||||
throw new Error(`key length must be 16, 24 or 32 bytes, got: ${len} bytes`);
|
||||
const xk = expandKeyLE(key);
|
||||
const encKey = new Uint8Array(len);
|
||||
const authKey = new Uint8Array(16);
|
||||
const n32 = (0, utils_js_1.u32)(nonce);
|
||||
// prettier-ignore
|
||||
let s0 = 0, s1 = n32[0], s2 = n32[1], s3 = n32[2];
|
||||
let counter = 0;
|
||||
for (const derivedKey of [authKey, encKey].map(utils_js_1.u32)) {
|
||||
const d32 = (0, utils_js_1.u32)(derivedKey);
|
||||
for (let i = 0; i < d32.length; i += 2) {
|
||||
// aes(u32le(0) || nonce)[:8] || aes(u32le(1) || nonce)[:8] ...
|
||||
const { s0: o0, s1: o1 } = encrypt(xk, s0, s1, s2, s3);
|
||||
d32[i + 0] = o0;
|
||||
d32[i + 1] = o1;
|
||||
s0 = ++counter; // increment counter inside state
|
||||
}
|
||||
}
|
||||
xk.fill(0);
|
||||
return { authKey, encKey: expandKeyLE(encKey) };
|
||||
}
|
||||
function _computeTag(encKey, authKey, data) {
|
||||
const tag = computeTag(_polyval_js_1.polyval, true, authKey, data, AAD);
|
||||
// Compute the expected tag by XORing S_s and the nonce, clearing the
|
||||
// most significant bit of the last byte and encrypting with the
|
||||
// message-encryption key.
|
||||
for (let i = 0; i < 12; i++)
|
||||
tag[i] ^= nonce[i];
|
||||
tag[15] &= 0x7f; // Clear the highest bit
|
||||
// encrypt tag as block
|
||||
const t32 = (0, utils_js_1.u32)(tag);
|
||||
// prettier-ignore
|
||||
let s0 = t32[0], s1 = t32[1], s2 = t32[2], s3 = t32[3];
|
||||
({ s0, s1, s2, s3 } = encrypt(encKey, s0, s1, s2, s3));
|
||||
(t32[0] = s0), (t32[1] = s1), (t32[2] = s2), (t32[3] = s3);
|
||||
return tag;
|
||||
}
|
||||
// actual decrypt/encrypt of message.
|
||||
function processSiv(encKey, tag, input) {
|
||||
let block = tag.slice();
|
||||
block[15] |= 0x80; // Force highest bit
|
||||
return ctr32(encKey, true, block, input);
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
(0, _assert_js_1.bytes)(plaintext);
|
||||
PLAIN_LIMIT(plaintext.length);
|
||||
const { encKey, authKey } = deriveKeys();
|
||||
const tag = _computeTag(encKey, authKey, plaintext);
|
||||
const out = new Uint8Array(plaintext.length + tagLength);
|
||||
out.set(tag, plaintext.length);
|
||||
out.set(processSiv(encKey, tag, plaintext));
|
||||
encKey.fill(0);
|
||||
authKey.fill(0);
|
||||
return out;
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
(0, _assert_js_1.bytes)(ciphertext);
|
||||
CIPHER_LIMIT(ciphertext.length);
|
||||
const tag = ciphertext.subarray(-tagLength);
|
||||
const { encKey, authKey } = deriveKeys();
|
||||
const plaintext = processSiv(encKey, tag, ciphertext.subarray(0, -tagLength));
|
||||
const expectedTag = _computeTag(encKey, authKey, plaintext);
|
||||
encKey.fill(0);
|
||||
authKey.fill(0);
|
||||
if (!(0, utils_js_1.equalBytes)(tag, expectedTag))
|
||||
throw new Error('invalid polyval tag');
|
||||
return plaintext;
|
||||
},
|
||||
};
|
||||
});
|
||||
function isBytes32(a) {
|
||||
return (a != null &&
|
||||
typeof a === 'object' &&
|
||||
(a instanceof Uint32Array || a.constructor.name === 'Uint32Array'));
|
||||
}
|
||||
function encryptBlock(xk, block) {
|
||||
(0, _assert_js_1.bytes)(block, 16);
|
||||
if (!isBytes32(xk))
|
||||
throw new Error('_encryptBlock accepts result of expandKeyLE');
|
||||
const b32 = (0, utils_js_1.u32)(block);
|
||||
let { s0, s1, s2, s3 } = encrypt(xk, b32[0], b32[1], b32[2], b32[3]);
|
||||
(b32[0] = s0), (b32[1] = s1), (b32[2] = s2), (b32[3] = s3);
|
||||
return block;
|
||||
}
|
||||
function decryptBlock(xk, block) {
|
||||
(0, _assert_js_1.bytes)(block, 16);
|
||||
if (!isBytes32(xk))
|
||||
throw new Error('_decryptBlock accepts result of expandKeyLE');
|
||||
const b32 = (0, utils_js_1.u32)(block);
|
||||
let { s0, s1, s2, s3 } = decrypt(xk, b32[0], b32[1], b32[2], b32[3]);
|
||||
(b32[0] = s0), (b32[1] = s1), (b32[2] = s2), (b32[3] = s3);
|
||||
return block;
|
||||
}
|
||||
// Highly unsafe private functions for implementing new modes or ciphers based on AES
|
||||
// Can change at any time, no API guarantees
|
||||
exports.unsafe = {
|
||||
expandKeyLE,
|
||||
expandKeyDecLE,
|
||||
encrypt,
|
||||
decrypt,
|
||||
encryptBlock,
|
||||
decryptBlock,
|
||||
ctrCounter,
|
||||
ctr32,
|
||||
};
|
||||
//# sourceMappingURL=aes.js.map
|
||||
1
node_modules/@noble/ciphers/aes.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/aes.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
61
node_modules/@noble/ciphers/chacha.d.ts
generated
vendored
Normal file
61
node_modules/@noble/ciphers/chacha.d.ts
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import { CipherWithOutput, XorStream } from './utils.js';
|
||||
/**
|
||||
* hchacha helper method, used primarily in xchacha, to hash
|
||||
* key and nonce into key' and nonce'.
|
||||
* Same as chachaCore, but there doesn't seem to be a way to move the block
|
||||
* out without 25% performance hit.
|
||||
*/
|
||||
export declare function hchacha(s: Uint32Array, k: Uint32Array, i: Uint32Array, o32: Uint32Array): void;
|
||||
/**
|
||||
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
|
||||
*/
|
||||
export declare const chacha20orig: XorStream;
|
||||
/**
|
||||
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
|
||||
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
|
||||
*/
|
||||
export declare const chacha20: XorStream;
|
||||
/**
|
||||
* XChaCha eXtended-nonce ChaCha. 24-byte nonce.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
*/
|
||||
export declare const xchacha20: XorStream;
|
||||
/**
|
||||
* Reduced 8-round chacha, described in original paper.
|
||||
*/
|
||||
export declare const chacha8: XorStream;
|
||||
/**
|
||||
* Reduced 12-round chacha, described in original paper.
|
||||
*/
|
||||
export declare const chacha12: XorStream;
|
||||
/**
|
||||
* AEAD algorithm from RFC 8439.
|
||||
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
|
||||
* We could have composed them similar to:
|
||||
* https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
|
||||
* But it's hard because of authKey:
|
||||
* In salsa20, authKey changes position in salsa stream.
|
||||
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
|
||||
*/
|
||||
export declare const _poly1305_aead: (xorStream: XorStream) => (key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => CipherWithOutput;
|
||||
/**
|
||||
* ChaCha20-Poly1305 from RFC 8439.
|
||||
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
|
||||
*/
|
||||
export declare const chacha20poly1305: ((key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => CipherWithOutput) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
tagLength: number;
|
||||
};
|
||||
/**
|
||||
* XChaCha20-Poly1305 extended-nonce chacha.
|
||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
*/
|
||||
export declare const xchacha20poly1305: ((key: Uint8Array, nonce: Uint8Array, AAD?: Uint8Array) => CipherWithOutput) & {
|
||||
blockSize: number;
|
||||
nonceLength: number;
|
||||
tagLength: number;
|
||||
};
|
||||
//# sourceMappingURL=chacha.d.ts.map
|
||||
1
node_modules/@noble/ciphers/chacha.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/chacha.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"chacha.d.ts","sourceRoot":"","sources":["src/chacha.ts"],"names":[],"mappings":"AACA,OAAO,EACO,gBAAgB,EAAE,SAAS,EACxC,MAAM,YAAY,CAAC;AA6EpB;;;;;GAKG;AAEH,wBAAgB,OAAO,CACrB,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,WAAW,QAoDjE;AACD;;GAEG;AACH,eAAO,MAAM,YAAY,WAIvB,CAAC;AACH;;;GAGG;AACH,eAAO,MAAM,QAAQ,WAInB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,SAAS,WAKpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,OAAO,WAIlB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,QAAQ,WAInB,CAAC;AAgCH;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,cACb,SAAS,WACf,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,gBAoCvD,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,gBAAgB,SA1CrB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,gBAAgB;;;;CA6CzE,CAAC;AACF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,SAnDtB,UAAU,SAAS,UAAU,QAAQ,UAAU,KAAG,gBAAgB;;;;CAsDzE,CAAC"}
|
||||
323
node_modules/@noble/ciphers/chacha.js
generated
vendored
Normal file
323
node_modules/@noble/ciphers/chacha.js
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.xchacha20poly1305 = exports.chacha20poly1305 = exports._poly1305_aead = exports.chacha12 = exports.chacha8 = exports.xchacha20 = exports.chacha20 = exports.chacha20orig = exports.hchacha = void 0;
|
||||
// prettier-ignore
|
||||
const utils_js_1 = require("./utils.js");
|
||||
const _poly1305_js_1 = require("./_poly1305.js");
|
||||
const _arx_js_1 = require("./_arx.js");
|
||||
const _assert_js_1 = require("./_assert.js");
|
||||
// ChaCha20 stream cipher was released in 2008. ChaCha aims to increase
|
||||
// the diffusion per round, but had slightly less cryptanalysis.
|
||||
// https://cr.yp.to/chacha.html, http://cr.yp.to/chacha/chacha-20080128.pdf
|
||||
/**
|
||||
* ChaCha core function.
|
||||
*/
|
||||
// prettier-ignore
|
||||
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
||||
let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], // "expa" "nd 3" "2-by" "te k"
|
||||
y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], // Key Key Key Key
|
||||
y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], // Key Key Key Key
|
||||
y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Counter Nonce Nonce
|
||||
// Save state to temporary variables
|
||||
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
|
||||
for (let r = 0; r < rounds; r += 2) {
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x00, 16);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x08, 12);
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x00, 8);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x08, 7);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x01, 16);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x09, 12);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x01, 8);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x09, 7);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x02, 16);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x10, 12);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x02, 8);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x10, 7);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x03, 16);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x11, 12);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x03, 8);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x11, 7);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x00, 16);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x10, 12);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x00, 8);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x10, 7);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x01, 16);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x11, 12);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x01, 8);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x11, 7);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x02, 16);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x08, 12);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x02, 8);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x08, 7);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x03, 16);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x09, 12);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x03, 8);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x09, 7);
|
||||
}
|
||||
// Write output
|
||||
let oi = 0;
|
||||
out[oi++] = (y00 + x00) | 0;
|
||||
out[oi++] = (y01 + x01) | 0;
|
||||
out[oi++] = (y02 + x02) | 0;
|
||||
out[oi++] = (y03 + x03) | 0;
|
||||
out[oi++] = (y04 + x04) | 0;
|
||||
out[oi++] = (y05 + x05) | 0;
|
||||
out[oi++] = (y06 + x06) | 0;
|
||||
out[oi++] = (y07 + x07) | 0;
|
||||
out[oi++] = (y08 + x08) | 0;
|
||||
out[oi++] = (y09 + x09) | 0;
|
||||
out[oi++] = (y10 + x10) | 0;
|
||||
out[oi++] = (y11 + x11) | 0;
|
||||
out[oi++] = (y12 + x12) | 0;
|
||||
out[oi++] = (y13 + x13) | 0;
|
||||
out[oi++] = (y14 + x14) | 0;
|
||||
out[oi++] = (y15 + x15) | 0;
|
||||
}
|
||||
/**
|
||||
* hchacha helper method, used primarily in xchacha, to hash
|
||||
* key and nonce into key' and nonce'.
|
||||
* Same as chachaCore, but there doesn't seem to be a way to move the block
|
||||
* out without 25% performance hit.
|
||||
*/
|
||||
// prettier-ignore
|
||||
function hchacha(s, k, i, o32) {
|
||||
let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3], x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3], x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7], x12 = i[0], x13 = i[1], x14 = i[2], x15 = i[3];
|
||||
for (let r = 0; r < 20; r += 2) {
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x00, 16);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x08, 12);
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x00, 8);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x08, 7);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x01, 16);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x09, 12);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x01, 8);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x09, 7);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x02, 16);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x10, 12);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x02, 8);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x10, 7);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x03, 16);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x11, 12);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x03, 8);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x11, 7);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x00, 16);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x10, 12);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = (0, _arx_js_1.rotl)(x15 ^ x00, 8);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = (0, _arx_js_1.rotl)(x05 ^ x10, 7);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x01, 16);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x11, 12);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = (0, _arx_js_1.rotl)(x12 ^ x01, 8);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = (0, _arx_js_1.rotl)(x06 ^ x11, 7);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x02, 16);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x08, 12);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = (0, _arx_js_1.rotl)(x13 ^ x02, 8);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = (0, _arx_js_1.rotl)(x07 ^ x08, 7);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x03, 16);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x09, 12);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = (0, _arx_js_1.rotl)(x14 ^ x03, 8);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = (0, _arx_js_1.rotl)(x04 ^ x09, 7);
|
||||
}
|
||||
let oi = 0;
|
||||
o32[oi++] = x00;
|
||||
o32[oi++] = x01;
|
||||
o32[oi++] = x02;
|
||||
o32[oi++] = x03;
|
||||
o32[oi++] = x12;
|
||||
o32[oi++] = x13;
|
||||
o32[oi++] = x14;
|
||||
o32[oi++] = x15;
|
||||
}
|
||||
exports.hchacha = hchacha;
|
||||
/**
|
||||
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
|
||||
*/
|
||||
exports.chacha20orig = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
allowShortKeys: true,
|
||||
});
|
||||
/**
|
||||
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
|
||||
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
|
||||
*/
|
||||
exports.chacha20 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
allowShortKeys: false,
|
||||
});
|
||||
/**
|
||||
* XChaCha eXtended-nonce ChaCha. 24-byte nonce.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
*/
|
||||
exports.xchacha20 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
extendNonceFn: hchacha,
|
||||
allowShortKeys: false,
|
||||
});
|
||||
/**
|
||||
* Reduced 8-round chacha, described in original paper.
|
||||
*/
|
||||
exports.chacha8 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 8,
|
||||
});
|
||||
/**
|
||||
* Reduced 12-round chacha, described in original paper.
|
||||
*/
|
||||
exports.chacha12 = (0, _arx_js_1.createCipher)(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 12,
|
||||
});
|
||||
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
||||
// Pad to digest size with zeros
|
||||
const updatePadded = (h, msg) => {
|
||||
h.update(msg);
|
||||
const left = msg.length % 16;
|
||||
if (left)
|
||||
h.update(ZEROS16.subarray(left));
|
||||
};
|
||||
const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
|
||||
function computeTag(fn, key, nonce, data, AAD) {
|
||||
const authKey = fn(key, nonce, ZEROS32);
|
||||
const h = _poly1305_js_1.poly1305.create(authKey);
|
||||
if (AAD)
|
||||
updatePadded(h, AAD);
|
||||
updatePadded(h, data);
|
||||
const num = new Uint8Array(16);
|
||||
const view = (0, utils_js_1.createView)(num);
|
||||
(0, utils_js_1.setBigUint64)(view, 0, BigInt(AAD ? AAD.length : 0), true);
|
||||
(0, utils_js_1.setBigUint64)(view, 8, BigInt(data.length), true);
|
||||
h.update(num);
|
||||
const res = h.digest();
|
||||
authKey.fill(0);
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* AEAD algorithm from RFC 8439.
|
||||
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
|
||||
* We could have composed them similar to:
|
||||
* https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
|
||||
* But it's hard because of authKey:
|
||||
* In salsa20, authKey changes position in salsa stream.
|
||||
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
|
||||
*/
|
||||
const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
||||
const tagLength = 16;
|
||||
(0, _assert_js_1.bytes)(key, 32);
|
||||
(0, _assert_js_1.bytes)(nonce);
|
||||
return {
|
||||
encrypt: (plaintext, output) => {
|
||||
const plength = plaintext.length;
|
||||
const clength = plength + tagLength;
|
||||
if (output) {
|
||||
(0, _assert_js_1.bytes)(output, clength);
|
||||
}
|
||||
else {
|
||||
output = new Uint8Array(clength);
|
||||
}
|
||||
xorStream(key, nonce, plaintext, output, 1);
|
||||
const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
|
||||
output.set(tag, plength); // append tag
|
||||
return output;
|
||||
},
|
||||
decrypt: (ciphertext, output) => {
|
||||
const clength = ciphertext.length;
|
||||
const plength = clength - tagLength;
|
||||
if (clength < tagLength)
|
||||
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
|
||||
if (output) {
|
||||
(0, _assert_js_1.bytes)(output, plength);
|
||||
}
|
||||
else {
|
||||
output = new Uint8Array(plength);
|
||||
}
|
||||
const data = ciphertext.subarray(0, -tagLength);
|
||||
const passedTag = ciphertext.subarray(-tagLength);
|
||||
const tag = computeTag(xorStream, key, nonce, data, AAD);
|
||||
if (!(0, utils_js_1.equalBytes)(passedTag, tag))
|
||||
throw new Error('invalid tag');
|
||||
xorStream(key, nonce, data, output, 1);
|
||||
return output;
|
||||
},
|
||||
};
|
||||
};
|
||||
exports._poly1305_aead = _poly1305_aead;
|
||||
/**
|
||||
* ChaCha20-Poly1305 from RFC 8439.
|
||||
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
|
||||
*/
|
||||
exports.chacha20poly1305 = (0, utils_js_1.wrapCipher)({ blockSize: 64, nonceLength: 12, tagLength: 16 }, (0, exports._poly1305_aead)(exports.chacha20));
|
||||
/**
|
||||
* XChaCha20-Poly1305 extended-nonce chacha.
|
||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
*/
|
||||
exports.xchacha20poly1305 = (0, utils_js_1.wrapCipher)({ blockSize: 64, nonceLength: 24, tagLength: 16 }, (0, exports._poly1305_aead)(exports.xchacha20));
|
||||
//# sourceMappingURL=chacha.js.map
|
||||
1
node_modules/@noble/ciphers/chacha.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/chacha.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
node_modules/@noble/ciphers/crypto.d.ts
generated
vendored
Normal file
3
node_modules/@noble/ciphers/crypto.d.ts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export declare function randomBytes(bytesLength?: number): Uint8Array;
|
||||
export declare function getWebcryptoSubtle(): any;
|
||||
//# sourceMappingURL=crypto.d.ts.map
|
||||
1
node_modules/@noble/ciphers/crypto.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/crypto.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["src/crypto.ts"],"names":[],"mappings":"AAKA,wBAAgB,WAAW,CAAC,WAAW,SAAK,GAAG,UAAU,CAIxD;AAED,wBAAgB,kBAAkB,QAGjC"}
|
||||
17
node_modules/@noble/ciphers/crypto.js
generated
vendored
Normal file
17
node_modules/@noble/ciphers/crypto.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getWebcryptoSubtle = exports.randomBytes = void 0;
|
||||
const cr = typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
|
||||
function randomBytes(bytesLength = 32) {
|
||||
if (cr && typeof cr.getRandomValues === 'function')
|
||||
return cr.getRandomValues(new Uint8Array(bytesLength));
|
||||
throw new Error('crypto.getRandomValues must be defined');
|
||||
}
|
||||
exports.randomBytes = randomBytes;
|
||||
function getWebcryptoSubtle() {
|
||||
if (cr && typeof cr.subtle === 'object' && cr.subtle != null)
|
||||
return cr.subtle;
|
||||
throw new Error('crypto.subtle must be defined');
|
||||
}
|
||||
exports.getWebcryptoSubtle = getWebcryptoSubtle;
|
||||
//# sourceMappingURL=crypto.js.map
|
||||
1
node_modules/@noble/ciphers/crypto.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/crypto.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["src/crypto.ts"],"names":[],"mappings":";;;AAGA,MAAM,EAAE,GAAG,OAAO,UAAU,KAAK,QAAQ,IAAI,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAEpG,SAAgB,WAAW,CAAC,WAAW,GAAG,EAAE;IAC1C,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,eAAe,KAAK,UAAU;QAChD,OAAO,EAAE,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IACzD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC5D,CAAC;AAJD,kCAIC;AAED,SAAgB,kBAAkB;IAChC,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC;IAC/E,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACnD,CAAC;AAHD,gDAGC"}
|
||||
3
node_modules/@noble/ciphers/cryptoNode.d.ts
generated
vendored
Normal file
3
node_modules/@noble/ciphers/cryptoNode.d.ts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export declare function randomBytes(bytesLength?: number): Uint8Array;
|
||||
export declare function getWebcryptoSubtle(): any;
|
||||
//# sourceMappingURL=cryptoNode.d.ts.map
|
||||
1
node_modules/@noble/ciphers/cryptoNode.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/cryptoNode.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cryptoNode.d.ts","sourceRoot":"","sources":["src/cryptoNode.ts"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,CAAC,WAAW,SAAK,GAAG,UAAU,CAIxD;AAED,wBAAgB,kBAAkB,QAGjC"}
|
||||
22
node_modules/@noble/ciphers/cryptoNode.js
generated
vendored
Normal file
22
node_modules/@noble/ciphers/cryptoNode.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getWebcryptoSubtle = exports.randomBytes = void 0;
|
||||
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
|
||||
// See utils.ts for details.
|
||||
// The file will throw on node.js 14 and earlier.
|
||||
// @ts-ignore
|
||||
const nc = require("node:crypto");
|
||||
const cr = nc && typeof nc === 'object' && 'webcrypto' in nc ? nc.webcrypto : undefined;
|
||||
function randomBytes(bytesLength = 32) {
|
||||
if (cr && typeof cr.getRandomValues === 'function')
|
||||
return cr.getRandomValues(new Uint8Array(bytesLength));
|
||||
throw new Error('crypto.getRandomValues must be defined');
|
||||
}
|
||||
exports.randomBytes = randomBytes;
|
||||
function getWebcryptoSubtle() {
|
||||
if (cr && typeof cr.subtle === 'object' && cr.subtle != null)
|
||||
return cr.subtle;
|
||||
throw new Error('crypto.subtle must be defined');
|
||||
}
|
||||
exports.getWebcryptoSubtle = getWebcryptoSubtle;
|
||||
//# sourceMappingURL=cryptoNode.js.map
|
||||
1
node_modules/@noble/ciphers/cryptoNode.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/cryptoNode.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cryptoNode.js","sourceRoot":"","sources":["src/cryptoNode.ts"],"names":[],"mappings":";;;AAAA,oFAAoF;AACpF,4BAA4B;AAC5B,iDAAiD;AACjD,aAAa;AACb,kCAAkC;AAClC,MAAM,EAAE,GAAG,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,WAAW,IAAI,EAAE,CAAC,CAAC,CAAE,EAAE,CAAC,SAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;AAEjG,SAAgB,WAAW,CAAC,WAAW,GAAG,EAAE;IAC1C,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,eAAe,KAAK,UAAU;QAChD,OAAO,EAAE,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IACzD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC5D,CAAC;AAJD,kCAIC;AAED,SAAgB,kBAAkB;IAChC,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC;IAC/E,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACnD,CAAC;AAHD,gDAGC"}
|
||||
170
node_modules/@noble/ciphers/esm/_arx.js
generated
vendored
Normal file
170
node_modules/@noble/ciphers/esm/_arx.js
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
// Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
|
||||
import { number as anumber, bytes as abytes, bool as abool } from './_assert.js';
|
||||
import { checkOpts, u32 } from './utils.js';
|
||||
/*
|
||||
RFC8439 requires multi-step cipher stream, where
|
||||
authKey starts with counter: 0, actual msg with counter: 1.
|
||||
|
||||
For this, we need a way to re-use nonce / counter:
|
||||
|
||||
const counter = new Uint8Array(4);
|
||||
chacha(..., counter, ...); // counter is now 1
|
||||
chacha(..., counter, ...); // counter is now 2
|
||||
|
||||
This is complicated:
|
||||
|
||||
- 32-bit counters are enough, no need for 64-bit: max ArrayBuffer size in JS is 4GB
|
||||
- Original papers don't allow mutating counters
|
||||
- Counter overflow is undefined [^1]
|
||||
- Idea A: allow providing (nonce | counter) instead of just nonce, re-use it
|
||||
- Caveat: Cannot be re-used through all cases:
|
||||
- * chacha has (counter | nonce)
|
||||
- * xchacha has (nonce16 | counter | nonce16)
|
||||
- Idea B: separate nonce / counter and provide separate API for counter re-use
|
||||
- Caveat: there are different counter sizes depending on an algorithm.
|
||||
- salsa & chacha also differ in structures of key & sigma:
|
||||
salsa20: s[0] | k(4) | s[1] | nonce(2) | ctr(2) | s[2] | k(4) | s[3]
|
||||
chacha: s(4) | k(8) | ctr(1) | nonce(3)
|
||||
chacha20orig: s(4) | k(8) | ctr(2) | nonce(2)
|
||||
- Idea C: helper method such as `setSalsaState(key, nonce, sigma, data)`
|
||||
- Caveat: we can't re-use counter array
|
||||
|
||||
xchacha [^2] uses the subkey and remaining 8 byte nonce with ChaCha20 as normal
|
||||
(prefixed by 4 NUL bytes, since [RFC8439] specifies a 12-byte nonce).
|
||||
|
||||
[^1]: https://mailarchive.ietf.org/arch/msg/cfrg/gsOnTJzcbgG6OqD8Sc0GO5aR_tU/
|
||||
[^2]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#appendix-A.2
|
||||
*/
|
||||
// We can't make top-level var depend on utils.utf8ToBytes
|
||||
// because it's not present in all envs. Creating a similar fn here
|
||||
const _utf8ToBytes = (str) => Uint8Array.from(str.split('').map((c) => c.charCodeAt(0)));
|
||||
const sigma16 = _utf8ToBytes('expand 16-byte k');
|
||||
const sigma32 = _utf8ToBytes('expand 32-byte k');
|
||||
const sigma16_32 = u32(sigma16);
|
||||
const sigma32_32 = u32(sigma32);
|
||||
export const sigma = sigma32_32.slice();
|
||||
export function rotl(a, b) {
|
||||
return (a << b) | (a >>> (32 - b));
|
||||
}
|
||||
// Is byte array aligned to 4 byte offset (u32)?
|
||||
function isAligned32(b) {
|
||||
return b.byteOffset % 4 === 0;
|
||||
}
|
||||
// Salsa and Chacha block length is always 512-bit
|
||||
const BLOCK_LEN = 64;
|
||||
const BLOCK_LEN32 = 16;
|
||||
// new Uint32Array([2**32]) // => Uint32Array(1) [ 0 ]
|
||||
// new Uint32Array([2**32-1]) // => Uint32Array(1) [ 4294967295 ]
|
||||
const MAX_COUNTER = 2 ** 32 - 1;
|
||||
const U32_EMPTY = new Uint32Array();
|
||||
function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
|
||||
const len = data.length;
|
||||
const block = new Uint8Array(BLOCK_LEN);
|
||||
const b32 = u32(block);
|
||||
// Make sure that buffers aligned to 4 bytes
|
||||
const isAligned = isAligned32(data) && isAligned32(output);
|
||||
const d32 = isAligned ? u32(data) : U32_EMPTY;
|
||||
const o32 = isAligned ? u32(output) : U32_EMPTY;
|
||||
for (let pos = 0; pos < len; counter++) {
|
||||
core(sigma, key, nonce, b32, counter, rounds);
|
||||
if (counter >= MAX_COUNTER)
|
||||
throw new Error('arx: counter overflow');
|
||||
const take = Math.min(BLOCK_LEN, len - pos);
|
||||
// aligned to 4 bytes
|
||||
if (isAligned && take === BLOCK_LEN) {
|
||||
const pos32 = pos / 4;
|
||||
if (pos % 4 !== 0)
|
||||
throw new Error('arx: invalid block position');
|
||||
for (let j = 0, posj; j < BLOCK_LEN32; j++) {
|
||||
posj = pos32 + j;
|
||||
o32[posj] = d32[posj] ^ b32[j];
|
||||
}
|
||||
pos += BLOCK_LEN;
|
||||
continue;
|
||||
}
|
||||
for (let j = 0, posj; j < take; j++) {
|
||||
posj = pos + j;
|
||||
output[posj] = data[posj] ^ block[j];
|
||||
}
|
||||
pos += take;
|
||||
}
|
||||
}
|
||||
export function createCipher(core, opts) {
|
||||
const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts);
|
||||
if (typeof core !== 'function')
|
||||
throw new Error('core must be a function');
|
||||
anumber(counterLength);
|
||||
anumber(rounds);
|
||||
abool(counterRight);
|
||||
abool(allowShortKeys);
|
||||
return (key, nonce, data, output, counter = 0) => {
|
||||
abytes(key);
|
||||
abytes(nonce);
|
||||
abytes(data);
|
||||
const len = data.length;
|
||||
if (!output)
|
||||
output = new Uint8Array(len);
|
||||
abytes(output);
|
||||
anumber(counter);
|
||||
if (counter < 0 || counter >= MAX_COUNTER)
|
||||
throw new Error('arx: counter overflow');
|
||||
if (output.length < len)
|
||||
throw new Error(`arx: output (${output.length}) is shorter than data (${len})`);
|
||||
const toClean = [];
|
||||
// Key & sigma
|
||||
// key=16 -> sigma16, k=key|key
|
||||
// key=32 -> sigma32, k=key
|
||||
let l = key.length, k, sigma;
|
||||
if (l === 32) {
|
||||
k = key.slice();
|
||||
toClean.push(k);
|
||||
sigma = sigma32_32;
|
||||
}
|
||||
else if (l === 16 && allowShortKeys) {
|
||||
k = new Uint8Array(32);
|
||||
k.set(key);
|
||||
k.set(key, 16);
|
||||
sigma = sigma16_32;
|
||||
toClean.push(k);
|
||||
}
|
||||
else {
|
||||
throw new Error(`arx: invalid 32-byte key, got length=${l}`);
|
||||
}
|
||||
// Nonce
|
||||
// salsa20: 8 (8-byte counter)
|
||||
// chacha20orig: 8 (8-byte counter)
|
||||
// chacha20: 12 (4-byte counter)
|
||||
// xsalsa20: 24 (16 -> hsalsa, 8 -> old nonce)
|
||||
// xchacha20: 24 (16 -> hchacha, 8 -> old nonce)
|
||||
// Align nonce to 4 bytes
|
||||
if (!isAligned32(nonce)) {
|
||||
nonce = nonce.slice();
|
||||
toClean.push(nonce);
|
||||
}
|
||||
const k32 = u32(k);
|
||||
// hsalsa & hchacha: handle extended nonce
|
||||
if (extendNonceFn) {
|
||||
if (nonce.length !== 24)
|
||||
throw new Error(`arx: extended nonce must be 24 bytes`);
|
||||
extendNonceFn(sigma, k32, u32(nonce.subarray(0, 16)), k32);
|
||||
nonce = nonce.subarray(16);
|
||||
}
|
||||
// Handle nonce counter
|
||||
const nonceNcLen = 16 - counterLength;
|
||||
if (nonceNcLen !== nonce.length)
|
||||
throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`);
|
||||
// Pad counter when nonce is 64 bit
|
||||
if (nonceNcLen !== 12) {
|
||||
const nc = new Uint8Array(12);
|
||||
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
|
||||
nonce = nc;
|
||||
toClean.push(nonce);
|
||||
}
|
||||
const n32 = u32(nonce);
|
||||
runCipher(core, sigma, k32, n32, data, output, counter, rounds);
|
||||
while (toClean.length > 0)
|
||||
toClean.pop().fill(0);
|
||||
return output;
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=_arx.js.map
|
||||
1
node_modules/@noble/ciphers/esm/_arx.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/_arx.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
41
node_modules/@noble/ciphers/esm/_assert.js
generated
vendored
Normal file
41
node_modules/@noble/ciphers/esm/_assert.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
function number(n) {
|
||||
if (!Number.isSafeInteger(n) || n < 0)
|
||||
throw new Error(`positive integer expected, not ${n}`);
|
||||
}
|
||||
function bool(b) {
|
||||
if (typeof b !== 'boolean')
|
||||
throw new Error(`boolean expected, not ${b}`);
|
||||
}
|
||||
export function isBytes(a) {
|
||||
return (a instanceof Uint8Array ||
|
||||
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array'));
|
||||
}
|
||||
function bytes(b, ...lengths) {
|
||||
if (!isBytes(b))
|
||||
throw new Error('Uint8Array expected');
|
||||
if (lengths.length > 0 && !lengths.includes(b.length))
|
||||
throw new Error(`Uint8Array expected of length ${lengths}, not of length=${b.length}`);
|
||||
}
|
||||
function hash(hash) {
|
||||
if (typeof hash !== 'function' || typeof hash.create !== 'function')
|
||||
throw new Error('hash must be wrapped by utils.wrapConstructor');
|
||||
number(hash.outputLen);
|
||||
number(hash.blockLen);
|
||||
}
|
||||
function exists(instance, checkFinished = true) {
|
||||
if (instance.destroyed)
|
||||
throw new Error('Hash instance has been destroyed');
|
||||
if (checkFinished && instance.finished)
|
||||
throw new Error('Hash#digest() has already been called');
|
||||
}
|
||||
function output(out, instance) {
|
||||
bytes(out);
|
||||
const min = instance.outputLen;
|
||||
if (out.length < min) {
|
||||
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
|
||||
}
|
||||
}
|
||||
export { number, bool, bytes, hash, exists, output };
|
||||
const assert = { number, bool, bytes, hash, exists, output };
|
||||
export default assert;
|
||||
//# sourceMappingURL=_assert.js.map
|
||||
1
node_modules/@noble/ciphers/esm/_assert.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/_assert.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"_assert.js","sourceRoot":"","sources":["../src/_assert.ts"],"names":[],"mappings":"AAAA,SAAS,MAAM,CAAC,CAAS;IACvB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,EAAE,CAAC,CAAC;AAChG,CAAC;AAED,SAAS,IAAI,CAAC,CAAU;IACtB,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,CAAU;IAChC,OAAO,CACL,CAAC,YAAY,UAAU;QACvB,CAAC,CAAC,IAAI,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,WAAW,CAAC,IAAI,KAAK,YAAY,CAAC,CAC5E,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,CAAyB,EAAE,GAAG,OAAiB;IAC5D,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,mBAAmB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AAC3F,CAAC;AAQD,SAAS,IAAI,CAAC,IAAU;IACtB,IAAI,OAAO,IAAI,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU;QACjE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,MAAM,CAAC,QAAa,EAAE,aAAa,GAAG,IAAI;IACjD,IAAI,QAAQ,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC5E,IAAI,aAAa,IAAI,QAAQ,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;AACnG,CAAC;AAED,SAAS,MAAM,CAAC,GAAQ,EAAE,QAAa;IACrC,KAAK,CAAC,GAAG,CAAC,CAAC;IACX,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC;IAC/B,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,yDAAyD,GAAG,EAAE,CAAC,CAAC;IAClF,CAAC;AACH,CAAC;AAED,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACrD,MAAM,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC7D,eAAe,MAAM,CAAC"}
|
||||
287
node_modules/@noble/ciphers/esm/_micro.js
generated
vendored
Normal file
287
node_modules/@noble/ciphers/esm/_micro.js
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
||||
// prettier-ignore
|
||||
import { createView, setBigUint64, wrapCipher, bytesToHex, concatBytes, equalBytes, hexToNumber, numberToBytesBE, } from './utils.js';
|
||||
import { createCipher, rotl } from './_arx.js';
|
||||
import { bytes as abytes } from './_assert.js';
|
||||
/*
|
||||
noble-ciphers-micro: more auditable, but slower version of salsa20, chacha & poly1305.
|
||||
Implements the same algorithms that are present in other files, but without
|
||||
unrolled loops (https://en.wikipedia.org/wiki/Loop_unrolling).
|
||||
*/
|
||||
function bytesToNumberLE(bytes) {
|
||||
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
||||
}
|
||||
function numberToBytesLE(n, len) {
|
||||
return numberToBytesBE(n, len).reverse();
|
||||
}
|
||||
function salsaQR(x, a, b, c, d) {
|
||||
x[b] ^= rotl((x[a] + x[d]) | 0, 7);
|
||||
x[c] ^= rotl((x[b] + x[a]) | 0, 9);
|
||||
x[d] ^= rotl((x[c] + x[b]) | 0, 13);
|
||||
x[a] ^= rotl((x[d] + x[c]) | 0, 18);
|
||||
}
|
||||
// prettier-ignore
|
||||
function chachaQR(x, a, b, c, d) {
|
||||
x[a] = (x[a] + x[b]) | 0;
|
||||
x[d] = rotl(x[d] ^ x[a], 16);
|
||||
x[c] = (x[c] + x[d]) | 0;
|
||||
x[b] = rotl(x[b] ^ x[c], 12);
|
||||
x[a] = (x[a] + x[b]) | 0;
|
||||
x[d] = rotl(x[d] ^ x[a], 8);
|
||||
x[c] = (x[c] + x[d]) | 0;
|
||||
x[b] = rotl(x[b] ^ x[c], 7);
|
||||
}
|
||||
function salsaRound(x, rounds = 20) {
|
||||
for (let r = 0; r < rounds; r += 2) {
|
||||
salsaQR(x, 0, 4, 8, 12);
|
||||
salsaQR(x, 5, 9, 13, 1);
|
||||
salsaQR(x, 10, 14, 2, 6);
|
||||
salsaQR(x, 15, 3, 7, 11);
|
||||
salsaQR(x, 0, 1, 2, 3);
|
||||
salsaQR(x, 5, 6, 7, 4);
|
||||
salsaQR(x, 10, 11, 8, 9);
|
||||
salsaQR(x, 15, 12, 13, 14);
|
||||
}
|
||||
}
|
||||
function chachaRound(x, rounds = 20) {
|
||||
for (let r = 0; r < rounds; r += 2) {
|
||||
chachaQR(x, 0, 4, 8, 12);
|
||||
chachaQR(x, 1, 5, 9, 13);
|
||||
chachaQR(x, 2, 6, 10, 14);
|
||||
chachaQR(x, 3, 7, 11, 15);
|
||||
chachaQR(x, 0, 5, 10, 15);
|
||||
chachaQR(x, 1, 6, 11, 12);
|
||||
chachaQR(x, 2, 7, 8, 13);
|
||||
chachaQR(x, 3, 4, 9, 14);
|
||||
}
|
||||
}
|
||||
function salsaCore(s, k, n, out, cnt, rounds = 20) {
|
||||
// prettier-ignore
|
||||
const y = new Uint32Array([
|
||||
s[0], k[0], k[1], k[2], // "expa" Key Key Key
|
||||
k[3], s[1], n[0], n[1], // Key "nd 3" Nonce Nonce
|
||||
cnt, 0, s[2], k[4], // Pos. Pos. "2-by" Key
|
||||
k[5], k[6], k[7], s[3], // Key Key Key "te k"
|
||||
]);
|
||||
const x = y.slice();
|
||||
salsaRound(x, rounds);
|
||||
for (let i = 0; i < 16; i++)
|
||||
out[i] = (y[i] + x[i]) | 0;
|
||||
}
|
||||
// prettier-ignore
|
||||
export function hsalsa(s, k, i, o32) {
|
||||
const x = new Uint32Array([
|
||||
s[0], k[0], k[1], k[2],
|
||||
k[3], s[1], i[0], i[1],
|
||||
i[2], i[3], s[2], k[4],
|
||||
k[5], k[6], k[7], s[3]
|
||||
]);
|
||||
salsaRound(x, 20);
|
||||
let oi = 0;
|
||||
o32[oi++] = x[0];
|
||||
o32[oi++] = x[5];
|
||||
o32[oi++] = x[10];
|
||||
o32[oi++] = x[15];
|
||||
o32[oi++] = x[6];
|
||||
o32[oi++] = x[7];
|
||||
o32[oi++] = x[8];
|
||||
o32[oi++] = x[9];
|
||||
}
|
||||
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
||||
// prettier-ignore
|
||||
const y = new Uint32Array([
|
||||
s[0], s[1], s[2], s[3], // "expa" "nd 3" "2-by" "te k"
|
||||
k[0], k[1], k[2], k[3], // Key Key Key Key
|
||||
k[4], k[5], k[6], k[7], // Key Key Key Key
|
||||
cnt, n[0], n[1], n[2], // Counter Counter Nonce Nonce
|
||||
]);
|
||||
const x = y.slice();
|
||||
chachaRound(x, rounds);
|
||||
for (let i = 0; i < 16; i++)
|
||||
out[i] = (y[i] + x[i]) | 0;
|
||||
}
|
||||
// prettier-ignore
|
||||
export function hchacha(s, k, i, o32) {
|
||||
const x = new Uint32Array([
|
||||
s[0], s[1], s[2], s[3],
|
||||
k[0], k[1], k[2], k[3],
|
||||
k[4], k[5], k[6], k[7],
|
||||
i[0], i[1], i[2], i[3],
|
||||
]);
|
||||
chachaRound(x, 20);
|
||||
let oi = 0;
|
||||
o32[oi++] = x[0];
|
||||
o32[oi++] = x[1];
|
||||
o32[oi++] = x[2];
|
||||
o32[oi++] = x[3];
|
||||
o32[oi++] = x[12];
|
||||
o32[oi++] = x[13];
|
||||
o32[oi++] = x[14];
|
||||
o32[oi++] = x[15];
|
||||
}
|
||||
/**
|
||||
* salsa20, 12-byte nonce.
|
||||
*/
|
||||
export const salsa20 = /* @__PURE__ */ createCipher(salsaCore, {
|
||||
allowShortKeys: true,
|
||||
counterRight: true,
|
||||
});
|
||||
/**
|
||||
* xsalsa20, 24-byte nonce.
|
||||
*/
|
||||
export const xsalsa20 = /* @__PURE__ */ createCipher(salsaCore, {
|
||||
counterRight: true,
|
||||
extendNonceFn: hsalsa,
|
||||
});
|
||||
/**
|
||||
* chacha20 non-RFC, original version by djb. 8-byte nonce, 8-byte counter.
|
||||
*/
|
||||
export const chacha20orig = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
allowShortKeys: true,
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
});
|
||||
/**
|
||||
* chacha20 RFC 8439 (IETF / TLS). 12-byte nonce, 4-byte counter.
|
||||
*/
|
||||
export const chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
});
|
||||
/**
|
||||
* xchacha20 eXtended-nonce. https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
*/
|
||||
export const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
extendNonceFn: hchacha,
|
||||
});
|
||||
/**
|
||||
* 8-round chacha from the original paper.
|
||||
*/
|
||||
export const chacha8 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 8,
|
||||
});
|
||||
/**
|
||||
* 12-round chacha from the original paper.
|
||||
*/
|
||||
export const chacha12 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 12,
|
||||
});
|
||||
const POW_2_130_5 = BigInt(2) ** BigInt(130) - BigInt(5);
|
||||
const POW_2_128_1 = BigInt(2) ** BigInt(16 * 8) - BigInt(1);
|
||||
const CLAMP_R = BigInt('0x0ffffffc0ffffffc0ffffffc0fffffff');
|
||||
const _0 = BigInt(0);
|
||||
const _1 = BigInt(1);
|
||||
// Can be speed-up using BigUint64Array, but would be more complicated
|
||||
export function poly1305(msg, key) {
|
||||
abytes(msg);
|
||||
abytes(key);
|
||||
let acc = _0;
|
||||
const r = bytesToNumberLE(key.subarray(0, 16)) & CLAMP_R;
|
||||
const s = bytesToNumberLE(key.subarray(16));
|
||||
// Process by 16 byte chunks
|
||||
for (let i = 0; i < msg.length; i += 16) {
|
||||
const m = msg.subarray(i, i + 16);
|
||||
const n = bytesToNumberLE(m) | (_1 << BigInt(8 * m.length));
|
||||
acc = ((acc + n) * r) % POW_2_130_5;
|
||||
}
|
||||
const res = (acc + s) & POW_2_128_1;
|
||||
return numberToBytesLE(res, 16);
|
||||
}
|
||||
function computeTag(fn, key, nonce, ciphertext, AAD) {
|
||||
const res = [];
|
||||
if (AAD) {
|
||||
res.push(AAD);
|
||||
const leftover = AAD.length % 16;
|
||||
if (leftover > 0)
|
||||
res.push(new Uint8Array(16 - leftover));
|
||||
}
|
||||
res.push(ciphertext);
|
||||
const leftover = ciphertext.length % 16;
|
||||
if (leftover > 0)
|
||||
res.push(new Uint8Array(16 - leftover));
|
||||
// Lengths
|
||||
const num = new Uint8Array(16);
|
||||
const view = createView(num);
|
||||
setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
|
||||
setBigUint64(view, 8, BigInt(ciphertext.length), true);
|
||||
res.push(num);
|
||||
const authKey = fn(key, nonce, new Uint8Array(32));
|
||||
return poly1305(concatBytes(...res), authKey);
|
||||
}
|
||||
/**
|
||||
* xsalsa20-poly1305 eXtended-nonce (24 bytes) salsa.
|
||||
*/
|
||||
export const xsalsa20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, function xsalsa20poly1305(key, nonce) {
|
||||
abytes(key);
|
||||
abytes(nonce);
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
abytes(plaintext);
|
||||
const m = concatBytes(new Uint8Array(32), plaintext);
|
||||
const c = xsalsa20(key, nonce, m);
|
||||
const authKey = c.subarray(0, 32);
|
||||
const data = c.subarray(32);
|
||||
const tag = poly1305(data, authKey);
|
||||
return concatBytes(tag, data);
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
abytes(ciphertext);
|
||||
if (ciphertext.length < 16)
|
||||
throw new Error('encrypted data must be at least 16 bytes');
|
||||
const c = concatBytes(new Uint8Array(16), ciphertext);
|
||||
const authKey = xsalsa20(key, nonce, new Uint8Array(32));
|
||||
const tag = poly1305(c.subarray(32), authKey);
|
||||
if (!equalBytes(c.subarray(16, 32), tag))
|
||||
throw new Error('invalid poly1305 tag');
|
||||
return xsalsa20(key, nonce, c).subarray(32);
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Alias to xsalsa20-poly1305
|
||||
*/
|
||||
export function secretbox(key, nonce) {
|
||||
const xs = xsalsa20poly1305(key, nonce);
|
||||
return { seal: xs.encrypt, open: xs.decrypt };
|
||||
}
|
||||
export const _poly1305_aead = (fn) => (key, nonce, AAD) => {
|
||||
const tagLength = 16;
|
||||
const keyLength = 32;
|
||||
abytes(key, keyLength);
|
||||
abytes(nonce);
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
abytes(plaintext);
|
||||
const res = fn(key, nonce, plaintext, undefined, 1);
|
||||
const tag = computeTag(fn, key, nonce, res, AAD);
|
||||
return concatBytes(res, tag);
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
abytes(ciphertext);
|
||||
if (ciphertext.length < tagLength)
|
||||
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
|
||||
const passedTag = ciphertext.subarray(-tagLength);
|
||||
const data = ciphertext.subarray(0, -tagLength);
|
||||
const tag = computeTag(fn, key, nonce, data, AAD);
|
||||
if (!equalBytes(passedTag, tag))
|
||||
throw new Error('invalid poly1305 tag');
|
||||
return fn(key, nonce, data, undefined, 1);
|
||||
},
|
||||
};
|
||||
};
|
||||
/**
|
||||
* chacha20-poly1305 12-byte-nonce chacha.
|
||||
*/
|
||||
export const chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16 }, _poly1305_aead(chacha20));
|
||||
/**
|
||||
* xchacha20-poly1305 eXtended-nonce (24 bytes) chacha.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
*/
|
||||
export const xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, _poly1305_aead(xchacha20));
|
||||
//# sourceMappingURL=_micro.js.map
|
||||
1
node_modules/@noble/ciphers/esm/_micro.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/_micro.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
264
node_modules/@noble/ciphers/esm/_poly1305.js
generated
vendored
Normal file
264
node_modules/@noble/ciphers/esm/_poly1305.js
generated
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
import { exists as aexists, bytes as abytes, output as aoutput } from './_assert.js';
|
||||
import { toBytes } from './utils.js';
|
||||
// Poly1305 is a fast and parallel secret-key message-authentication code.
|
||||
// https://cr.yp.to/mac.html, https://cr.yp.to/mac/poly1305-20050329.pdf
|
||||
// https://datatracker.ietf.org/doc/html/rfc8439
|
||||
// Based on Public Domain poly1305-donna https://github.com/floodyberry/poly1305-donna
|
||||
const u8to16 = (a, i) => (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
|
||||
class Poly1305 {
|
||||
constructor(key) {
|
||||
this.blockLen = 16;
|
||||
this.outputLen = 16;
|
||||
this.buffer = new Uint8Array(16);
|
||||
this.r = new Uint16Array(10);
|
||||
this.h = new Uint16Array(10);
|
||||
this.pad = new Uint16Array(8);
|
||||
this.pos = 0;
|
||||
this.finished = false;
|
||||
key = toBytes(key);
|
||||
abytes(key, 32);
|
||||
const t0 = u8to16(key, 0);
|
||||
const t1 = u8to16(key, 2);
|
||||
const t2 = u8to16(key, 4);
|
||||
const t3 = u8to16(key, 6);
|
||||
const t4 = u8to16(key, 8);
|
||||
const t5 = u8to16(key, 10);
|
||||
const t6 = u8to16(key, 12);
|
||||
const t7 = u8to16(key, 14);
|
||||
// https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47
|
||||
this.r[0] = t0 & 0x1fff;
|
||||
this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
|
||||
this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
|
||||
this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
|
||||
this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
|
||||
this.r[5] = (t4 >>> 1) & 0x1ffe;
|
||||
this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
|
||||
this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
|
||||
this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
|
||||
this.r[9] = (t7 >>> 5) & 0x007f;
|
||||
for (let i = 0; i < 8; i++)
|
||||
this.pad[i] = u8to16(key, 16 + 2 * i);
|
||||
}
|
||||
process(data, offset, isLast = false) {
|
||||
const hibit = isLast ? 0 : 1 << 11;
|
||||
const { h, r } = this;
|
||||
const r0 = r[0];
|
||||
const r1 = r[1];
|
||||
const r2 = r[2];
|
||||
const r3 = r[3];
|
||||
const r4 = r[4];
|
||||
const r5 = r[5];
|
||||
const r6 = r[6];
|
||||
const r7 = r[7];
|
||||
const r8 = r[8];
|
||||
const r9 = r[9];
|
||||
const t0 = u8to16(data, offset + 0);
|
||||
const t1 = u8to16(data, offset + 2);
|
||||
const t2 = u8to16(data, offset + 4);
|
||||
const t3 = u8to16(data, offset + 6);
|
||||
const t4 = u8to16(data, offset + 8);
|
||||
const t5 = u8to16(data, offset + 10);
|
||||
const t6 = u8to16(data, offset + 12);
|
||||
const t7 = u8to16(data, offset + 14);
|
||||
let h0 = h[0] + (t0 & 0x1fff);
|
||||
let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
|
||||
let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
|
||||
let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
|
||||
let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
|
||||
let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
|
||||
let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
|
||||
let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
|
||||
let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
|
||||
let h9 = h[9] + ((t7 >>> 5) | hibit);
|
||||
let c = 0;
|
||||
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
|
||||
c = d0 >>> 13;
|
||||
d0 &= 0x1fff;
|
||||
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
|
||||
c += d0 >>> 13;
|
||||
d0 &= 0x1fff;
|
||||
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
|
||||
c = d1 >>> 13;
|
||||
d1 &= 0x1fff;
|
||||
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
|
||||
c += d1 >>> 13;
|
||||
d1 &= 0x1fff;
|
||||
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
|
||||
c = d2 >>> 13;
|
||||
d2 &= 0x1fff;
|
||||
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
|
||||
c += d2 >>> 13;
|
||||
d2 &= 0x1fff;
|
||||
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
|
||||
c = d3 >>> 13;
|
||||
d3 &= 0x1fff;
|
||||
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
|
||||
c += d3 >>> 13;
|
||||
d3 &= 0x1fff;
|
||||
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
|
||||
c = d4 >>> 13;
|
||||
d4 &= 0x1fff;
|
||||
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
|
||||
c += d4 >>> 13;
|
||||
d4 &= 0x1fff;
|
||||
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
|
||||
c = d5 >>> 13;
|
||||
d5 &= 0x1fff;
|
||||
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
|
||||
c += d5 >>> 13;
|
||||
d5 &= 0x1fff;
|
||||
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
|
||||
c = d6 >>> 13;
|
||||
d6 &= 0x1fff;
|
||||
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
|
||||
c += d6 >>> 13;
|
||||
d6 &= 0x1fff;
|
||||
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
|
||||
c = d7 >>> 13;
|
||||
d7 &= 0x1fff;
|
||||
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
|
||||
c += d7 >>> 13;
|
||||
d7 &= 0x1fff;
|
||||
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
|
||||
c = d8 >>> 13;
|
||||
d8 &= 0x1fff;
|
||||
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
|
||||
c += d8 >>> 13;
|
||||
d8 &= 0x1fff;
|
||||
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
|
||||
c = d9 >>> 13;
|
||||
d9 &= 0x1fff;
|
||||
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
|
||||
c += d9 >>> 13;
|
||||
d9 &= 0x1fff;
|
||||
c = ((c << 2) + c) | 0;
|
||||
c = (c + d0) | 0;
|
||||
d0 = c & 0x1fff;
|
||||
c = c >>> 13;
|
||||
d1 += c;
|
||||
h[0] = d0;
|
||||
h[1] = d1;
|
||||
h[2] = d2;
|
||||
h[3] = d3;
|
||||
h[4] = d4;
|
||||
h[5] = d5;
|
||||
h[6] = d6;
|
||||
h[7] = d7;
|
||||
h[8] = d8;
|
||||
h[9] = d9;
|
||||
}
|
||||
finalize() {
|
||||
const { h, pad } = this;
|
||||
const g = new Uint16Array(10);
|
||||
let c = h[1] >>> 13;
|
||||
h[1] &= 0x1fff;
|
||||
for (let i = 2; i < 10; i++) {
|
||||
h[i] += c;
|
||||
c = h[i] >>> 13;
|
||||
h[i] &= 0x1fff;
|
||||
}
|
||||
h[0] += c * 5;
|
||||
c = h[0] >>> 13;
|
||||
h[0] &= 0x1fff;
|
||||
h[1] += c;
|
||||
c = h[1] >>> 13;
|
||||
h[1] &= 0x1fff;
|
||||
h[2] += c;
|
||||
g[0] = h[0] + 5;
|
||||
c = g[0] >>> 13;
|
||||
g[0] &= 0x1fff;
|
||||
for (let i = 1; i < 10; i++) {
|
||||
g[i] = h[i] + c;
|
||||
c = g[i] >>> 13;
|
||||
g[i] &= 0x1fff;
|
||||
}
|
||||
g[9] -= 1 << 13;
|
||||
let mask = (c ^ 1) - 1;
|
||||
for (let i = 0; i < 10; i++)
|
||||
g[i] &= mask;
|
||||
mask = ~mask;
|
||||
for (let i = 0; i < 10; i++)
|
||||
h[i] = (h[i] & mask) | g[i];
|
||||
h[0] = (h[0] | (h[1] << 13)) & 0xffff;
|
||||
h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
|
||||
h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
|
||||
h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
|
||||
h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
|
||||
h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
|
||||
h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
|
||||
h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
|
||||
let f = h[0] + pad[0];
|
||||
h[0] = f & 0xffff;
|
||||
for (let i = 1; i < 8; i++) {
|
||||
f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
|
||||
h[i] = f & 0xffff;
|
||||
}
|
||||
}
|
||||
update(data) {
|
||||
aexists(this);
|
||||
const { buffer, blockLen } = this;
|
||||
data = toBytes(data);
|
||||
const len = data.length;
|
||||
for (let pos = 0; pos < len;) {
|
||||
const take = Math.min(blockLen - this.pos, len - pos);
|
||||
// Fast path: we have at least one block in input
|
||||
if (take === blockLen) {
|
||||
for (; blockLen <= len - pos; pos += blockLen)
|
||||
this.process(data, pos);
|
||||
continue;
|
||||
}
|
||||
buffer.set(data.subarray(pos, pos + take), this.pos);
|
||||
this.pos += take;
|
||||
pos += take;
|
||||
if (this.pos === blockLen) {
|
||||
this.process(buffer, 0, false);
|
||||
this.pos = 0;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
destroy() {
|
||||
this.h.fill(0);
|
||||
this.r.fill(0);
|
||||
this.buffer.fill(0);
|
||||
this.pad.fill(0);
|
||||
}
|
||||
digestInto(out) {
|
||||
aexists(this);
|
||||
aoutput(out, this);
|
||||
this.finished = true;
|
||||
const { buffer, h } = this;
|
||||
let { pos } = this;
|
||||
if (pos) {
|
||||
buffer[pos++] = 1;
|
||||
// buffer.subarray(pos).fill(0);
|
||||
for (; pos < 16; pos++)
|
||||
buffer[pos] = 0;
|
||||
this.process(buffer, 0, true);
|
||||
}
|
||||
this.finalize();
|
||||
let opos = 0;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
out[opos++] = h[i] >>> 0;
|
||||
out[opos++] = h[i] >>> 8;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
digest() {
|
||||
const { buffer, outputLen } = this;
|
||||
this.digestInto(buffer);
|
||||
const res = buffer.slice(0, outputLen);
|
||||
this.destroy();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
export function wrapConstructorWithKey(hashCons) {
|
||||
const hashC = (msg, key) => hashCons(key).update(toBytes(msg)).digest();
|
||||
const tmp = hashCons(new Uint8Array(32));
|
||||
hashC.outputLen = tmp.outputLen;
|
||||
hashC.blockLen = tmp.blockLen;
|
||||
hashC.create = (key) => hashCons(key);
|
||||
return hashC;
|
||||
}
|
||||
export const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
|
||||
//# sourceMappingURL=_poly1305.js.map
|
||||
1
node_modules/@noble/ciphers/esm/_poly1305.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/_poly1305.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
217
node_modules/@noble/ciphers/esm/_polyval.js
generated
vendored
Normal file
217
node_modules/@noble/ciphers/esm/_polyval.js
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
import { createView, toBytes, u32 } from './utils.js';
|
||||
import { bytes as abytes, exists as aexists, output as aoutput } from './_assert.js';
|
||||
// GHash from AES-GCM and its little-endian "mirror image" Polyval from AES-SIV.
|
||||
// Implemented in terms of GHash with conversion function for keys
|
||||
// GCM GHASH from NIST SP800-38d, SIV from RFC 8452.
|
||||
// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
// GHASH modulo: x^128 + x^7 + x^2 + x + 1
|
||||
// POLYVAL modulo: x^128 + x^127 + x^126 + x^121 + 1
|
||||
const BLOCK_SIZE = 16;
|
||||
// TODO: rewrite
|
||||
// temporary padding buffer
|
||||
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
||||
const ZEROS32 = u32(ZEROS16);
|
||||
const POLY = 0xe1; // v = 2*v % POLY
|
||||
// v = 2*v % POLY
|
||||
// NOTE: because x + x = 0 (add/sub is same), mul2(x) != x+x
|
||||
// We can multiply any number using montgomery ladder and this function (works as double, add is simple xor)
|
||||
const mul2 = (s0, s1, s2, s3) => {
|
||||
const hiBit = s3 & 1;
|
||||
return {
|
||||
s3: (s2 << 31) | (s3 >>> 1),
|
||||
s2: (s1 << 31) | (s2 >>> 1),
|
||||
s1: (s0 << 31) | (s1 >>> 1),
|
||||
s0: (s0 >>> 1) ^ ((POLY << 24) & -(hiBit & 1)), // reduce % poly
|
||||
};
|
||||
};
|
||||
const swapLE = (n) => (((n >>> 0) & 0xff) << 24) |
|
||||
(((n >>> 8) & 0xff) << 16) |
|
||||
(((n >>> 16) & 0xff) << 8) |
|
||||
((n >>> 24) & 0xff) |
|
||||
0;
|
||||
/**
|
||||
* `mulX_POLYVAL(ByteReverse(H))` from spec
|
||||
* @param k mutated in place
|
||||
*/
|
||||
export function _toGHASHKey(k) {
|
||||
k.reverse();
|
||||
const hiBit = k[15] & 1;
|
||||
// k >>= 1
|
||||
let carry = 0;
|
||||
for (let i = 0; i < k.length; i++) {
|
||||
const t = k[i];
|
||||
k[i] = (t >>> 1) | carry;
|
||||
carry = (t & 1) << 7;
|
||||
}
|
||||
k[0] ^= -hiBit & 0xe1; // if (hiBit) n ^= 0xe1000000000000000000000000000000;
|
||||
return k;
|
||||
}
|
||||
const estimateWindow = (bytes) => {
|
||||
if (bytes > 64 * 1024)
|
||||
return 8;
|
||||
if (bytes > 1024)
|
||||
return 4;
|
||||
return 2;
|
||||
};
|
||||
class GHASH {
|
||||
// We select bits per window adaptively based on expectedLength
|
||||
constructor(key, expectedLength) {
|
||||
this.blockLen = BLOCK_SIZE;
|
||||
this.outputLen = BLOCK_SIZE;
|
||||
this.s0 = 0;
|
||||
this.s1 = 0;
|
||||
this.s2 = 0;
|
||||
this.s3 = 0;
|
||||
this.finished = false;
|
||||
key = toBytes(key);
|
||||
abytes(key, 16);
|
||||
const kView = createView(key);
|
||||
let k0 = kView.getUint32(0, false);
|
||||
let k1 = kView.getUint32(4, false);
|
||||
let k2 = kView.getUint32(8, false);
|
||||
let k3 = kView.getUint32(12, false);
|
||||
// generate table of doubled keys (half of montgomery ladder)
|
||||
const doubles = [];
|
||||
for (let i = 0; i < 128; i++) {
|
||||
doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
|
||||
({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3));
|
||||
}
|
||||
const W = estimateWindow(expectedLength || 1024);
|
||||
if (![1, 2, 4, 8].includes(W))
|
||||
throw new Error(`ghash: wrong window size=${W}, should be 2, 4 or 8`);
|
||||
this.W = W;
|
||||
const bits = 128; // always 128 bits;
|
||||
const windows = bits / W;
|
||||
const windowSize = (this.windowSize = 2 ** W);
|
||||
const items = [];
|
||||
// Create precompute table for window of W bits
|
||||
for (let w = 0; w < windows; w++) {
|
||||
// truth table: 00, 01, 10, 11
|
||||
for (let byte = 0; byte < windowSize; byte++) {
|
||||
// prettier-ignore
|
||||
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
||||
for (let j = 0; j < W; j++) {
|
||||
const bit = (byte >>> (W - j - 1)) & 1;
|
||||
if (!bit)
|
||||
continue;
|
||||
const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
|
||||
(s0 ^= d0), (s1 ^= d1), (s2 ^= d2), (s3 ^= d3);
|
||||
}
|
||||
items.push({ s0, s1, s2, s3 });
|
||||
}
|
||||
}
|
||||
this.t = items;
|
||||
}
|
||||
_updateBlock(s0, s1, s2, s3) {
|
||||
(s0 ^= this.s0), (s1 ^= this.s1), (s2 ^= this.s2), (s3 ^= this.s3);
|
||||
const { W, t, windowSize } = this;
|
||||
// prettier-ignore
|
||||
let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
|
||||
const mask = (1 << W) - 1; // 2**W will kill performance.
|
||||
let w = 0;
|
||||
for (const num of [s0, s1, s2, s3]) {
|
||||
for (let bytePos = 0; bytePos < 4; bytePos++) {
|
||||
const byte = (num >>> (8 * bytePos)) & 0xff;
|
||||
for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
|
||||
const bit = (byte >>> (W * bitPos)) & mask;
|
||||
const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
|
||||
(o0 ^= e0), (o1 ^= e1), (o2 ^= e2), (o3 ^= e3);
|
||||
w += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.s0 = o0;
|
||||
this.s1 = o1;
|
||||
this.s2 = o2;
|
||||
this.s3 = o3;
|
||||
}
|
||||
update(data) {
|
||||
data = toBytes(data);
|
||||
aexists(this);
|
||||
const b32 = u32(data);
|
||||
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
||||
const left = data.length % BLOCK_SIZE;
|
||||
for (let i = 0; i < blocks; i++) {
|
||||
this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
|
||||
}
|
||||
if (left) {
|
||||
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
||||
this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
|
||||
ZEROS32.fill(0); // clean tmp buffer
|
||||
}
|
||||
return this;
|
||||
}
|
||||
destroy() {
|
||||
const { t } = this;
|
||||
// clean precompute table
|
||||
for (const elm of t) {
|
||||
(elm.s0 = 0), (elm.s1 = 0), (elm.s2 = 0), (elm.s3 = 0);
|
||||
}
|
||||
}
|
||||
digestInto(out) {
|
||||
aexists(this);
|
||||
aoutput(out, this);
|
||||
this.finished = true;
|
||||
const { s0, s1, s2, s3 } = this;
|
||||
const o32 = u32(out);
|
||||
o32[0] = s0;
|
||||
o32[1] = s1;
|
||||
o32[2] = s2;
|
||||
o32[3] = s3;
|
||||
return out;
|
||||
}
|
||||
digest() {
|
||||
const res = new Uint8Array(BLOCK_SIZE);
|
||||
this.digestInto(res);
|
||||
this.destroy();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
class Polyval extends GHASH {
|
||||
constructor(key, expectedLength) {
|
||||
key = toBytes(key);
|
||||
const ghKey = _toGHASHKey(key.slice());
|
||||
super(ghKey, expectedLength);
|
||||
ghKey.fill(0);
|
||||
}
|
||||
update(data) {
|
||||
data = toBytes(data);
|
||||
aexists(this);
|
||||
const b32 = u32(data);
|
||||
const left = data.length % BLOCK_SIZE;
|
||||
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
||||
for (let i = 0; i < blocks; i++) {
|
||||
this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
|
||||
}
|
||||
if (left) {
|
||||
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
||||
this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
|
||||
ZEROS32.fill(0); // clean tmp buffer
|
||||
}
|
||||
return this;
|
||||
}
|
||||
digestInto(out) {
|
||||
aexists(this);
|
||||
aoutput(out, this);
|
||||
this.finished = true;
|
||||
// tmp ugly hack
|
||||
const { s0, s1, s2, s3 } = this;
|
||||
const o32 = u32(out);
|
||||
o32[0] = s0;
|
||||
o32[1] = s1;
|
||||
o32[2] = s2;
|
||||
o32[3] = s3;
|
||||
return out.reverse();
|
||||
}
|
||||
}
|
||||
function wrapConstructorWithKey(hashCons) {
|
||||
const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes(msg)).digest();
|
||||
const tmp = hashCons(new Uint8Array(16), 0);
|
||||
hashC.outputLen = tmp.outputLen;
|
||||
hashC.blockLen = tmp.blockLen;
|
||||
hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
|
||||
return hashC;
|
||||
}
|
||||
export const ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
|
||||
export const polyval = wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
|
||||
//# sourceMappingURL=_polyval.js.map
|
||||
1
node_modules/@noble/ciphers/esm/_polyval.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/_polyval.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
670
node_modules/@noble/ciphers/esm/aes.js
generated
vendored
Normal file
670
node_modules/@noble/ciphers/esm/aes.js
generated
vendored
Normal file
@@ -0,0 +1,670 @@
|
||||
// prettier-ignore
|
||||
import { wrapCipher, createView, setBigUint64, equalBytes, u32, u8, } from './utils.js';
|
||||
import { ghash, polyval } from './_polyval.js';
|
||||
import { bytes as abytes } from './_assert.js';
|
||||
/*
|
||||
AES (Advanced Encryption Standard) aka Rijndael block cipher.
|
||||
|
||||
Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256 bits). In every round:
|
||||
1. **S-box**, table substitution
|
||||
2. **Shift rows**, cyclic shift left of all rows of data array
|
||||
3. **Mix columns**, multiplying every column by fixed polynomial
|
||||
4. **Add round key**, round_key xor i-th column of array
|
||||
|
||||
Resources:
|
||||
- FIPS-197 https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf
|
||||
- Original proposal: https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf
|
||||
*/
|
||||
const BLOCK_SIZE = 16;
|
||||
const BLOCK_SIZE32 = 4;
|
||||
const EMPTY_BLOCK = new Uint8Array(BLOCK_SIZE);
|
||||
const POLY = 0x11b; // 1 + x + x**3 + x**4 + x**8
|
||||
// TODO: remove multiplication, binary ops only
|
||||
function mul2(n) {
|
||||
return (n << 1) ^ (POLY & -(n >> 7));
|
||||
}
|
||||
function mul(a, b) {
|
||||
let res = 0;
|
||||
for (; b > 0; b >>= 1) {
|
||||
// Montgomery ladder
|
||||
res ^= a & -(b & 1); // if (b&1) res ^=a (but const-time).
|
||||
a = mul2(a); // a = 2*a
|
||||
}
|
||||
return res;
|
||||
}
|
||||
// AES S-box is generated using finite field inversion,
|
||||
// an affine transform, and xor of a constant 0x63.
|
||||
const sbox = /* @__PURE__ */ (() => {
|
||||
let t = new Uint8Array(256);
|
||||
for (let i = 0, x = 1; i < 256; i++, x ^= mul2(x))
|
||||
t[i] = x;
|
||||
const box = new Uint8Array(256);
|
||||
box[0] = 0x63; // first elm
|
||||
for (let i = 0; i < 255; i++) {
|
||||
let x = t[255 - i];
|
||||
x |= x << 8;
|
||||
box[t[i]] = (x ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63) & 0xff;
|
||||
}
|
||||
return box;
|
||||
})();
|
||||
// Inverted S-box
|
||||
const invSbox = /* @__PURE__ */ sbox.map((_, j) => sbox.indexOf(j));
|
||||
// Rotate u32 by 8
|
||||
const rotr32_8 = (n) => (n << 24) | (n >>> 8);
|
||||
const rotl32_8 = (n) => (n << 8) | (n >>> 24);
|
||||
// T-table is optimization suggested in 5.2 of original proposal (missed from FIPS-197). Changes:
|
||||
// - LE instead of BE
|
||||
// - bigger tables: T0 and T1 are merged into T01 table and T2 & T3 into T23;
|
||||
// so index is u16, instead of u8. This speeds up things, unexpectedly
|
||||
function genTtable(sbox, fn) {
|
||||
if (sbox.length !== 256)
|
||||
throw new Error('Wrong sbox length');
|
||||
const T0 = new Uint32Array(256).map((_, j) => fn(sbox[j]));
|
||||
const T1 = T0.map(rotl32_8);
|
||||
const T2 = T1.map(rotl32_8);
|
||||
const T3 = T2.map(rotl32_8);
|
||||
const T01 = new Uint32Array(256 * 256);
|
||||
const T23 = new Uint32Array(256 * 256);
|
||||
const sbox2 = new Uint16Array(256 * 256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
for (let j = 0; j < 256; j++) {
|
||||
const idx = i * 256 + j;
|
||||
T01[idx] = T0[i] ^ T1[j];
|
||||
T23[idx] = T2[i] ^ T3[j];
|
||||
sbox2[idx] = (sbox[i] << 8) | sbox[j];
|
||||
}
|
||||
}
|
||||
return { sbox, sbox2, T0, T1, T2, T3, T01, T23 };
|
||||
}
|
||||
const tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => (mul(s, 3) << 24) | (s << 16) | (s << 8) | mul(s, 2));
|
||||
const tableDecoding = /* @__PURE__ */ genTtable(invSbox, (s) => (mul(s, 11) << 24) | (mul(s, 13) << 16) | (mul(s, 9) << 8) | mul(s, 14));
|
||||
const xPowers = /* @__PURE__ */ (() => {
|
||||
const p = new Uint8Array(16);
|
||||
for (let i = 0, x = 1; i < 16; i++, x = mul2(x))
|
||||
p[i] = x;
|
||||
return p;
|
||||
})();
|
||||
export function expandKeyLE(key) {
|
||||
abytes(key);
|
||||
const len = key.length;
|
||||
if (![16, 24, 32].includes(len))
|
||||
throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`);
|
||||
const { sbox2 } = tableEncoding;
|
||||
const k32 = u32(key);
|
||||
const Nk = k32.length;
|
||||
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
||||
const xk = new Uint32Array(len + 28); // expanded key
|
||||
xk.set(k32);
|
||||
// 4.3.1 Key expansion
|
||||
for (let i = Nk; i < xk.length; i++) {
|
||||
let t = xk[i - 1];
|
||||
if (i % Nk === 0)
|
||||
t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
|
||||
else if (Nk > 6 && i % Nk === 4)
|
||||
t = subByte(t);
|
||||
xk[i] = xk[i - Nk] ^ t;
|
||||
}
|
||||
return xk;
|
||||
}
|
||||
export function expandKeyDecLE(key) {
|
||||
const encKey = expandKeyLE(key);
|
||||
const xk = encKey.slice();
|
||||
const Nk = encKey.length;
|
||||
const { sbox2 } = tableEncoding;
|
||||
const { T0, T1, T2, T3 } = tableDecoding;
|
||||
// Inverse key by chunks of 4 (rounds)
|
||||
for (let i = 0; i < Nk; i += 4) {
|
||||
for (let j = 0; j < 4; j++)
|
||||
xk[i + j] = encKey[Nk - i - 4 + j];
|
||||
}
|
||||
encKey.fill(0);
|
||||
// apply InvMixColumn except first & last round
|
||||
for (let i = 4; i < Nk - 4; i++) {
|
||||
const x = xk[i];
|
||||
const w = applySbox(sbox2, x, x, x, x);
|
||||
xk[i] = T0[w & 0xff] ^ T1[(w >>> 8) & 0xff] ^ T2[(w >>> 16) & 0xff] ^ T3[w >>> 24];
|
||||
}
|
||||
return xk;
|
||||
}
|
||||
// Apply tables
|
||||
function apply0123(T01, T23, s0, s1, s2, s3) {
|
||||
return (T01[((s0 << 8) & 0xff00) | ((s1 >>> 8) & 0xff)] ^
|
||||
T23[((s2 >>> 8) & 0xff00) | ((s3 >>> 24) & 0xff)]);
|
||||
}
|
||||
function applySbox(sbox2, s0, s1, s2, s3) {
|
||||
return (sbox2[(s0 & 0xff) | (s1 & 0xff00)] |
|
||||
(sbox2[((s2 >>> 16) & 0xff) | ((s3 >>> 16) & 0xff00)] << 16));
|
||||
}
|
||||
function encrypt(xk, s0, s1, s2, s3) {
|
||||
const { sbox2, T01, T23 } = tableEncoding;
|
||||
let k = 0;
|
||||
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
|
||||
const rounds = xk.length / 4 - 2;
|
||||
for (let i = 0; i < rounds; i++) {
|
||||
const t0 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
|
||||
const t1 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
|
||||
const t2 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
|
||||
const t3 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
|
||||
(s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
|
||||
}
|
||||
// last round (without mixcolumns, so using SBOX2 table)
|
||||
const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
|
||||
const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
|
||||
const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
|
||||
const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
|
||||
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
||||
}
|
||||
function decrypt(xk, s0, s1, s2, s3) {
|
||||
const { sbox2, T01, T23 } = tableDecoding;
|
||||
let k = 0;
|
||||
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
|
||||
const rounds = xk.length / 4 - 2;
|
||||
for (let i = 0; i < rounds; i++) {
|
||||
const t0 = xk[k++] ^ apply0123(T01, T23, s0, s3, s2, s1);
|
||||
const t1 = xk[k++] ^ apply0123(T01, T23, s1, s0, s3, s2);
|
||||
const t2 = xk[k++] ^ apply0123(T01, T23, s2, s1, s0, s3);
|
||||
const t3 = xk[k++] ^ apply0123(T01, T23, s3, s2, s1, s0);
|
||||
(s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
|
||||
}
|
||||
// Last round
|
||||
const t0 = xk[k++] ^ applySbox(sbox2, s0, s3, s2, s1);
|
||||
const t1 = xk[k++] ^ applySbox(sbox2, s1, s0, s3, s2);
|
||||
const t2 = xk[k++] ^ applySbox(sbox2, s2, s1, s0, s3);
|
||||
const t3 = xk[k++] ^ applySbox(sbox2, s3, s2, s1, s0);
|
||||
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
||||
}
|
||||
function getDst(len, dst) {
|
||||
if (!dst)
|
||||
return new Uint8Array(len);
|
||||
abytes(dst);
|
||||
if (dst.length < len)
|
||||
throw new Error(`aes: wrong destination length, expected at least ${len}, got: ${dst.length}`);
|
||||
return dst;
|
||||
}
|
||||
// TODO: investigate merging with ctr32
|
||||
function ctrCounter(xk, nonce, src, dst) {
|
||||
abytes(nonce, BLOCK_SIZE);
|
||||
abytes(src);
|
||||
const srcLen = src.length;
|
||||
dst = getDst(srcLen, dst);
|
||||
const ctr = nonce;
|
||||
const c32 = u32(ctr);
|
||||
// Fill block (empty, ctr=0)
|
||||
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
||||
const src32 = u32(src);
|
||||
const dst32 = u32(dst);
|
||||
// process blocks
|
||||
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
||||
dst32[i + 0] = src32[i + 0] ^ s0;
|
||||
dst32[i + 1] = src32[i + 1] ^ s1;
|
||||
dst32[i + 2] = src32[i + 2] ^ s2;
|
||||
dst32[i + 3] = src32[i + 3] ^ s3;
|
||||
// Full 128 bit counter with wrap around
|
||||
let carry = 1;
|
||||
for (let i = ctr.length - 1; i >= 0; i--) {
|
||||
carry = (carry + (ctr[i] & 0xff)) | 0;
|
||||
ctr[i] = carry & 0xff;
|
||||
carry >>>= 8;
|
||||
}
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
||||
}
|
||||
// leftovers (less than block)
|
||||
// It's possible to handle > u32 fast, but is it worth it?
|
||||
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
||||
if (start < srcLen) {
|
||||
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
||||
const buf = u8(b32);
|
||||
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
||||
dst[i] = src[i] ^ buf[pos];
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
// AES CTR with overflowing 32 bit counter
|
||||
// It's possible to do 32le significantly simpler (and probably faster) by using u32.
|
||||
// But, we need both, and perf bottleneck is in ghash anyway.
|
||||
function ctr32(xk, isLE, nonce, src, dst) {
|
||||
abytes(nonce, BLOCK_SIZE);
|
||||
abytes(src);
|
||||
dst = getDst(src.length, dst);
|
||||
const ctr = nonce; // write new value to nonce, so it can be re-used
|
||||
const c32 = u32(ctr);
|
||||
const view = createView(ctr);
|
||||
const src32 = u32(src);
|
||||
const dst32 = u32(dst);
|
||||
const ctrPos = isLE ? 0 : 12;
|
||||
const srcLen = src.length;
|
||||
// Fill block (empty, ctr=0)
|
||||
let ctrNum = view.getUint32(ctrPos, isLE); // read current counter value
|
||||
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
||||
// process blocks
|
||||
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
||||
dst32[i + 0] = src32[i + 0] ^ s0;
|
||||
dst32[i + 1] = src32[i + 1] ^ s1;
|
||||
dst32[i + 2] = src32[i + 2] ^ s2;
|
||||
dst32[i + 3] = src32[i + 3] ^ s3;
|
||||
ctrNum = (ctrNum + 1) >>> 0; // u32 wrap
|
||||
view.setUint32(ctrPos, ctrNum, isLE);
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
||||
}
|
||||
// leftovers (less than a block)
|
||||
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
||||
if (start < srcLen) {
|
||||
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
||||
const buf = u8(b32);
|
||||
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
||||
dst[i] = src[i] ^ buf[pos];
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
/**
|
||||
* CTR: counter mode. Creates stream cipher.
|
||||
* Requires good IV. Parallelizable. OK, but no MAC.
|
||||
*/
|
||||
export const ctr = wrapCipher({ blockSize: 16, nonceLength: 16 }, function ctr(key, nonce) {
|
||||
abytes(key);
|
||||
abytes(nonce, BLOCK_SIZE);
|
||||
function processCtr(buf, dst) {
|
||||
const xk = expandKeyLE(key);
|
||||
const n = nonce.slice();
|
||||
const out = ctrCounter(xk, n, buf, dst);
|
||||
xk.fill(0);
|
||||
n.fill(0);
|
||||
return out;
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext, dst) => processCtr(plaintext, dst),
|
||||
decrypt: (ciphertext, dst) => processCtr(ciphertext, dst),
|
||||
};
|
||||
});
|
||||
function validateBlockDecrypt(data) {
|
||||
abytes(data);
|
||||
if (data.length % BLOCK_SIZE !== 0) {
|
||||
throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE}`);
|
||||
}
|
||||
}
|
||||
function validateBlockEncrypt(plaintext, pcks5, dst) {
|
||||
let outLen = plaintext.length;
|
||||
const remaining = outLen % BLOCK_SIZE;
|
||||
if (!pcks5 && remaining !== 0)
|
||||
throw new Error('aec/(cbc-ecb): unpadded plaintext with disabled padding');
|
||||
const b = u32(plaintext);
|
||||
if (pcks5) {
|
||||
let left = BLOCK_SIZE - remaining;
|
||||
if (!left)
|
||||
left = BLOCK_SIZE; // if no bytes left, create empty padding block
|
||||
outLen = outLen + left;
|
||||
}
|
||||
const out = getDst(outLen, dst);
|
||||
const o = u32(out);
|
||||
return { b, o, out };
|
||||
}
|
||||
function validatePCKS(data, pcks5) {
|
||||
if (!pcks5)
|
||||
return data;
|
||||
const len = data.length;
|
||||
if (!len)
|
||||
throw new Error(`aes/pcks5: empty ciphertext not allowed`);
|
||||
const lastByte = data[len - 1];
|
||||
if (lastByte <= 0 || lastByte > 16)
|
||||
throw new Error(`aes/pcks5: wrong padding byte: ${lastByte}`);
|
||||
const out = data.subarray(0, -lastByte);
|
||||
for (let i = 0; i < lastByte; i++)
|
||||
if (data[len - i - 1] !== lastByte)
|
||||
throw new Error(`aes/pcks5: wrong padding`);
|
||||
return out;
|
||||
}
|
||||
function padPCKS(left) {
|
||||
const tmp = new Uint8Array(16);
|
||||
const tmp32 = u32(tmp);
|
||||
tmp.set(left);
|
||||
const paddingByte = BLOCK_SIZE - left.length;
|
||||
for (let i = BLOCK_SIZE - paddingByte; i < BLOCK_SIZE; i++)
|
||||
tmp[i] = paddingByte;
|
||||
return tmp32;
|
||||
}
|
||||
/**
|
||||
* ECB: Electronic CodeBook. Simple deterministic replacement.
|
||||
* Dangerous: always map x to y. See [AES Penguin](https://words.filippo.io/the-ecb-penguin/).
|
||||
*/
|
||||
export const ecb = wrapCipher({ blockSize: 16 }, function ecb(key, opts = {}) {
|
||||
abytes(key);
|
||||
const pcks5 = !opts.disablePadding;
|
||||
return {
|
||||
encrypt: (plaintext, dst) => {
|
||||
abytes(plaintext);
|
||||
const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
|
||||
const xk = expandKeyLE(key);
|
||||
let i = 0;
|
||||
for (; i + 4 <= b.length;) {
|
||||
const { s0, s1, s2, s3 } = encrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
if (pcks5) {
|
||||
const tmp32 = padPCKS(plaintext.subarray(i * 4));
|
||||
const { s0, s1, s2, s3 } = encrypt(xk, tmp32[0], tmp32[1], tmp32[2], tmp32[3]);
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return _out;
|
||||
},
|
||||
decrypt: (ciphertext, dst) => {
|
||||
validateBlockDecrypt(ciphertext);
|
||||
const xk = expandKeyDecLE(key);
|
||||
const out = getDst(ciphertext.length, dst);
|
||||
const b = u32(ciphertext);
|
||||
const o = u32(out);
|
||||
for (let i = 0; i + 4 <= b.length;) {
|
||||
const { s0, s1, s2, s3 } = decrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return validatePCKS(out, pcks5);
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* CBC: Cipher-Block-Chaining. Key is previous round’s block.
|
||||
* Fragile: needs proper padding. Unauthenticated: needs MAC.
|
||||
*/
|
||||
export const cbc = wrapCipher({ blockSize: 16, nonceLength: 16 }, function cbc(key, iv, opts = {}) {
|
||||
abytes(key);
|
||||
abytes(iv, 16);
|
||||
const pcks5 = !opts.disablePadding;
|
||||
return {
|
||||
encrypt: (plaintext, dst) => {
|
||||
const xk = expandKeyLE(key);
|
||||
const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
|
||||
const n32 = u32(iv);
|
||||
// prettier-ignore
|
||||
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
||||
let i = 0;
|
||||
for (; i + 4 <= b.length;) {
|
||||
(s0 ^= b[i + 0]), (s1 ^= b[i + 1]), (s2 ^= b[i + 2]), (s3 ^= b[i + 3]);
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
if (pcks5) {
|
||||
const tmp32 = padPCKS(plaintext.subarray(i * 4));
|
||||
(s0 ^= tmp32[0]), (s1 ^= tmp32[1]), (s2 ^= tmp32[2]), (s3 ^= tmp32[3]);
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
||||
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return _out;
|
||||
},
|
||||
decrypt: (ciphertext, dst) => {
|
||||
validateBlockDecrypt(ciphertext);
|
||||
const xk = expandKeyDecLE(key);
|
||||
const n32 = u32(iv);
|
||||
const out = getDst(ciphertext.length, dst);
|
||||
const b = u32(ciphertext);
|
||||
const o = u32(out);
|
||||
// prettier-ignore
|
||||
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
||||
for (let i = 0; i + 4 <= b.length;) {
|
||||
// prettier-ignore
|
||||
const ps0 = s0, ps1 = s1, ps2 = s2, ps3 = s3;
|
||||
(s0 = b[i + 0]), (s1 = b[i + 1]), (s2 = b[i + 2]), (s3 = b[i + 3]);
|
||||
const { s0: o0, s1: o1, s2: o2, s3: o3 } = decrypt(xk, s0, s1, s2, s3);
|
||||
(o[i++] = o0 ^ ps0), (o[i++] = o1 ^ ps1), (o[i++] = o2 ^ ps2), (o[i++] = o3 ^ ps3);
|
||||
}
|
||||
xk.fill(0);
|
||||
return validatePCKS(out, pcks5);
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* CFB: Cipher Feedback Mode. The input for the block cipher is the previous cipher output.
|
||||
* Unauthenticated: needs MAC.
|
||||
*/
|
||||
export const cfb = wrapCipher({ blockSize: 16, nonceLength: 16 }, function cfb(key, iv) {
|
||||
abytes(key);
|
||||
abytes(iv, 16);
|
||||
function processCfb(src, isEncrypt, dst) {
|
||||
const xk = expandKeyLE(key);
|
||||
const srcLen = src.length;
|
||||
dst = getDst(srcLen, dst);
|
||||
const src32 = u32(src);
|
||||
const dst32 = u32(dst);
|
||||
const next32 = isEncrypt ? dst32 : src32;
|
||||
const n32 = u32(iv);
|
||||
// prettier-ignore
|
||||
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
||||
for (let i = 0; i + 4 <= src32.length;) {
|
||||
const { s0: e0, s1: e1, s2: e2, s3: e3 } = encrypt(xk, s0, s1, s2, s3);
|
||||
dst32[i + 0] = src32[i + 0] ^ e0;
|
||||
dst32[i + 1] = src32[i + 1] ^ e1;
|
||||
dst32[i + 2] = src32[i + 2] ^ e2;
|
||||
dst32[i + 3] = src32[i + 3] ^ e3;
|
||||
(s0 = next32[i++]), (s1 = next32[i++]), (s2 = next32[i++]), (s3 = next32[i++]);
|
||||
}
|
||||
// leftovers (less than block)
|
||||
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
||||
if (start < srcLen) {
|
||||
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
||||
const buf = u8(new Uint32Array([s0, s1, s2, s3]));
|
||||
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
||||
dst[i] = src[i] ^ buf[pos];
|
||||
buf.fill(0);
|
||||
}
|
||||
xk.fill(0);
|
||||
return dst;
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext, dst) => processCfb(plaintext, true, dst),
|
||||
decrypt: (ciphertext, dst) => processCfb(ciphertext, false, dst),
|
||||
};
|
||||
});
|
||||
// TODO: merge with chacha, however gcm has bitLen while chacha has byteLen
|
||||
function computeTag(fn, isLE, key, data, AAD) {
|
||||
const h = fn.create(key, data.length + (AAD?.length || 0));
|
||||
if (AAD)
|
||||
h.update(AAD);
|
||||
h.update(data);
|
||||
const num = new Uint8Array(16);
|
||||
const view = createView(num);
|
||||
if (AAD)
|
||||
setBigUint64(view, 0, BigInt(AAD.length * 8), isLE);
|
||||
setBigUint64(view, 8, BigInt(data.length * 8), isLE);
|
||||
h.update(num);
|
||||
return h.digest();
|
||||
}
|
||||
/**
|
||||
* GCM: Galois/Counter Mode.
|
||||
* Good, modern version of CTR, parallel, with MAC.
|
||||
* Be careful: MACs can be forged.
|
||||
*/
|
||||
export const gcm = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function gcm(key, nonce, AAD) {
|
||||
abytes(nonce);
|
||||
// Nonce can be pretty much anything (even 1 byte). But smaller nonces less secure.
|
||||
if (nonce.length === 0)
|
||||
throw new Error('aes/gcm: empty nonce');
|
||||
const tagLength = 16;
|
||||
function _computeTag(authKey, tagMask, data) {
|
||||
const tag = computeTag(ghash, false, authKey, data, AAD);
|
||||
for (let i = 0; i < tagMask.length; i++)
|
||||
tag[i] ^= tagMask[i];
|
||||
return tag;
|
||||
}
|
||||
function deriveKeys() {
|
||||
const xk = expandKeyLE(key);
|
||||
const authKey = EMPTY_BLOCK.slice();
|
||||
const counter = EMPTY_BLOCK.slice();
|
||||
ctr32(xk, false, counter, counter, authKey);
|
||||
if (nonce.length === 12) {
|
||||
counter.set(nonce);
|
||||
}
|
||||
else {
|
||||
// Spec (NIST 800-38d) supports variable size nonce.
|
||||
// Not supported for now, but can be useful.
|
||||
const nonceLen = EMPTY_BLOCK.slice();
|
||||
const view = createView(nonceLen);
|
||||
setBigUint64(view, 8, BigInt(nonce.length * 8), false);
|
||||
// ghash(nonce || u64be(0) || u64be(nonceLen*8))
|
||||
ghash.create(authKey).update(nonce).update(nonceLen).digestInto(counter);
|
||||
}
|
||||
const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
|
||||
return { xk, authKey, counter, tagMask };
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
abytes(plaintext);
|
||||
const { xk, authKey, counter, tagMask } = deriveKeys();
|
||||
const out = new Uint8Array(plaintext.length + tagLength);
|
||||
ctr32(xk, false, counter, plaintext, out);
|
||||
const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
|
||||
out.set(tag, plaintext.length);
|
||||
xk.fill(0);
|
||||
return out;
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
abytes(ciphertext);
|
||||
if (ciphertext.length < tagLength)
|
||||
throw new Error(`aes/gcm: ciphertext less than tagLen (${tagLength})`);
|
||||
const { xk, authKey, counter, tagMask } = deriveKeys();
|
||||
const data = ciphertext.subarray(0, -tagLength);
|
||||
const passedTag = ciphertext.subarray(-tagLength);
|
||||
const tag = _computeTag(authKey, tagMask, data);
|
||||
if (!equalBytes(tag, passedTag))
|
||||
throw new Error('aes/gcm: invalid ghash tag');
|
||||
const out = ctr32(xk, false, counter, data);
|
||||
authKey.fill(0);
|
||||
tagMask.fill(0);
|
||||
xk.fill(0);
|
||||
return out;
|
||||
},
|
||||
};
|
||||
});
|
||||
const limit = (name, min, max) => (value) => {
|
||||
if (!Number.isSafeInteger(value) || min > value || value > max)
|
||||
throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`);
|
||||
};
|
||||
/**
|
||||
* AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance.
|
||||
* Guarantees that, when a nonce is repeated, the only security loss is that identical
|
||||
* plaintexts will produce identical ciphertexts.
|
||||
* RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452
|
||||
*/
|
||||
export const siv = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function siv(key, nonce, AAD) {
|
||||
const tagLength = 16;
|
||||
// From RFC 8452: Section 6
|
||||
const AAD_LIMIT = limit('AAD', 0, 2 ** 36);
|
||||
const PLAIN_LIMIT = limit('plaintext', 0, 2 ** 36);
|
||||
const NONCE_LIMIT = limit('nonce', 12, 12);
|
||||
const CIPHER_LIMIT = limit('ciphertext', 16, 2 ** 36 + 16);
|
||||
abytes(nonce);
|
||||
NONCE_LIMIT(nonce.length);
|
||||
if (AAD) {
|
||||
abytes(AAD);
|
||||
AAD_LIMIT(AAD.length);
|
||||
}
|
||||
function deriveKeys() {
|
||||
const len = key.length;
|
||||
if (len !== 16 && len !== 24 && len !== 32)
|
||||
throw new Error(`key length must be 16, 24 or 32 bytes, got: ${len} bytes`);
|
||||
const xk = expandKeyLE(key);
|
||||
const encKey = new Uint8Array(len);
|
||||
const authKey = new Uint8Array(16);
|
||||
const n32 = u32(nonce);
|
||||
// prettier-ignore
|
||||
let s0 = 0, s1 = n32[0], s2 = n32[1], s3 = n32[2];
|
||||
let counter = 0;
|
||||
for (const derivedKey of [authKey, encKey].map(u32)) {
|
||||
const d32 = u32(derivedKey);
|
||||
for (let i = 0; i < d32.length; i += 2) {
|
||||
// aes(u32le(0) || nonce)[:8] || aes(u32le(1) || nonce)[:8] ...
|
||||
const { s0: o0, s1: o1 } = encrypt(xk, s0, s1, s2, s3);
|
||||
d32[i + 0] = o0;
|
||||
d32[i + 1] = o1;
|
||||
s0 = ++counter; // increment counter inside state
|
||||
}
|
||||
}
|
||||
xk.fill(0);
|
||||
return { authKey, encKey: expandKeyLE(encKey) };
|
||||
}
|
||||
function _computeTag(encKey, authKey, data) {
|
||||
const tag = computeTag(polyval, true, authKey, data, AAD);
|
||||
// Compute the expected tag by XORing S_s and the nonce, clearing the
|
||||
// most significant bit of the last byte and encrypting with the
|
||||
// message-encryption key.
|
||||
for (let i = 0; i < 12; i++)
|
||||
tag[i] ^= nonce[i];
|
||||
tag[15] &= 0x7f; // Clear the highest bit
|
||||
// encrypt tag as block
|
||||
const t32 = u32(tag);
|
||||
// prettier-ignore
|
||||
let s0 = t32[0], s1 = t32[1], s2 = t32[2], s3 = t32[3];
|
||||
({ s0, s1, s2, s3 } = encrypt(encKey, s0, s1, s2, s3));
|
||||
(t32[0] = s0), (t32[1] = s1), (t32[2] = s2), (t32[3] = s3);
|
||||
return tag;
|
||||
}
|
||||
// actual decrypt/encrypt of message.
|
||||
function processSiv(encKey, tag, input) {
|
||||
let block = tag.slice();
|
||||
block[15] |= 0x80; // Force highest bit
|
||||
return ctr32(encKey, true, block, input);
|
||||
}
|
||||
return {
|
||||
encrypt: (plaintext) => {
|
||||
abytes(plaintext);
|
||||
PLAIN_LIMIT(plaintext.length);
|
||||
const { encKey, authKey } = deriveKeys();
|
||||
const tag = _computeTag(encKey, authKey, plaintext);
|
||||
const out = new Uint8Array(plaintext.length + tagLength);
|
||||
out.set(tag, plaintext.length);
|
||||
out.set(processSiv(encKey, tag, plaintext));
|
||||
encKey.fill(0);
|
||||
authKey.fill(0);
|
||||
return out;
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
abytes(ciphertext);
|
||||
CIPHER_LIMIT(ciphertext.length);
|
||||
const tag = ciphertext.subarray(-tagLength);
|
||||
const { encKey, authKey } = deriveKeys();
|
||||
const plaintext = processSiv(encKey, tag, ciphertext.subarray(0, -tagLength));
|
||||
const expectedTag = _computeTag(encKey, authKey, plaintext);
|
||||
encKey.fill(0);
|
||||
authKey.fill(0);
|
||||
if (!equalBytes(tag, expectedTag))
|
||||
throw new Error('invalid polyval tag');
|
||||
return plaintext;
|
||||
},
|
||||
};
|
||||
});
|
||||
function isBytes32(a) {
|
||||
return (a != null &&
|
||||
typeof a === 'object' &&
|
||||
(a instanceof Uint32Array || a.constructor.name === 'Uint32Array'));
|
||||
}
|
||||
function encryptBlock(xk, block) {
|
||||
abytes(block, 16);
|
||||
if (!isBytes32(xk))
|
||||
throw new Error('_encryptBlock accepts result of expandKeyLE');
|
||||
const b32 = u32(block);
|
||||
let { s0, s1, s2, s3 } = encrypt(xk, b32[0], b32[1], b32[2], b32[3]);
|
||||
(b32[0] = s0), (b32[1] = s1), (b32[2] = s2), (b32[3] = s3);
|
||||
return block;
|
||||
}
|
||||
function decryptBlock(xk, block) {
|
||||
abytes(block, 16);
|
||||
if (!isBytes32(xk))
|
||||
throw new Error('_decryptBlock accepts result of expandKeyLE');
|
||||
const b32 = u32(block);
|
||||
let { s0, s1, s2, s3 } = decrypt(xk, b32[0], b32[1], b32[2], b32[3]);
|
||||
(b32[0] = s0), (b32[1] = s1), (b32[2] = s2), (b32[3] = s3);
|
||||
return block;
|
||||
}
|
||||
// Highly unsafe private functions for implementing new modes or ciphers based on AES
|
||||
// Can change at any time, no API guarantees
|
||||
export const unsafe = {
|
||||
expandKeyLE,
|
||||
expandKeyDecLE,
|
||||
encrypt,
|
||||
decrypt,
|
||||
encryptBlock,
|
||||
decryptBlock,
|
||||
ctrCounter,
|
||||
ctr32,
|
||||
};
|
||||
//# sourceMappingURL=aes.js.map
|
||||
1
node_modules/@noble/ciphers/esm/aes.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/aes.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
318
node_modules/@noble/ciphers/esm/chacha.js
generated
vendored
Normal file
318
node_modules/@noble/ciphers/esm/chacha.js
generated
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
// prettier-ignore
|
||||
import { wrapCipher, createView, equalBytes, setBigUint64, } from './utils.js';
|
||||
import { poly1305 } from './_poly1305.js';
|
||||
import { createCipher, rotl } from './_arx.js';
|
||||
import { bytes as abytes } from './_assert.js';
|
||||
// ChaCha20 stream cipher was released in 2008. ChaCha aims to increase
|
||||
// the diffusion per round, but had slightly less cryptanalysis.
|
||||
// https://cr.yp.to/chacha.html, http://cr.yp.to/chacha/chacha-20080128.pdf
|
||||
/**
|
||||
* ChaCha core function.
|
||||
*/
|
||||
// prettier-ignore
|
||||
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
||||
let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], // "expa" "nd 3" "2-by" "te k"
|
||||
y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], // Key Key Key Key
|
||||
y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], // Key Key Key Key
|
||||
y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Counter Nonce Nonce
|
||||
// Save state to temporary variables
|
||||
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
|
||||
for (let r = 0; r < rounds; r += 2) {
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = rotl(x12 ^ x00, 16);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = rotl(x04 ^ x08, 12);
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = rotl(x12 ^ x00, 8);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = rotl(x04 ^ x08, 7);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = rotl(x13 ^ x01, 16);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = rotl(x05 ^ x09, 12);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = rotl(x13 ^ x01, 8);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = rotl(x05 ^ x09, 7);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = rotl(x14 ^ x02, 16);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = rotl(x06 ^ x10, 12);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = rotl(x14 ^ x02, 8);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = rotl(x06 ^ x10, 7);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = rotl(x15 ^ x03, 16);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = rotl(x07 ^ x11, 12);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = rotl(x15 ^ x03, 8);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = rotl(x07 ^ x11, 7);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = rotl(x15 ^ x00, 16);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = rotl(x05 ^ x10, 12);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = rotl(x15 ^ x00, 8);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = rotl(x05 ^ x10, 7);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = rotl(x12 ^ x01, 16);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = rotl(x06 ^ x11, 12);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = rotl(x12 ^ x01, 8);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = rotl(x06 ^ x11, 7);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = rotl(x13 ^ x02, 16);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = rotl(x07 ^ x08, 12);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = rotl(x13 ^ x02, 8);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = rotl(x07 ^ x08, 7);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = rotl(x14 ^ x03, 16);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = rotl(x04 ^ x09, 12);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = rotl(x14 ^ x03, 8);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = rotl(x04 ^ x09, 7);
|
||||
}
|
||||
// Write output
|
||||
let oi = 0;
|
||||
out[oi++] = (y00 + x00) | 0;
|
||||
out[oi++] = (y01 + x01) | 0;
|
||||
out[oi++] = (y02 + x02) | 0;
|
||||
out[oi++] = (y03 + x03) | 0;
|
||||
out[oi++] = (y04 + x04) | 0;
|
||||
out[oi++] = (y05 + x05) | 0;
|
||||
out[oi++] = (y06 + x06) | 0;
|
||||
out[oi++] = (y07 + x07) | 0;
|
||||
out[oi++] = (y08 + x08) | 0;
|
||||
out[oi++] = (y09 + x09) | 0;
|
||||
out[oi++] = (y10 + x10) | 0;
|
||||
out[oi++] = (y11 + x11) | 0;
|
||||
out[oi++] = (y12 + x12) | 0;
|
||||
out[oi++] = (y13 + x13) | 0;
|
||||
out[oi++] = (y14 + x14) | 0;
|
||||
out[oi++] = (y15 + x15) | 0;
|
||||
}
|
||||
/**
|
||||
* hchacha helper method, used primarily in xchacha, to hash
|
||||
* key and nonce into key' and nonce'.
|
||||
* Same as chachaCore, but there doesn't seem to be a way to move the block
|
||||
* out without 25% performance hit.
|
||||
*/
|
||||
// prettier-ignore
|
||||
export function hchacha(s, k, i, o32) {
|
||||
let x00 = s[0], x01 = s[1], x02 = s[2], x03 = s[3], x04 = k[0], x05 = k[1], x06 = k[2], x07 = k[3], x08 = k[4], x09 = k[5], x10 = k[6], x11 = k[7], x12 = i[0], x13 = i[1], x14 = i[2], x15 = i[3];
|
||||
for (let r = 0; r < 20; r += 2) {
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = rotl(x12 ^ x00, 16);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = rotl(x04 ^ x08, 12);
|
||||
x00 = (x00 + x04) | 0;
|
||||
x12 = rotl(x12 ^ x00, 8);
|
||||
x08 = (x08 + x12) | 0;
|
||||
x04 = rotl(x04 ^ x08, 7);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = rotl(x13 ^ x01, 16);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = rotl(x05 ^ x09, 12);
|
||||
x01 = (x01 + x05) | 0;
|
||||
x13 = rotl(x13 ^ x01, 8);
|
||||
x09 = (x09 + x13) | 0;
|
||||
x05 = rotl(x05 ^ x09, 7);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = rotl(x14 ^ x02, 16);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = rotl(x06 ^ x10, 12);
|
||||
x02 = (x02 + x06) | 0;
|
||||
x14 = rotl(x14 ^ x02, 8);
|
||||
x10 = (x10 + x14) | 0;
|
||||
x06 = rotl(x06 ^ x10, 7);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = rotl(x15 ^ x03, 16);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = rotl(x07 ^ x11, 12);
|
||||
x03 = (x03 + x07) | 0;
|
||||
x15 = rotl(x15 ^ x03, 8);
|
||||
x11 = (x11 + x15) | 0;
|
||||
x07 = rotl(x07 ^ x11, 7);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = rotl(x15 ^ x00, 16);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = rotl(x05 ^ x10, 12);
|
||||
x00 = (x00 + x05) | 0;
|
||||
x15 = rotl(x15 ^ x00, 8);
|
||||
x10 = (x10 + x15) | 0;
|
||||
x05 = rotl(x05 ^ x10, 7);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = rotl(x12 ^ x01, 16);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = rotl(x06 ^ x11, 12);
|
||||
x01 = (x01 + x06) | 0;
|
||||
x12 = rotl(x12 ^ x01, 8);
|
||||
x11 = (x11 + x12) | 0;
|
||||
x06 = rotl(x06 ^ x11, 7);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = rotl(x13 ^ x02, 16);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = rotl(x07 ^ x08, 12);
|
||||
x02 = (x02 + x07) | 0;
|
||||
x13 = rotl(x13 ^ x02, 8);
|
||||
x08 = (x08 + x13) | 0;
|
||||
x07 = rotl(x07 ^ x08, 7);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = rotl(x14 ^ x03, 16);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = rotl(x04 ^ x09, 12);
|
||||
x03 = (x03 + x04) | 0;
|
||||
x14 = rotl(x14 ^ x03, 8);
|
||||
x09 = (x09 + x14) | 0;
|
||||
x04 = rotl(x04 ^ x09, 7);
|
||||
}
|
||||
let oi = 0;
|
||||
o32[oi++] = x00;
|
||||
o32[oi++] = x01;
|
||||
o32[oi++] = x02;
|
||||
o32[oi++] = x03;
|
||||
o32[oi++] = x12;
|
||||
o32[oi++] = x13;
|
||||
o32[oi++] = x14;
|
||||
o32[oi++] = x15;
|
||||
}
|
||||
/**
|
||||
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
|
||||
*/
|
||||
export const chacha20orig = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
allowShortKeys: true,
|
||||
});
|
||||
/**
|
||||
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
|
||||
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
|
||||
*/
|
||||
export const chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
allowShortKeys: false,
|
||||
});
|
||||
/**
|
||||
* XChaCha eXtended-nonce ChaCha. 24-byte nonce.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
*/
|
||||
export const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 8,
|
||||
extendNonceFn: hchacha,
|
||||
allowShortKeys: false,
|
||||
});
|
||||
/**
|
||||
* Reduced 8-round chacha, described in original paper.
|
||||
*/
|
||||
export const chacha8 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 8,
|
||||
});
|
||||
/**
|
||||
* Reduced 12-round chacha, described in original paper.
|
||||
*/
|
||||
export const chacha12 = /* @__PURE__ */ createCipher(chachaCore, {
|
||||
counterRight: false,
|
||||
counterLength: 4,
|
||||
rounds: 12,
|
||||
});
|
||||
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
||||
// Pad to digest size with zeros
|
||||
const updatePadded = (h, msg) => {
|
||||
h.update(msg);
|
||||
const left = msg.length % 16;
|
||||
if (left)
|
||||
h.update(ZEROS16.subarray(left));
|
||||
};
|
||||
const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
|
||||
function computeTag(fn, key, nonce, data, AAD) {
|
||||
const authKey = fn(key, nonce, ZEROS32);
|
||||
const h = poly1305.create(authKey);
|
||||
if (AAD)
|
||||
updatePadded(h, AAD);
|
||||
updatePadded(h, data);
|
||||
const num = new Uint8Array(16);
|
||||
const view = createView(num);
|
||||
setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
|
||||
setBigUint64(view, 8, BigInt(data.length), true);
|
||||
h.update(num);
|
||||
const res = h.digest();
|
||||
authKey.fill(0);
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* AEAD algorithm from RFC 8439.
|
||||
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
|
||||
* We could have composed them similar to:
|
||||
* https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
|
||||
* But it's hard because of authKey:
|
||||
* In salsa20, authKey changes position in salsa stream.
|
||||
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
|
||||
*/
|
||||
export const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
||||
const tagLength = 16;
|
||||
abytes(key, 32);
|
||||
abytes(nonce);
|
||||
return {
|
||||
encrypt: (plaintext, output) => {
|
||||
const plength = plaintext.length;
|
||||
const clength = plength + tagLength;
|
||||
if (output) {
|
||||
abytes(output, clength);
|
||||
}
|
||||
else {
|
||||
output = new Uint8Array(clength);
|
||||
}
|
||||
xorStream(key, nonce, plaintext, output, 1);
|
||||
const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
|
||||
output.set(tag, plength); // append tag
|
||||
return output;
|
||||
},
|
||||
decrypt: (ciphertext, output) => {
|
||||
const clength = ciphertext.length;
|
||||
const plength = clength - tagLength;
|
||||
if (clength < tagLength)
|
||||
throw new Error(`encrypted data must be at least ${tagLength} bytes`);
|
||||
if (output) {
|
||||
abytes(output, plength);
|
||||
}
|
||||
else {
|
||||
output = new Uint8Array(plength);
|
||||
}
|
||||
const data = ciphertext.subarray(0, -tagLength);
|
||||
const passedTag = ciphertext.subarray(-tagLength);
|
||||
const tag = computeTag(xorStream, key, nonce, data, AAD);
|
||||
if (!equalBytes(passedTag, tag))
|
||||
throw new Error('invalid tag');
|
||||
xorStream(key, nonce, data, output, 1);
|
||||
return output;
|
||||
},
|
||||
};
|
||||
};
|
||||
/**
|
||||
* ChaCha20-Poly1305 from RFC 8439.
|
||||
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
|
||||
*/
|
||||
export const chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16 }, _poly1305_aead(chacha20));
|
||||
/**
|
||||
* XChaCha20-Poly1305 extended-nonce chacha.
|
||||
* https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
*/
|
||||
export const xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, _poly1305_aead(xchacha20));
|
||||
//# sourceMappingURL=chacha.js.map
|
||||
1
node_modules/@noble/ciphers/esm/chacha.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/chacha.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
12
node_modules/@noble/ciphers/esm/crypto.js
generated
vendored
Normal file
12
node_modules/@noble/ciphers/esm/crypto.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
const cr = typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;
|
||||
export function randomBytes(bytesLength = 32) {
|
||||
if (cr && typeof cr.getRandomValues === 'function')
|
||||
return cr.getRandomValues(new Uint8Array(bytesLength));
|
||||
throw new Error('crypto.getRandomValues must be defined');
|
||||
}
|
||||
export function getWebcryptoSubtle() {
|
||||
if (cr && typeof cr.subtle === 'object' && cr.subtle != null)
|
||||
return cr.subtle;
|
||||
throw new Error('crypto.subtle must be defined');
|
||||
}
|
||||
//# sourceMappingURL=crypto.js.map
|
||||
1
node_modules/@noble/ciphers/esm/crypto.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/crypto.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAGA,MAAM,EAAE,GAAG,OAAO,UAAU,KAAK,QAAQ,IAAI,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAEpG,MAAM,UAAU,WAAW,CAAC,WAAW,GAAG,EAAE;IAC1C,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,eAAe,KAAK,UAAU;QAChD,OAAO,EAAE,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IACzD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC;IAC/E,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACnD,CAAC"}
|
||||
17
node_modules/@noble/ciphers/esm/cryptoNode.js
generated
vendored
Normal file
17
node_modules/@noble/ciphers/esm/cryptoNode.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
|
||||
// See utils.ts for details.
|
||||
// The file will throw on node.js 14 and earlier.
|
||||
// @ts-ignore
|
||||
import * as nc from 'node:crypto';
|
||||
const cr = nc && typeof nc === 'object' && 'webcrypto' in nc ? nc.webcrypto : undefined;
|
||||
export function randomBytes(bytesLength = 32) {
|
||||
if (cr && typeof cr.getRandomValues === 'function')
|
||||
return cr.getRandomValues(new Uint8Array(bytesLength));
|
||||
throw new Error('crypto.getRandomValues must be defined');
|
||||
}
|
||||
export function getWebcryptoSubtle() {
|
||||
if (cr && typeof cr.subtle === 'object' && cr.subtle != null)
|
||||
return cr.subtle;
|
||||
throw new Error('crypto.subtle must be defined');
|
||||
}
|
||||
//# sourceMappingURL=cryptoNode.js.map
|
||||
1
node_modules/@noble/ciphers/esm/cryptoNode.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/cryptoNode.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cryptoNode.js","sourceRoot":"","sources":["../src/cryptoNode.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,4BAA4B;AAC5B,iDAAiD;AACjD,aAAa;AACb,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,MAAM,EAAE,GAAG,EAAE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,WAAW,IAAI,EAAE,CAAC,CAAC,CAAE,EAAE,CAAC,SAAiB,CAAC,CAAC,CAAC,SAAS,CAAC;AAEjG,MAAM,UAAU,WAAW,CAAC,WAAW,GAAG,EAAE;IAC1C,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,eAAe,KAAK,UAAU;QAChD,OAAO,EAAE,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IACzD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,IAAI,EAAE,IAAI,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC;IAC/E,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACnD,CAAC"}
|
||||
149
node_modules/@noble/ciphers/esm/ff1.js
generated
vendored
Normal file
149
node_modules/@noble/ciphers/esm/ff1.js
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
import { bytesToNumberBE, numberToBytesBE } from './utils.js';
|
||||
import { unsafe } from './aes.js';
|
||||
// NOTE: no point in inlining encrypt instead of encryptBlock, since BigInt stuff will be slow
|
||||
const { expandKeyLE, encryptBlock } = unsafe;
|
||||
// Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
|
||||
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf
|
||||
const BLOCK_LEN = 16;
|
||||
function mod(a, b) {
|
||||
const result = a % b;
|
||||
return result >= 0 ? result : b + result;
|
||||
}
|
||||
function NUMradix(radix, data) {
|
||||
let res = BigInt(0);
|
||||
for (let i of data)
|
||||
res = res * BigInt(radix) + BigInt(i);
|
||||
return res;
|
||||
}
|
||||
function getRound(radix, key, tweak, x) {
|
||||
if (radix > 2 ** 16 - 1)
|
||||
throw new Error(`Invalid radix: ${radix}`);
|
||||
// radix**minlen ≥ 100
|
||||
const minLen = Math.ceil(Math.log(100) / Math.log(radix));
|
||||
const maxLen = 2 ** 32 - 1;
|
||||
// 2 ≤ minlen ≤ maxlen < 2**32
|
||||
if (2 > minLen || minLen > maxLen || maxLen >= 2 ** 32)
|
||||
throw new Error('Invalid radix: 2 ≤ minlen ≤ maxlen < 2**32');
|
||||
if (x.length < minLen || x.length > maxLen)
|
||||
throw new Error('X is outside minLen..maxLen bounds');
|
||||
const u = Math.floor(x.length / 2);
|
||||
const v = x.length - u;
|
||||
const b = Math.ceil(Math.ceil(v * Math.log2(radix)) / 8);
|
||||
const d = 4 * Math.ceil(b / 4) + 4;
|
||||
const padding = mod(-tweak.length - b - 1, 16);
|
||||
// P = [1]1 || [2]1 || [1]1 || [radix]3 || [10]1 || [u mod 256]1 || [n]4 || [t]4.
|
||||
const P = new Uint8Array([1, 2, 1, 0, 0, 0, 10, u, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
const view = new DataView(P.buffer);
|
||||
view.setUint16(4, radix, false);
|
||||
view.setUint32(8, x.length, false);
|
||||
view.setUint32(12, tweak.length, false);
|
||||
// Q = T || [0](−t−b−1) mod 16 || [i]1 || [NUMradix(B)]b.
|
||||
const PQ = new Uint8Array(P.length + tweak.length + padding + 1 + b);
|
||||
PQ.set(P);
|
||||
P.fill(0);
|
||||
PQ.set(tweak, P.length);
|
||||
const xk = expandKeyLE(key);
|
||||
const round = (A, B, i, decrypt = false) => {
|
||||
// Q = ... || [i]1 || [NUMradix(B)]b.
|
||||
PQ[PQ.length - b - 1] = i;
|
||||
if (b)
|
||||
PQ.set(numberToBytesBE(NUMradix(radix, B), b), PQ.length - b);
|
||||
// PRF
|
||||
let r = new Uint8Array(16);
|
||||
for (let j = 0; j < PQ.length / BLOCK_LEN; j++) {
|
||||
for (let i = 0; i < BLOCK_LEN; i++)
|
||||
r[i] ^= PQ[j * BLOCK_LEN + i];
|
||||
encryptBlock(xk, r);
|
||||
}
|
||||
// Let S be the first d bytes of the following string of ⎡d/16⎤ blocks:
|
||||
// R || CIPHK(R ⊕[1]16) || CIPHK(R ⊕[2]16) ...CIPHK(R ⊕[⎡d / 16⎤ – 1]16).
|
||||
let s = Array.from(r);
|
||||
for (let j = 1; s.length < d; j++) {
|
||||
const block = numberToBytesBE(BigInt(j), 16);
|
||||
for (let k = 0; k < BLOCK_LEN; k++)
|
||||
block[k] ^= r[k];
|
||||
s.push(...Array.from(encryptBlock(xk, block)));
|
||||
}
|
||||
let y = bytesToNumberBE(Uint8Array.from(s.slice(0, d)));
|
||||
s.fill(0);
|
||||
if (decrypt)
|
||||
y = -y;
|
||||
const m = i % 2 === 0 ? u : v;
|
||||
let c = mod(NUMradix(radix, A) + y, BigInt(radix) ** BigInt(m));
|
||||
// STR(radix, m, c)
|
||||
const C = Array(m).fill(0);
|
||||
for (let i = 0; i < m; i++, c /= BigInt(radix))
|
||||
C[m - 1 - i] = Number(c % BigInt(radix));
|
||||
A.fill(0);
|
||||
A = B;
|
||||
B = C;
|
||||
return [A, B];
|
||||
};
|
||||
const destroy = () => {
|
||||
xk.fill(0);
|
||||
PQ.fill(0);
|
||||
};
|
||||
return { u, round, destroy };
|
||||
}
|
||||
const EMPTY_BUF = new Uint8Array([]);
|
||||
export function FF1(radix, key, tweak = EMPTY_BUF) {
|
||||
const PQ = getRound.bind(null, radix, key, tweak);
|
||||
return {
|
||||
encrypt(x) {
|
||||
const { u, round, destroy } = PQ(x);
|
||||
let [A, B] = [x.slice(0, u), x.slice(u)];
|
||||
for (let i = 0; i < 10; i++)
|
||||
[A, B] = round(A, B, i);
|
||||
destroy();
|
||||
const res = A.concat(B);
|
||||
A.fill(0);
|
||||
B.fill(0);
|
||||
return res;
|
||||
},
|
||||
decrypt(x) {
|
||||
const { u, round, destroy } = PQ(x);
|
||||
// The FF1.Decrypt algorithm is similar to the FF1.Encrypt algorithm;
|
||||
// the differences are in Step 6, where:
|
||||
// 1) the order of the indices is reversed,
|
||||
// 2) the roles of A and B are swapped
|
||||
// 3) modular addition is replaced by modular subtraction, in Step 6vi.
|
||||
let [B, A] = [x.slice(0, u), x.slice(u)];
|
||||
for (let i = 9; i >= 0; i--)
|
||||
[A, B] = round(A, B, i, true);
|
||||
destroy();
|
||||
const res = B.concat(A);
|
||||
A.fill(0);
|
||||
B.fill(0);
|
||||
return res;
|
||||
},
|
||||
};
|
||||
}
|
||||
// Binary string which encodes each byte in little-endian byte order
|
||||
const binLE = {
|
||||
encode(bytes) {
|
||||
const x = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
for (let j = 0, tmp = bytes[i]; j < 8; j++, tmp >>= 1)
|
||||
x.push(tmp & 1);
|
||||
}
|
||||
return x;
|
||||
},
|
||||
decode(b) {
|
||||
if (b.length % 8)
|
||||
throw new Error('Invalid binary string');
|
||||
const res = new Uint8Array(b.length / 8);
|
||||
for (let i = 0, j = 0; i < res.length; i++) {
|
||||
res[i] = b[j++] | (b[j++] << 1) | (b[j++] << 2) | (b[j++] << 3);
|
||||
res[i] |= (b[j++] << 4) | (b[j++] << 5) | (b[j++] << 6) | (b[j++] << 7);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
};
|
||||
export function BinaryFF1(key, tweak = EMPTY_BUF) {
|
||||
const ff1 = FF1(2, key, tweak);
|
||||
return {
|
||||
encrypt: (x) => binLE.decode(ff1.encrypt(binLE.encode(x))),
|
||||
decrypt: (x) => binLE.decode(ff1.decrypt(binLE.encode(x))),
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=ff1.js.map
|
||||
1
node_modules/@noble/ciphers/esm/ff1.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/ff1.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
3
node_modules/@noble/ciphers/esm/index.js
generated
vendored
Normal file
3
node_modules/@noble/ciphers/esm/index.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
throw new Error('noble-ciphers have no entry-point: consult README for usage');
|
||||
export {};
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/@noble/ciphers/esm/index.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC"}
|
||||
11
node_modules/@noble/ciphers/esm/package.json
generated
vendored
Normal file
11
node_modules/@noble/ciphers/esm/package.json
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"browser": {
|
||||
"node:crypto": false
|
||||
},
|
||||
"node": {
|
||||
"./crypto.js": "./esm/cryptoNode.js",
|
||||
"./crypto": "./esm/cryptoNode.js"
|
||||
}
|
||||
}
|
||||
205
node_modules/@noble/ciphers/esm/salsa.js
generated
vendored
Normal file
205
node_modules/@noble/ciphers/esm/salsa.js
generated
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
import { bytes as abytes } from './_assert.js';
|
||||
import { createCipher, rotl } from './_arx.js';
|
||||
import { poly1305 } from './_poly1305.js';
|
||||
import { wrapCipher, equalBytes } from './utils.js';
|
||||
// Salsa20 stream cipher was released in 2005.
|
||||
// Salsa's goal was to implement AES replacement that does not rely on S-Boxes,
|
||||
// which are hard to implement in a constant-time manner.
|
||||
// https://cr.yp.to/snuffle.html, https://cr.yp.to/snuffle/salsafamily-20071225.pdf
|
||||
/**
|
||||
* Salsa20 core function.
|
||||
*/
|
||||
// prettier-ignore
|
||||
function salsaCore(s, k, n, out, cnt, rounds = 20) {
|
||||
// Based on https://cr.yp.to/salsa20.html
|
||||
let y00 = s[0], y01 = k[0], y02 = k[1], y03 = k[2], // "expa" Key Key Key
|
||||
y04 = k[3], y05 = s[1], y06 = n[0], y07 = n[1], // Key "nd 3" Nonce Nonce
|
||||
y08 = cnt, y09 = 0, y10 = s[2], y11 = k[4], // Pos. Pos. "2-by" Key
|
||||
y12 = k[5], y13 = k[6], y14 = k[7], y15 = s[3]; // Key Key Key "te k"
|
||||
// Save state to temporary variables
|
||||
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
|
||||
for (let r = 0; r < rounds; r += 2) {
|
||||
x04 ^= rotl(x00 + x12 | 0, 7);
|
||||
x08 ^= rotl(x04 + x00 | 0, 9);
|
||||
x12 ^= rotl(x08 + x04 | 0, 13);
|
||||
x00 ^= rotl(x12 + x08 | 0, 18);
|
||||
x09 ^= rotl(x05 + x01 | 0, 7);
|
||||
x13 ^= rotl(x09 + x05 | 0, 9);
|
||||
x01 ^= rotl(x13 + x09 | 0, 13);
|
||||
x05 ^= rotl(x01 + x13 | 0, 18);
|
||||
x14 ^= rotl(x10 + x06 | 0, 7);
|
||||
x02 ^= rotl(x14 + x10 | 0, 9);
|
||||
x06 ^= rotl(x02 + x14 | 0, 13);
|
||||
x10 ^= rotl(x06 + x02 | 0, 18);
|
||||
x03 ^= rotl(x15 + x11 | 0, 7);
|
||||
x07 ^= rotl(x03 + x15 | 0, 9);
|
||||
x11 ^= rotl(x07 + x03 | 0, 13);
|
||||
x15 ^= rotl(x11 + x07 | 0, 18);
|
||||
x01 ^= rotl(x00 + x03 | 0, 7);
|
||||
x02 ^= rotl(x01 + x00 | 0, 9);
|
||||
x03 ^= rotl(x02 + x01 | 0, 13);
|
||||
x00 ^= rotl(x03 + x02 | 0, 18);
|
||||
x06 ^= rotl(x05 + x04 | 0, 7);
|
||||
x07 ^= rotl(x06 + x05 | 0, 9);
|
||||
x04 ^= rotl(x07 + x06 | 0, 13);
|
||||
x05 ^= rotl(x04 + x07 | 0, 18);
|
||||
x11 ^= rotl(x10 + x09 | 0, 7);
|
||||
x08 ^= rotl(x11 + x10 | 0, 9);
|
||||
x09 ^= rotl(x08 + x11 | 0, 13);
|
||||
x10 ^= rotl(x09 + x08 | 0, 18);
|
||||
x12 ^= rotl(x15 + x14 | 0, 7);
|
||||
x13 ^= rotl(x12 + x15 | 0, 9);
|
||||
x14 ^= rotl(x13 + x12 | 0, 13);
|
||||
x15 ^= rotl(x14 + x13 | 0, 18);
|
||||
}
|
||||
// Write output
|
||||
let oi = 0;
|
||||
out[oi++] = (y00 + x00) | 0;
|
||||
out[oi++] = (y01 + x01) | 0;
|
||||
out[oi++] = (y02 + x02) | 0;
|
||||
out[oi++] = (y03 + x03) | 0;
|
||||
out[oi++] = (y04 + x04) | 0;
|
||||
out[oi++] = (y05 + x05) | 0;
|
||||
out[oi++] = (y06 + x06) | 0;
|
||||
out[oi++] = (y07 + x07) | 0;
|
||||
out[oi++] = (y08 + x08) | 0;
|
||||
out[oi++] = (y09 + x09) | 0;
|
||||
out[oi++] = (y10 + x10) | 0;
|
||||
out[oi++] = (y11 + x11) | 0;
|
||||
out[oi++] = (y12 + x12) | 0;
|
||||
out[oi++] = (y13 + x13) | 0;
|
||||
out[oi++] = (y14 + x14) | 0;
|
||||
out[oi++] = (y15 + x15) | 0;
|
||||
}
|
||||
/**
|
||||
* hsalsa hashing function, used primarily in xsalsa, to hash
|
||||
* key and nonce into key' and nonce'.
|
||||
* Same as salsaCore, but there doesn't seem to be a way to move the block
|
||||
* out without 25% performance hit.
|
||||
*/
|
||||
// prettier-ignore
|
||||
export function hsalsa(s, k, i, o32) {
|
||||
let x00 = s[0], x01 = k[0], x02 = k[1], x03 = k[2], x04 = k[3], x05 = s[1], x06 = i[0], x07 = i[1], x08 = i[2], x09 = i[3], x10 = s[2], x11 = k[4], x12 = k[5], x13 = k[6], x14 = k[7], x15 = s[3];
|
||||
for (let r = 0; r < 20; r += 2) {
|
||||
x04 ^= rotl(x00 + x12 | 0, 7);
|
||||
x08 ^= rotl(x04 + x00 | 0, 9);
|
||||
x12 ^= rotl(x08 + x04 | 0, 13);
|
||||
x00 ^= rotl(x12 + x08 | 0, 18);
|
||||
x09 ^= rotl(x05 + x01 | 0, 7);
|
||||
x13 ^= rotl(x09 + x05 | 0, 9);
|
||||
x01 ^= rotl(x13 + x09 | 0, 13);
|
||||
x05 ^= rotl(x01 + x13 | 0, 18);
|
||||
x14 ^= rotl(x10 + x06 | 0, 7);
|
||||
x02 ^= rotl(x14 + x10 | 0, 9);
|
||||
x06 ^= rotl(x02 + x14 | 0, 13);
|
||||
x10 ^= rotl(x06 + x02 | 0, 18);
|
||||
x03 ^= rotl(x15 + x11 | 0, 7);
|
||||
x07 ^= rotl(x03 + x15 | 0, 9);
|
||||
x11 ^= rotl(x07 + x03 | 0, 13);
|
||||
x15 ^= rotl(x11 + x07 | 0, 18);
|
||||
x01 ^= rotl(x00 + x03 | 0, 7);
|
||||
x02 ^= rotl(x01 + x00 | 0, 9);
|
||||
x03 ^= rotl(x02 + x01 | 0, 13);
|
||||
x00 ^= rotl(x03 + x02 | 0, 18);
|
||||
x06 ^= rotl(x05 + x04 | 0, 7);
|
||||
x07 ^= rotl(x06 + x05 | 0, 9);
|
||||
x04 ^= rotl(x07 + x06 | 0, 13);
|
||||
x05 ^= rotl(x04 + x07 | 0, 18);
|
||||
x11 ^= rotl(x10 + x09 | 0, 7);
|
||||
x08 ^= rotl(x11 + x10 | 0, 9);
|
||||
x09 ^= rotl(x08 + x11 | 0, 13);
|
||||
x10 ^= rotl(x09 + x08 | 0, 18);
|
||||
x12 ^= rotl(x15 + x14 | 0, 7);
|
||||
x13 ^= rotl(x12 + x15 | 0, 9);
|
||||
x14 ^= rotl(x13 + x12 | 0, 13);
|
||||
x15 ^= rotl(x14 + x13 | 0, 18);
|
||||
}
|
||||
let oi = 0;
|
||||
o32[oi++] = x00;
|
||||
o32[oi++] = x05;
|
||||
o32[oi++] = x10;
|
||||
o32[oi++] = x15;
|
||||
o32[oi++] = x06;
|
||||
o32[oi++] = x07;
|
||||
o32[oi++] = x08;
|
||||
o32[oi++] = x09;
|
||||
}
|
||||
/**
|
||||
* Salsa20 from original paper.
|
||||
* With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
|
||||
*/
|
||||
export const salsa20 = /* @__PURE__ */ createCipher(salsaCore, {
|
||||
allowShortKeys: true,
|
||||
counterRight: true,
|
||||
});
|
||||
/**
|
||||
* xsalsa20 eXtended-nonce salsa.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
*/
|
||||
export const xsalsa20 = /* @__PURE__ */ createCipher(salsaCore, {
|
||||
counterRight: true,
|
||||
extendNonceFn: hsalsa,
|
||||
});
|
||||
/**
|
||||
* xsalsa20-poly1305 eXtended-nonce salsa.
|
||||
* With 24-byte nonce, it's safe to use fill it with random (CSPRNG).
|
||||
* Also known as secretbox from libsodium / nacl.
|
||||
*/
|
||||
export const xsalsa20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16 }, (key, nonce) => {
|
||||
const tagLength = 16;
|
||||
abytes(key, 32);
|
||||
abytes(nonce, 24);
|
||||
return {
|
||||
encrypt: (plaintext, output) => {
|
||||
abytes(plaintext);
|
||||
// This is small optimization (calculate auth key with same call as encryption itself) makes it hard
|
||||
// to separate tag calculation and encryption itself, since 32 byte is half-block of salsa (64 byte)
|
||||
const clength = plaintext.length + 32;
|
||||
if (output) {
|
||||
abytes(output, clength);
|
||||
}
|
||||
else {
|
||||
output = new Uint8Array(clength);
|
||||
}
|
||||
output.set(plaintext, 32);
|
||||
xsalsa20(key, nonce, output, output);
|
||||
const authKey = output.subarray(0, 32);
|
||||
const tag = poly1305(output.subarray(32), authKey);
|
||||
// Clean auth key, even though JS provides no guarantees about memory cleaning
|
||||
output.set(tag, tagLength);
|
||||
output.subarray(0, tagLength).fill(0);
|
||||
return output.subarray(tagLength);
|
||||
},
|
||||
decrypt: (ciphertext) => {
|
||||
abytes(ciphertext);
|
||||
const clength = ciphertext.length;
|
||||
if (clength < tagLength)
|
||||
throw new Error('encrypted data should be at least 16 bytes');
|
||||
// Create new ciphertext array:
|
||||
// auth tag auth tag from ciphertext ciphertext
|
||||
// [bytes 0..16] [bytes 16..32] [bytes 32..]
|
||||
// 16 instead of 32, because we already have 16 byte tag
|
||||
const ciphertext_ = new Uint8Array(clength + tagLength); // alloc
|
||||
ciphertext_.set(ciphertext, tagLength);
|
||||
// Each xsalsa20 calls to hsalsa to calculate key, but seems not much perf difference
|
||||
// Separate call to calculate authkey, since first bytes contains tag
|
||||
const authKey = xsalsa20(key, nonce, new Uint8Array(32)); // alloc(32)
|
||||
const tag = poly1305(ciphertext_.subarray(32), authKey);
|
||||
if (!equalBytes(ciphertext_.subarray(16, 32), tag))
|
||||
throw new Error('invalid tag');
|
||||
const plaintext = xsalsa20(key, nonce, ciphertext_); // alloc
|
||||
// Clean auth key, even though JS provides no guarantees about memory cleaning
|
||||
plaintext.subarray(0, 32).fill(0);
|
||||
authKey.fill(0);
|
||||
return plaintext.subarray(32);
|
||||
},
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Alias to xsalsa20poly1305, for compatibility with libsodium / nacl
|
||||
*/
|
||||
export function secretbox(key, nonce) {
|
||||
const xs = xsalsa20poly1305(key, nonce);
|
||||
return { seal: xs.encrypt, open: xs.decrypt };
|
||||
}
|
||||
//# sourceMappingURL=salsa.js.map
|
||||
1
node_modules/@noble/ciphers/esm/salsa.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/salsa.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
182
node_modules/@noble/ciphers/esm/utils.js
generated
vendored
Normal file
182
node_modules/@noble/ciphers/esm/utils.js
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
||||
import { bytes as abytes, isBytes } from './_assert.js';
|
||||
// Cast array to different type
|
||||
export const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
export const u16 = (arr) => new Uint16Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 2));
|
||||
export const u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
||||
// Cast array to view
|
||||
export const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
||||
// big-endian hardware is rare. Just in case someone still decides to run ciphers:
|
||||
// early-throw an error because we don't support BE yet.
|
||||
export const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
|
||||
if (!isLE)
|
||||
throw new Error('Non little-endian hardware is not supported');
|
||||
// Array where index 0xf0 (240) is mapped to string 'f0'
|
||||
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
|
||||
/**
|
||||
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
||||
*/
|
||||
export function bytesToHex(bytes) {
|
||||
abytes(bytes);
|
||||
// pre-caching improves the speed 6x
|
||||
let hex = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
hex += hexes[bytes[i]];
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
// We use optimized technique to convert hex string to byte array
|
||||
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 };
|
||||
function asciiToBase16(char) {
|
||||
if (char >= asciis._0 && char <= asciis._9)
|
||||
return char - asciis._0;
|
||||
if (char >= asciis._A && char <= asciis._F)
|
||||
return char - (asciis._A - 10);
|
||||
if (char >= asciis._a && char <= asciis._f)
|
||||
return char - (asciis._a - 10);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
||||
*/
|
||||
export function hexToBytes(hex) {
|
||||
if (typeof hex !== 'string')
|
||||
throw new Error('hex string expected, got ' + typeof hex);
|
||||
const hl = hex.length;
|
||||
const al = hl / 2;
|
||||
if (hl % 2)
|
||||
throw new Error('padded hex string expected, got unpadded hex of length ' + hl);
|
||||
const array = new Uint8Array(al);
|
||||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
||||
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
||||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
||||
if (n1 === undefined || n2 === undefined) {
|
||||
const char = hex[hi] + hex[hi + 1];
|
||||
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
||||
}
|
||||
array[ai] = n1 * 16 + n2;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
export function hexToNumber(hex) {
|
||||
if (typeof hex !== 'string')
|
||||
throw new Error('hex string expected, got ' + typeof hex);
|
||||
// Big Endian
|
||||
return BigInt(hex === '' ? '0' : `0x${hex}`);
|
||||
}
|
||||
// BE: Big Endian, LE: Little Endian
|
||||
export function bytesToNumberBE(bytes) {
|
||||
return hexToNumber(bytesToHex(bytes));
|
||||
}
|
||||
export function numberToBytesBE(n, len) {
|
||||
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
||||
}
|
||||
// There is no setImmediate in browser and setTimeout is slow.
|
||||
// call of async fn will return Promise, which will be fullfiled only on
|
||||
// next scheduler queue processing step and this is exactly what we need.
|
||||
export const nextTick = async () => { };
|
||||
// Returns control to thread each 'tick' ms to avoid blocking
|
||||
export async function asyncLoop(iters, tick, cb) {
|
||||
let ts = Date.now();
|
||||
for (let i = 0; i < iters; i++) {
|
||||
cb(i);
|
||||
// Date.now() is not monotonic, so in case if clock goes backwards we return return control too
|
||||
const diff = Date.now() - ts;
|
||||
if (diff >= 0 && diff < tick)
|
||||
continue;
|
||||
await nextTick();
|
||||
ts += diff;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
||||
*/
|
||||
export function utf8ToBytes(str) {
|
||||
if (typeof str !== 'string')
|
||||
throw new Error(`string expected, got ${typeof str}`);
|
||||
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
||||
}
|
||||
/**
|
||||
* @example bytesToUtf8(new Uint8Array([97, 98, 99])) // 'abc'
|
||||
*/
|
||||
export function bytesToUtf8(bytes) {
|
||||
return new TextDecoder().decode(bytes);
|
||||
}
|
||||
/**
|
||||
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
||||
* Warning: when Uint8Array is passed, it would NOT get copied.
|
||||
* Keep in mind for future mutable operations.
|
||||
*/
|
||||
export function toBytes(data) {
|
||||
if (typeof data === 'string')
|
||||
data = utf8ToBytes(data);
|
||||
else if (isBytes(data))
|
||||
data = data.slice();
|
||||
else
|
||||
throw new Error(`Uint8Array expected, got ${typeof data}`);
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* Copies several Uint8Arrays into one.
|
||||
*/
|
||||
export function concatBytes(...arrays) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < arrays.length; i++) {
|
||||
const a = arrays[i];
|
||||
abytes(a);
|
||||
sum += a.length;
|
||||
}
|
||||
const res = new Uint8Array(sum);
|
||||
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
||||
const a = arrays[i];
|
||||
res.set(a, pad);
|
||||
pad += a.length;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
export function checkOpts(defaults, opts) {
|
||||
if (opts == null || typeof opts !== 'object')
|
||||
throw new Error('options must be defined');
|
||||
const merged = Object.assign(defaults, opts);
|
||||
return merged;
|
||||
}
|
||||
// Compares 2 u8a-s in kinda constant time
|
||||
export function equalBytes(a, b) {
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
let diff = 0;
|
||||
for (let i = 0; i < a.length; i++)
|
||||
diff |= a[i] ^ b[i];
|
||||
return diff === 0;
|
||||
}
|
||||
// For runtime check if class implements interface
|
||||
export class Hash {
|
||||
}
|
||||
/**
|
||||
* @__NO_SIDE_EFFECTS__
|
||||
*/
|
||||
export const wrapCipher = (params, c) => {
|
||||
Object.assign(c, params);
|
||||
return c;
|
||||
};
|
||||
// Polyfill for Safari 14
|
||||
export function setBigUint64(view, byteOffset, value, isLE) {
|
||||
if (typeof view.setBigUint64 === 'function')
|
||||
return view.setBigUint64(byteOffset, value, isLE);
|
||||
const _32n = BigInt(32);
|
||||
const _u32_max = BigInt(0xffffffff);
|
||||
const wh = Number((value >> _32n) & _u32_max);
|
||||
const wl = Number(value & _u32_max);
|
||||
const h = isLE ? 4 : 0;
|
||||
const l = isLE ? 0 : 4;
|
||||
view.setUint32(byteOffset + h, wh, isLE);
|
||||
view.setUint32(byteOffset + l, wl, isLE);
|
||||
}
|
||||
export function u64Lengths(ciphertext, AAD) {
|
||||
const num = new Uint8Array(16);
|
||||
const view = createView(num);
|
||||
setBigUint64(view, 0, BigInt(AAD ? AAD.length : 0), true);
|
||||
setBigUint64(view, 8, BigInt(ciphertext.length), true);
|
||||
return num;
|
||||
}
|
||||
//# sourceMappingURL=utils.js.map
|
||||
1
node_modules/@noble/ciphers/esm/utils.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/utils.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
105
node_modules/@noble/ciphers/esm/webcrypto.js
generated
vendored
Normal file
105
node_modules/@noble/ciphers/esm/webcrypto.js
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
|
||||
// node.js versions earlier than v19 don't declare it in global scope.
|
||||
// For node.js, package.js on#exports field mapping rewrites import
|
||||
// from `crypto` to `cryptoNode`, which imports native module.
|
||||
// Makes the utils un-importable in browsers without a bundler.
|
||||
// Once node.js 18 is deprecated, we can just drop the import.
|
||||
//
|
||||
// Use full path so that Node.js can rewrite it to `cryptoNode.js`.
|
||||
import { randomBytes, getWebcryptoSubtle } from '@noble/ciphers/crypto';
|
||||
import { concatBytes } from './utils.js';
|
||||
import { number, bytes as abytes } from './_assert.js';
|
||||
/**
|
||||
* Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS.
|
||||
*/
|
||||
export { randomBytes, getWebcryptoSubtle };
|
||||
// Uses CSPRG for nonce, nonce injected in ciphertext
|
||||
export function managedNonce(fn) {
|
||||
number(fn.nonceLength);
|
||||
return ((key, ...args) => ({
|
||||
encrypt: (plaintext, ...argsEnc) => {
|
||||
const { nonceLength } = fn;
|
||||
const nonce = randomBytes(nonceLength);
|
||||
const ciphertext = fn(key, nonce, ...args).encrypt(plaintext, ...argsEnc);
|
||||
const out = concatBytes(nonce, ciphertext);
|
||||
ciphertext.fill(0);
|
||||
return out;
|
||||
},
|
||||
decrypt: (ciphertext, ...argsDec) => {
|
||||
const { nonceLength } = fn;
|
||||
const nonce = ciphertext.subarray(0, nonceLength);
|
||||
const data = ciphertext.subarray(nonceLength);
|
||||
return fn(key, nonce, ...args).decrypt(data, ...argsDec);
|
||||
},
|
||||
}));
|
||||
}
|
||||
// Overridable
|
||||
export const utils = {
|
||||
async encrypt(key, keyParams, cryptParams, plaintext) {
|
||||
const cr = getWebcryptoSubtle();
|
||||
const iKey = await cr.importKey('raw', key, keyParams, true, ['encrypt']);
|
||||
const ciphertext = await cr.encrypt(cryptParams, iKey, plaintext);
|
||||
return new Uint8Array(ciphertext);
|
||||
},
|
||||
async decrypt(key, keyParams, cryptParams, ciphertext) {
|
||||
const cr = getWebcryptoSubtle();
|
||||
const iKey = await cr.importKey('raw', key, keyParams, true, ['decrypt']);
|
||||
const plaintext = await cr.decrypt(cryptParams, iKey, ciphertext);
|
||||
return new Uint8Array(plaintext);
|
||||
},
|
||||
};
|
||||
const mode = {
|
||||
CBC: 'AES-CBC',
|
||||
CTR: 'AES-CTR',
|
||||
GCM: 'AES-GCM',
|
||||
};
|
||||
function getCryptParams(algo, nonce, AAD) {
|
||||
if (algo === mode.CBC)
|
||||
return { name: mode.CBC, iv: nonce };
|
||||
if (algo === mode.CTR)
|
||||
return { name: mode.CTR, counter: nonce, length: 64 };
|
||||
if (algo === mode.GCM) {
|
||||
if (AAD)
|
||||
return { name: mode.GCM, iv: nonce, additionalData: AAD };
|
||||
else
|
||||
return { name: mode.GCM, iv: nonce };
|
||||
}
|
||||
throw new Error('unknown aes block mode');
|
||||
}
|
||||
function generate(algo) {
|
||||
return (key, nonce, AAD) => {
|
||||
abytes(key);
|
||||
abytes(nonce);
|
||||
const keyParams = { name: algo, length: key.length * 8 };
|
||||
const cryptParams = getCryptParams(algo, nonce, AAD);
|
||||
return {
|
||||
// keyLength,
|
||||
encrypt(plaintext) {
|
||||
abytes(plaintext);
|
||||
return utils.encrypt(key, keyParams, cryptParams, plaintext);
|
||||
},
|
||||
decrypt(ciphertext) {
|
||||
abytes(ciphertext);
|
||||
return utils.decrypt(key, keyParams, cryptParams, ciphertext);
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
export const cbc = generate(mode.CBC);
|
||||
export const ctr = generate(mode.CTR);
|
||||
export const gcm = generate(mode.GCM);
|
||||
// // Type tests
|
||||
// import { siv, gcm, ctr, ecb, cbc } from '../aes.js';
|
||||
// import { xsalsa20poly1305 } from '../salsa.js';
|
||||
// import { chacha20poly1305, xchacha20poly1305 } from '../chacha.js';
|
||||
// const wsiv = managedNonce(siv);
|
||||
// const wgcm = managedNonce(gcm);
|
||||
// const wctr = managedNonce(ctr);
|
||||
// const wcbc = managedNonce(cbc);
|
||||
// const wsalsapoly = managedNonce(xsalsa20poly1305);
|
||||
// const wchacha = managedNonce(chacha20poly1305);
|
||||
// const wxchacha = managedNonce(xchacha20poly1305);
|
||||
// // should fail
|
||||
// const wcbc2 = managedNonce(managedNonce(cbc));
|
||||
// const wecb = managedNonce(ecb);
|
||||
//# sourceMappingURL=webcrypto.js.map
|
||||
1
node_modules/@noble/ciphers/esm/webcrypto.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/esm/webcrypto.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"webcrypto.js","sourceRoot":"","sources":["../src/webcrypto.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,sEAAsE;AACtE,mEAAmE;AACnE,8DAA8D;AAC9D,+DAA+D;AAC/D,8DAA8D;AAC9D,EAAE;AACF,mEAAmE;AACnE,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAuB,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAEvD;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,CAAC;AAe3C,qDAAqD;AACrD,MAAM,UAAU,YAAY,CAA4B,EAAK;IAC3D,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC;IACvB,OAAO,CAAC,CAAC,GAAe,EAAE,GAAG,IAAW,EAAO,EAAE,CAAC,CAAC;QACjD,OAAO,EAAE,CAAC,SAAqB,EAAE,GAAG,OAAc,EAAE,EAAE;YACpD,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;YACvC,MAAM,UAAU,GAAI,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,OAAe,CAAC,SAAS,EAAE,GAAG,OAAO,CAAC,CAAC;YACnF,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YAC3C,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACnB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,EAAE,CAAC,UAAsB,EAAE,GAAG,OAAc,EAAE,EAAE;YACrD,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;YAClD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YAC9C,OAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,OAAe,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,CAAC;QACpE,CAAC;KACF,CAAC,CAAmB,CAAC;AACxB,CAAC;AAED,cAAc;AACd,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK,CAAC,OAAO,CAAC,GAAe,EAAE,SAAc,EAAE,WAAgB,EAAE,SAAqB;QACpF,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1E,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAClE,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,GAAe,EAAE,SAAc,EAAE,WAAgB,EAAE,UAAsB;QACrF,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1E,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAClE,OAAO,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;CACF,CAAC;AAEF,MAAM,IAAI,GAAG;IACX,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,SAAS;CACN,CAAC;AAGX,SAAS,cAAc,CAAC,IAAe,EAAE,KAAiB,EAAE,GAAgB;IAC1E,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAC5D,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC7E,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,IAAI,GAAG;YAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC;;YAC9D,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAC5C,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,QAAQ,CAAC,IAAe;IAC/B,OAAO,CAAC,GAAe,EAAE,KAAiB,EAAE,GAAgB,EAAe,EAAE;QAC3E,MAAM,CAAC,GAAG,CAAC,CAAC;QACZ,MAAM,CAAC,KAAK,CAAC,CAAC;QACd,MAAM,SAAS,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QACrD,OAAO;YACL,aAAa;YACb,OAAO,CAAC,SAAqB;gBAC3B,MAAM,CAAC,SAAS,CAAC,CAAC;gBAClB,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;YAC/D,CAAC;YACD,OAAO,CAAC,UAAsB;gBAC5B,MAAM,CAAC,UAAU,CAAC,CAAC;gBACnB,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;YAChE,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtC,MAAM,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtC,MAAM,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEtC,gBAAgB;AAChB,uDAAuD;AACvD,kDAAkD;AAClD,sEAAsE;AAEtE,kCAAkC;AAClC,kCAAkC;AAClC,kCAAkC;AAClC,kCAAkC;AAClC,qDAAqD;AACrD,kDAAkD;AAClD,oDAAoD;AAEpD,iBAAiB;AACjB,iDAAiD;AACjD,kCAAkC"}
|
||||
7
node_modules/@noble/ciphers/ff1.d.ts
generated
vendored
Normal file
7
node_modules/@noble/ciphers/ff1.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Cipher } from './utils.js';
|
||||
export declare function FF1(radix: number, key: Uint8Array, tweak?: Uint8Array): {
|
||||
encrypt(x: number[]): number[];
|
||||
decrypt(x: number[]): number[];
|
||||
};
|
||||
export declare function BinaryFF1(key: Uint8Array, tweak?: Uint8Array): Cipher;
|
||||
//# sourceMappingURL=ff1.d.ts.map
|
||||
1
node_modules/@noble/ciphers/ff1.d.ts.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/ff1.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ff1.d.ts","sourceRoot":"","sources":["src/ff1.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoC,MAAM,YAAY,CAAC;AA0FtE,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,GAAE,UAAsB;eAGlE,MAAM,EAAE;eAUR,MAAM,EAAE;EAgBtB;AAqBD,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,GAAE,UAAsB,GAAG,MAAM,CAMhF"}
|
||||
154
node_modules/@noble/ciphers/ff1.js
generated
vendored
Normal file
154
node_modules/@noble/ciphers/ff1.js
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BinaryFF1 = exports.FF1 = void 0;
|
||||
const utils_js_1 = require("./utils.js");
|
||||
const aes_js_1 = require("./aes.js");
|
||||
// NOTE: no point in inlining encrypt instead of encryptBlock, since BigInt stuff will be slow
|
||||
const { expandKeyLE, encryptBlock } = aes_js_1.unsafe;
|
||||
// Format-preserving encryption algorithm (FPE-FF1) specified in NIST Special Publication 800-38G.
|
||||
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38G.pdf
|
||||
const BLOCK_LEN = 16;
|
||||
function mod(a, b) {
|
||||
const result = a % b;
|
||||
return result >= 0 ? result : b + result;
|
||||
}
|
||||
function NUMradix(radix, data) {
|
||||
let res = BigInt(0);
|
||||
for (let i of data)
|
||||
res = res * BigInt(radix) + BigInt(i);
|
||||
return res;
|
||||
}
|
||||
function getRound(radix, key, tweak, x) {
|
||||
if (radix > 2 ** 16 - 1)
|
||||
throw new Error(`Invalid radix: ${radix}`);
|
||||
// radix**minlen ≥ 100
|
||||
const minLen = Math.ceil(Math.log(100) / Math.log(radix));
|
||||
const maxLen = 2 ** 32 - 1;
|
||||
// 2 ≤ minlen ≤ maxlen < 2**32
|
||||
if (2 > minLen || minLen > maxLen || maxLen >= 2 ** 32)
|
||||
throw new Error('Invalid radix: 2 ≤ minlen ≤ maxlen < 2**32');
|
||||
if (x.length < minLen || x.length > maxLen)
|
||||
throw new Error('X is outside minLen..maxLen bounds');
|
||||
const u = Math.floor(x.length / 2);
|
||||
const v = x.length - u;
|
||||
const b = Math.ceil(Math.ceil(v * Math.log2(radix)) / 8);
|
||||
const d = 4 * Math.ceil(b / 4) + 4;
|
||||
const padding = mod(-tweak.length - b - 1, 16);
|
||||
// P = [1]1 || [2]1 || [1]1 || [radix]3 || [10]1 || [u mod 256]1 || [n]4 || [t]4.
|
||||
const P = new Uint8Array([1, 2, 1, 0, 0, 0, 10, u, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
const view = new DataView(P.buffer);
|
||||
view.setUint16(4, radix, false);
|
||||
view.setUint32(8, x.length, false);
|
||||
view.setUint32(12, tweak.length, false);
|
||||
// Q = T || [0](−t−b−1) mod 16 || [i]1 || [NUMradix(B)]b.
|
||||
const PQ = new Uint8Array(P.length + tweak.length + padding + 1 + b);
|
||||
PQ.set(P);
|
||||
P.fill(0);
|
||||
PQ.set(tweak, P.length);
|
||||
const xk = expandKeyLE(key);
|
||||
const round = (A, B, i, decrypt = false) => {
|
||||
// Q = ... || [i]1 || [NUMradix(B)]b.
|
||||
PQ[PQ.length - b - 1] = i;
|
||||
if (b)
|
||||
PQ.set((0, utils_js_1.numberToBytesBE)(NUMradix(radix, B), b), PQ.length - b);
|
||||
// PRF
|
||||
let r = new Uint8Array(16);
|
||||
for (let j = 0; j < PQ.length / BLOCK_LEN; j++) {
|
||||
for (let i = 0; i < BLOCK_LEN; i++)
|
||||
r[i] ^= PQ[j * BLOCK_LEN + i];
|
||||
encryptBlock(xk, r);
|
||||
}
|
||||
// Let S be the first d bytes of the following string of ⎡d/16⎤ blocks:
|
||||
// R || CIPHK(R ⊕[1]16) || CIPHK(R ⊕[2]16) ...CIPHK(R ⊕[⎡d / 16⎤ – 1]16).
|
||||
let s = Array.from(r);
|
||||
for (let j = 1; s.length < d; j++) {
|
||||
const block = (0, utils_js_1.numberToBytesBE)(BigInt(j), 16);
|
||||
for (let k = 0; k < BLOCK_LEN; k++)
|
||||
block[k] ^= r[k];
|
||||
s.push(...Array.from(encryptBlock(xk, block)));
|
||||
}
|
||||
let y = (0, utils_js_1.bytesToNumberBE)(Uint8Array.from(s.slice(0, d)));
|
||||
s.fill(0);
|
||||
if (decrypt)
|
||||
y = -y;
|
||||
const m = i % 2 === 0 ? u : v;
|
||||
let c = mod(NUMradix(radix, A) + y, BigInt(radix) ** BigInt(m));
|
||||
// STR(radix, m, c)
|
||||
const C = Array(m).fill(0);
|
||||
for (let i = 0; i < m; i++, c /= BigInt(radix))
|
||||
C[m - 1 - i] = Number(c % BigInt(radix));
|
||||
A.fill(0);
|
||||
A = B;
|
||||
B = C;
|
||||
return [A, B];
|
||||
};
|
||||
const destroy = () => {
|
||||
xk.fill(0);
|
||||
PQ.fill(0);
|
||||
};
|
||||
return { u, round, destroy };
|
||||
}
|
||||
const EMPTY_BUF = new Uint8Array([]);
|
||||
function FF1(radix, key, tweak = EMPTY_BUF) {
|
||||
const PQ = getRound.bind(null, radix, key, tweak);
|
||||
return {
|
||||
encrypt(x) {
|
||||
const { u, round, destroy } = PQ(x);
|
||||
let [A, B] = [x.slice(0, u), x.slice(u)];
|
||||
for (let i = 0; i < 10; i++)
|
||||
[A, B] = round(A, B, i);
|
||||
destroy();
|
||||
const res = A.concat(B);
|
||||
A.fill(0);
|
||||
B.fill(0);
|
||||
return res;
|
||||
},
|
||||
decrypt(x) {
|
||||
const { u, round, destroy } = PQ(x);
|
||||
// The FF1.Decrypt algorithm is similar to the FF1.Encrypt algorithm;
|
||||
// the differences are in Step 6, where:
|
||||
// 1) the order of the indices is reversed,
|
||||
// 2) the roles of A and B are swapped
|
||||
// 3) modular addition is replaced by modular subtraction, in Step 6vi.
|
||||
let [B, A] = [x.slice(0, u), x.slice(u)];
|
||||
for (let i = 9; i >= 0; i--)
|
||||
[A, B] = round(A, B, i, true);
|
||||
destroy();
|
||||
const res = B.concat(A);
|
||||
A.fill(0);
|
||||
B.fill(0);
|
||||
return res;
|
||||
},
|
||||
};
|
||||
}
|
||||
exports.FF1 = FF1;
|
||||
// Binary string which encodes each byte in little-endian byte order
|
||||
const binLE = {
|
||||
encode(bytes) {
|
||||
const x = [];
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
for (let j = 0, tmp = bytes[i]; j < 8; j++, tmp >>= 1)
|
||||
x.push(tmp & 1);
|
||||
}
|
||||
return x;
|
||||
},
|
||||
decode(b) {
|
||||
if (b.length % 8)
|
||||
throw new Error('Invalid binary string');
|
||||
const res = new Uint8Array(b.length / 8);
|
||||
for (let i = 0, j = 0; i < res.length; i++) {
|
||||
res[i] = b[j++] | (b[j++] << 1) | (b[j++] << 2) | (b[j++] << 3);
|
||||
res[i] |= (b[j++] << 4) | (b[j++] << 5) | (b[j++] << 6) | (b[j++] << 7);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
};
|
||||
function BinaryFF1(key, tweak = EMPTY_BUF) {
|
||||
const ff1 = FF1(2, key, tweak);
|
||||
return {
|
||||
encrypt: (x) => binLE.decode(ff1.encrypt(binLE.encode(x))),
|
||||
decrypt: (x) => binLE.decode(ff1.decrypt(binLE.encode(x))),
|
||||
};
|
||||
}
|
||||
exports.BinaryFF1 = BinaryFF1;
|
||||
//# sourceMappingURL=ff1.js.map
|
||||
1
node_modules/@noble/ciphers/ff1.js.map
generated
vendored
Normal file
1
node_modules/@noble/ciphers/ff1.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user