use soroban_sdk::{contracttype, Address, Bytes, BytesN, Env, Symbol, Vec};
use super::error::AccountError;
#[contracttype]
#[derive(Clone, Debug)]
pub struct Secp256r1Key {
pub public_key: BytesN<65>,
pub label: Symbol,
pub registered_at: u64,
}
const SECP256R1_KEYS_PREFIX: &str = "p256_keys";
pub struct Secp256r1Storage;
impl Secp256r1Storage {
pub fn store(env: &Env, account: &Address, key: &Secp256r1Key) {
let keys = Self::load_all(env, account);
let mut new_keys: Vec<Secp256r1Key> = Vec::new(env);
for i in 0..keys.len() {
if let Some(k) = keys.get(i) {
if k.label != key.label {
new_keys.push_back(k);
}
}
}
new_keys.push_back(key.clone());
let storage_key = Self::storage_key(env, account);
env.storage().persistent().set(&storage_key, &new_keys);
}
pub fn load_all(env: &Env, account: &Address) -> Vec<Secp256r1Key> {
let storage_key = Self::storage_key(env, account);
env.storage()
.persistent()
.get(&storage_key)
.unwrap_or_else(|| Vec::new(env))
}
pub fn remove(env: &Env, account: &Address, label: &Symbol) -> bool {
let keys = Self::load_all(env, account);
let mut new_keys: Vec<Secp256r1Key> = Vec::new(env);
let mut found = false;
for i in 0..keys.len() {
if let Some(k) = keys.get(i) {
if &k.label == label {
found = true;
} else {
new_keys.push_back(k);
}
}
}
if found {
let storage_key = Self::storage_key(env, account);
if new_keys.is_empty() {
env.storage().persistent().remove(&storage_key);
} else {
env.storage().persistent().set(&storage_key, &new_keys);
}
}
found
}
pub fn find_by_label(env: &Env, account: &Address, label: &Symbol) -> Option<Secp256r1Key> {
let keys = Self::load_all(env, account);
for i in 0..keys.len() {
if let Some(k) = keys.get(i) {
if &k.label == label {
return Some(k);
}
}
}
None
}
fn storage_key(env: &Env, account: &Address) -> (Symbol, Address) {
(Symbol::new(env, SECP256R1_KEYS_PREFIX), account.clone())
}
}
pub fn verify_secp256r1(
env: &Env,
public_key: &BytesN<65>,
message: &Bytes,
signature: &BytesN<64>,
) -> Result<(), AccountError> {
let digest = env.crypto().sha256(message);
env.crypto()
.secp256r1_verify(public_key, &digest, signature);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{contract, contractimpl, symbol_short, testutils::Address as _, Env};
#[contract]
pub struct TestContract;
#[contractimpl]
impl TestContract {}
fn make_key(env: &Env, label_str: &str, pubkey_byte: u8) -> Secp256r1Key {
let mut bytes = [0u8; 65];
bytes[0] = 0x04; bytes[1] = pubkey_byte;
Secp256r1Key {
public_key: BytesN::from_array(env, &bytes),
label: Symbol::new(env, label_str),
registered_at: 0,
}
}
#[test]
fn test_store_and_load_keys() {
let env = Env::default();
let contract_id = env.register(TestContract, ());
let addr = Address::generate(&env);
env.as_contract(&contract_id, || {
let key1 = make_key(&env, "passkey1", 1);
let key2 = make_key(&env, "passkey2", 2);
Secp256r1Storage::store(&env, &addr, &key1);
Secp256r1Storage::store(&env, &addr, &key2);
let all = Secp256r1Storage::load_all(&env, &addr);
assert_eq!(all.len(), 2);
});
}
#[test]
fn test_store_overwrites_same_label() {
let env = Env::default();
let contract_id = env.register(TestContract, ());
let addr = Address::generate(&env);
env.as_contract(&contract_id, || {
let key1 = make_key(&env, "passkey1", 1);
Secp256r1Storage::store(&env, &addr, &key1);
let key1_v2 = make_key(&env, "passkey1", 99);
Secp256r1Storage::store(&env, &addr, &key1_v2);
let all = Secp256r1Storage::load_all(&env, &addr);
assert_eq!(all.len(), 1); });
}
#[test]
fn test_remove_key() {
let env = Env::default();
let contract_id = env.register(TestContract, ());
let addr = Address::generate(&env);
env.as_contract(&contract_id, || {
let key = make_key(&env, "passkey1", 1);
Secp256r1Storage::store(&env, &addr, &key);
let label = symbol_short!("passkey1");
assert!(Secp256r1Storage::remove(&env, &addr, &label));
assert_eq!(Secp256r1Storage::load_all(&env, &addr).len(), 0);
});
}
#[test]
fn test_remove_nonexistent() {
let env = Env::default();
let contract_id = env.register(TestContract, ());
let addr = Address::generate(&env);
env.as_contract(&contract_id, || {
let label = symbol_short!("nope");
assert!(!Secp256r1Storage::remove(&env, &addr, &label));
});
}
#[test]
fn test_find_by_label() {
let env = Env::default();
let contract_id = env.register(TestContract, ());
let addr = Address::generate(&env);
env.as_contract(&contract_id, || {
let key = make_key(&env, "passkey1", 42);
Secp256r1Storage::store(&env, &addr, &key);
let label = symbol_short!("passkey1");
let found = Secp256r1Storage::find_by_label(&env, &addr, &label);
assert!(found.is_some());
let missing = symbol_short!("nope");
assert!(Secp256r1Storage::find_by_label(&env, &addr, &missing).is_none());
});
}
#[test]
fn test_load_empty() {
let env = Env::default();
let contract_id = env.register(TestContract, ());
let addr = Address::generate(&env);
env.as_contract(&contract_id, || {
let all = Secp256r1Storage::load_all(&env, &addr);
assert_eq!(all.len(), 0);
});
}
}