use borderless_id_types::{aid_prefix, cid_prefix, AgentId};
use std::array::TryFromSliceError;
use crate::ContractId;
pub const BASE_KEY_METADATA: u64 = 0;
pub const BASE_KEY_ACTION_LOG: u64 = 1;
pub const BASE_KEY_LOGS: u64 = 2;
pub const BASE_KEY_METRICS: u64 = 3;
pub const BASE_KEY_MASK_LEDGER: u64 = 0x0FFFFFFFFFFF0000;
pub const BASE_KEY_RESERVED: u64 = u64::MAX & !(1 << 63);
pub const META_SUB_KEY_CONTRACT_ID: u64 = 0;
pub const META_SUB_KEY_PARTICIPANTS: u64 = 1;
pub const META_SUB_KEY_ROLES: u64 = 2;
pub const META_SUB_KEY_SINKS: u64 = 3;
pub const META_SUB_KEY_DESC: u64 = 4;
pub const META_SUB_KEY_META: u64 = 5;
pub const META_SUB_KEY_INIT_STATE: u64 = 6;
pub const META_SUB_KEY_REVOKED_TS: u64 = 7;
pub const META_SUB_KEY_REVOCATION: u64 = 8;
pub const META_SUB_KEY_PACKAGE_DEF: u64 = 9;
pub const META_SUB_KEY_PACKAGE_SOURCE: u64 = 10;
pub const META_SUB_KEY_RESERVED: u64 = u64::MAX & !(1 << 63);
pub struct StorageKey([u8; 32]);
impl StorageKey {
pub fn new(id: impl AsRef<[u8; 16]>, base_key: u64, sub_key: u64) -> Self {
let key = calc_storage_key(id.as_ref(), base_key, sub_key);
Self(key)
}
pub fn user_key(id: impl AsRef<[u8; 16]>, base_key: u64, sub_key: u64) -> Self {
let base_key = make_user_key(base_key);
debug_assert!(
is_user_key(base_key) && !is_system_key(base_key),
"blinded base_key must be in user-space"
);
let key = calc_storage_key(id.as_ref(), base_key, sub_key);
Self(key)
}
pub fn system_key(id: impl AsRef<[u8; 16]>, base_key: u64, sub_key: u64) -> Self {
let base_key = base_key & !(1 << 63);
debug_assert!(
!is_user_key(base_key) && is_system_key(base_key),
"system base_key must be in system-space"
);
let key = calc_storage_key(id.as_ref(), base_key, sub_key);
Self(key)
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
use std::fmt::Write;
self.0.iter().fold(String::new(), |mut output, b| {
let _ = write!(output, "{b:02x}");
output
})
}
pub fn contract_id(&self) -> Option<ContractId> {
if self.is_contract_key() {
let id = self.get_id_prefix();
Some(ContractId::from_bytes(id))
} else {
None
}
}
pub fn agent_id(&self) -> Option<AgentId> {
if self.is_agent_key() {
let id = self.get_id_prefix();
Some(AgentId::from_bytes(id))
} else {
None
}
}
pub fn get_id_prefix(&self) -> [u8; 16] {
let mut id = [0u8; 16];
id.copy_from_slice(&self.0[0..16]);
id
}
pub fn get_prefix(&self) -> [u8; 24] {
let mut id = [0u8; 24];
id.copy_from_slice(&self.0[0..24]);
id
}
pub fn base_key(&self) -> u64 {
u64::from_be_bytes(self.0[16..24].try_into().expect("base_key slice invalid"))
}
pub fn sub_key(&self) -> u64 {
u64::from_be_bytes(self.0[24..32].try_into().expect("sub_key slice invalid"))
}
pub fn is_user_key(&self) -> bool {
is_user_key(self.base_key())
}
pub fn is_system_key(&self) -> bool {
is_system_key(self.base_key())
}
pub fn is_contract_key(&self) -> bool {
cid_prefix(self.0)
}
pub fn is_agent_key(&self) -> bool {
aid_prefix(self.0)
}
}
impl TryFrom<&[u8]> for StorageKey {
type Error = TryFromSliceError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let buf = value.try_into()?;
Ok(StorageKey(buf))
}
}
impl AsRef<[u8]> for StorageKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
pub fn calc_storage_key(id: &[u8; 16], base_key: u64, sub_key: u64) -> [u8; 32] {
let mut out = [0u8; 32];
out[0..16].copy_from_slice(id);
out[16..24].copy_from_slice(&base_key.to_be_bytes());
out[24..32].copy_from_slice(&sub_key.to_be_bytes());
out
}
#[inline]
pub fn make_user_key(base_key: u64) -> u64 {
base_key | (1 << 63)
}
#[inline]
pub fn is_user_key(base_key: u64) -> bool {
base_key & (1 << 63) != 0
}
#[inline]
pub fn is_system_key(base_key: u64) -> bool {
base_key & (1 << 63) == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn user_key_encoding_and_extraction() {
let cid = ContractId::generate();
let base = 42;
let sub = 99;
let key = StorageKey::user_key(&cid, base, sub);
assert!(key.contract_id().is_some());
assert_eq!(key.contract_id().unwrap(), cid);
assert_eq!(key.base_key(), make_user_key(base));
assert_eq!(key.sub_key(), sub);
assert!(key.is_user_key());
assert!(!key.is_system_key());
}
#[test]
fn system_key_encoding_and_extraction() {
let cid = ContractId::generate();
let base = BASE_KEY_METADATA;
let sub = 0;
let key = StorageKey::system_key(&cid, base, sub);
assert!(key.contract_id().is_some());
assert_eq!(key.contract_id().unwrap(), cid);
assert_eq!(key.base_key(), base);
assert_eq!(key.sub_key(), sub);
assert!(!key.is_user_key());
assert!(key.is_system_key());
}
#[test]
fn disjunct_id_prefix() {
let aid = AgentId::generate();
let cid = ContractId::generate();
let base = 42;
let sub = 99;
let aid_key = StorageKey::user_key(&aid, base, sub);
let cid_key = StorageKey::user_key(&cid, base, sub);
assert_ne!(aid_key.0, cid_key.0);
assert!(aid_prefix(&aid_key.0));
assert!(!aid_prefix(&cid_key.0));
assert!(!cid_prefix(&aid_key.0));
assert!(cid_prefix(&cid_key.0));
}
#[test]
fn return_correct_id_type() {
let aid = AgentId::generate();
let cid = ContractId::generate();
let base = 42;
let sub = 99;
let aid_key = StorageKey::user_key(&aid, base, sub);
let cid_key = StorageKey::user_key(&cid, base, sub);
assert!(aid_key.contract_id().is_none());
assert!(aid_key.agent_id().is_some());
assert_eq!(aid_key.agent_id().unwrap(), aid);
assert!(cid_key.contract_id().is_some());
assert!(cid_key.agent_id().is_none());
assert_eq!(cid_key.contract_id().unwrap(), cid);
}
#[test]
fn to_hex_format() {
let cid: ContractId = "cc8ca79c-3bbb-89d2-bb28-29636c170387"
.parse()
.expect("valid contract-id");
let base = 123;
let sub = 456;
let key = StorageKey::user_key(&cid, base, sub);
let hex = key.to_hex();
assert_eq!(hex.len(), 64);
assert_eq!(
hex,
"cc8ca79c3bbb89d2bb2829636c170387800000000000007b00000000000001c8"
);
}
#[test]
fn blinding_sets_high_bit() {
let base: u64 = 12345;
let blinded = make_user_key(base);
assert_eq!(blinded >> 63, 1);
}
#[test]
fn base_key_checks() {
for _ in 0..1_000_000 {
let user_key = make_user_key(rand::random());
let system_key = rand::random::<u64>() & !(1 << 63);
assert!(is_user_key(user_key));
assert!(!is_user_key(system_key));
assert!(is_system_key(system_key));
assert!(!is_system_key(user_key));
}
}
#[test]
fn is_system_key_metadata() {
assert!(is_system_key(BASE_KEY_METADATA));
assert!(!is_user_key(BASE_KEY_METADATA));
}
#[test]
fn is_system_key_actions() {
assert!(is_system_key(BASE_KEY_ACTION_LOG));
assert!(!is_user_key(BASE_KEY_ACTION_LOG));
}
#[test]
fn is_system_key_logs() {
assert!(is_system_key(BASE_KEY_LOGS));
assert!(!is_user_key(BASE_KEY_LOGS));
}
#[test]
fn is_system_key_reserved() {
assert!(is_system_key(BASE_KEY_RESERVED));
assert!(!is_user_key(BASE_KEY_RESERVED));
}
#[test]
fn base_keys_differ() {
let mut keys = vec![
BASE_KEY_METADATA,
BASE_KEY_ACTION_LOG,
BASE_KEY_LOGS,
BASE_KEY_METRICS,
BASE_KEY_RESERVED,
];
let n_keys = keys.len();
keys.sort();
keys.dedup(); assert_eq!(n_keys, keys.len());
}
}