use anyhow::{Result, anyhow};
use ark_bn254::Fr;
use ark_ff::PrimeField;
use nostr_sdk::prelude::*;
const POSEIDON2_FULL_ROUNDS: usize = 8; const POSEIDON2_PARTIAL_ROUNDS: usize = 56;
pub fn derive_nsec_from_leaf_secret(leaf_secret: &[Fr; 5]) -> Result<SecretKey> {
let input = [leaf_secret[0], leaf_secret[1], leaf_secret[2]];
let hash = poseidon2_hash_bn254(&input)?;
let hash_bytes = fr_to_bytes(&hash);
let secret_key = SecretKey::from_slice(&hash_bytes)
.map_err(|e| anyhow!("Failed to create secret key: {}", e))?;
Ok(secret_key)
}
pub fn derive_npub_from_nsec(nsec: &SecretKey) -> PublicKey {
Keys::new(nsec.clone()).public_key()
}
pub fn derive_nostr_keypair(leaf_secret: &[Fr; 5]) -> Result<Keys> {
let nsec = derive_nsec_from_leaf_secret(leaf_secret)?;
Ok(Keys::new(nsec))
}
fn fr_to_bytes(fr: &Fr) -> [u8; 32] {
let bigint = fr.into_bigint();
let mut bytes = [0u8; 32];
let limbs = bigint.0;
for (i, limb) in limbs.iter().enumerate() {
let limb_bytes = limb.to_le_bytes();
let offset = i * 8;
if offset + 8 <= 32 {
bytes[24 - offset..32 - offset].copy_from_slice(&limb_bytes);
}
}
bytes.reverse();
bytes
}
fn poseidon2_hash_bn254(inputs: &[Fr; 3]) -> Result<Fr> {
let mut state = [Fr::from(0u64), inputs[0], inputs[1], inputs[2]];
for r in 0..POSEIDON2_FULL_ROUNDS / 2 {
state = poseidon2_full_round(&state, r);
}
for r in 0..POSEIDON2_PARTIAL_ROUNDS {
state = poseidon2_partial_round(&state, POSEIDON2_FULL_ROUNDS / 2 + r);
}
for r in 0..POSEIDON2_FULL_ROUNDS / 2 {
state = poseidon2_full_round(&state, POSEIDON2_FULL_ROUNDS / 2 + POSEIDON2_PARTIAL_ROUNDS + r);
}
Ok(state[1])
}
fn poseidon2_full_round(state: &[Fr; 4], round: usize) -> [Fr; 4] {
let rc = get_round_constants(round);
let mut new_state = [Fr::from(0u64); 4];
for i in 0..4 {
let t = state[i] + rc[i];
let t2 = t * t;
let t4 = t2 * t2;
new_state[i] = t4 * t; }
apply_m4_matrix(&new_state)
}
fn poseidon2_partial_round(state: &[Fr; 4], round: usize) -> [Fr; 4] {
let rc = get_round_constants(round);
let mut new_state = *state;
for i in 0..4 {
new_state[i] = new_state[i] + rc[i];
}
let t = new_state[0];
let t2 = t * t;
let t4 = t2 * t2;
new_state[0] = t4 * t;
apply_m4_matrix(&new_state)
}
fn apply_m4_matrix(state: &[Fr; 4]) -> [Fr; 4] {
let two = Fr::from(2u64);
let three = Fr::from(3u64);
let one = Fr::from(1u64);
[
two * state[0] + three * state[1] + one * state[2] + one * state[3],
one * state[0] + two * state[1] + three * state[2] + one * state[3],
one * state[0] + one * state[1] + two * state[2] + three * state[3],
three * state[0] + one * state[1] + one * state[2] + two * state[3],
]
}
fn get_round_constants(round: usize) -> [Fr; 4] {
let base = Fr::from((round as u64 + 1) * 0x1234567890abcdef_u64);
[
base,
base + Fr::from(1u64),
base + Fr::from(2u64),
base + Fr::from(3u64),
]
}
#[cfg(test)]
mod tests {
use super::*;
use ark_ff::UniformRand;
use ark_std::rand::thread_rng;
#[test]
fn test_derive_nsec_deterministic() {
let mut rng = thread_rng();
let leaf_secret: [Fr; 5] = [
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
];
let nsec1 = derive_nsec_from_leaf_secret(&leaf_secret).unwrap();
let nsec2 = derive_nsec_from_leaf_secret(&leaf_secret).unwrap();
assert_eq!(nsec1.secret_bytes(), nsec2.secret_bytes());
}
#[test]
fn test_derive_keypair() {
let mut rng = thread_rng();
let leaf_secret: [Fr; 5] = [
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
];
let keys = derive_nostr_keypair(&leaf_secret).unwrap();
let npub = keys.public_key().to_hex();
assert_eq!(npub.len(), 64); }
#[test]
fn test_global_npub_no_client_id() {
let mut rng = thread_rng();
let leaf_secret: [Fr; 5] = [
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
Fr::rand(&mut rng),
];
let npub_amazon = derive_nostr_keypair(&leaf_secret).unwrap().public_key();
let npub_uber = derive_nostr_keypair(&leaf_secret).unwrap().public_key();
assert_eq!(npub_amazon, npub_uber);
}
}