use crate::overlay::types::Protocol;
use crate::primitives::PublicKey;
use crate::script::templates::PushDrop;
use crate::script::LockingScript;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct OverlayAdminTokenData {
pub protocol: Protocol,
pub identity_key: PublicKey,
pub domain: String,
pub topic_or_service: String,
}
impl OverlayAdminTokenData {
pub fn identity_key_hex(&self) -> String {
crate::primitives::to_hex(&self.identity_key.to_compressed())
}
}
pub fn decode_overlay_admin_token(script: &LockingScript) -> Result<OverlayAdminTokenData> {
let pushdrop = PushDrop::decode(script)
.map_err(|_| Error::OverlayError("Script is not a valid PushDrop format".into()))?;
let fields = &pushdrop.fields;
if fields.len() < 4 {
return Err(Error::OverlayError(format!(
"Invalid SHIP/SLAP advertisement: expected at least 4 fields, got {}",
fields.len()
)));
}
let protocol_str = std::str::from_utf8(&fields[0])
.map_err(|_| Error::OverlayError("Protocol field is not valid UTF-8".into()))?;
let protocol = Protocol::parse(protocol_str)
.ok_or_else(|| Error::OverlayError(format!("Invalid protocol type: {}", protocol_str)))?;
let identity_key = PublicKey::from_bytes(&fields[1])
.map_err(|e| Error::OverlayError(format!("Invalid identity key: {}", e)))?;
let domain = std::str::from_utf8(&fields[2])
.map_err(|_| Error::OverlayError("Domain field is not valid UTF-8".into()))?
.to_string();
let topic_or_service = std::str::from_utf8(&fields[3])
.map_err(|_| Error::OverlayError("Topic/service field is not valid UTF-8".into()))?
.to_string();
Ok(OverlayAdminTokenData {
protocol,
identity_key,
domain,
topic_or_service,
})
}
pub fn create_overlay_admin_token(
protocol: Protocol,
identity_key: &PublicKey,
domain: &str,
topic_or_service: &str,
) -> LockingScript {
let fields = vec![
protocol.as_str().as_bytes().to_vec(),
identity_key.to_compressed().to_vec(),
domain.as_bytes().to_vec(),
topic_or_service.as_bytes().to_vec(),
];
let pushdrop = PushDrop::new(identity_key.clone(), fields);
pushdrop.lock()
}
pub fn is_overlay_admin_token(script: &LockingScript) -> bool {
decode_overlay_admin_token(script).is_ok()
}
pub fn is_ship_token(script: &LockingScript) -> bool {
decode_overlay_admin_token(script)
.map(|t| t.protocol == Protocol::Ship)
.unwrap_or(false)
}
pub fn is_slap_token(script: &LockingScript) -> bool {
decode_overlay_admin_token(script)
.map(|t| t.protocol == Protocol::Slap)
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::PrivateKey;
#[test]
fn test_create_and_decode_ship_token() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let script = create_overlay_admin_token(
Protocol::Ship,
&public_key,
"https://example.com",
"tm_test_topic",
);
let decoded = decode_overlay_admin_token(&script).unwrap();
assert_eq!(decoded.protocol, Protocol::Ship);
assert_eq!(
decoded.identity_key.to_compressed(),
public_key.to_compressed()
);
assert_eq!(decoded.domain, "https://example.com");
assert_eq!(decoded.topic_or_service, "tm_test_topic");
}
#[test]
fn test_create_and_decode_slap_token() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let script = create_overlay_admin_token(
Protocol::Slap,
&public_key,
"https://service.example.com",
"ls_myservice",
);
let decoded = decode_overlay_admin_token(&script).unwrap();
assert_eq!(decoded.protocol, Protocol::Slap);
assert_eq!(decoded.domain, "https://service.example.com");
assert_eq!(decoded.topic_or_service, "ls_myservice");
}
#[test]
fn test_is_ship_token() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let ship_script = create_overlay_admin_token(
Protocol::Ship,
&public_key,
"https://example.com",
"tm_test",
);
let slap_script = create_overlay_admin_token(
Protocol::Slap,
&public_key,
"https://example.com",
"ls_test",
);
assert!(is_ship_token(&ship_script));
assert!(!is_ship_token(&slap_script));
}
#[test]
fn test_is_slap_token() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let slap_script = create_overlay_admin_token(
Protocol::Slap,
&public_key,
"https://example.com",
"ls_test",
);
let ship_script = create_overlay_admin_token(
Protocol::Ship,
&public_key,
"https://example.com",
"tm_test",
);
assert!(is_slap_token(&slap_script));
assert!(!is_slap_token(&ship_script));
}
#[test]
fn test_is_overlay_admin_token() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let admin_script = create_overlay_admin_token(
Protocol::Ship,
&public_key,
"https://example.com",
"tm_test",
);
let regular_script =
LockingScript::from_asm("OP_DUP OP_HASH160 0x14 OP_EQUALVERIFY OP_CHECKSIG");
assert!(is_overlay_admin_token(&admin_script));
assert!(regular_script.is_err() || !is_overlay_admin_token(®ular_script.unwrap()));
}
#[test]
fn test_identity_key_hex() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let script = create_overlay_admin_token(
Protocol::Ship,
&public_key,
"https://example.com",
"tm_test",
);
let decoded = decode_overlay_admin_token(&script).unwrap();
let hex = decoded.identity_key_hex();
assert_eq!(hex.len(), 66);
}
#[test]
fn test_decode_invalid_script() {
let script = LockingScript::new();
assert!(decode_overlay_admin_token(&script).is_err());
}
#[test]
fn test_decode_insufficient_fields() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let fields = vec![b"SHIP".to_vec(), b"domain".to_vec()];
let pushdrop = PushDrop::new(public_key, fields);
let script = pushdrop.lock();
let result = decode_overlay_admin_token(&script);
assert!(result.is_err());
}
#[test]
fn test_decode_invalid_protocol() {
let private_key = PrivateKey::random();
let public_key = private_key.public_key();
let fields = vec![
b"INVALID".to_vec(),
public_key.to_compressed().to_vec(),
b"domain".to_vec(),
b"topic".to_vec(),
];
let pushdrop = PushDrop::new(public_key, fields);
let script = pushdrop.lock();
let result = decode_overlay_admin_token(&script);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid protocol"));
}
}