use anyhow::{Context, Result};
use chacha20poly1305::aead::Aead;
use chacha20poly1305::{AeadCore, ChaCha20Poly1305, KeyInit};
use hkdf::Hkdf;
use rand::rngs::OsRng;
use x25519_dalek::{EphemeralSecret, PublicKey as X25519PublicKey};
const HKDF_INFO: &[u8] = b"outlayer-keystore-v1";
const ECIES_VERSION: u8 = 0x01;
pub fn encrypt_secrets(pubkey_hex: &str, plaintext: &str) -> Result<String> {
let recipient_bytes = hex::decode(pubkey_hex).context("Invalid hex pubkey")?;
anyhow::ensure!(
recipient_bytes.len() == 32,
"Pubkey must be 32 bytes (X25519 public key)"
);
let mut recipient_arr = [0u8; 32];
recipient_arr.copy_from_slice(&recipient_bytes);
let recipient_pub = X25519PublicKey::from(recipient_arr);
let ephemeral_secret = EphemeralSecret::random_from_rng(OsRng);
let ephemeral_public = X25519PublicKey::from(&ephemeral_secret);
let shared = ephemeral_secret.diffie_hellman(&recipient_pub);
let hk = Hkdf::<sha2::Sha256>::new(None, shared.as_bytes());
let mut sym_key = [0u8; 32];
hk.expand(HKDF_INFO, &mut sym_key)
.map_err(|e| anyhow::anyhow!("HKDF expand failed: {e}"))?;
let cipher = ChaCha20Poly1305::new((&sym_key).into());
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
let ciphertext = cipher
.encrypt(&nonce, plaintext.as_bytes())
.map_err(|e| anyhow::anyhow!("Encryption failed: {e}"))?;
let mut out = Vec::with_capacity(1 + 32 + 12 + ciphertext.len());
out.push(ECIES_VERSION);
out.extend_from_slice(ephemeral_public.as_bytes());
out.extend_from_slice(&nonce);
out.extend_from_slice(&ciphertext);
Ok(base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
&out,
))
}
pub fn generate_payment_key_secret() -> String {
let mut bytes = [0u8; 32];
rand::RngCore::fill_bytes(&mut OsRng, &mut bytes);
hex::encode(bytes)
}
pub fn sign_nep413(
private_key: &str,
message: &str,
recipient: &str,
) -> Result<(String, String, String)> {
use borsh::BorshSerialize;
use near_crypto::SecretKey;
use sha2::{Digest, Sha256};
let secret_key: SecretKey = private_key
.parse()
.context("Invalid private key for NEP-413 signing")?;
let public_key = secret_key.public_key();
let mut nonce = [0u8; 32];
rand::RngCore::fill_bytes(&mut OsRng, &mut nonce);
#[derive(BorshSerialize)]
struct Nep413Payload {
message: String,
nonce: [u8; 32],
recipient: String,
callback_url: Option<String>,
}
let payload = Nep413Payload {
message: message.to_string(),
nonce,
recipient: recipient.to_string(),
callback_url: None,
};
let tag: u32 = 2_147_484_061;
let mut data = borsh::to_vec(&tag)?;
let payload_bytes = borsh::to_vec(&payload)?;
data.extend_from_slice(&payload_bytes);
let hash = Sha256::digest(&data);
let signature = secret_key.sign(&hash);
let nonce_base64 = base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
&nonce,
);
Ok((signature.to_string(), public_key.to_string(), nonce_base64))
}