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