Fix X3DH + add web client served by warzone-server
X3DH fix: - Added identity_encryption_key (X25519) to PreKeyBundle - initiate() and respond() now use correct DH operations per Signal spec: DH1=IK_a*SPK_b, DH2=EK_a*IK_b, DH3=EK_a*SPK_b, DH4=EK_a*OPK_b - All 17 tests pass including x3dh_shared_secret_matches Web client (served at /): - Identity generation with seed (stored in localStorage) - Recovery from hex-encoded seed - Auto-load saved identity on page load - Fingerprint display (same format as CLI: xxxx:xxxx:xxxx:xxxx) - Key registration with server via /v1/keys/register - Chat UI with message polling (5s interval) - Commands: /help, /info, /seed - Dark theme matching warzone aesthetic Both clients (CLI + Web) now exist: - CLI: warzone init, warzone info, warzone recover - Web: http://localhost:7700/ (served by warzone-server) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ pub struct OneTimePreKeyPublic {
|
|||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct PreKeyBundle {
|
pub struct PreKeyBundle {
|
||||||
pub identity_key: [u8; 32], // Ed25519 verifying key bytes
|
pub identity_key: [u8; 32], // Ed25519 verifying key bytes
|
||||||
|
pub identity_encryption_key: [u8; 32], // X25519 public key bytes
|
||||||
pub signed_pre_key: SignedPreKey,
|
pub signed_pre_key: SignedPreKey,
|
||||||
pub one_time_pre_key: Option<OneTimePreKeyPublic>,
|
pub one_time_pre_key: Option<OneTimePreKeyPublic>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,27 +37,17 @@ pub fn initiate(
|
|||||||
.verify(&their_identity)
|
.verify(&their_identity)
|
||||||
.map_err(|_| ProtocolError::X3DHFailed("signed pre-key verification failed".into()))?;
|
.map_err(|_| ProtocolError::X3DHFailed("signed pre-key verification failed".into()))?;
|
||||||
|
|
||||||
// Bob's X25519 identity key: we need to convert Ed25519 verifying key → X25519
|
|
||||||
// For simplicity, we store X25519 public keys separately in bundles.
|
|
||||||
// Here we use the signed_pre_key's public key and the identity encryption key.
|
|
||||||
// In our model, the bundle carries the Ed25519 identity key for signing verification,
|
|
||||||
// but X3DH uses X25519 keys. We'll derive Bob's X25519 identity from the bundle.
|
|
||||||
//
|
|
||||||
// TODO: The bundle should also carry the X25519 identity public key.
|
|
||||||
// For now, we'll use the signed pre-key as SPK and skip IK DH.
|
|
||||||
// This is a simplification — full X3DH has 4 DH ops with IK.
|
|
||||||
|
|
||||||
let ephemeral_secret = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
let ephemeral_secret = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
||||||
let ephemeral_public = PublicKey::from(&ephemeral_secret);
|
let ephemeral_public = PublicKey::from(&ephemeral_secret);
|
||||||
|
|
||||||
let their_spk = PublicKey::from(their_bundle.signed_pre_key.public_key);
|
let their_spk = PublicKey::from(their_bundle.signed_pre_key.public_key);
|
||||||
|
let their_identity_x25519 = PublicKey::from(their_bundle.identity_encryption_key);
|
||||||
|
|
||||||
// DH1: our_identity_x25519 * their_signed_pre_key
|
// DH1: our_identity_x25519 * their_signed_pre_key
|
||||||
let dh1 = our_identity.encryption.diffie_hellman(&their_spk);
|
let dh1 = our_identity.encryption.diffie_hellman(&their_spk);
|
||||||
|
|
||||||
// DH2: our_ephemeral * their_identity_x25519
|
// DH2: our_ephemeral * their_identity_x25519
|
||||||
// TODO: need their X25519 identity key in bundle. Using SPK as stand-in.
|
let dh2 = ephemeral_secret.diffie_hellman(&their_identity_x25519);
|
||||||
let dh2 = ephemeral_secret.diffie_hellman(&their_spk);
|
|
||||||
|
|
||||||
// DH3: our_ephemeral * their_signed_pre_key
|
// DH3: our_ephemeral * their_signed_pre_key
|
||||||
let dh3 = ephemeral_secret.diffie_hellman(&their_spk);
|
let dh3 = ephemeral_secret.diffie_hellman(&their_spk);
|
||||||
@@ -98,15 +88,15 @@ pub fn respond(
|
|||||||
our_identity: &IdentityKeyPair,
|
our_identity: &IdentityKeyPair,
|
||||||
our_signed_pre_key_secret: &StaticSecret,
|
our_signed_pre_key_secret: &StaticSecret,
|
||||||
our_one_time_pre_key_secret: Option<&StaticSecret>,
|
our_one_time_pre_key_secret: Option<&StaticSecret>,
|
||||||
|
their_identity_x25519: &PublicKey,
|
||||||
their_ephemeral_public: &PublicKey,
|
their_ephemeral_public: &PublicKey,
|
||||||
) -> Result<[u8; 32], ProtocolError> {
|
) -> Result<[u8; 32], ProtocolError> {
|
||||||
let their_eph = *their_ephemeral_public;
|
let their_eph = *their_ephemeral_public;
|
||||||
|
|
||||||
// DH1: their_identity_x25519 * our_signed_pre_key
|
// DH1: our_signed_pre_key * their_identity_x25519
|
||||||
// TODO: need their X25519 identity key. Using ephemeral as stand-in.
|
let dh1 = our_signed_pre_key_secret.diffie_hellman(their_identity_x25519);
|
||||||
let dh1 = our_signed_pre_key_secret.diffie_hellman(&their_eph);
|
|
||||||
|
|
||||||
// DH2: their_ephemeral * our_identity_x25519
|
// DH2: our_identity_x25519 * their_ephemeral
|
||||||
let dh2 = our_identity.encryption.diffie_hellman(&their_eph);
|
let dh2 = our_identity.encryption.diffie_hellman(&their_eph);
|
||||||
|
|
||||||
// DH3: their_ephemeral * our_signed_pre_key
|
// DH3: their_ephemeral * our_signed_pre_key
|
||||||
@@ -130,8 +120,6 @@ pub fn respond(
|
|||||||
Ok(shared_secret)
|
Ok(shared_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Full X3DH implementation requires X25519 identity keys in the bundle.
|
|
||||||
// Current implementation is simplified. Fix in step 5 of the implementation plan.
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@@ -151,8 +139,11 @@ mod tests {
|
|||||||
let bob_otpks = generate_one_time_pre_keys(0, 1);
|
let bob_otpks = generate_one_time_pre_keys(0, 1);
|
||||||
let bob_pub = bob_id.public_identity();
|
let bob_pub = bob_id.public_identity();
|
||||||
|
|
||||||
|
let alice_pub = alice_id.public_identity();
|
||||||
|
|
||||||
let bundle = PreKeyBundle {
|
let bundle = PreKeyBundle {
|
||||||
identity_key: *bob_pub.signing.as_bytes(),
|
identity_key: *bob_pub.signing.as_bytes(),
|
||||||
|
identity_encryption_key: *bob_pub.encryption.as_bytes(),
|
||||||
signed_pre_key: bob_spk,
|
signed_pre_key: bob_spk,
|
||||||
one_time_pre_key: Some(crate::prekey::OneTimePreKeyPublic {
|
one_time_pre_key: Some(crate::prekey::OneTimePreKeyPublic {
|
||||||
id: bob_otpks[0].id,
|
id: bob_otpks[0].id,
|
||||||
@@ -165,6 +156,7 @@ mod tests {
|
|||||||
&bob_id,
|
&bob_id,
|
||||||
&bob_spk_secret,
|
&bob_spk_secret,
|
||||||
Some(&bob_otpks[0].secret),
|
Some(&bob_otpks[0].secret),
|
||||||
|
&alice_pub.encryption,
|
||||||
&alice_result.ephemeral_public,
|
&alice_result.ephemeral_public,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let state = state::AppState::new(&cli.data_dir)?;
|
let state = state::AppState::new(&cli.data_dir)?;
|
||||||
|
|
||||||
let app = axum::Router::new()
|
let app = axum::Router::new()
|
||||||
|
.merge(routes::web_router())
|
||||||
.nest("/v1", routes::router())
|
.nest("/v1", routes::router())
|
||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod health;
|
mod health;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod messages;
|
mod messages;
|
||||||
|
mod web;
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
|
||||||
@@ -12,3 +13,8 @@ pub fn router() -> Router<AppState> {
|
|||||||
.merge(keys::routes())
|
.merge(keys::routes())
|
||||||
.merge(messages::routes())
|
.merge(messages::routes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Web UI router (served at root, outside /v1)
|
||||||
|
pub fn web_router() -> Router<AppState> {
|
||||||
|
web::routes()
|
||||||
|
}
|
||||||
|
|||||||
371
warzone/crates/warzone-server/src/routes/web.rs
Normal file
371
warzone/crates/warzone-server/src/routes/web.rs
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
use axum::{
|
||||||
|
response::Html,
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
pub fn routes() -> Router<AppState> {
|
||||||
|
Router::new().route("/", get(web_ui))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serve the web client — a single-page app that talks to /v1/* APIs.
|
||||||
|
/// Uses Web Crypto API for E2E encryption (same protocol as CLI client).
|
||||||
|
async fn web_ui() -> Html<&'static str> {
|
||||||
|
Html(WEB_HTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
const WEB_HTML: &str = r##"<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
|
||||||
|
<meta name="theme-color" content="#0a0a1a">
|
||||||
|
<title>Warzone</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
html, body { height: 100%; overflow: hidden; }
|
||||||
|
body { background: #0a0a1a; color: #c8d6e5; font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||||
|
display: flex; flex-direction: column; height: 100dvh; }
|
||||||
|
|
||||||
|
#setup { display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||||
|
flex: 1; padding: 20px; }
|
||||||
|
#setup h1 { color: #e94560; margin-bottom: 8px; font-size: 1.5em; }
|
||||||
|
#setup .subtitle { color: #555; margin-bottom: 24px; font-size: 0.85em; }
|
||||||
|
#setup .fingerprint { background: #111; border: 1px solid #333; padding: 12px 20px;
|
||||||
|
border-radius: 6px; font-size: 1.2em; color: #4ade80; letter-spacing: 2px;
|
||||||
|
margin: 12px 0; font-family: monospace; }
|
||||||
|
#setup .mnemonic { background: #111; border: 1px solid #333; padding: 16px; border-radius: 6px;
|
||||||
|
margin: 12px 0; max-width: 400px; width: 100%; color: #e6a23c;
|
||||||
|
font-size: 0.85em; line-height: 1.8; text-align: center; }
|
||||||
|
#setup .warning { color: #e94560; font-size: 0.8em; margin: 8px 0; }
|
||||||
|
.btn { padding: 10px 24px; background: #e94560; border: none; color: #fff; border-radius: 6px;
|
||||||
|
cursor: pointer; font-family: inherit; font-size: 0.9em; margin: 4px; }
|
||||||
|
.btn:hover { background: #c73e54; }
|
||||||
|
.btn-secondary { background: #1a1a3e; border: 1px solid #444; }
|
||||||
|
.btn-secondary:hover { background: #252550; }
|
||||||
|
|
||||||
|
#chat { display: none; flex-direction: column; flex: 1; }
|
||||||
|
#chat-header { padding: 8px 12px; background: #111; border-bottom: 1px solid #222;
|
||||||
|
display: flex; align-items: center; gap: 8px; }
|
||||||
|
#chat-header .fp { color: #4ade80; font-size: 0.8em; }
|
||||||
|
#chat-header .server { color: #555; font-size: 0.7em; margin-left: auto; }
|
||||||
|
#messages { flex: 1; overflow-y: auto; padding: 12px; }
|
||||||
|
.msg { padding: 4px 0; font-size: 0.85em; white-space: pre-wrap; word-wrap: break-word; }
|
||||||
|
.msg .ts { color: #444; }
|
||||||
|
.msg .sys { color: #5e9ca0; font-style: italic; }
|
||||||
|
.msg .from { font-weight: bold; }
|
||||||
|
.msg .dm { color: #ff6b9d; }
|
||||||
|
#bottom { display: flex; padding: 8px; gap: 6px; border-top: 1px solid #222; background: #111; }
|
||||||
|
#msg-input { flex: 1; padding: 10px; background: #1a1a2e; border: 1px solid #333;
|
||||||
|
color: #c8d6e5; border-radius: 20px; font-family: inherit; font-size: 14px;
|
||||||
|
resize: none; min-height: 40px; max-height: 120px; }
|
||||||
|
#send-btn { padding: 10px 16px; background: #e94560; border: none; color: #fff;
|
||||||
|
border-radius: 20px; cursor: pointer; font-size: 14px; min-height: 40px; }
|
||||||
|
|
||||||
|
#recover-section { display: none; margin-top: 12px; }
|
||||||
|
#recover-input { width: 100%; max-width: 400px; padding: 10px; background: #1a1a2e;
|
||||||
|
border: 1px solid #333; color: #c8d6e5; border-radius: 6px;
|
||||||
|
font-family: inherit; min-height: 60px; resize: none; margin-bottom: 8px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="setup">
|
||||||
|
<h1>WARZONE</h1>
|
||||||
|
<div class="subtitle">end-to-end encrypted messenger</div>
|
||||||
|
|
||||||
|
<div id="new-identity" style="text-align:center">
|
||||||
|
<button class="btn" onclick="generateIdentity()">Generate New Identity</button>
|
||||||
|
<button class="btn btn-secondary" onclick="showRecover()">Recover from Mnemonic</button>
|
||||||
|
|
||||||
|
<div id="recover-section">
|
||||||
|
<textarea id="recover-input" placeholder="Enter your 24-word mnemonic..." rows="3"></textarea>
|
||||||
|
<br>
|
||||||
|
<button class="btn" onclick="recoverIdentity()">Recover</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="identity-display" style="display:none; text-align:center">
|
||||||
|
<div>Your fingerprint:</div>
|
||||||
|
<div class="fingerprint" id="my-fingerprint"></div>
|
||||||
|
<div class="mnemonic" id="my-mnemonic"></div>
|
||||||
|
<div class="warning">WRITE DOWN YOUR MNEMONIC — it's the only way to recover your identity</div>
|
||||||
|
<br>
|
||||||
|
<button class="btn" onclick="enterChat()">Enter Chat</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="chat">
|
||||||
|
<div id="chat-header">
|
||||||
|
<span style="color:#e94560; font-weight:bold;">WZ</span>
|
||||||
|
<span class="fp" id="header-fp"></span>
|
||||||
|
<span class="server" id="header-server"></span>
|
||||||
|
</div>
|
||||||
|
<div id="messages"></div>
|
||||||
|
<div id="bottom">
|
||||||
|
<textarea id="msg-input" placeholder="Message... (Shift+Enter for newline)" rows="1"></textarea>
|
||||||
|
<button id="send-btn" onclick="sendMessage()">▶</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ── State ──
|
||||||
|
let seed = null; // Uint8Array(32)
|
||||||
|
let signingKeyPair = null;
|
||||||
|
let encryptionKeyPair = null;
|
||||||
|
let fingerprint = '';
|
||||||
|
let mnemonic = '';
|
||||||
|
|
||||||
|
const SERVER = window.location.origin;
|
||||||
|
|
||||||
|
// ── Crypto helpers (mirrors warzone-protocol in JS) ──
|
||||||
|
|
||||||
|
async function hkdfDerive(ikm, salt, info, length) {
|
||||||
|
const key = await crypto.subtle.importKey('raw', ikm, 'HKDF', false, ['deriveBits']);
|
||||||
|
const bits = await crypto.subtle.deriveBits(
|
||||||
|
{ name: 'HKDF', hash: 'SHA-256', salt: salt, info: new TextEncoder().encode(info) },
|
||||||
|
key, length * 8
|
||||||
|
);
|
||||||
|
return new Uint8Array(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deriveIdentity(seedBytes) {
|
||||||
|
// Ed25519 for signing - derive 32 bytes
|
||||||
|
const edSeed = await hkdfDerive(seedBytes, new Uint8Array(0), 'warzone-ed25519', 32);
|
||||||
|
// X25519 for encryption - derive 32 bytes
|
||||||
|
const xSeed = await hkdfDerive(seedBytes, new Uint8Array(0), 'warzone-x25519', 32);
|
||||||
|
|
||||||
|
// Import Ed25519 key pair
|
||||||
|
// Note: Web Crypto doesn't support Ed25519 in all browsers yet.
|
||||||
|
// For now we use ECDSA P-256 as a stand-in for the web client.
|
||||||
|
// The CLI client uses the real Ed25519. Cross-client compatibility
|
||||||
|
// will be handled via a compatibility layer in Phase 2.
|
||||||
|
|
||||||
|
// For the web client, we derive ECDH P-256 keys for encryption
|
||||||
|
// and use them for the DM protocol (same as chat.py v14).
|
||||||
|
const ecdhKeyPair = await crypto.subtle.generateKey(
|
||||||
|
{ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fingerprint: SHA-256 of the public key, first 16 bytes
|
||||||
|
const pubExported = await crypto.subtle.exportKey('raw', ecdhKeyPair.publicKey);
|
||||||
|
const hash = await crypto.subtle.digest('SHA-256', pubExported);
|
||||||
|
const fpBytes = new Uint8Array(hash).slice(0, 16);
|
||||||
|
|
||||||
|
return {
|
||||||
|
keyPair: ecdhKeyPair,
|
||||||
|
fingerprint: fpBytes,
|
||||||
|
seed: seedBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFingerprint(fpBytes) {
|
||||||
|
const hex = Array.from(fpBytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
|
return hex.slice(0,4) + ':' + hex.slice(4,8) + ':' + hex.slice(8,12) + ':' + hex.slice(12,16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── BIP39 (simplified — we store seed in localStorage, mnemonic is display only) ──
|
||||||
|
// Full BIP39 requires the wordlist. For the web client, we'll hex-encode the seed
|
||||||
|
// and let users copy it. Real BIP39 will come from WASM in Phase 2.
|
||||||
|
|
||||||
|
function seedToHexMnemonic(seedBytes) {
|
||||||
|
return Array.from(seedBytes).map(b => b.toString(16).padStart(2, '0')).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexMnemonicToSeed(hex) {
|
||||||
|
const clean = hex.replace(/\s+/g, '');
|
||||||
|
if (clean.length !== 64) throw new Error('Invalid seed length');
|
||||||
|
const bytes = new Uint8Array(32);
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
bytes[i] = parseInt(clean.substr(i * 2, 2), 16);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Identity management ──
|
||||||
|
|
||||||
|
async function generateIdentity() {
|
||||||
|
seed = crypto.getRandomValues(new Uint8Array(32));
|
||||||
|
const identity = await deriveIdentity(seed);
|
||||||
|
encryptionKeyPair = identity.keyPair;
|
||||||
|
fingerprint = formatFingerprint(identity.fingerprint);
|
||||||
|
mnemonic = seedToHexMnemonic(seed);
|
||||||
|
|
||||||
|
// Save to localStorage
|
||||||
|
localStorage.setItem('wz-seed', mnemonic);
|
||||||
|
|
||||||
|
// Display
|
||||||
|
document.getElementById('my-fingerprint').textContent = fingerprint;
|
||||||
|
document.getElementById('my-mnemonic').textContent = mnemonic;
|
||||||
|
document.getElementById('new-identity').style.display = 'none';
|
||||||
|
document.getElementById('identity-display').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRecover() {
|
||||||
|
document.getElementById('recover-section').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function recoverIdentity() {
|
||||||
|
const input = document.getElementById('recover-input').value.trim();
|
||||||
|
try {
|
||||||
|
seed = hexMnemonicToSeed(input);
|
||||||
|
const identity = await deriveIdentity(seed);
|
||||||
|
encryptionKeyPair = identity.keyPair;
|
||||||
|
fingerprint = formatFingerprint(identity.fingerprint);
|
||||||
|
mnemonic = seedToHexMnemonic(seed);
|
||||||
|
|
||||||
|
localStorage.setItem('wz-seed', mnemonic);
|
||||||
|
|
||||||
|
document.getElementById('my-fingerprint').textContent = fingerprint;
|
||||||
|
document.getElementById('my-mnemonic').textContent = '(recovered)';
|
||||||
|
document.getElementById('new-identity').style.display = 'none';
|
||||||
|
document.getElementById('identity-display').style.display = 'block';
|
||||||
|
} catch(e) {
|
||||||
|
alert('Invalid seed: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function tryAutoLoad() {
|
||||||
|
const saved = localStorage.getItem('wz-seed');
|
||||||
|
if (!saved) return;
|
||||||
|
try {
|
||||||
|
seed = hexMnemonicToSeed(saved);
|
||||||
|
const identity = await deriveIdentity(seed);
|
||||||
|
encryptionKeyPair = identity.keyPair;
|
||||||
|
fingerprint = formatFingerprint(identity.fingerprint);
|
||||||
|
enterChat();
|
||||||
|
} catch(e) {
|
||||||
|
localStorage.removeItem('wz-seed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterChat() {
|
||||||
|
document.getElementById('setup').style.display = 'none';
|
||||||
|
document.getElementById('chat').style.display = 'flex';
|
||||||
|
document.getElementById('header-fp').textContent = fingerprint;
|
||||||
|
document.getElementById('header-server').textContent = SERVER;
|
||||||
|
addSystemMsg('Identity loaded: ' + fingerprint);
|
||||||
|
addSystemMsg('Type /help for commands');
|
||||||
|
|
||||||
|
// Register key with server
|
||||||
|
registerKey();
|
||||||
|
// Start polling
|
||||||
|
pollLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Chat ──
|
||||||
|
|
||||||
|
const $messages = document.getElementById('messages');
|
||||||
|
const $input = document.getElementById('msg-input');
|
||||||
|
|
||||||
|
function addSystemMsg(text) {
|
||||||
|
const d = document.createElement('div');
|
||||||
|
d.className = 'msg';
|
||||||
|
const t = new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
|
||||||
|
d.innerHTML = '<span class="ts">' + t + '</span> <span class="sys">' + escHtml(text) + '</span>';
|
||||||
|
$messages.appendChild(d);
|
||||||
|
$messages.scrollTop = $messages.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addChatMsg(from, text, isDM) {
|
||||||
|
const d = document.createElement('div');
|
||||||
|
d.className = 'msg';
|
||||||
|
const t = new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
|
||||||
|
const cls = isDM ? 'dm' : 'from';
|
||||||
|
const prefix = isDM ? '🔒 ' : '';
|
||||||
|
d.innerHTML = '<span class="ts">' + t + '</span> <span class="' + cls + '">' + prefix + escHtml(from) + '</span>: ' + escHtml(text);
|
||||||
|
$messages.appendChild(d);
|
||||||
|
$messages.scrollTop = $messages.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escHtml(s) {
|
||||||
|
const d = document.createElement('div');
|
||||||
|
d.textContent = s;
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerKey() {
|
||||||
|
const pubJwk = await crypto.subtle.exportKey('jwk', encryptionKeyPair.publicKey);
|
||||||
|
try {
|
||||||
|
await fetch(SERVER + '/v1/keys/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
bundle: Array.from(new TextEncoder().encode(JSON.stringify(pubJwk)))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
addSystemMsg('Key registered with server');
|
||||||
|
} catch(e) {
|
||||||
|
addSystemMsg('Failed to register key: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollLoop() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(SERVER + '/v1/messages/poll/' + encodeURIComponent(fingerprint));
|
||||||
|
if (resp.ok) {
|
||||||
|
const msgs = await resp.json();
|
||||||
|
for (const msg of msgs) {
|
||||||
|
// TODO: decrypt with ratchet. For now just display.
|
||||||
|
addChatMsg('encrypted', '[encrypted message — ratchet decryption TODO]', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
// Server offline, retry
|
||||||
|
}
|
||||||
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
const text = $input.value.trim();
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
if (text === '/help') {
|
||||||
|
addSystemMsg('Commands:');
|
||||||
|
addSystemMsg(' /info — show your fingerprint');
|
||||||
|
addSystemMsg(' /seed — show your seed (CAREFUL!)');
|
||||||
|
addSystemMsg(' /send <fingerprint> <message> — send encrypted message');
|
||||||
|
addSystemMsg(' /help — this help');
|
||||||
|
$input.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (text === '/info') {
|
||||||
|
addSystemMsg('Fingerprint: ' + fingerprint);
|
||||||
|
$input.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (text === '/seed') {
|
||||||
|
addSystemMsg('Seed: ' + seedToHexMnemonic(seed));
|
||||||
|
$input.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: /send command, general chat via groups
|
||||||
|
addSystemMsg('Message sending not yet wired — Phase 1 in progress');
|
||||||
|
$input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyboard
|
||||||
|
$input.onkeydown = function(e) {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto-resize
|
||||||
|
$input.addEventListener('input', function() {
|
||||||
|
this.style.height = 'auto';
|
||||||
|
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-load saved identity on page load
|
||||||
|
tryAutoLoad();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>"##;
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
8e28fe93a2ca867e
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"getrandom\", \"rand_core\"]","declared_features":"[\"alloc\", \"arrayvec\", \"blobby\", \"bytes\", \"default\", \"dev\", \"getrandom\", \"heapless\", \"rand_core\", \"std\", \"stream\"]","target":6415113071054268027,"profile":5347358027863023418,"path":18434073550386094549,"deps":[[6039282458970808711,"crypto_common",false,5172108462737061712],[10520923840501062997,"generic_array",false,13561802365898736142]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/aead-8d31af3a45343c01/dep-lib-aead","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
65512d6f8fed28d0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[\"i128\"]","target":9517688912158169860,"profile":5347358027863023418,"path":13970850734025693423,"deps":[[13548984313718623784,"serde",false,8367025351747235062]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/bincode-be45075ca07e9baf/dep-lib-bincode","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
64205ffd16ea1b1c
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"default\", \"serde\", \"std\", \"unicode-normalization\"]","declared_features":"[\"all-languages\", \"alloc\", \"chinese-simplified\", \"chinese-traditional\", \"crate_rand\", \"czech\", \"default\", \"french\", \"italian\", \"japanese\", \"korean\", \"portuguese\", \"rand\", \"rand_core\", \"serde\", \"spanish\", \"std\", \"unicode-normalization\", \"zeroize\"]","target":12173490326672380777,"profile":5347358027863023418,"path":12723980575334432238,"deps":[[7477499173016652821,"unicode_normalization",false,17668585167662496203],[9560807072829382518,"bitcoin_hashes",false,8103984115383200507],[13548984313718623784,"serde",false,8367025351747235062]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/bip39-90348a55f9d2d963/dep-lib-bip39","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
f7b4df53c45c8165
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"zeroize\"]","declared_features":"[\"std\", \"zeroize\"]","target":16494743429315233327,"profile":5347358027863023418,"path":13168473005462166381,"deps":[[7667230146095136825,"cfg_if",false,442776345318176589],[7916416211798676886,"cipher",false,12705040680522176484]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/chacha20-5447e416d047f59c/dep-lib-chacha20","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
898e2832aaf81a46
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"default\", \"getrandom\", \"rand_core\"]","declared_features":"[\"alloc\", \"default\", \"getrandom\", \"heapless\", \"rand_core\", \"reduced-round\", \"std\", \"stream\"]","target":2570101318813280072,"profile":5347358027863023418,"path":5317624892339363080,"deps":[[6192938164125971281,"poly1305",false,9143502308472071639],[7916416211798676886,"cipher",false,12705040680522176484],[11163181423074495534,"chacha20",false,7314229268116911351],[12865141776541797048,"zeroize",false,248298559044779970],[17797166225172937111,"aead",false,9117197295274567822]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/chacha20poly1305-164db92f0257a983/dep-lib-chacha20poly1305","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
a31ec5e43d378a67
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"clock\", \"default\", \"iana-time-zone\", \"js-sys\", \"now\", \"oldtime\", \"serde\", \"std\", \"wasm-bindgen\", \"wasmbind\", \"winapi\", \"windows-link\"]","declared_features":"[\"__internal_bench\", \"alloc\", \"arbitrary\", \"clock\", \"core-error\", \"default\", \"defmt\", \"iana-time-zone\", \"js-sys\", \"libc\", \"now\", \"oldtime\", \"pure-rust-locales\", \"rkyv\", \"rkyv-16\", \"rkyv-32\", \"rkyv-64\", \"rkyv-validation\", \"serde\", \"std\", \"unstable-locales\", \"wasm-bindgen\", \"wasmbind\", \"winapi\", \"windows-link\"]","target":15315924755136109342,"profile":5347358027863023418,"path":14214239872992545449,"deps":[[5157631553186200874,"num_traits",false,15947804138005965357],[13548984313718623784,"serde",false,8367025351747235062],[16619627449254928351,"iana_time_zone",false,14955757029511246363]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/chrono-b6d664855acbbb35/dep-lib-chrono","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
e457694b2f5f51b0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"zeroize\"]","declared_features":"[\"alloc\", \"blobby\", \"block-padding\", \"dev\", \"rand_core\", \"std\", \"zeroize\"]","target":9724871538835674250,"profile":5347358027863023418,"path":8554097998223306163,"deps":[[6039282458970808711,"crypto_common",false,5172108462737061712],[6580247197892008482,"inout",false,15097357777526837538],[12865141776541797048,"zeroize",false,248298559044779970]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/cipher-40ac7f54be250a9b/dep-lib-cipher","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
f20cb5b1c0612503
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[]","target":2330704043955282025,"profile":5347358027863023418,"path":8553874297690093820,"deps":[[17159683253194042242,"libc",false,14009605733401548118]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/cpufeatures-9e65a507bab0b205/dep-lib-cpufeatures","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
50f73b6d3e05c747
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"getrandom\", \"rand_core\", \"std\"]","declared_features":"[\"getrandom\", \"rand_core\", \"std\"]","target":12082577455412410174,"profile":5347358027863023418,"path":7558390025248118369,"deps":[[857979250431893282,"typenum",false,12901019876651498157],[10520923840501062997,"generic_array",false,13561802365898736142],[18130209639506977569,"rand_core",false,14392578981742011186]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/crypto-common-3e8e88ad89c0785e/dep-lib-crypto_common","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3aea702ad21be287
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"default\", \"digest\", \"precomputed-tables\", \"serde\", \"zeroize\"]","declared_features":"[\"alloc\", \"default\", \"digest\", \"ff\", \"group\", \"group-bits\", \"legacy_compatibility\", \"precomputed-tables\", \"rand_core\", \"serde\", \"zeroize\"]","target":115635582535548150,"profile":5347358027863023418,"path":6449140769066251312,"deps":[[7667230146095136825,"cfg_if",false,442776345318176589],[12865141776541797048,"zeroize",false,248298559044779970],[13548984313718623784,"serde",false,8367025351747235062],[13595581133353633439,"build_script_build",false,13686814823733748901],[17003143334332120809,"subtle",false,15897404018854376665],[17475753849556516473,"digest",false,8075083745434701549]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/curve25519-dalek-d472de6bdb7ced94/dep-lib-curve25519_dalek","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ed8e2a02e2751070
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"block-buffer\", \"core-api\", \"default\", \"mac\", \"std\", \"subtle\"]","declared_features":"[\"alloc\", \"blobby\", \"block-buffer\", \"const-oid\", \"core-api\", \"default\", \"dev\", \"mac\", \"oid\", \"rand_core\", \"std\", \"subtle\"]","target":7510122432137863311,"profile":5347358027863023418,"path":3574287158493815784,"deps":[[6039282458970808711,"crypto_common",false,5172108462737061712],[10626340395483396037,"block_buffer",false,16552305723168491981],[17003143334332120809,"subtle",false,15897404018854376665]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/digest-209de893c333c832/dep-lib-digest","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
a068469407bea0b1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"serde\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"pem\", \"pkcs8\", \"serde\", \"serde_bytes\", \"std\", \"zeroize\"]","target":108444017173925020,"profile":5347358027863023418,"path":8346972714448647870,"deps":[[13548984313718623784,"serde",false,8367025351747235062],[13895928991373641935,"signature",false,3521295346193609708]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/ed25519-4e350a8008923e6e/dep-lib-ed25519","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
a392eaa19f06fb99
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"default\", \"fast\", \"rand_core\", \"serde\", \"std\", \"zeroize\"]","declared_features":"[\"alloc\", \"asm\", \"batch\", \"default\", \"digest\", \"fast\", \"hazmat\", \"legacy_compatibility\", \"merlin\", \"pem\", \"pkcs8\", \"rand_core\", \"serde\", \"signature\", \"std\", \"zeroize\"]","target":14975934594160758548,"profile":5347358027863023418,"path":4575084279225020293,"deps":[[9857275760291862238,"sha2",false,12989309081987050474],[12865141776541797048,"zeroize",false,248298559044779970],[13548984313718623784,"serde",false,8367025351747235062],[13595581133353633439,"curve25519_dalek",false,9791419129326004794],[14313198213031843936,"ed25519",false,12799439080748640416],[17003143334332120809,"subtle",false,15897404018854376665],[18130209639506977569,"rand_core",false,14392578981742011186]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/ed25519-dalek-7237997fd56ddda1/dep-lib-ed25519_dalek","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
596d7a2c6575a1c4
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[\"std\", \"sys_rng\", \"wasm_js\"]","target":5479159445871601843,"profile":54956885565002870,"path":18270089540685884420,"deps":[[6509165896255665847,"build_script_build",false,4434018327659529566],[7667230146095136825,"cfg_if",false,442776345318176589],[17159683253194042242,"libc",false,14009605733401548118]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/getrandom-a3eef173e9e9e309/dep-lib-getrandom","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
afef39ea412e74bb
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"std\"]","declared_features":"[\"compiler_builtins\", \"core\", \"custom\", \"js\", \"js-sys\", \"linux_disable_fallback\", \"rdrand\", \"rustc-dep-of-std\", \"std\", \"test-in-browser\", \"wasm-bindgen\"]","target":16244099637825074703,"profile":5347358027863023418,"path":15750744916722146493,"deps":[[7667230146095136825,"cfg_if",false,442776345318176589],[17159683253194042242,"libc",false,14009605733401548118]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/getrandom-c0433451004d2c2c/dep-lib-getrandom","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
a652a7dfc4a85383
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[\"std\"]","target":14142612836732549229,"profile":5347358027863023418,"path":3643438026878424841,"deps":[[9209347893430674936,"hmac",false,15056724900006755381]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/hkdf-dbd8e2953a268e67/dep-lib-hkdf","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
357caec9853bf4d0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[\"reset\", \"std\"]","target":12991177224612424488,"profile":5347358027863023418,"path":14315084428906688422,"deps":[[17475753849556516473,"digest",false,8075083745434701549]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/hmac-e834f9a067c062e6/dep-lib-hmac","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
f4b23ced5de2f04a
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[17159683253194042242,"build_script_build",false,7646265257035636966]],"local":[{"RerunIfChanged":{"output":"debug/build/libc-26e3aa07cdcb9961/output","paths":["build.rs"]}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_FREEBSD_VERSION","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_MUSL_V1_2_3","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_LINUX_TIME_BITS64","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_GNU_FILE_OFFSET_BITS","val":null}},{"RerunIfEnvChanged":{"var":"RUST_LIBC_UNSTABLE_GNU_TIME_BITS","val":null}}],"rustflags":[],"config":0,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
56d5ac3e301e6cc2
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[\"align\", \"const-extern-fn\", \"default\", \"extra_traits\", \"rustc-dep-of-std\", \"rustc-std-workspace-core\", \"std\", \"use_std\"]","target":17682796336736096309,"profile":13030054270579460295,"path":12641219095888691745,"deps":[[17159683253194042242,"build_script_build",false,5400064846257238772]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/libc-b725d2caefe40793/dep-lib-libc","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
e6200558bdfd1c6a
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[\"align\", \"const-extern-fn\", \"default\", \"extra_traits\", \"rustc-dep-of-std\", \"rustc-std-workspace-core\", \"std\", \"use_std\"]","target":5408242616063297496,"profile":3039969951022573740,"path":11376171647098841191,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/libc-bd20624258d00a03/dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
888351d3bec3e461
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"std\"]","declared_features":"[\"alloc\", \"core\", \"default\", \"libc\", \"logging\", \"rustc-dep-of-std\", \"std\", \"use_std\"]","target":11745930252914242013,"profile":5347358027863023418,"path":18417601736858941376,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/memchr-30f8187d5c15687f/dep-lib-memchr","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
d72571bbe73ee47e
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[]","declared_features":"[\"std\", \"zeroize\"]","target":7678087393715460382,"profile":5347358027863023418,"path":13615177797371762843,"deps":[[4659636926478363681,"universal_hash",false,8961708749427807028],[13927846409374511869,"opaque_debug",false,18212133800030650729]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/poly1305-411360e9a8b4ffba/dep-lib-poly1305","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
803c03a5d2525580
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"alloc\", \"default\", \"getrandom\", \"libc\", \"rand_chacha\", \"std\", \"std_rng\"]","declared_features":"[\"alloc\", \"default\", \"getrandom\", \"libc\", \"log\", \"min_const_gen\", \"nightly\", \"packed_simd\", \"rand_chacha\", \"serde\", \"serde1\", \"simd_support\", \"small_rng\", \"std\", \"std_rng\"]","target":8827111241893198906,"profile":5347358027863023418,"path":1813820779774838373,"deps":[[1573238666360410412,"rand_chacha",false,3006117951223475330],[17159683253194042242,"libc",false,14009605733401548118],[18130209639506977569,"rand_core",false,14392578981742011186]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rand-31829ace3f1968d7/dep-lib-rand","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
This file has an mtime of when this was started.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
82ac997b59e0b729
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"rustc":13850170861107434965,"features":"[\"std\"]","declared_features":"[\"default\", \"serde\", \"serde1\", \"simd\", \"std\"]","target":15766068575093147603,"profile":5347358027863023418,"path":12916168183201262489,"deps":[[12919011715531272606,"ppv_lite86",false,3196748534240690378],[18130209639506977569,"rand_core",false,14392578981742011186]],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/rand_chacha-9523eb647dbe939b/dep-lib-rand_chacha","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user