
π Storing Private Keys as Encrypted Images
A contract-less innovation in wallet architecture
Our blockchain uses a novel approach to wallet key storage: every private key is encrypted, embedded into an image, and stored as a password-protected Base64 string.
This means no seed phrases, no plaintext keys, and no JSON exports. Instead, each wallet has an image β and only with the right password can the node decode it.
β
Why we do this
π Encrypted at rest
Even if someone gets your wallet file, they still need the encryption password.
πΌ Stored as images
Private keys are never stored or displayed as raw strings β they live inside image data.
π« No seed phrases
Thereβs no clipboard exposure, no mnemonic leaks, and no "write down 12 words" workflow.
π§ Human-obscured format
Viewing the image gives no hint it holds key data. It looks like a simple colored or watermarked image.
π§ͺ How it works
We use the encrypted_images
Rust crate.
π Key creation (image embedding)
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Wallet {
pub saved: SavedWallet,
pub encryption_key: String,
}
impl Wallet {
pub fn keypair(
&self,
network_byte: u8,
) -> Wallet {
// Generate a new key pair
let (public_key, private_key) = Self::generate_keypair(network_byte);
let address = Self::generate_address(&encode(&public_key));
let r = Some(120);
let g = Some(234);
let b = Some(100);
// Encrypt the private key
let encrypted_private_key = encrypts(&private_key, Some(&self.encryption_key), None).unwrap();
let watermark = "empty";
let image_data = create_img(&encrypted_private_key, "h", watermark, r, g, b, None, None, None).expect("Failed to create image");
let saved = SavedWallet {
address,
public_key: encode(public_key),
private_key: image_data,
};
Wallet {
saved,
encryption_key: self.encryption_key.clone()
}
}
pub fn display_wallet(
&self,
) {
println!("Address: {}", self.saved.address);
println!("Public Key: {}", self.saved.public_key);
}
}
We then store image_data
(a base64 PNG) into the wallet struct.
π Key decryption (image extraction)
Later, during load process in our mining nodes we use the following code to retrieve the pivate keys from those images:
impl Wallet {
async fn private_key_from_wallet(
wallet_path: &Path,
wallet_key: String,
) -> Result<Wallet, String> {
if let Ok(wallet_content) = read(wallet_path).await {
let mut wallet: SavedWallet = from_slice(&wallet_content).map_err(|e| {
format!("Deserialization of wallet failed: {}", e)
})?;
if let Some(encrypted_text) = decode_image_and_extract_text(&wallet.private_key) {
// Decrypt the encrypted text to get the real private key
if let Some(decrypted_private_key) = decrypts(&encrypted_text, Some(&wallet_key)) {
// Update the wallet's private key with the decrypted private key
wallet.private_key = decrypted_private_key;
let full_wallet = Wallet {
saved: wallet,
encryption_key: wallet_key.clone(),
};
Ok(full_wallet)
} else {
println!("Decryption of private key failed.");
Err("Decryption of private key failed.".into())
}
} else {
println!("Decryption of image failed.");
Err("Decryption of image failed.".into())
}
} else {
println!("Wallet path did not exist");
Err("Wallet path did not exist".into())
}
}
async fn load_wallet(
wallet_path: &Path,
wallet_key: String,
) -> Result<Wallet, String> {
if metadata(wallet_path).await.is_ok() {
Self::private_key_from_wallet(wallet_path, wallet_key).await
} else {
Ok(Self::create_and_save_wallet(wallet_path, wallet_key).await)
}
}
}
π§© Why itβs better than traditional keys
Traditional Wallets:
- Use seed phrases the user must manage
- Store private keys as plaintext or hex
- Are easily leaked via copy/paste
- Can be reverse-engineered with AI if poorly secured
Contractless Image Wallets:
- Never use seed phrases
- Store keys as encrypted Base64 images
- Cannot be leaked via clipboard
- Hide key material visually inside image data
π What this unlocks for our chain
- Nodes can store wallets securely on disk without exposing key material.
- Images can be saved, backed up, or even printed.
- Passwords can be long or short; we use
wallet_key
as the single decryption factor.
No other chain does this. And it fits the Contractless philosophy perfectly β off-chain security, zero contract dependency, and full user custody.
π§ Looking ahead
Weβre open to feedback:
- Could this evolve into a multi-chain format?
- Should we add steganographic noise or distortion for added privacy?
- Would visual verification (QR / RGB signature) be useful?