chore: rename project from idfoto to relicario

Sweeping rename across crates, CLI binary, WASM bindings, extension, docs,
and vault metadata paths. Git remote updated to relicario.git.

- crates/idfoto-{core,cli,wasm} -> crates/relicario-{core,cli,wasm}
- IdfotoError -> RelicarioError
- IDFOTO_IMAGE env var -> RELICARIO_IMAGE
- ~/.config/idfoto -> ~/.config/relicario
- .idfoto/ vault metadata dir -> .relicario/ (breaking; pre-release)
- Binary name idfoto -> relicario
- Extension wasm module idfoto_wasm -> relicario_wasm
- Storage key idfotoSettings -> relicarioSettings
- All doc filenames and content references updated

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-19 16:47:02 -04:00
parent 20ff1d9f47
commit 519a6f0e36
51 changed files with 949 additions and 949 deletions

View File

@@ -1,15 +1,15 @@
[package]
name = "idfoto-cli"
name = "relicario-cli"
version = "0.1.0"
edition = "2021"
description = "CLI for idfoto password manager"
description = "CLI for relicario password manager"
[[bin]]
name = "idfoto"
name = "relicario"
path = "src/main.rs"
[dependencies]
idfoto-core = { path = "../idfoto-core" }
relicario-core = { path = "../relicario-core" }
clap = { version = "4", features = ["derive"] }
anyhow = "1"
rpassword = "5"

View File

@@ -1,14 +1,14 @@
//! idfoto CLI -- the platform layer for the idfoto password manager.
//! relicario CLI -- the platform layer for the relicario password manager.
//!
//! This binary provides the filesystem, git, and terminal I/O that
//! [`idfoto_core`] intentionally excludes. It is the "glue" between the
//! [`relicario_core`] intentionally excludes. It is the "glue" between the
//! platform-agnostic core library and the user's local environment.
//!
//! ## Vault layout on disk
//!
//! ```text
//! <vault_dir>/
//! .idfoto/
//! .relicario/
//! salt # 32-byte random salt for Argon2id KDF
//! params.json # KDF tuning parameters (m, t, p)
//! devices.json # registered device public keys
@@ -23,10 +23,10 @@
//!
//! Every command that accesses vault data follows this sequence:
//!
//! 1. Locate the reference image (via `IDFOTO_IMAGE` env var or interactive prompt).
//! 1. Locate the reference image (via `RELICARIO_IMAGE` env var or interactive prompt).
//! 2. Prompt for the passphrase (read from stderr, not echoed).
//! 3. Extract the 32-byte image secret from the reference JPEG via DCT steganography.
//! 4. Read the vault salt and KDF params from `.idfoto/`.
//! 4. Read the vault salt and KDF params from `.relicario/`.
//! 5. Derive the master key: `Argon2id(passphrase || image_secret, salt, params)`.
//! 6. Use the master key to decrypt the manifest and/or individual entries.
//!
@@ -39,7 +39,7 @@
use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
use idfoto_core::{
use relicario_core::{
decrypt_entry, decrypt_manifest, encrypt_entry, encrypt_manifest, generate_entry_id,
Entry, KdfParams, Manifest, ManifestEntry,
};
@@ -56,7 +56,7 @@ use std::process::Command;
/// Top-level CLI argument parser.
#[derive(Parser)]
#[command(
name = "idfoto",
name = "relicario",
version,
about = "Git-backed password manager with reference image authentication"
)]
@@ -68,7 +68,7 @@ struct Cli {
/// All available CLI subcommands.
#[derive(Subcommand)]
enum Commands {
/// Initialize a new idfoto vault in the current directory.
/// Initialize a new relicario vault in the current directory.
/// Creates the directory structure, generates a random image secret,
/// embeds it in the carrier image, and sets up git.
Init {
@@ -132,7 +132,7 @@ enum DeviceCommands {
// ─── Device entry ───────────────────────────────────────────────────────────
/// A registered device, stored in `.idfoto/devices.json`.
/// A registered device, stored in `.relicario/devices.json`.
///
/// Each device has an ed25519 keypair. The private key lives on the device
/// itself (in the user's config directory); only the public key is stored
@@ -149,23 +149,23 @@ struct DeviceEntry {
// ─── Helper functions ───────────────────────────────────────────────────────
/// Returns the vault root directory (the current working directory).
/// The vault is always rooted at the directory where `idfoto` is invoked.
/// The vault is always rooted at the directory where `relicario` is invoked.
fn vault_dir() -> PathBuf {
std::env::current_dir().expect("failed to get current directory")
}
/// Returns the path to the `.idfoto/` configuration directory within the vault.
fn idfoto_dir() -> PathBuf {
vault_dir().join(".idfoto")
/// Returns the path to the `.relicario/` configuration directory within the vault.
fn relicario_dir() -> PathBuf {
vault_dir().join(".relicario")
}
/// Read the 32-byte vault salt from `.idfoto/salt`.
/// Read the 32-byte vault salt from `.relicario/salt`.
///
/// The salt is generated once during `init` and is unique per vault. It is
/// not secret (stored in plaintext) -- its purpose is to prevent precomputed
/// rainbow table attacks against the Argon2id KDF.
fn read_salt() -> Result<[u8; 32]> {
let data = fs::read(idfoto_dir().join("salt")).context("failed to read salt")?;
let data = fs::read(relicario_dir().join("salt")).context("failed to read salt")?;
let mut salt = [0u8; 32];
if data.len() != 32 {
bail!("invalid salt file: expected 32 bytes, got {}", data.len());
@@ -174,9 +174,9 @@ fn read_salt() -> Result<[u8; 32]> {
Ok(salt)
}
/// Read the KDF parameters from `.idfoto/params.json`.
/// Read the KDF parameters from `.relicario/params.json`.
fn read_params() -> Result<KdfParams> {
let data = fs::read_to_string(idfoto_dir().join("params.json"))
let data = fs::read_to_string(relicario_dir().join("params.json"))
.context("failed to read params.json")?;
let params: KdfParams = serde_json::from_str(&data).context("failed to parse params.json")?;
Ok(params)
@@ -184,10 +184,10 @@ fn read_params() -> Result<KdfParams> {
/// Locate the reference image path.
///
/// First checks the `IDFOTO_IMAGE` environment variable (useful for scripting
/// First checks the `RELICARIO_IMAGE` environment variable (useful for scripting
/// and testing). If not set, prompts the user interactively.
fn get_image_path() -> Result<PathBuf> {
if let Ok(path) = std::env::var("IDFOTO_IMAGE") {
if let Ok(path) = std::env::var("RELICARIO_IMAGE") {
return Ok(PathBuf::from(path));
}
let path = prompt("Reference image path")?;
@@ -206,12 +206,12 @@ fn unlock(image_path: &PathBuf) -> Result<[u8; 32]> {
let jpeg_data = fs::read(image_path).context("failed to read reference image")?;
let image_secret =
idfoto_core::imgsecret::extract(&jpeg_data).context("failed to extract image secret")?;
relicario_core::imgsecret::extract(&jpeg_data).context("failed to extract image secret")?;
let salt = read_salt()?;
let params = read_params()?;
let master_key = idfoto_core::derive_master_key(passphrase.as_bytes(), &image_secret, &salt, &params)
let master_key = relicario_core::derive_master_key(passphrase.as_bytes(), &image_secret, &salt, &params)
.context("failed to derive master key")?;
Ok(master_key)
@@ -318,7 +318,7 @@ fn generate_password(length: usize) -> String {
// ─── Command implementations ────────────────────────────────────────────────
/// Initialize a new idfoto vault in the current directory.
/// Initialize a new relicario vault in the current directory.
///
/// Full sequence:
/// 1. Read the carrier JPEG provided by the user.
@@ -328,7 +328,7 @@ fn generate_password(length: usize) -> String {
/// 5. Prompt for a passphrase (minimum 8 characters, with confirmation).
/// 6. Generate a random 32-byte salt.
/// 7. Derive the master key from passphrase + image_secret + salt.
/// 8. Create the vault directory structure (.idfoto/, entries/).
/// 8. Create the vault directory structure (.relicario/, entries/).
/// 9. Write salt, KDF params, empty devices list, and encrypted empty manifest.
/// 10. Initialize git and create the first commit.
fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
@@ -341,7 +341,7 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
// 3. Embed secret into carrier
let reference_jpeg =
idfoto_core::imgsecret::embed(&carrier, &image_secret).context("failed to embed secret")?;
relicario_core::imgsecret::embed(&carrier, &image_secret).context("failed to embed secret")?;
// 4. Save reference JPEG
fs::write(&output, &reference_jpeg).context("failed to write reference image")?;
@@ -370,22 +370,22 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
// 7. Derive master key
let params = KdfParams::default();
let master_key = idfoto_core::derive_master_key(passphrase.as_bytes(), &image_secret, &salt, &params)
let master_key = relicario_core::derive_master_key(passphrase.as_bytes(), &image_secret, &salt, &params)
.context("failed to derive master key")?;
// 8. Create directory structure
let idfoto = idfoto_dir();
fs::create_dir_all(&idfoto).context("failed to create .idfoto directory")?;
let relicario = relicario_dir();
fs::create_dir_all(&relicario).context("failed to create .relicario directory")?;
fs::create_dir_all(vault_dir().join("entries")).context("failed to create entries directory")?;
// 9. Write config files
fs::write(idfoto.join("salt"), &salt).context("failed to write salt")?;
fs::write(relicario.join("salt"), &salt).context("failed to write salt")?;
fs::write(
idfoto.join("params.json"),
relicario.join("params.json"),
serde_json::to_string_pretty(&params)?,
)
.context("failed to write params.json")?;
fs::write(idfoto.join("devices.json"), "[]").context("failed to write devices.json")?;
fs::write(relicario.join("devices.json"), "[]").context("failed to write devices.json")?;
// 10. Encrypt empty manifest
let manifest = Manifest::new();
@@ -403,7 +403,7 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
if !status.success() {
bail!("git init failed");
}
git_commit("feat: initialize idfoto vault")?;
git_commit("feat: initialize relicario vault")?;
// 13. Success
eprintln!("Vault initialized successfully.");
@@ -757,24 +757,24 @@ fn cmd_sync() -> Result<()> {
// ─── Device management ──────────────────────────────────────────────────────
/// Read the device registry from `.idfoto/devices.json`.
/// Read the device registry from `.relicario/devices.json`.
fn read_devices() -> Result<Vec<DeviceEntry>> {
let path = idfoto_dir().join("devices.json");
let path = relicario_dir().join("devices.json");
let data = fs::read_to_string(&path).context("failed to read devices.json")?;
let devices: Vec<DeviceEntry> = serde_json::from_str(&data).context("failed to parse devices.json")?;
Ok(devices)
}
/// Write the device registry to `.idfoto/devices.json`.
/// Write the device registry to `.relicario/devices.json`.
fn write_devices(devices: &[DeviceEntry]) -> Result<()> {
let data = serde_json::to_string_pretty(devices)?;
fs::write(idfoto_dir().join("devices.json"), data).context("failed to write devices.json")?;
fs::write(relicario_dir().join("devices.json"), data).context("failed to write devices.json")?;
Ok(())
}
/// Register a new device by generating an ed25519 keypair.
///
/// The private key is saved to `~/.config/idfoto/<name>.key` with
/// The private key is saved to `~/.config/relicario/<name>.key` with
/// restrictive permissions (0600 on Unix). The public key is added to
/// the vault's devices.json and committed to git.
///
@@ -800,7 +800,7 @@ fn cmd_device_add(name: String) -> Result<()> {
// Save private key to the user's config directory (NOT in the vault)
let config_dir = dirs::config_dir()
.context("failed to find config directory")?
.join("idfoto");
.join("relicario");
fs::create_dir_all(&config_dir).context("failed to create config directory")?;
let key_path = config_dir.join(format!("{}.key", name));
fs::write(&key_path, &private_key_hex).context("failed to write private key")?;

View File

@@ -1,8 +1,8 @@
[package]
name = "idfoto-core"
name = "relicario-core"
version = "0.1.0"
edition = "2021"
description = "Core library for idfoto password manager"
description = "Core library for relicario password manager"
[dependencies]
thiserror = "2"

View File

@@ -41,7 +41,7 @@
//! ```
//!
//! Both factors contribute to the derived key -- compromising one without the
//! other is insufficient. The salt is vault-specific and stored in `.idfoto/salt`.
//! other is insufficient. The salt is vault-specific and stored in `.relicario/salt`.
use argon2::{Algorithm, Argon2, Params, Version};
use chacha20poly1305::{
@@ -51,7 +51,7 @@ use chacha20poly1305::{
use rand::{rngs::OsRng, RngCore};
use serde::{Deserialize, Serialize};
use crate::error::{IdfotoError, Result};
use crate::error::{RelicarioError, Result};
/// Current binary format version. Increment this if the ciphertext layout changes.
const VERSION_BYTE: u8 = 0x01;
@@ -74,7 +74,7 @@ const HEADER_LEN: usize = 1 + NONCE_LEN; // version + nonce
///
/// # Errors
///
/// Returns [`IdfotoError::Encrypt`] if the underlying AEAD operation fails
/// Returns [`RelicarioError::Encrypt`] if the underlying AEAD operation fails
/// (extremely unlikely in practice).
pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>> {
let cipher = XChaCha20Poly1305::new(key.into());
@@ -88,7 +88,7 @@ pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>> {
let ciphertext = cipher
.encrypt(&nonce, plaintext)
.map_err(|e| IdfotoError::Encrypt(e.to_string()))?;
.map_err(|e| RelicarioError::Encrypt(e.to_string()))?;
// Output: version(1) || nonce(24) || ciphertext+tag
let mut output = Vec::with_capacity(HEADER_LEN + ciphertext.len());
@@ -103,27 +103,27 @@ pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>> {
///
/// Validates the version byte and minimum blob length before attempting
/// authenticated decryption. If the key is wrong or the data has been
/// tampered with, the Poly1305 tag verification fails and [`IdfotoError::Decrypt`]
/// tampered with, the Poly1305 tag verification fails and [`RelicarioError::Decrypt`]
/// is returned -- with no information about which bytes were wrong (preventing
/// padding oracle / chosen-ciphertext attacks).
///
/// # Errors
///
/// - [`IdfotoError::Format`] if the data is too short or has an unknown version byte.
/// - [`IdfotoError::Decrypt`] if the AEAD tag verification fails (wrong key or
/// - [`RelicarioError::Format`] if the data is too short or has an unknown version byte.
/// - [`RelicarioError::Decrypt`] if the AEAD tag verification fails (wrong key or
/// tampered data).
pub fn decrypt(key: &[u8; 32], data: &[u8]) -> Result<Vec<u8>> {
// Minimum valid blob: 1 (version) + 24 (nonce) + 16 (tag) = 41 bytes.
// A zero-length plaintext produces exactly 41 bytes of output.
if data.len() < HEADER_LEN + TAG_LEN {
return Err(IdfotoError::Format(
return Err(RelicarioError::Format(
"data too short to be valid ciphertext".into(),
));
}
let version = data[0];
if version != VERSION_BYTE {
return Err(IdfotoError::Format(format!(
return Err(RelicarioError::Format(format!(
"unknown version byte: 0x{:02x}",
version
)));
@@ -135,14 +135,14 @@ pub fn decrypt(key: &[u8; 32], data: &[u8]) -> Result<Vec<u8>> {
let cipher = XChaCha20Poly1305::new(key.into());
let plaintext = cipher
.decrypt(nonce, ciphertext)
.map_err(|_| IdfotoError::Decrypt)?;
.map_err(|_| RelicarioError::Decrypt)?;
Ok(plaintext)
}
/// Tunable parameters for the Argon2id key derivation function.
///
/// These are stored in the vault's `.idfoto/params.json` so that every client
/// These are stored in the vault's `.relicario/params.json` so that every client
/// derives the same master key from the same inputs. Making them configurable
/// lets tests use fast params (m=256, t=1, p=1) while production uses strong
/// params (m=64MiB, t=3, p=4).
@@ -191,8 +191,8 @@ impl Default for KdfParams {
/// - `passphrase`: the user's passphrase as raw UTF-8 bytes.
/// - `image_secret`: the 32-byte secret extracted from the reference JPEG via
/// [`crate::imgsecret::extract`].
/// - `salt`: a 32-byte vault-specific salt (stored in `.idfoto/salt`).
/// - `params`: the Argon2id tuning parameters (stored in `.idfoto/params.json`).
/// - `salt`: a 32-byte vault-specific salt (stored in `.relicario/salt`).
/// - `params`: the Argon2id tuning parameters (stored in `.relicario/params.json`).
///
/// # Returns
///
@@ -200,7 +200,7 @@ impl Default for KdfParams {
///
/// # Errors
///
/// Returns [`IdfotoError::Kdf`] if the Argon2id parameters are invalid (e.g.,
/// Returns [`RelicarioError::Kdf`] if the Argon2id parameters are invalid (e.g.,
/// memory cost below the library's minimum).
pub fn derive_master_key(
passphrase: &[u8],
@@ -214,7 +214,7 @@ pub fn derive_master_key(
params.argon2_p,
Some(32),
)
.map_err(|e| IdfotoError::Kdf(e.to_string()))?;
.map_err(|e| RelicarioError::Kdf(e.to_string()))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, argon2_params);
@@ -229,7 +229,7 @@ pub fn derive_master_key(
let mut output = [0u8; 32];
argon2
.hash_password_into(&password, salt, &mut output)
.map_err(|e| IdfotoError::Kdf(e.to_string()))?;
.map_err(|e| RelicarioError::Kdf(e.to_string()))?;
Ok(output)
}
@@ -289,7 +289,7 @@ mod tests {
#[test]
fn encrypt_decrypt_round_trip() {
let key = [0xABu8; 32];
let plaintext = b"hello, idfoto!";
let plaintext = b"hello, relicario!";
let ciphertext = encrypt(&key, plaintext).unwrap();
let decrypted = decrypt(&key, &ciphertext).unwrap();
@@ -307,7 +307,7 @@ mod tests {
let result = decrypt(&wrong_key, &ciphertext);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), IdfotoError::Decrypt));
assert!(matches!(result.unwrap_err(), RelicarioError::Decrypt));
}
#[test]

View File

@@ -1,19 +1,19 @@
//! Unified error type for the idfoto-core crate.
//! Unified error type for the relicario-core crate.
//!
//! Every fallible function in this crate returns [`Result<T>`], which is an alias
//! for `std::result::Result<T, IdfotoError>`. Using a single error enum keeps the
//! for `std::result::Result<T, RelicarioError>`. Using a single error enum keeps the
//! public API surface predictable and makes error handling in callers (CLI, WASM
//! bindings, mobile FFI) straightforward.
use thiserror::Error;
/// All errors that can originate from idfoto-core operations.
/// All errors that can originate from relicario-core operations.
///
/// Variants are ordered roughly by the pipeline stage where they occur:
/// KDF -> encryption -> decryption -> format parsing -> entry lookup -> image
/// steganography -> serialization -> device keys.
#[derive(Debug, Error)]
pub enum IdfotoError {
pub enum RelicarioError {
/// The Argon2id key derivation failed. This typically means invalid KDF
/// parameters were supplied (e.g., memory cost below Argon2's minimum).
#[error("key derivation failed: {0}")]
@@ -83,4 +83,4 @@ pub enum IdfotoError {
}
/// Crate-wide result alias, reducing boilerplate in function signatures.
pub type Result<T> = std::result::Result<T, IdfotoError>;
pub type Result<T> = std::result::Result<T, RelicarioError>;

View File

@@ -1,6 +1,6 @@
//! DCT-based steganographic embedding of a 256-bit secret in JPEG images.
//!
//! This is the novel component of idfoto. It hides a 32-byte secret inside a
//! This is the novel component of relicario. It hides a 32-byte secret inside a
//! JPEG image's luminance channel using Quantization Index Modulation (QIM) on
//! mid-frequency DCT coefficients, with majority voting across multiple redundant
//! copies for robustness.
@@ -39,7 +39,7 @@
//! - Mild cropping (up to ~10% from edges, within the 15% crumple zone)
//! - Color space conversions (embedding is in luminance only)
use crate::error::{IdfotoError, Result};
use crate::error::{RelicarioError, Result};
use image::codecs::jpeg::JpegEncoder;
use image::ImageReader;
use image::{ImageEncoder, Rgb, RgbImage};
@@ -179,10 +179,10 @@ struct EmbedRegion {
fn extract_y_channel(jpeg_bytes: &[u8]) -> Result<YChannel> {
let reader = ImageReader::new(Cursor::new(jpeg_bytes))
.with_guessed_format()
.map_err(|e| IdfotoError::ImgSecret(format!("failed to read image: {e}")))?;
.map_err(|e| RelicarioError::ImgSecret(format!("failed to read image: {e}")))?;
let img = reader
.decode()
.map_err(|e| IdfotoError::ImgSecret(format!("failed to decode image: {e}")))?;
.map_err(|e| RelicarioError::ImgSecret(format!("failed to decode image: {e}")))?;
let rgb = img.to_rgb8();
let (width, height) = (rgb.width() as usize, rgb.height() as usize);
let mut data = Vec::with_capacity(width * height);
@@ -527,10 +527,10 @@ fn select_embed_blocks(region: &EmbedRegion, target_count: usize) -> Vec<(usize,
fn reconstruct_jpeg(original_jpeg: &[u8], y_modified: &YChannel) -> Result<Vec<u8>> {
let reader = ImageReader::new(Cursor::new(original_jpeg))
.with_guessed_format()
.map_err(|e| IdfotoError::ImgSecret(format!("failed to read image: {e}")))?;
.map_err(|e| RelicarioError::ImgSecret(format!("failed to read image: {e}")))?;
let img = reader
.decode()
.map_err(|e| IdfotoError::ImgSecret(format!("failed to decode image: {e}")))?;
.map_err(|e| RelicarioError::ImgSecret(format!("failed to decode image: {e}")))?;
let rgb = img.to_rgb8();
let (width, height) = (rgb.width(), rgb.height());
@@ -572,7 +572,7 @@ fn reconstruct_jpeg(original_jpeg: &[u8], y_modified: &YChannel) -> Result<Vec<u
let encoder = JpegEncoder::new_with_quality(&mut buf, 92);
encoder
.write_image(output.as_raw(), width, height, image::ExtendedColorType::Rgb8)
.map_err(|e| IdfotoError::ImgSecret(format!("failed to encode JPEG: {e}")))?;
.map_err(|e| RelicarioError::ImgSecret(format!("failed to encode JPEG: {e}")))?;
Ok(buf)
}
@@ -597,14 +597,14 @@ fn reconstruct_jpeg(original_jpeg: &[u8], y_modified: &YChannel) -> Result<Vec<u
///
/// # Errors
///
/// - [`IdfotoError::ImageTooSmall`] if the image is below minimum dimensions
/// - [`RelicarioError::ImageTooSmall`] if the image is below minimum dimensions
/// or does not have enough blocks for reliable embedding.
/// - [`IdfotoError::ImgSecret`] if the image cannot be decoded or re-encoded.
/// - [`RelicarioError::ImgSecret`] if the image cannot be decoded or re-encoded.
pub fn embed(carrier_jpeg: &[u8], secret: &[u8; 32]) -> Result<Vec<u8>> {
let mut y = extract_y_channel(carrier_jpeg)?;
if (y.width as u32) < MIN_DIMENSION || (y.height as u32) < MIN_DIMENSION {
return Err(IdfotoError::ImageTooSmall {
return Err(RelicarioError::ImageTooSmall {
min_width: MIN_DIMENSION,
min_height: MIN_DIMENSION,
actual_width: y.width as u32,
@@ -616,7 +616,7 @@ pub fn embed(carrier_jpeg: &[u8], secret: &[u8; 32]) -> Result<Vec<u8>> {
let total_blocks = region.blocks_x * region.blocks_y;
if total_blocks < BLOCKS_PER_COPY * MIN_COPIES {
return Err(IdfotoError::ImageTooSmall {
return Err(RelicarioError::ImageTooSmall {
min_width: MIN_DIMENSION,
min_height: MIN_DIMENSION,
actual_width: y.width as u32,
@@ -669,7 +669,7 @@ pub fn embed(carrier_jpeg: &[u8], secret: &[u8; 32]) -> Result<Vec<u8>> {
///
/// # Errors
///
/// - [`IdfotoError::ExtractionFailed`] if no valid secret could be recovered
/// - [`RelicarioError::ExtractionFailed`] if no valid secret could be recovered
/// (image was never watermarked, or was too heavily recompressed/cropped).
pub fn extract(jpeg_bytes: &[u8]) -> Result<[u8; 32]> {
extract_with_crop_recovery(jpeg_bytes)
@@ -695,7 +695,7 @@ fn try_extract_with_layout(
) -> Result<[u8; 32]> {
let positions = compute_embed_positions(orig_w, orig_h);
if positions.is_empty() {
return Err(IdfotoError::ExtractionFailed);
return Err(RelicarioError::ExtractionFailed);
}
let region = compute_region(orig_w, orig_h);
@@ -750,14 +750,14 @@ fn try_extract_with_layout(
let mut result_bits = vec![0u8; SECRET_BITS];
for i in 0..SECRET_BITS {
if votes_total[i] == 0 {
return Err(IdfotoError::ExtractionFailed);
return Err(RelicarioError::ExtractionFailed);
}
let ones = votes_one[i];
let zeros = votes_total[i] - ones;
let majority = ones.max(zeros);
let confidence = majority as f64 / votes_total[i] as f64;
if confidence < 0.60 {
return Err(IdfotoError::ExtractionFailed);
return Err(RelicarioError::ExtractionFailed);
}
result_bits[i] = if ones > zeros { 1 } else { 0 };
}
@@ -785,7 +785,7 @@ fn extract_with_crop_recovery(jpeg_bytes: &[u8]) -> Result<[u8; 32]> {
let y = extract_y_channel(jpeg_bytes)?;
if (y.width as u32) < MIN_DIMENSION || (y.height as u32) < MIN_DIMENSION {
return Err(IdfotoError::ExtractionFailed);
return Err(RelicarioError::ExtractionFailed);
}
// Try 1: assume the image is uncropped (original size = current size)
@@ -830,7 +830,7 @@ fn extract_with_crop_recovery(jpeg_bytes: &[u8]) -> Result<[u8; 32]> {
}
}
Err(IdfotoError::ExtractionFailed)
Err(RelicarioError::ExtractionFailed)
}
// ─── Tests ───────────────────────────────────────────────────────────────────

View File

@@ -1,6 +1,6 @@
//! # idfoto-core
//! # relicario-core
//!
//! Platform-agnostic core library for the idfoto password manager.
//! Platform-agnostic core library for the relicario password manager.
//!
//! This crate is intentionally **bytes-in/bytes-out** -- it performs no filesystem
//! access, no network I/O, and no git operations. All inputs arrive as byte slices
@@ -10,7 +10,7 @@
//!
//! ## Modules
//!
//! - [`error`] -- The unified error type ([`IdfotoError`]) used across the crate.
//! - [`error`] -- The unified error type ([`RelicarioError`]) used across the crate.
//! - [`crypto`] -- Argon2id key derivation and XChaCha20-Poly1305 authenticated
//! encryption. This is the low-level "encrypt bytes / decrypt bytes" layer.
//! - [`entry`] -- The vault data model: [`Entry`] (full credential),
@@ -33,7 +33,7 @@
//! ```
pub mod error;
pub use error::{IdfotoError, Result};
pub use error::{RelicarioError, Result};
pub mod crypto;
pub use crypto::{decrypt, derive_master_key, encrypt, KdfParams};

View File

@@ -28,9 +28,9 @@ use crate::error::Result;
///
/// # Errors
///
/// - [`crate::IdfotoError::Json`] if JSON serialization fails (should not happen
/// - [`crate::RelicarioError::Json`] if JSON serialization fails (should not happen
/// with well-formed Entry structs).
/// - [`crate::IdfotoError::Encrypt`] if the underlying AEAD operation fails.
/// - [`crate::RelicarioError::Encrypt`] if the underlying AEAD operation fails.
pub fn encrypt_entry(master_key: &[u8; 32], entry: &Entry) -> Result<Vec<u8>> {
let json = serde_json::to_vec(entry)?;
crypto::encrypt(master_key, &json)
@@ -40,10 +40,10 @@ pub fn encrypt_entry(master_key: &[u8; 32], entry: &Entry) -> Result<Vec<u8>> {
///
/// # Errors
///
/// - [`crate::IdfotoError::Decrypt`] if the master key is wrong or the data is
/// - [`crate::RelicarioError::Decrypt`] if the master key is wrong or the data is
/// tampered.
/// - [`crate::IdfotoError::Format`] if the ciphertext blob has an invalid header.
/// - [`crate::IdfotoError::Json`] if the decrypted JSON is malformed.
/// - [`crate::RelicarioError::Format`] if the ciphertext blob has an invalid header.
/// - [`crate::RelicarioError::Json`] if the decrypted JSON is malformed.
pub fn decrypt_entry(master_key: &[u8; 32], data: &[u8]) -> Result<Entry> {
let json = crypto::decrypt(master_key, data)?;
let entry: Entry = serde_json::from_slice(&json)?;

View File

@@ -1,4 +1,4 @@
use idfoto_core::{
use relicario_core::{
decrypt_entry, decrypt_manifest, derive_master_key, encrypt_entry, encrypt_manifest,
generate_entry_id, Entry, KdfParams, Manifest, ManifestEntry,
};
@@ -38,10 +38,10 @@ fn full_vault_workflow() {
// 2. Generate random image_secret and embed
let mut image_secret = [0u8; 32];
rand::thread_rng().fill_bytes(&mut image_secret);
let stego = idfoto_core::imgsecret::embed(&carrier, &image_secret).unwrap();
let stego = relicario_core::imgsecret::embed(&carrier, &image_secret).unwrap();
// 3. Extract and verify
let extracted = idfoto_core::imgsecret::extract(&stego).unwrap();
let extracted = relicario_core::imgsecret::extract(&stego).unwrap();
assert_eq!(extracted, image_secret, "extracted image_secret must match embedded");
// 4. Derive master_key with fast params

View File

@@ -1,14 +1,14 @@
[package]
name = "idfoto-wasm"
name = "relicario-wasm"
version = "0.1.0"
edition = "2021"
description = "WASM bindings for idfoto password manager"
description = "WASM bindings for relicario password manager"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
idfoto-core = { path = "../idfoto-core" }
relicario-core = { path = "../relicario-core" }
wasm-bindgen = "0.2"
js-sys = "0.3"
serde_json = "1"

View File

@@ -1,6 +1,6 @@
//! WASM bindings for the idfoto password manager.
//! WASM bindings for the relicario password manager.
//!
//! This crate wraps [`idfoto_core`] for use in a Chrome MV3 browser extension via
//! This crate wraps [`relicario_core`] for use in a Chrome MV3 browser extension via
//! `wasm-bindgen`. Every function marked `#[wasm_bindgen]` is callable from
//! JavaScript after loading the compiled `.wasm` module.
//!
@@ -21,10 +21,10 @@
use wasm_bindgen::prelude::*;
use idfoto_core::crypto::{self, KdfParams};
use idfoto_core::entry::Entry;
use idfoto_core::vault;
use idfoto_core::imgsecret;
use relicario_core::crypto::{self, KdfParams};
use relicario_core::entry::Entry;
use relicario_core::vault;
use relicario_core::imgsecret;
use hmac::{Hmac, Mac};
use sha1::Sha1;
@@ -103,7 +103,7 @@ pub fn embed_image_secret(carrier_jpeg: &[u8], secret: &[u8]) -> Result<Vec<u8>,
let secret: [u8; 32] = secret
.try_into()
.map_err(|_| JsValue::from_str("secret must be exactly 32 bytes"))?;
idfoto_core::imgsecret::embed(carrier_jpeg, &secret)
relicario_core::imgsecret::embed(carrier_jpeg, &secret)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
@@ -142,7 +142,7 @@ pub fn encrypt_manifest(manifest_json: &str, key: &[u8]) -> Result<Vec<u8>, JsVa
let key: &[u8; 32] = key
.try_into()
.map_err(|_| JsValue::from_str("key must be exactly 32 bytes"))?;
let manifest: idfoto_core::entry::Manifest =
let manifest: relicario_core::entry::Manifest =
serde_json::from_str(manifest_json).map_err(|e| JsValue::from_str(&e.to_string()))?;
vault::encrypt_manifest(key, &manifest).map_err(|e| JsValue::from_str(&e.to_string()))
}