1use alloc::vec::Vec;
27use serde::{Deserialize, Serialize};
28
29use crate::hash::Hash;
30use crate::tagged_hash::csv_tagged_hash;
31
32#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub struct RightId(pub Hash);
38
39#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
45pub struct OwnershipProof {
46 pub proof: Vec<u8>,
48 pub owner: Vec<u8>,
50 pub scheme: Option<crate::signature::SignatureScheme>,
53}
54
55#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
63pub struct Right {
64 pub id: RightId,
66 pub commitment: Hash,
68 pub owner: OwnershipProof,
70 pub salt: Vec<u8>,
73 pub nullifier: Option<Hash>,
79 pub state_root: Option<Hash>,
85 pub execution_proof: Option<Vec<u8>>,
90}
91
92impl Right {
93 pub fn new(commitment: Hash, owner: OwnershipProof, salt: &[u8]) -> Self {
98 let id = {
99 let mut data = Vec::with_capacity(32 + salt.len());
101 data.extend_from_slice(commitment.as_bytes());
102 data.extend_from_slice(salt);
103 let result = csv_tagged_hash("right-id", &data);
104 RightId(Hash::new(result))
105 };
106
107 Self {
108 id,
109 commitment,
110 owner,
111 salt: salt.to_vec(),
112 nullifier: None,
113 state_root: None,
114 execution_proof: None,
115 }
116 }
117
118 pub fn consume(
148 &mut self,
149 secret: Option<&[u8]>,
150 chain_context: Option<&[u8; 32]>,
151 ) -> Option<Hash> {
152 if let Some(secret) = secret {
153 let context_bytes = chain_context.unwrap_or(&[0u8; 32]);
156
157 let mut data =
160 Vec::with_capacity(32 + self.id.0.as_bytes().len() + secret.len() + 32);
161 data.extend_from_slice(self.id.0.as_bytes());
162 data.extend_from_slice(secret);
163 data.extend_from_slice(context_bytes);
164 let nullifier = Hash::new(csv_tagged_hash("csv-nullifier", &data));
165 self.nullifier = Some(nullifier);
166 Some(nullifier)
167 } else {
168 None
171 }
172 }
173
174 pub fn transfer(&self, new_owner: OwnershipProof, transfer_salt: &[u8]) -> Right {
187 let mut new_right = Right::new(self.commitment, new_owner, transfer_salt);
189
190 new_right.state_root = self.state_root;
192
193 new_right.execution_proof = self.execution_proof.clone();
195
196 new_right
197 }
198
199 pub fn verify(&self) -> Result<(), RightError> {
210 let expected_id = {
212 let mut data = Vec::with_capacity(32 + self.salt.len());
213 data.extend_from_slice(self.commitment.as_bytes());
214 data.extend_from_slice(&self.salt);
215 RightId(Hash::new(csv_tagged_hash("right-id", &data)))
216 };
217 if self.id != expected_id {
218 return Err(RightError::InvalidRightId);
219 }
220
221 if let Some(scheme) = self.owner.scheme {
223 let signature = crate::signature::Signature::new(
224 self.owner.proof.clone(),
225 self.owner.owner.clone(),
226 self.commitment.as_bytes().to_vec(),
227 );
228 signature
229 .verify(scheme)
230 .map_err(|_| RightError::InvalidOwnershipProof)?;
231 } else {
232 if self.owner.proof.is_empty() {
236 return Err(RightError::MissingOwnershipProof);
237 }
238 }
239
240 if self.commitment.as_bytes() == &[0u8; 32] {
242 return Err(RightError::InvalidCommitment);
243 }
244
245 if self.nullifier.is_some() {
247 return Err(RightError::AlreadyConsumed);
248 }
249
250 Ok(())
251 }
252
253 pub fn to_canonical_bytes(&self) -> Vec<u8> {
257 let mut out = Vec::new();
258 out.extend_from_slice(self.id.0.as_bytes());
259 out.extend_from_slice(self.commitment.as_bytes());
260 out.extend_from_slice(&(self.owner.proof.len() as u32).to_le_bytes());
261 out.extend_from_slice(&self.owner.proof);
262 out.extend_from_slice(&(self.owner.owner.len() as u32).to_le_bytes());
263 out.extend_from_slice(&self.owner.owner);
264 out.push(match self.owner.scheme {
266 None => 0,
267 Some(crate::signature::SignatureScheme::Secp256k1) => 1,
268 Some(crate::signature::SignatureScheme::Ed25519) => 2,
269 });
270 out.extend_from_slice(&(self.salt.len() as u32).to_le_bytes());
272 out.extend_from_slice(&self.salt);
273 out.push(if self.nullifier.is_some() { 1 } else { 0 });
274 if let Some(nullifier) = &self.nullifier {
275 out.extend_from_slice(nullifier.as_bytes());
276 }
277 out.push(if self.state_root.is_some() { 1 } else { 0 });
278 if let Some(state_root) = &self.state_root {
279 out.extend_from_slice(state_root.as_bytes());
280 }
281 out.extend_from_slice(
282 &(self.execution_proof.as_ref().map_or(0, |p| p.len()) as u32).to_le_bytes(),
283 );
284 if let Some(proof) = &self.execution_proof {
285 out.extend_from_slice(proof);
286 }
287 out
288 }
289
290 pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, RightError> {
295 let mut pos = 0;
296
297 if bytes.len() < 32 {
299 return Err(RightError::InvalidEncoding);
300 }
301 let mut id_bytes = [0u8; 32];
302 id_bytes.copy_from_slice(&bytes[0..32]);
303 pos += 32;
304
305 if bytes.len() < pos + 32 {
307 return Err(RightError::InvalidEncoding);
308 }
309 let mut commitment_bytes = [0u8; 32];
310 commitment_bytes.copy_from_slice(&bytes[pos..pos + 32]);
311 pos += 32;
312
313 if bytes.len() < pos + 4 {
315 return Err(RightError::InvalidEncoding);
316 }
317 let proof_len = u32::from_le_bytes(
318 bytes[pos..pos + 4]
319 .try_into()
320 .map_err(|_| RightError::InvalidEncoding)?,
321 ) as usize;
322 pos += 4;
323
324 if bytes.len() < pos + proof_len {
325 return Err(RightError::InvalidEncoding);
326 }
327 let proof = bytes[pos..pos + proof_len].to_vec();
328 pos += proof_len;
329
330 if bytes.len() < pos + 4 {
332 return Err(RightError::InvalidEncoding);
333 }
334 let owner_len = u32::from_le_bytes(
335 bytes[pos..pos + 4]
336 .try_into()
337 .map_err(|_| RightError::InvalidEncoding)?,
338 ) as usize;
339 pos += 4;
340
341 if bytes.len() < pos + owner_len {
342 return Err(RightError::InvalidEncoding);
343 }
344 let owner_data = bytes[pos..pos + owner_len].to_vec();
345 pos += owner_len;
346
347 if pos >= bytes.len() {
349 return Err(RightError::InvalidEncoding);
350 }
351 let scheme = match bytes[pos] {
352 0 => None,
353 1 => Some(crate::signature::SignatureScheme::Secp256k1),
354 2 => Some(crate::signature::SignatureScheme::Ed25519),
355 _ => return Err(RightError::InvalidEncoding),
356 };
357 pos += 1;
358
359 if bytes.len() < pos + 4 {
361 return Err(RightError::InvalidEncoding);
362 }
363 let salt_len = u32::from_le_bytes(
364 bytes[pos..pos + 4]
365 .try_into()
366 .map_err(|_| RightError::InvalidEncoding)?,
367 ) as usize;
368 pos += 4;
369
370 if bytes.len() < pos + salt_len {
371 return Err(RightError::InvalidEncoding);
372 }
373 let salt = bytes[pos..pos + salt_len].to_vec();
374 pos += salt_len;
375
376 if pos >= bytes.len() {
378 return Err(RightError::InvalidEncoding);
379 }
380 let has_nullifier = bytes[pos] == 1;
381 pos += 1;
382
383 let nullifier = if has_nullifier {
384 if bytes.len() < pos + 32 {
385 return Err(RightError::InvalidEncoding);
386 }
387 let mut nullifier_bytes = [0u8; 32];
388 nullifier_bytes.copy_from_slice(&bytes[pos..pos + 32]);
389 pos += 32;
390 Some(Hash::new(nullifier_bytes))
391 } else {
392 None
393 };
394
395 if pos >= bytes.len() {
397 return Err(RightError::InvalidEncoding);
398 }
399 let has_state_root = bytes[pos] == 1;
400 pos += 1;
401
402 let state_root = if has_state_root {
403 if bytes.len() < pos + 32 {
404 return Err(RightError::InvalidEncoding);
405 }
406 let mut state_root_bytes = [0u8; 32];
407 state_root_bytes.copy_from_slice(&bytes[pos..pos + 32]);
408 pos += 32;
409 Some(Hash::new(state_root_bytes))
410 } else {
411 None
412 };
413
414 if bytes.len() < pos + 4 {
416 return Err(RightError::InvalidEncoding);
417 }
418 let proof_data_len = u32::from_le_bytes(
419 bytes[pos..pos + 4]
420 .try_into()
421 .map_err(|_| RightError::InvalidEncoding)?,
422 ) as usize;
423 pos += 4;
424
425 let execution_proof = if proof_data_len > 0 {
426 if bytes.len() < pos + proof_data_len {
427 return Err(RightError::InvalidEncoding);
428 }
429 Some(bytes[pos..pos + proof_data_len].to_vec())
430 } else {
431 None
432 };
433
434 let id = RightId(Hash::new(id_bytes));
436 let commitment = Hash::new(commitment_bytes);
437 let owner = OwnershipProof {
438 proof,
439 owner: owner_data,
440 scheme,
441 };
442
443 let expected_id = {
445 let mut data = Vec::with_capacity(32 + salt.len());
446 data.extend_from_slice(commitment.as_bytes());
447 data.extend_from_slice(&salt);
448 RightId(Hash::new(csv_tagged_hash("right-id", &data)))
449 };
450 if id != expected_id {
451 return Err(RightError::InvalidRightId);
452 }
453
454 let right = Self {
455 id,
456 commitment,
457 owner,
458 salt,
459 nullifier,
460 state_root,
461 execution_proof,
462 };
463
464 Ok(right)
465 }
466
467 pub fn is_consumed(&self) -> bool {
469 self.nullifier.is_some()
470 }
471
472 pub fn requires_nullifier(&self) -> bool {
477 self.nullifier.is_some()
478 }
479}
480
481#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
483pub enum RightError {
484 #[error("Missing ownership proof")]
486 MissingOwnershipProof,
487 #[error("Invalid ownership proof: signature verification failed")]
489 InvalidOwnershipProof,
490 #[error("Invalid commitment (zero hash)")]
492 InvalidCommitment,
493 #[error("Right has already been consumed")]
495 AlreadyConsumed,
496 #[error("Invalid nullifier")]
498 InvalidNullifier,
499 #[error("Invalid canonical encoding")]
501 InvalidEncoding,
502 #[error("Invalid RightId: does not match H(commitment || salt)")]
504 InvalidRightId,
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 fn test_right() -> Right {
512 Right::new(
513 Hash::new([0xAB; 32]),
514 OwnershipProof {
515 proof: vec![0x01, 0x02, 0x03],
516 owner: vec![0xFF; 32],
517 scheme: None,
518 },
519 &[0x42; 16],
520 )
521 }
522
523 #[test]
524 fn test_right_creation() {
525 let right = test_right();
526 assert_eq!(right.commitment.as_bytes(), &[0xAB; 32]);
527 assert!(right.nullifier.is_none());
528 assert!(right.state_root.is_none());
529 assert!(right.execution_proof.is_none());
530 }
531
532 #[test]
533 fn test_right_id_deterministic() {
534 let r1 = test_right();
535 let r2 = test_right();
536 assert_eq!(r1.id, r2.id);
537 }
538
539 #[test]
540 fn test_right_id_unique_per_salt() {
541 let r1 = Right::new(
542 Hash::new([0xAB; 32]),
543 OwnershipProof {
544 proof: vec![0x01],
545 owner: vec![0xFF; 32],
546 scheme: None,
547 },
548 &[0x42; 16],
549 );
550 let r2 = Right::new(
551 Hash::new([0xAB; 32]),
552 OwnershipProof {
553 proof: vec![0x01],
554 owner: vec![0xFF; 32],
555 scheme: None,
556 },
557 &[0x99; 16],
558 );
559 assert_ne!(r1.id, r2.id);
560 }
561
562 #[test]
563 fn test_right_verify_valid() {
564 let right = test_right();
565 assert!(right.verify().is_ok());
566 }
567
568 #[test]
569 fn test_right_verify_missing_proof() {
570 let mut right = test_right();
571 right.owner.proof = vec![];
572 assert_eq!(right.verify(), Err(RightError::MissingOwnershipProof));
573 }
574
575 #[test]
576 fn test_right_verify_zero_commitment() {
577 let mut right = test_right();
578 right.commitment = Hash::new([0u8; 32]);
579 let mut data = Vec::with_capacity(32 + right.salt.len());
581 data.extend_from_slice(right.commitment.as_bytes());
582 data.extend_from_slice(&right.salt);
583 right.id = RightId(Hash::new(csv_tagged_hash("right-id", &data)));
584 assert_eq!(right.verify(), Err(RightError::InvalidCommitment));
585 }
586
587 #[test]
588 fn test_right_consume_with_nullifier() {
589 let mut right = test_right();
590 let chain_context = [0x01u8; 32]; let nullifier = right.consume(Some(b"secret"), Some(&chain_context));
592 assert!(nullifier.is_some());
593 assert!(right.nullifier.is_some());
594 assert_eq!(right.verify(), Err(RightError::AlreadyConsumed));
595 }
596
597 #[test]
598 fn test_right_consume_without_nullifier() {
599 let mut right = test_right();
600 let result = right.consume(None, None);
601 assert!(result.is_none());
602 assert!(right.nullifier.is_none());
603 assert!(right.verify().is_ok());
605 }
606
607 #[test]
608 fn test_right_canonical_roundtrip() {
609 let right = test_right();
610 let bytes = right.to_canonical_bytes();
611 let decoded = Right::from_canonical_bytes(&bytes).expect("Should decode");
612 assert_eq!(decoded.id, right.id);
613 assert_eq!(decoded.commitment, right.commitment);
614 assert_eq!(decoded.owner, right.owner);
615 assert_eq!(decoded.nullifier, right.nullifier);
616 assert_eq!(decoded.state_root, right.state_root);
617 }
618
619 #[test]
620 fn test_right_canonical_roundtrip_with_nullifier() {
621 let mut right = test_right();
622 let chain_context = [0x01u8; 32];
623 right.consume(Some(b"secret"), Some(&chain_context));
624 let bytes = right.to_canonical_bytes();
625 let decoded = Right::from_canonical_bytes(&bytes).expect("Should decode");
626 assert_eq!(decoded.nullifier, right.nullifier);
627 assert!(decoded.is_consumed());
628 }
629
630 #[test]
631 fn test_right_from_canonical_bytes_invalid() {
632 assert!(Right::from_canonical_bytes(&[]).is_err());
634 assert!(Right::from_canonical_bytes(&[0u8; 16]).is_err());
636 }
637
638 #[test]
639 fn test_right_transfer() {
640 let right = test_right();
641 let new_owner = OwnershipProof {
642 proof: vec![0xAA, 0xBB, 0xCC],
643 owner: vec![0xDD; 32],
644 scheme: None,
645 };
646 let transferred = right.transfer(new_owner.clone(), b"transfer-salt");
647
648 assert_ne!(transferred.id, right.id);
651 assert_eq!(transferred.commitment, right.commitment);
653 assert_eq!(transferred.owner, new_owner);
655 assert!(!transferred.is_consumed());
657 assert!(!right.is_consumed());
659 }
660
661 #[test]
662 fn test_right_transfer_preserves_state_root() {
663 let mut right = test_right();
664 right.state_root = Some(Hash::new([0xCD; 32]));
665
666 let new_owner = OwnershipProof {
667 proof: vec![0x01],
668 owner: vec![0xFF; 32],
669 scheme: None,
670 };
671 let transferred = right.transfer(new_owner, b"transfer");
672
673 assert_eq!(transferred.state_root, right.state_root);
674 }
675
676 #[test]
677 fn test_right_is_consumed() {
678 let mut right = test_right();
679 assert!(!right.is_consumed());
680
681 right.consume(Some(b"secret"), Some(&[0x01u8; 32]));
682 assert!(right.is_consumed());
683 }
684
685 #[test]
686 fn test_right_requires_nullifier() {
687 let right_l1 = test_right(); assert!(!right_l1.requires_nullifier());
689
690 let mut right_l3 = test_right();
691 right_l3.consume(Some(b"secret"), Some(&[0x01u8; 32])); assert!(right_l3.requires_nullifier());
693 }
694
695 #[test]
696 fn test_nullifier_context_binding() {
697 let right1 = Right::new(
700 Hash::new([0xAB; 32]),
701 OwnershipProof {
702 proof: vec![0x01, 0x02, 0x03],
703 owner: vec![0xFF; 32],
704 scheme: None,
705 },
706 &[0x42; 16],
707 );
708 let mut right2 = right1.clone();
709 let mut right3 = right1.clone();
710
711 let ethereum_context = [0x03u8; 32]; let sui_context = [0x01u8; 32]; let n1 = right3.consume(Some(b"same-secret"), Some(ðereum_context));
715 let n2 = right2.consume(Some(b"same-secret"), Some(&sui_context));
716
717 assert_ne!(n1, n2, "Nullifiers must be context-bound");
719
720 let mut right_no_context = right1.clone();
722 let n3 = right_no_context.consume(Some(b"same-secret"), None);
723 assert_ne!(n1, n3, "Context must affect nullifier computation");
724 }
725
726 #[test]
727 fn test_nullifier_determinism() {
728 let mut right1 = Right::new(
730 Hash::new([0xAB; 32]),
731 OwnershipProof {
732 proof: vec![0x01],
733 owner: vec![0xFF; 32],
734 scheme: None,
735 },
736 &[0x42; 16],
737 );
738 let mut right2 = right1.clone();
739 let context = [0x03u8; 32];
740
741 let n1 = right1.consume(Some(b"secret"), Some(&context));
742 let n2 = right2.consume(Some(b"secret"), Some(&context));
743
744 assert_eq!(n1, n2, "Nullifier must be deterministic");
745 }
746
747 #[test]
748 fn test_right_verify_ed25519_signature() {
749 use ed25519_dalek::{Signer, SigningKey, VerifyingKey};
750 use rand::rngs::OsRng;
751
752 let signing_key = SigningKey::generate(&mut OsRng);
753 let verifying_key: VerifyingKey = signing_key.verifying_key();
754 let commitment = Hash::new([0xAB; 32]);
755
756 let signature = signing_key.sign(commitment.as_bytes());
758
759 let right = Right::new(
760 commitment,
761 OwnershipProof {
762 proof: signature.to_bytes().to_vec(),
763 owner: verifying_key.to_bytes().to_vec(),
764 scheme: Some(crate::signature::SignatureScheme::Ed25519),
765 },
766 &[0x42; 16],
767 );
768
769 assert!(right.verify().is_ok());
770 }
771
772 #[test]
773 fn test_right_verify_ed25519_wrong_message_fails() {
774 use ed25519_dalek::{Signer, SigningKey, VerifyingKey};
775 use rand::rngs::OsRng;
776
777 let signing_key = SigningKey::generate(&mut OsRng);
778 let verifying_key: VerifyingKey = signing_key.verifying_key();
779
780 let wrong_message = [0xCD; 32];
782 let signature = signing_key.sign(&wrong_message);
783
784 let right = Right::new(
785 Hash::new([0xAB; 32]), OwnershipProof {
787 proof: signature.to_bytes().to_vec(),
788 owner: verifying_key.to_bytes().to_vec(),
789 scheme: Some(crate::signature::SignatureScheme::Ed25519),
790 },
791 &[0x42; 16],
792 );
793
794 assert_eq!(right.verify(), Err(RightError::InvalidOwnershipProof));
796 }
797
798 #[test]
799 fn test_right_verify_secp256k1_signature() {
800 use secp256k1::{Message, Secp256k1, SecretKey};
801
802 let secp = Secp256k1::new();
803 let secret_key = SecretKey::new(&mut secp256k1::rand::thread_rng());
804 let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key);
805 let commitment = Hash::new([0xAB; 32]);
806
807 let msg = Message::from_digest_slice(commitment.as_bytes()).unwrap();
809 let signature = secp.sign_ecdsa(&msg, &secret_key);
810
811 let right = Right::new(
812 commitment,
813 OwnershipProof {
814 proof: signature.serialize_compact().to_vec(),
815 owner: public_key.serialize().to_vec(),
816 scheme: Some(crate::signature::SignatureScheme::Secp256k1),
817 },
818 &[0x42; 16],
819 );
820
821 assert!(right.verify().is_ok());
822 }
823
824 #[test]
825 fn test_right_verify_tampered_proof_fails() {
826 use ed25519_dalek::{Signer, SigningKey, VerifyingKey};
827 use rand::rngs::OsRng;
828
829 let signing_key = SigningKey::generate(&mut OsRng);
830 let verifying_key: VerifyingKey = signing_key.verifying_key();
831 let commitment = Hash::new([0xAB; 32]);
832
833 let signature = signing_key.sign(commitment.as_bytes());
834 let mut tampered_sig = signature.to_bytes().to_vec();
835 tampered_sig[0] ^= 0xFF; let right = Right::new(
838 commitment,
839 OwnershipProof {
840 proof: tampered_sig,
841 owner: verifying_key.to_bytes().to_vec(),
842 scheme: Some(crate::signature::SignatureScheme::Ed25519),
843 },
844 &[0x42; 16],
845 );
846
847 assert_eq!(right.verify(), Err(RightError::InvalidOwnershipProof));
848 }
849
850 #[test]
851 fn test_right_id_spoofing_fails() {
852 let mut right = test_right();
854 right.id = RightId(Hash::new([0xFF; 32]));
856 assert_eq!(right.verify(), Err(RightError::InvalidRightId));
857 }
858
859 #[test]
860 fn test_from_canonical_bytes_rejects_spoofed_id() {
861 let right = test_right();
862 let mut bytes = right.to_canonical_bytes();
863
864 for byte in &mut bytes[0..32] {
866 *byte ^= 0xFF;
867 }
868
869 assert_eq!(
870 Right::from_canonical_bytes(&bytes),
871 Err(RightError::InvalidRightId)
872 );
873 }
874}