1use std::fmt;
55
56use secp256k1::schnorr::Signature;
57use sha2::{Digest, Sha256};
58use thiserror::Error;
59
60use crate::event::{Event, Kind, Tag, TagKind, Tags};
61use crate::key::{Keys, PublicKey};
62use crate::types::Timestamp;
63
64pub const DELEGATION_TAG_KEY: &str = "delegation";
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69#[non_exhaustive]
70pub enum Condition {
71 Kind(Kind),
73 CreatedAfter(Timestamp),
76 CreatedBefore(Timestamp),
79}
80
81impl fmt::Display for Condition {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::Kind(k) => write!(f, "kind={}", k.as_u16()),
85 Self::CreatedAfter(ts) => write!(f, "created_at>{}", ts.as_secs()),
86 Self::CreatedBefore(ts) => write!(f, "created_at<{}", ts.as_secs()),
87 }
88 }
89}
90
91#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
97pub struct Conditions {
98 items: Vec<Condition>,
99}
100
101impl Conditions {
102 #[must_use]
104 pub const fn new() -> Self {
105 Self { items: Vec::new() }
106 }
107
108 #[must_use]
110 pub fn allow_kind(mut self, kind: Kind) -> Self {
111 self.items.push(Condition::Kind(kind));
112 self
113 }
114
115 #[must_use]
118 pub fn after(mut self, ts: Timestamp) -> Self {
119 self.items.push(Condition::CreatedAfter(ts));
120 self
121 }
122
123 #[must_use]
126 pub fn before(mut self, ts: Timestamp) -> Self {
127 self.items.push(Condition::CreatedBefore(ts));
128 self
129 }
130
131 pub fn iter(&self) -> impl Iterator<Item = &Condition> {
133 self.items.iter()
134 }
135
136 #[must_use]
138 pub const fn is_empty(&self) -> bool {
139 self.items.is_empty()
140 }
141
142 #[must_use]
147 pub fn render(&self) -> String {
148 let mut out = String::new();
149 for (i, c) in self.items.iter().enumerate() {
150 if i > 0 {
151 out.push('&');
152 }
153 out.push_str(&c.to_string());
154 }
155 out
156 }
157
158 pub fn parse(s: &str) -> Result<Self, ConditionsError> {
166 if s.is_empty() {
167 return Ok(Self::new());
168 }
169 let mut items = Vec::with_capacity(s.matches('&').count() + 1);
170 for raw in s.split('&') {
171 items.push(parse_one(raw)?);
172 }
173 Ok(Self { items })
174 }
175
176 #[must_use]
180 pub fn matches(&self, kind: Kind, created_at: Timestamp) -> bool {
181 self.items.iter().all(|c| match c {
182 Condition::Kind(k) => *k == kind,
183 Condition::CreatedAfter(ts) => created_at.as_secs() > ts.as_secs(),
184 Condition::CreatedBefore(ts) => created_at.as_secs() < ts.as_secs(),
185 })
186 }
187}
188
189fn parse_one(raw: &str) -> Result<Condition, ConditionsError> {
190 if let Some(rest) = raw.strip_prefix("kind=") {
191 let n: u16 = rest
192 .parse()
193 .map_err(|_| ConditionsError::InvalidValue(raw.to_owned()))?;
194 return Ok(Condition::Kind(Kind::new(n)));
195 }
196 if let Some(rest) = raw.strip_prefix("created_at>") {
197 let n: u64 = rest
198 .parse()
199 .map_err(|_| ConditionsError::InvalidValue(raw.to_owned()))?;
200 return Ok(Condition::CreatedAfter(Timestamp::from_secs(n)));
201 }
202 if let Some(rest) = raw.strip_prefix("created_at<") {
203 let n: u64 = rest
204 .parse()
205 .map_err(|_| ConditionsError::InvalidValue(raw.to_owned()))?;
206 return Ok(Condition::CreatedBefore(Timestamp::from_secs(n)));
207 }
208 Err(ConditionsError::UnsupportedClause(raw.to_owned()))
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Error)]
213#[non_exhaustive]
214pub enum ConditionsError {
215 #[error("unsupported delegation clause `{0}`")]
217 UnsupportedClause(String),
218 #[error("invalid value in delegation clause `{0}`")]
220 InvalidValue(String),
221}
222
223#[must_use]
230pub fn delegation_message(delegatee: &PublicKey, conditions: &Conditions) -> String {
231 format!(
232 "nostr:delegation:{}:{}",
233 delegatee.to_hex(),
234 conditions.render()
235 )
236}
237
238#[must_use]
240pub fn delegation_hash(delegatee: &PublicKey, conditions: &Conditions) -> [u8; 32] {
241 let msg = delegation_message(delegatee, conditions);
242 let digest = Sha256::digest(msg.as_bytes());
243 digest.into()
244}
245
246pub type DelegationToken = Signature;
248
249#[must_use]
251pub fn sign_delegation(
252 delegator: &Keys,
253 delegatee: &PublicKey,
254 conditions: &Conditions,
255) -> DelegationToken {
256 let h = delegation_hash(delegatee, conditions);
257 delegator.sign_schnorr(&h)
258}
259
260#[must_use]
262pub fn verify_delegation(
263 delegator: &PublicKey,
264 delegatee: &PublicKey,
265 conditions: &Conditions,
266 token: &DelegationToken,
267) -> bool {
268 let h = delegation_hash(delegatee, conditions);
269 delegator.verify_schnorr(&h, token)
270}
271
272impl Tag {
273 #[must_use]
277 pub fn delegation(
278 delegator: PublicKey,
279 conditions: &Conditions,
280 token: &DelegationToken,
281 ) -> Self {
282 Self::with(
283 &TagKind::Custom(DELEGATION_TAG_KEY.to_owned()),
284 [delegator.to_hex(), conditions.render(), token.to_string()],
285 )
286 }
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct Delegation {
292 pub delegator: PublicKey,
294 pub conditions: Conditions,
296 pub token: DelegationToken,
298}
299
300#[derive(Debug, Error)]
302#[non_exhaustive]
303pub enum DelegationError {
304 #[error("event has no well-formed `delegation` tag")]
306 Missing,
307 #[error(transparent)]
309 Pubkey(#[from] crate::key::PublicKeyError),
310 #[error(transparent)]
312 Conditions(#[from] ConditionsError),
313 #[error("invalid delegation token: {0}")]
315 Token(String),
316 #[error("delegation token does not verify against the declared delegator")]
318 InvalidSignature,
319 #[error("event does not satisfy the delegation conditions")]
322 ConditionsViolated,
323}
324
325pub fn parse_delegation(tags: &Tags) -> Result<Delegation, DelegationError> {
338 for tag in tags {
339 if !is_delegation_tag(&tag.kind()) {
340 continue;
341 }
342 let values = tag.values();
343 let (Some(d), Some(c), Some(t)) = (values.get(1), values.get(2), values.get(3)) else {
344 return Err(DelegationError::Missing);
345 };
346 let delegator = PublicKey::parse(d)?;
347 let conditions = Conditions::parse(c)?;
348 let token = t
349 .parse::<Signature>()
350 .map_err(|e| DelegationError::Token(e.to_string()))?;
351 return Ok(Delegation {
352 delegator,
353 conditions,
354 token,
355 });
356 }
357 Err(DelegationError::Missing)
358}
359
360fn is_delegation_tag(kind: &TagKind) -> bool {
361 matches!(kind, TagKind::Custom(s) if s == DELEGATION_TAG_KEY)
362}
363
364pub fn verify_event_delegation(event: &Event) -> Result<Delegation, DelegationError> {
380 let delegation = parse_delegation(&event.tags)?;
381 if !verify_delegation(
382 &delegation.delegator,
383 &event.pubkey,
384 &delegation.conditions,
385 &delegation.token,
386 ) {
387 return Err(DelegationError::InvalidSignature);
388 }
389 if !delegation.conditions.matches(event.kind, event.created_at) {
390 return Err(DelegationError::ConditionsViolated);
391 }
392 Ok(delegation)
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::EventBuilder;
399 use crate::event::Kind;
400
401 fn fixture_keys(byte: u8) -> Keys {
402 let hex: String = format!("{byte:064x}");
403 Keys::parse(&hex).unwrap()
404 }
405
406 #[test]
407 fn conditions_render_and_parse_round_trip() {
408 let c = Conditions::new()
409 .allow_kind(Kind::TEXT_NOTE)
410 .after(Timestamp::from_secs(1_700_000_000))
411 .before(Timestamp::from_secs(1_800_000_000));
412 let rendered = c.render();
413 assert_eq!(
414 rendered,
415 "kind=1&created_at>1700000000&created_at<1800000000"
416 );
417 assert_eq!(Conditions::parse(&rendered).unwrap(), c);
418 }
419
420 #[test]
421 fn empty_conditions_render_to_empty_string() {
422 let empty = Conditions::new();
423 assert_eq!(empty.render(), "");
424 assert_eq!(Conditions::parse("").unwrap(), empty);
425 assert!(empty.matches(Kind::TEXT_NOTE, Timestamp::from_secs(1)));
427 }
428
429 #[test]
430 fn parse_rejects_malformed_clauses() {
431 assert!(matches!(
432 Conditions::parse("foobar=1"),
433 Err(ConditionsError::UnsupportedClause(s)) if s == "foobar=1"
434 ));
435 assert!(matches!(
436 Conditions::parse("kind=abc"),
437 Err(ConditionsError::InvalidValue(s)) if s == "kind=abc"
438 ));
439 }
440
441 #[test]
442 fn matches_enforces_strict_inequalities() {
443 let c = Conditions::new()
444 .after(Timestamp::from_secs(100))
445 .before(Timestamp::from_secs(200));
446 assert!(c.matches(Kind::TEXT_NOTE, Timestamp::from_secs(150)));
447 assert!(!c.matches(Kind::TEXT_NOTE, Timestamp::from_secs(100)));
449 assert!(!c.matches(Kind::TEXT_NOTE, Timestamp::from_secs(200)));
450 }
451
452 #[test]
453 fn delegation_token_verifies_with_correct_inputs() {
454 let delegator = fixture_keys(1);
455 let delegatee = fixture_keys(2);
456 let conditions = Conditions::new().allow_kind(Kind::TEXT_NOTE);
457
458 let token = sign_delegation(&delegator, delegatee.public_key(), &conditions);
459 assert!(verify_delegation(
460 delegator.public_key(),
461 delegatee.public_key(),
462 &conditions,
463 &token,
464 ));
465 }
466
467 #[test]
468 fn delegation_token_fails_when_delegatee_changes() {
469 let delegator = fixture_keys(1);
470 let delegatee_a = fixture_keys(2);
471 let delegatee_b = fixture_keys(3);
472 let conditions = Conditions::new().allow_kind(Kind::TEXT_NOTE);
473
474 let token = sign_delegation(&delegator, delegatee_a.public_key(), &conditions);
475 assert!(!verify_delegation(
476 delegator.public_key(),
477 delegatee_b.public_key(),
478 &conditions,
479 &token,
480 ));
481 }
482
483 #[test]
484 fn delegation_token_fails_when_conditions_change() {
485 let delegator = fixture_keys(1);
486 let delegatee = fixture_keys(2);
487 let signed_conditions = Conditions::new().allow_kind(Kind::TEXT_NOTE);
488 let mutated_conditions = Conditions::new().allow_kind(Kind::REACTION);
489
490 let token = sign_delegation(&delegator, delegatee.public_key(), &signed_conditions);
491 assert!(!verify_delegation(
492 delegator.public_key(),
493 delegatee.public_key(),
494 &mutated_conditions,
495 &token,
496 ));
497 }
498
499 #[test]
500 fn parse_delegation_round_trips_through_a_built_event() {
501 let delegator = fixture_keys(1);
502 let delegatee = fixture_keys(2);
503 let conditions = Conditions::new().allow_kind(Kind::TEXT_NOTE);
504 let token = sign_delegation(&delegator, delegatee.public_key(), &conditions);
505
506 let event = EventBuilder::text_note("delegated note")
507 .tag(Tag::delegation(
508 *delegator.public_key(),
509 &conditions,
510 &token,
511 ))
512 .sign_with_keys(&delegatee)
513 .unwrap();
514
515 let parsed = parse_delegation(&event.tags).unwrap();
516 assert_eq!(&parsed.delegator, delegator.public_key());
517 assert_eq!(parsed.conditions, conditions);
518
519 let verified = verify_event_delegation(&event).expect("delegation must verify");
520 assert_eq!(&verified.delegator, delegator.public_key());
521 }
522
523 #[test]
524 fn verify_event_delegation_rejects_violated_conditions() {
525 let delegator = fixture_keys(1);
526 let delegatee = fixture_keys(2);
527 let conditions = Conditions::new().allow_kind(Kind::TEXT_NOTE);
529 let token = sign_delegation(&delegator, delegatee.public_key(), &conditions);
530
531 let event = EventBuilder::new(Kind::REACTION, "+")
532 .tag(Tag::delegation(
533 *delegator.public_key(),
534 &conditions,
535 &token,
536 ))
537 .sign_with_keys(&delegatee)
538 .unwrap();
539
540 assert!(matches!(
541 verify_event_delegation(&event),
542 Err(DelegationError::ConditionsViolated)
543 ));
544 }
545
546 #[test]
547 fn verify_event_delegation_detects_token_tampering() {
548 let delegator = fixture_keys(1);
549 let delegatee = fixture_keys(2);
550 let real = Conditions::new().allow_kind(Kind::TEXT_NOTE);
551 let real_token = sign_delegation(&delegator, delegatee.public_key(), &real);
552
553 let mutated = Conditions::new().allow_kind(Kind::REACTION);
556 let event = EventBuilder::new(Kind::REACTION, "+")
557 .tag(Tag::delegation(
558 *delegator.public_key(),
559 &mutated,
560 &real_token,
561 ))
562 .sign_with_keys(&delegatee)
563 .unwrap();
564
565 assert!(matches!(
566 verify_event_delegation(&event),
567 Err(DelegationError::InvalidSignature)
568 ));
569 }
570}