use crate::error::{Error, Result};
use crate::msg::pqc as pqc_msg;
use crate::pqc::{pqc_keypair_from_seed, pqc_sign, PqcKeypair};
use crate::proto::qorechain::pqc::v1 as pb;
use sha2::{Digest, Sha256};
use sha3::digest::{ExtendableOutput, Update, XofReader};
use sha3::Shake256;
fn be64(n: u64) -> [u8; 8] {
n.to_be_bytes()
}
fn lp(out: &mut Vec<u8>, bytes: &[u8]) {
out.extend_from_slice(&be64(bytes.len() as u64));
out.extend_from_slice(bytes);
}
fn sha256_32(bytes: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
Digest::update(&mut hasher, bytes);
hasher.finalize().into()
}
fn shake256_32(data: &[u8]) -> [u8; 32] {
let mut hasher = Shake256::default();
Update::update(&mut hasher, data);
let mut reader = hasher.finalize_xof();
let mut out = [0u8; 32];
reader.read(&mut out);
out
}
fn to_hex_lower(bytes: &[u8]) -> String {
hex::encode(bytes)
}
pub fn evm_auth_sign_bytes(
chain_id: &str,
account: &str,
pubkey: &[u8],
to: &str,
value: &str,
data: &[u8],
nonce: u64,
) -> [u8; 32] {
let mut body = Vec::new();
body.extend_from_slice(b"qorechain-evm-auth-v1");
lp(&mut body, chain_id.as_bytes());
lp(&mut body, account.as_bytes());
lp(&mut body, pubkey);
lp(&mut body, to.as_bytes());
lp(&mut body, value.as_bytes());
lp(&mut body, data);
body.extend_from_slice(&be64(nonce));
sha256_32(&body)
}
pub fn cosmos_auth_sign_bytes(
chain_id: &str,
account: &str,
pubkey: &[u8],
to: &str,
amount: &str,
nonce: u64,
) -> [u8; 32] {
let mut body = Vec::new();
body.extend_from_slice(b"qorechain-cosmos-auth-v1");
lp(&mut body, chain_id.as_bytes());
lp(&mut body, account.as_bytes());
lp(&mut body, pubkey);
lp(&mut body, to.as_bytes());
lp(&mut body, amount.as_bytes());
body.extend_from_slice(&be64(nonce));
sha256_32(&body)
}
pub fn rotation_sign_bytes(
chain_id: &str,
algorithm_id: u32,
account: &str,
old_pub: &[u8],
new_pub: &[u8],
) -> String {
format!(
"qorechain-pqc-rotate-v1|{chain_id}|{algorithm_id}|{account}|{}|{}",
to_hex_lower(old_pub),
to_hex_lower(new_pub),
)
}
pub const DERIVATION_LEGACY: &str = "bridge";
pub const DERIVATION_CANONICAL: &str = "adapter";
pub fn derive_pqc_legacy(mnemonic: &str) -> PqcKeypair {
let seed = shake256_32(mnemonic.as_bytes());
pqc_keypair_from_seed(&seed)
}
pub fn derive_pqc_canonical(account: &str, mnemonic: &str) -> PqcKeypair {
let mut msg = Vec::new();
msg.extend_from_slice(b"qorechain:pqc:v1|");
msg.extend_from_slice(account.as_bytes());
msg.push(b'|');
msg.extend_from_slice(mnemonic.as_bytes());
let seed = shake256_32(&msg);
pqc_keypair_from_seed(&seed)
}
fn derive_pqc_by_scheme(scheme: &str, account: &str, mnemonic: &str) -> Result<PqcKeypair> {
match scheme {
DERIVATION_LEGACY | "mnemonic-only" => Ok(derive_pqc_legacy(mnemonic)),
DERIVATION_CANONICAL | "" => Ok(derive_pqc_canonical(account, mnemonic)),
other => Err(Error::Pqc(format!(
"unknown derivation \"{other}\" (use adapter|bridge)"
))),
}
}
#[derive(Debug, Clone)]
pub struct RotateFromMnemonic {
pub msg: pb::MsgRotatePqcKey,
pub old_keypair: PqcKeypair,
pub new_keypair: PqcKeypair,
}
#[derive(Debug, Clone)]
pub struct RotateFromMnemonicOptions<'a> {
pub account: &'a str,
pub mnemonic: &'a str,
pub chain_id: &'a str,
pub algorithm_id: u32,
pub old_derivation: &'a str,
pub new_derivation: &'a str,
}
impl<'a> RotateFromMnemonicOptions<'a> {
pub fn new(account: &'a str, mnemonic: &'a str, chain_id: &'a str) -> Self {
Self {
account,
mnemonic,
chain_id,
algorithm_id: 1,
old_derivation: DERIVATION_LEGACY,
new_derivation: DERIVATION_CANONICAL,
}
}
}
pub fn rotate_pqc_key_msg_from_mnemonic(
opts: &RotateFromMnemonicOptions<'_>,
) -> Result<RotateFromMnemonic> {
let old_kp = derive_pqc_by_scheme(opts.old_derivation, opts.account, opts.mnemonic)?;
let new_kp = derive_pqc_by_scheme(opts.new_derivation, opts.account, opts.mnemonic)?;
if old_kp.public_key == new_kp.public_key {
return Err(Error::Pqc(
"old and new derivations produce the same key — rotation would be a no-op".into(),
));
}
let sb = rotation_sign_bytes(
opts.chain_id,
opts.algorithm_id,
opts.account,
&old_kp.public_key,
&new_kp.public_key,
);
let old_signature = pqc_sign(&old_kp.secret_key, sb.as_bytes())?;
let new_signature = pqc_sign(&new_kp.secret_key, sb.as_bytes())?;
let msg = pqc_msg::rotate_pqc_key(
opts.account,
old_kp.public_key.clone(),
new_kp.public_key.clone(),
old_signature,
new_signature,
);
Ok(RotateFromMnemonic {
msg,
old_keypair: old_kp,
new_keypair: new_kp,
})
}