use k256::ecdsa::SigningKey;
use super::*;
pub fn devices_topic(owner_addr: &str) -> [u8; 32] {
let mut pre = b"localharness.devices".to_vec();
if let Ok(a) = parse_eth_address(owner_addr) {
pre.extend_from_slice(&a);
}
keccak_key(&pre)
}
pub fn team_topic(team_id: u64) -> [u8; 32] {
let mut pre = b"localharness.team".to_vec();
pre.extend_from_slice(&u256_be(team_id as u128));
keccak_key(&pre)
}
pub(crate) fn address_word(addr: &[u8; 20]) -> [u8; 32] {
let mut w = [0u8; 32];
w[12..32].copy_from_slice(addr);
w
}
pub(crate) fn push_abi_bytes(d: &mut Vec<u8>, bytes: &[u8]) {
d.extend_from_slice(&u256_be(bytes.len() as u128));
d.extend_from_slice(bytes);
let pad = (32 - (bytes.len() % 32)) % 32;
d.extend(std::iter::repeat_n(0u8, pad));
}
pub fn announce_digest(topic: &[u8; 32], ephemeral: &[u8; 20], pubkey: &[u8]) -> [u8; 32] {
let mut pre = Vec::with_capacity(32 + 20 + pubkey.len());
pre.extend_from_slice(topic);
pre.extend_from_slice(ephemeral);
pre.extend_from_slice(pubkey);
keccak32(&pre)
}
pub fn leave_digest(topic: &[u8; 32], ephemeral: &[u8; 20]) -> [u8; 32] {
let mut pre = Vec::with_capacity(18 + 32 + 20);
pre.extend_from_slice(b"localharness.leave");
pre.extend_from_slice(topic);
pre.extend_from_slice(ephemeral);
keccak32(&pre)
}
pub(crate) fn encode_leave(
topic: &[u8; 32],
owner: &[u8; 20],
ephemeral: &[u8; 20],
sig: &[u8; 65],
) -> Vec<u8> {
let mut d = selector("leave(bytes32,address,address,bytes)").to_vec();
d.extend_from_slice(topic);
d.extend_from_slice(&address_word(owner));
d.extend_from_slice(&address_word(ephemeral));
d.extend_from_slice(&u256_be(0x80)); push_abi_bytes(&mut d, sig);
d
}
pub async fn leave_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
owner_key: &SigningKey,
owner: &[u8; 20],
topic: &[u8; 32],
ephemeral: &[u8; 20],
fee_token: &str,
) -> Result<String, String> {
let digest = leave_digest(topic, ephemeral);
let sig = crate::wallet::sign_hash(owner_key, &digest); sponsored_diamond_call(
sender,
fee_payer,
encode_leave(topic, owner, ephemeral, &sig),
fee_token,
1_200_000,
)
.await
}
pub(crate) fn encode_announce(
topic: &[u8; 32],
owner: &[u8; 20],
ephemeral: &[u8; 20],
pubkey: &[u8],
sig: &[u8; 65],
) -> Vec<u8> {
let mut d = selector("announce(bytes32,address,address,bytes,bytes)").to_vec();
d.extend_from_slice(topic);
d.extend_from_slice(&address_word(owner));
d.extend_from_slice(&address_word(ephemeral));
d.extend_from_slice(&u256_be(0xa0)); let pubkey_tail = 32 + pubkey.len().div_ceil(32) * 32;
d.extend_from_slice(&u256_be((0xa0 + pubkey_tail) as u128)); push_abi_bytes(&mut d, pubkey);
push_abi_bytes(&mut d, sig);
d
}
#[allow(clippy::too_many_arguments)]
pub async fn announce_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
owner_key: &SigningKey,
owner: &[u8; 20],
topic: &[u8; 32],
ephemeral: &[u8; 20],
pubkey: &[u8],
fee_token: &str,
) -> Result<String, String> {
let digest = announce_digest(topic, ephemeral, pubkey);
let sig = crate::wallet::sign_hash(owner_key, &digest); let gas = 1_200_000u128 + (pubkey.len() as u128) * 9_000;
sponsored_diamond_call(
sender,
fee_payer,
encode_announce(topic, owner, ephemeral, pubkey, &sig),
fee_token,
gas,
)
.await
}
pub(crate) fn encode_post_signal(to: &[u8; 20], blob: &[u8]) -> Vec<u8> {
let mut d = selector("postSignal(address,bytes)").to_vec();
d.extend_from_slice(&address_word(to));
d.extend_from_slice(&u256_be(0x40)); push_abi_bytes(&mut d, blob);
d
}
pub async fn post_signal_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
to: &[u8; 20],
blob: &[u8],
fee_token: &str,
) -> Result<String, String> {
let gas = 1_200_000u128 + (blob.len() as u128) * 9_000;
sponsored_diamond_call(sender, fee_payer, encode_post_signal(to, blob), fee_token, gas).await
}
pub type AddrTsBytes = (String, u64, Vec<u8>);
pub(crate) fn decode_addr_ts_bytes_array(result_hex: &str) -> Vec<AddrTsBytes> {
let raw = match hex_to_bytes(result_hex) {
Ok(b) => b,
Err(_) => return Vec::new(),
};
let read_usize = |off: usize| -> Option<usize> {
let end = off.checked_add(32)?;
let w = raw.get(off..end)?;
Some(u64::from_be_bytes(w[24..32].try_into().ok()?) as usize)
};
let mut out = Vec::new();
let arr_off = match read_usize(0) {
Some(o) => o,
None => return out,
};
let len = match read_usize(arr_off) {
Some(l) => l,
None => return out,
};
let heads = match arr_off.checked_add(32) {
Some(h) => h, None => return out,
};
for i in 0..len {
let head_slot = match i.checked_mul(32).and_then(|o| heads.checked_add(o)) {
Some(s) => s,
None => break,
};
let elem = match read_usize(head_slot) {
Some(rel) => match heads.checked_add(rel) {
Some(e) => e,
None => break,
},
None => break,
};
let addr = match elem
.checked_add(12)
.zip(elem.checked_add(32))
.and_then(|(a, b)| raw.get(a..b))
{
Some(a) => format!("0x{}", bytes_to_hex(a)),
None => break,
};
let ts = match elem
.checked_add(56)
.zip(elem.checked_add(64))
.and_then(|(a, b)| raw.get(a..b))
{
Some(t) => u64::from_be_bytes(t.try_into().unwrap_or_default()),
None => break,
};
let boff = match elem.checked_add(64).and_then(read_usize) {
Some(rel) => match elem.checked_add(rel) {
Some(b) => b,
None => break,
},
None => break,
};
let blen = match read_usize(boff) {
Some(l) => l,
None => break,
};
let bytes = boff
.checked_add(32)
.and_then(|start| start.checked_add(blen).map(|end| (start, end)))
.and_then(|(start, end)| raw.get(start..end))
.map(|s| s.to_vec())
.unwrap_or_default();
out.push((addr, ts, bytes));
}
out
}
pub async fn peers_of(topic: &[u8; 32]) -> Result<Vec<AddrTsBytes>, String> {
let res = read_view(selector("peersOf(bytes32)"), &[*topic]).await?;
Ok(decode_addr_ts_bytes_array(&res))
}
pub async fn inbox_of(peer: &[u8; 20], from_index: u64) -> Result<Vec<AddrTsBytes>, String> {
let res = read_view(
selector("inboxOf(address,uint256)"),
&[address_word(peer), u256_be(from_index as u128)],
)
.await?;
Ok(decode_addr_ts_bytes_array(&res))
}
pub async fn inbox_length(peer: &[u8; 20]) -> Result<u64, String> {
let res = read_view(selector("inboxLength(address)"), &[address_word(peer)]).await?;
let raw = hex_to_bytes(&res)?;
if raw.len() < 32 {
return Ok(0);
}
Ok(u64::from_be_bytes(raw[24..32].try_into().map_err(|_| "bad len")?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_presence_signal_array() {
let hex = String::from("0x")
+ "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000020" + "0000000000000000000000001111111111111111111111111111111111111111" + "0000000000000000000000000000000000000000000000000000000000000005" + "0000000000000000000000000000000000000000000000000000000000000060" + "0000000000000000000000000000000000000000000000000000000000000002" + "aabb000000000000000000000000000000000000000000000000000000000000"; let out = decode_addr_ts_bytes_array(&hex);
assert_eq!(out.len(), 1);
assert_eq!(out[0].0, "0x1111111111111111111111111111111111111111");
assert_eq!(out[0].1, 5);
assert_eq!(out[0].2, vec![0xAA, 0xBB]);
let empty = String::from("0x")
+ "0000000000000000000000000000000000000000000000000000000000000020"
+ "0000000000000000000000000000000000000000000000000000000000000000";
assert!(decode_addr_ts_bytes_array(&empty).is_empty());
}
#[test]
fn devices_topic_preimage_is_label_then_raw_address() {
let owner = "0x1111111111111111111111111111111111111111";
let topic = devices_topic(owner);
let mut pre = b"localharness.devices".to_vec();
pre.extend_from_slice(&parse_eth_address(owner).unwrap());
assert_eq!(topic, keccak_key(&pre));
assert_eq!(pre.len(), 40);
}
#[test]
fn announce_digest_is_packed_topic_ephemeral_pubkey() {
let topic = [0xABu8; 32];
let eph = [0x22u8; 20];
let pubkey = vec![0x02u8; 33];
let mut pre = Vec::new();
pre.extend_from_slice(&topic);
pre.extend_from_slice(&eph);
pre.extend_from_slice(&pubkey);
assert_eq!(pre.len(), 32 + 20 + 33);
assert_eq!(announce_digest(&topic, &eph, &pubkey), keccak32(&pre));
}
#[test]
fn leave_digest_is_prefixed_packed_topic_ephemeral() {
let topic = [0xABu8; 32];
let eph = [0x22u8; 20];
let mut pre = b"localharness.leave".to_vec();
pre.extend_from_slice(&topic);
pre.extend_from_slice(&eph);
assert_eq!(pre.len(), 18 + 32 + 20);
assert_eq!(leave_digest(&topic, &eph), keccak32(&pre));
let pubkey = vec![0x02u8; 33];
assert_ne!(leave_digest(&topic, &eph), announce_digest(&topic, &eph, &pubkey));
}
#[test]
fn encode_leave_4arg_layout() {
let topic = [0x11u8; 32];
let owner = [0x22u8; 20];
let eph = [0x33u8; 20];
let sig = [0x44u8; 65];
let cd = encode_leave(&topic, &owner, &eph, &sig);
assert_eq!(&cd[..4], &selector("leave(bytes32,address,address,bytes)"));
assert_eq!(&cd[4..36], &topic[..]);
assert_eq!(&cd[36..68], &address_word(&owner)[..]);
assert_eq!(&cd[68..100], &address_word(&eph)[..]);
assert_eq!(&cd[100..132], &u256_be(0x80)[..]); let sig_off = 4 + 0x80;
assert_eq!(&cd[sig_off..sig_off + 32], &u256_be(65)[..]);
assert_eq!(&cd[sig_off + 32..sig_off + 32 + 65], &sig[..]);
assert_eq!(cd.len(), 4 + 4 * 32 + (32 + 96));
}
#[test]
fn leave_digest_signature_recovers_to_owner() {
let w = crate::wallet::generate();
let owner = crate::wallet::address(&w.signer); let topic = [0x11u8; 32];
let eph = [0x99u8; 20];
let digest = leave_digest(&topic, &eph);
let sig = crate::wallet::sign_hash(&w.signer, &digest); let recovered = crate::wallet::recover_address(&sig, &digest)
.expect("sig recovers");
assert_eq!(recovered, owner, "leave sig recovers to the owner");
}
#[test]
fn announce_digest_signature_recovers_to_owner() {
let w = crate::wallet::generate();
let owner = crate::wallet::address(&w.signer); let topic = [0x11u8; 32];
let eph = [0x99u8; 20];
let pubkey = vec![0x03u8; 33];
let digest = announce_digest(&topic, &eph, &pubkey);
let sig = crate::wallet::sign_hash(&w.signer, &digest); let recovered = crate::wallet::recover_address(&sig, &digest)
.expect("sig recovers");
assert_eq!(recovered, owner, "announce sig recovers to the owner");
}
#[test]
fn encode_announce_5arg_layout() {
let topic = [0x11u8; 32];
let owner = [0x22u8; 20];
let eph = [0x33u8; 20];
let pubkey = vec![0x02u8; 33]; let sig = [0x44u8; 65];
let cd = encode_announce(&topic, &owner, &eph, &pubkey, &sig);
assert_eq!(
&cd[..4],
&selector("announce(bytes32,address,address,bytes,bytes)")
);
assert_eq!(&cd[4..36], &topic[..]);
assert_eq!(&cd[36..68], &address_word(&owner)[..]);
assert_eq!(&cd[68..100], &address_word(&eph)[..]);
assert_eq!(&cd[100..132], &u256_be(0xa0)[..]); assert_eq!(&cd[132..164], &u256_be(0x100)[..]); let pk_off = 4 + 0xa0;
assert_eq!(&cd[pk_off..pk_off + 32], &u256_be(33)[..]);
assert_eq!(&cd[pk_off + 32..pk_off + 32 + 33], &pubkey[..]);
let sig_off = 4 + 0x100;
assert_eq!(&cd[sig_off..sig_off + 32], &u256_be(65)[..]);
assert_eq!(&cd[sig_off + 32..sig_off + 32 + 65], &sig[..]);
assert_eq!(cd.len(), 4 + 5 * 32 + (32 + 64) + (32 + 96));
}
#[test]
fn addr_ts_bytes_array_empty_and_short_inputs() {
assert!(decode_addr_ts_bytes_array("0x").is_empty());
assert!(decode_addr_ts_bytes_array("0x00").is_empty());
assert!(decode_addr_ts_bytes_array("0xabc").is_empty());
assert!(decode_addr_ts_bytes_array("0xzz").is_empty());
assert!(decode_addr_ts_bytes_array("nonsense").is_empty());
let off_oob = format!("0x{}", word_usize(0x40)); assert!(decode_addr_ts_bytes_array(&off_oob).is_empty());
}
#[test]
fn addr_ts_bytes_array_hostile_offsets_dont_overflow() {
let huge_off = format!("0x{}", word_u64_max());
assert!(decode_addr_ts_bytes_array(&huge_off).is_empty());
let huge_len = format!("0x{}{}", word_usize(0x20), word_u64_max());
assert!(decode_addr_ts_bytes_array(&huge_len).is_empty());
let bad_head = String::from("0x")
+ &word_usize(0x20) + &word_usize(1) + &word_u64_max(); assert!(decode_addr_ts_bytes_array(&bad_head).is_empty());
let bad_bytes_off = String::from("0x")
+ &word_usize(0x20) + &word_usize(1) + &word_usize(0x20) + &word_usize(0x1111) + &word_usize(7) + &word_u64_max(); assert!(decode_addr_ts_bytes_array(&bad_bytes_off).is_empty());
}
#[test]
fn addr_ts_bytes_array_multi_element_decodes() {
let elem0 = String::from("")
+ "0000000000000000000000001111111111111111111111111111111111111111" + &word_usize(1) + &word_usize(0x60) + &word_usize(1) + "aa00000000000000000000000000000000000000000000000000000000000000"; let elem1 = String::from("")
+ "0000000000000000000000002222222222222222222222222222222222222222"
+ &word_usize(2)
+ &word_usize(0x60)
+ &word_usize(2)
+ "bbcc000000000000000000000000000000000000000000000000000000000000";
let hex = String::from("0x")
+ &word_usize(0x20) + &word_usize(2) + &word_usize(0x40) + &word_usize(0xE0) + &elem0
+ &elem1;
let out = decode_addr_ts_bytes_array(&hex);
assert_eq!(out.len(), 2);
assert_eq!(out[0].0, "0x1111111111111111111111111111111111111111");
assert_eq!(out[0].1, 1);
assert_eq!(out[0].2, vec![0xAA]);
assert_eq!(out[1].0, "0x2222222222222222222222222222222222222222");
assert_eq!(out[1].1, 2);
assert_eq!(out[1].2, vec![0xBB, 0xCC]);
}
}