1use alloy_primitives::{Address, B256, FixedBytes, Keccak256, Signature, address, b256, hex};
7use alloy_signer::SignerSync;
8use alloy_signer_local::PrivateKeySigner;
9use bytes::{Bytes, BytesMut};
10use std::fmt;
11use std::marker::PhantomData;
12
13use crate::PrimitivesError;
14use crate::bmt::DEFAULT_BODY_SIZE;
15use crate::cache::OnceCache;
16use crate::chunk::error::{self, ChunkError};
17use crate::error::Result;
18
19use super::bmt_body::BmtBody;
20use super::content::ContentChunk;
21use super::traits::{BmtChunk, Chunk, ChunkAddress, ChunkHeader, ChunkMetadata};
22
23const ID_SIZE: usize = std::mem::size_of::<B256>();
25const SIGNATURE_SIZE: usize = 65;
26const MIN_SOC_FIELDS_SIZE: usize = ID_SIZE + SIGNATURE_SIZE;
27
28const DISPERSED_REPLICA_OWNER: Address = address!("0xdc5b20847f43d67928f49cd4f85d696b5a7617b5");
30const DISPERSED_REPLICA_OWNER_PK: B256 =
32 b256!("0x0100000000000000000000000000000000000000000000000000000000000000");
33
34#[derive(Debug, Clone)]
39pub struct SingleOwnerChunk<const BODY_SIZE: usize = DEFAULT_BODY_SIZE> {
40 header: SingleOwnerChunkHeader,
42 body: BmtBody<BODY_SIZE>,
44 chunk_address_cache: OnceCache<ChunkAddress>,
46 owner_cache: OnceCache<Address>,
48}
49
50#[derive(Debug, Clone)]
52pub struct SingleOwnerChunkMetadata {
53 id: B256,
55 signature: Signature,
57}
58
59impl SingleOwnerChunkMetadata {
60 pub const fn new(id: B256, signature: Signature) -> Self {
62 Self { id, signature }
63 }
64
65 pub const fn id(&self) -> B256 {
67 self.id
68 }
69
70 pub const fn signature(&self) -> &Signature {
72 &self.signature
73 }
74}
75
76impl ChunkMetadata for SingleOwnerChunkMetadata {
77 fn bytes(&self) -> Bytes {
78 let mut bytes = BytesMut::with_capacity(ID_SIZE + SIGNATURE_SIZE);
79 bytes.extend_from_slice(self.id.as_ref());
80 bytes.extend_from_slice(&self.signature.as_bytes());
81 bytes.freeze()
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct SingleOwnerChunkHeader {
88 metadata: SingleOwnerChunkMetadata,
89}
90
91impl SingleOwnerChunkHeader {
92 pub const fn new(metadata: SingleOwnerChunkMetadata) -> Self {
94 Self { metadata }
95 }
96}
97
98impl ChunkHeader for SingleOwnerChunkHeader {
99 type Metadata = SingleOwnerChunkMetadata;
100
101 fn id(&self) -> u8 {
102 1
103 }
104
105 fn version(&self) -> u8 {
106 1
107 }
108
109 fn metadata(&self) -> &Self::Metadata {
110 &self.metadata
111 }
112
113 fn bytes(&self) -> Bytes {
114 self.metadata.bytes()
115 }
116}
117
118impl<const BODY_SIZE: usize> SingleOwnerChunk<BODY_SIZE> {
119 #[must_use = "this returns a new chunk without modifying the input"]
134 pub fn new(id: B256, data: impl Into<Bytes>, signer: &impl SignerSync) -> Result<Self> {
135 SingleOwnerChunkBuilderImpl::<BODY_SIZE, Initial>::default()
136 .auto_from_data(data)?
137 .with_id(id)
138 .with_signer(signer)?
139 .build()
140 }
141
142 #[must_use = "this returns a new chunk without modifying the input"]
157 pub fn with_signature(id: B256, signature: Signature, data: impl Into<Bytes>) -> Result<Self> {
158 SingleOwnerChunkBuilderImpl::<BODY_SIZE, Initial>::default()
159 .auto_from_data(data)?
160 .with_id(id)
161 .with_signature(signature)?
162 .build()
163 }
164
165 #[must_use = "this returns a new chunk without modifying the input"]
171 pub fn new_dispersed_replica(mined_byte: u8, body: BmtBody<BODY_SIZE>) -> Result<Self> {
172 SingleOwnerChunkBuilderImpl::<BODY_SIZE, Initial>::default()
173 .with_body(body)
174 .dispersed_replica(mined_byte)?
175 .build()
176 }
177
178 #[must_use]
189 pub const fn from_parts(id: B256, signature: Signature, body: BmtBody<BODY_SIZE>) -> Self {
190 let metadata = SingleOwnerChunkMetadata::new(id, signature);
191 let header = SingleOwnerChunkHeader::new(metadata);
192
193 Self {
194 header,
195 body,
196 chunk_address_cache: OnceCache::new(),
197 owner_cache: OnceCache::new(),
198 }
199 }
200
201 #[must_use]
206 pub fn from_parts_with_caches(
207 id: B256,
208 signature: Signature,
209 body: BmtBody<BODY_SIZE>,
210 address: ChunkAddress,
211 owner: Address,
212 ) -> Self {
213 let metadata = SingleOwnerChunkMetadata::new(id, signature);
214 let header = SingleOwnerChunkHeader::new(metadata);
215
216 Self {
217 header,
218 body,
219 chunk_address_cache: OnceCache::with_value(address),
220 owner_cache: OnceCache::with_value(owner),
221 }
222 }
223
224 pub fn owner(&self) -> error::Result<Address> {
239 if let Some(addr) = self.owner_cache.get() {
241 return Ok(*addr);
242 }
243
244 let addr = self.calculate_owner()?;
246 let _ = self.owner_cache.try_set(addr);
248 Ok(addr)
249 }
250
251 fn calculate_owner(&self) -> error::Result<Address> {
253 let hash = Self::to_sign(&self.header.metadata.id, &self.body);
255
256 self.signature()
258 .recover_address_from_msg(hash)
259 .map_err(Into::into)
260 }
261
262 fn to_sign(id: &B256, body: &BmtBody<BODY_SIZE>) -> B256 {
276 let mut hasher = Keccak256::new();
277 hasher.update(id);
278 hasher.update(body.hash());
279 hasher.finalize()
280 }
281
282 fn is_valid_replica(&self) -> bool {
284 self.id()[1..] == self.body.hash().as_slice()[1..]
285 }
286
287 pub const fn id(&self) -> B256 {
289 self.header.metadata.id
290 }
291
292 pub const fn signature(&self) -> &Signature {
294 &self.header.metadata.signature
295 }
296
297 pub const fn inner_body(&self) -> &BmtBody<BODY_SIZE> {
305 &self.body
306 }
307
308 #[must_use]
316 pub fn unwrap_cac(&self) -> ContentChunk<BODY_SIZE> {
317 ContentChunk::from_body(self.body.clone())
318 }
319}
320
321impl<const BODY_SIZE: usize> Chunk for SingleOwnerChunk<BODY_SIZE> {
322 type Header = SingleOwnerChunkHeader;
323
324 fn address(&self) -> &ChunkAddress {
325 self.chunk_address_cache.get_or_compute(|| {
326 let owner = self.owner().unwrap_or(Address::ZERO);
330 let mut hasher = Keccak256::new();
331 hasher.update(self.id());
332 hasher.update(owner);
333
334 hasher.finalize().into()
335 })
336 }
337
338 fn data(&self) -> &Bytes {
339 self.body.data()
340 }
341
342 fn size(&self) -> usize {
343 self.header().bytes().len() + self.body.size()
344 }
345
346 fn header(&self) -> &Self::Header {
347 &self.header
348 }
349
350 fn verify(&self, expected: &ChunkAddress) -> Result<()> {
351 let actual = self.address();
352
353 let owner = self.owner()?;
356 if owner == DISPERSED_REPLICA_OWNER && !self.is_valid_replica() {
357 return Err(error::ChunkError::invalid_format("invalid dispersed replica").into());
358 }
359
360 if actual != expected {
361 return Err(error::ChunkError::verification_failed(*expected, *actual).into());
362 }
363 Ok(())
364 }
365}
366
367impl<const BODY_SIZE: usize> BmtChunk for SingleOwnerChunk<BODY_SIZE> {
368 fn span(&self) -> u64 {
369 self.body.span()
370 }
371}
372
373impl<const BODY_SIZE: usize> From<SingleOwnerChunk<BODY_SIZE>> for Bytes {
374 fn from(chunk: SingleOwnerChunk<BODY_SIZE>) -> Self {
375 let mut bytes = BytesMut::with_capacity(chunk.size());
376 bytes.extend_from_slice(chunk.header().bytes().as_ref());
377 bytes.extend_from_slice(&Self::from(chunk.body));
378 bytes.freeze()
379 }
380}
381
382impl<const BODY_SIZE: usize> TryFrom<Bytes> for SingleOwnerChunk<BODY_SIZE> {
383 type Error = PrimitivesError;
384
385 fn try_from(bytes: Bytes) -> Result<Self> {
386 if bytes.len() < MIN_SOC_FIELDS_SIZE {
387 return Err(ChunkError::invalid_size(
388 "insufficient data for single-owner chunk",
389 MIN_SOC_FIELDS_SIZE,
390 bytes.len(),
391 )
392 .into());
393 }
394
395 let id_slice = &bytes.slice(0..ID_SIZE);
397 let mut id = FixedBytes::<32>::default();
398 id.copy_from_slice(id_slice);
399
400 let sig_slice = &bytes.slice(ID_SIZE..ID_SIZE + SIGNATURE_SIZE);
402 let signature = Signature::from_raw(sig_slice).map_err(ChunkError::from)?;
403
404 let body_bytes = bytes.slice(ID_SIZE + SIGNATURE_SIZE..);
406 let body = BmtBody::try_from(body_bytes)?;
407
408 let metadata = SingleOwnerChunkMetadata::new(id, signature);
410 let header = SingleOwnerChunkHeader::new(metadata);
411
412 Ok(Self {
413 header,
414 body,
415 chunk_address_cache: OnceCache::new(),
416 owner_cache: OnceCache::new(),
417 })
418 }
419}
420
421impl<const BODY_SIZE: usize> TryFrom<&[u8]> for SingleOwnerChunk<BODY_SIZE> {
422 type Error = PrimitivesError;
423
424 fn try_from(bytes: &[u8]) -> Result<Self> {
425 Self::try_from(Bytes::copy_from_slice(bytes))
426 }
427}
428
429impl<const BODY_SIZE: usize> fmt::Display for SingleOwnerChunk<BODY_SIZE> {
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 let owner_str = self.owner().map_or_else(
432 |_| "invalid".to_string(),
433 |addr| hex::encode(addr.as_slice()),
434 );
435 write!(
436 f,
437 "SingleOwnerChunk[id={}, owner={}]",
438 hex::encode(&self.id()[..8]),
439 owner_str
440 )
441 }
442}
443
444impl<const BODY_SIZE: usize> PartialEq for SingleOwnerChunk<BODY_SIZE> {
445 fn eq(&self, other: &Self) -> bool {
446 match (self.owner(), other.owner()) {
448 (Ok(a), Ok(b)) => self.id() == other.id() && a == b,
449 _ => false,
450 }
451 }
452}
453
454impl<const BODY_SIZE: usize> Eq for SingleOwnerChunk<BODY_SIZE> {}
455
456impl<const BODY_SIZE: usize> super::chunk_type::ChunkType for SingleOwnerChunk<BODY_SIZE> {
457 const TYPE_ID: super::type_id::ChunkTypeId = super::type_id::ChunkTypeId::SINGLE_OWNER;
458 const TYPE_NAME: &'static str = "single_owner";
459}
460
461trait BuilderState {}
463
464#[derive(Debug, Default)]
465struct Initial;
466impl BuilderState for Initial {}
467
468#[derive(Debug)]
469struct WithData;
470impl BuilderState for WithData {}
471
472#[derive(Debug)]
473struct WithId;
474impl BuilderState for WithId {}
475
476#[derive(Debug)]
477struct ReadyToBuild;
478impl BuilderState for ReadyToBuild {}
479
480#[derive(Debug)]
482struct SingleOwnerChunkBuilderImpl<const BODY_SIZE: usize, S: BuilderState = Initial> {
483 body: Option<BmtBody<BODY_SIZE>>,
485 id: Option<B256>,
487 signature: Option<Signature>,
489 _state: PhantomData<S>,
491}
492
493impl<const BODY_SIZE: usize> Default for SingleOwnerChunkBuilderImpl<BODY_SIZE, Initial> {
494 fn default() -> Self {
495 Self {
496 body: None,
497 id: None,
498 signature: None,
499 _state: PhantomData,
500 }
501 }
502}
503
504impl<const BODY_SIZE: usize> SingleOwnerChunkBuilderImpl<BODY_SIZE, Initial> {
505 fn auto_from_data(
507 mut self,
508 data: impl Into<Bytes>,
509 ) -> Result<SingleOwnerChunkBuilderImpl<BODY_SIZE, WithData>> {
510 let body = BmtBody::<BODY_SIZE>::builder()
511 .auto_from_data(data)?
512 .build()?;
513 self.body = Some(body);
514
515 Ok(SingleOwnerChunkBuilderImpl {
516 body: self.body,
517 id: self.id,
518 signature: self.signature,
519 _state: PhantomData,
520 })
521 }
522
523 fn with_body(
525 mut self,
526 body: BmtBody<BODY_SIZE>,
527 ) -> SingleOwnerChunkBuilderImpl<BODY_SIZE, WithData> {
528 self.body = Some(body);
529
530 SingleOwnerChunkBuilderImpl {
531 body: self.body,
532 id: self.id,
533 signature: self.signature,
534 _state: PhantomData,
535 }
536 }
537}
538
539impl<const BODY_SIZE: usize> SingleOwnerChunkBuilderImpl<BODY_SIZE, WithData> {
540 fn with_id(mut self, id: B256) -> SingleOwnerChunkBuilderImpl<BODY_SIZE, WithId> {
542 self.id = Some(id);
543
544 SingleOwnerChunkBuilderImpl {
545 body: self.body,
546 id: self.id,
547 signature: self.signature,
548 _state: PhantomData,
549 }
550 }
551
552 fn dispersed_replica(
554 self,
555 first_byte: u8,
556 ) -> Result<SingleOwnerChunkBuilderImpl<BODY_SIZE, ReadyToBuild>> {
557 let body_hash = self.body.as_ref().unwrap().hash();
558 let mut id = B256::default();
559 id[0] = first_byte;
560 id[1..].copy_from_slice(&body_hash.as_slice()[1..]);
561
562 let signer = PrivateKeySigner::from_slice(DISPERSED_REPLICA_OWNER_PK.as_slice()).unwrap();
563
564 self.with_id(id).with_signer(&signer)
565 }
566}
567
568impl<const BODY_SIZE: usize> SingleOwnerChunkBuilderImpl<BODY_SIZE, WithId> {
569 fn with_signer(
571 self,
572 signer: &impl SignerSync,
573 ) -> Result<SingleOwnerChunkBuilderImpl<BODY_SIZE, ReadyToBuild>> {
574 let body = self.body.as_ref().unwrap();
576 let id = self.id.as_ref().unwrap();
577
578 let hash = SingleOwnerChunk::<BODY_SIZE>::to_sign(id, body);
580
581 let signature = signer
583 .sign_message_sync(hash.as_ref())
584 .map_err(ChunkError::from)?;
585
586 self.with_signature(signature)
587 }
588
589 fn with_signature(
591 mut self,
592 signature: Signature,
593 ) -> Result<SingleOwnerChunkBuilderImpl<BODY_SIZE, ReadyToBuild>> {
594 self.signature = Some(signature);
595
596 Ok(SingleOwnerChunkBuilderImpl {
597 body: self.body,
598 id: self.id,
599 signature: self.signature,
600 _state: PhantomData,
601 })
602 }
603}
604
605impl<const BODY_SIZE: usize> SingleOwnerChunkBuilderImpl<BODY_SIZE, ReadyToBuild> {
606 fn build(self) -> Result<SingleOwnerChunk<BODY_SIZE>> {
608 let body = self.body.unwrap();
609 let id = self.id.unwrap();
610 let signature = self.signature.unwrap();
611
612 Ok(SingleOwnerChunk::from_parts(id, signature, body))
613 }
614}
615
616#[cfg(any(test, feature = "arbitrary"))]
617impl<'a, const BODY_SIZE: usize> arbitrary::Arbitrary<'a> for SingleOwnerChunk<BODY_SIZE> {
618 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
619 let id = B256::arbitrary(u)?;
620 let body = BmtBody::<BODY_SIZE>::arbitrary(u)?;
621 let signer = alloy_signer_local::PrivateKeySigner::random();
622
623 Ok(SingleOwnerChunkBuilderImpl::<BODY_SIZE, Initial>::default()
624 .with_body(body)
625 .with_id(id)
626 .with_signer(&signer)
627 .unwrap()
628 .build()
629 .unwrap())
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use crate::DEFAULT_BODY_SIZE;
636
637 use super::*;
638 use alloy_primitives::hex;
639 use proptest::prelude::*;
640 use proptest_arbitrary_interop::arb;
641
642 type DefaultSingleOwnerChunk = SingleOwnerChunk<DEFAULT_BODY_SIZE>;
643
644 fn get_test_wallet() -> PrivateKeySigner {
645 let pk = hex!("2c7536e3605d9c16a7a3d7b1898e529396a65c23a3bcbd4012a11cf2731b0fbc");
647 PrivateKeySigner::from_slice(&pk).unwrap()
648 }
649
650 fn chunk_strategy() -> impl Strategy<Value = DefaultSingleOwnerChunk> {
652 arb::<DefaultSingleOwnerChunk>()
653 }
654
655 proptest! {
656 #[test]
657 fn test_chunk_properties(chunk in chunk_strategy()) {
658 prop_assert!(chunk.size() >= MIN_SOC_FIELDS_SIZE);
659
660 let bytes: Bytes = chunk.clone().into();
662 let decoded = DefaultSingleOwnerChunk::try_from(bytes.as_ref()).unwrap();
663 prop_assert_eq!(chunk.id(), decoded.id());
664 prop_assert_eq!(chunk.signature(), decoded.signature());
665 prop_assert_eq!(chunk.data(), decoded.data());
666 prop_assert_eq!(chunk.owner().unwrap(), decoded.owner().unwrap());
667
668 let address = chunk.address();
670 prop_assert!(chunk.verify(address).is_ok());
671 }
672
673 #[test]
674 fn test_dispersed_replica_properties(first_byte in any::<u8>(), data in proptest::collection::vec(any::<u8>(), 1..DEFAULT_BODY_SIZE)) {
675 let chunk = DefaultSingleOwnerChunk::new_dispersed_replica(first_byte, BmtBody::<DEFAULT_BODY_SIZE>::builder().auto_from_data(data).unwrap().build().unwrap()).unwrap();
676
677 prop_assert!(chunk.is_valid_replica());
679 prop_assert_eq!(chunk.id()[0], first_byte);
680 prop_assert_eq!(chunk.owner().unwrap(), DISPERSED_REPLICA_OWNER);
681
682 prop_assert!(chunk.verify(chunk.address()).is_ok());
684 }
685
686 #[test]
687 fn test_chunk_creation(id in arb::<B256>(), data in proptest::collection::vec(any::<u8>(), 1..DEFAULT_BODY_SIZE)) {
688 let wallet = get_test_wallet();
689
690 let chunk = SingleOwnerChunkBuilderImpl::<DEFAULT_BODY_SIZE, Initial>::default()
692 .with_body(
693 BmtBody::<DEFAULT_BODY_SIZE>::builder()
694 .auto_from_data(data.clone())
695 .unwrap()
696 .build()
697 .unwrap(),
698 )
699 .with_id(id)
700 .with_signer(&wallet)
701 .unwrap()
702 .build()
703 .unwrap();
704
705 prop_assert_eq!(chunk.id(), id);
706 prop_assert_eq!(chunk.data(), &data);
707 prop_assert!(!chunk.owner().unwrap().is_zero());
708 }
709
710 #[test]
711 fn test_dispersed_replica_mismatched_address(first_byte in any::<u8>(), data in proptest::collection::vec(any::<u8>(), 1..DEFAULT_BODY_SIZE)) {
712 let chunk = SingleOwnerChunkBuilderImpl::<DEFAULT_BODY_SIZE, Initial>::default().with_body(
713 BmtBody::<DEFAULT_BODY_SIZE>::builder()
714 .auto_from_data(data)
715 .unwrap()
716 .build()
717 .unwrap(),
718 ).dispersed_replica(first_byte).unwrap().build().unwrap();
719 let replica_address = *chunk.address();
720 let bytes: Bytes = chunk.into();
722
723 let mut modified_bytes = bytes.to_vec();
726 modified_bytes[1..ID_SIZE].copy_from_slice(&[0x01; 31]);
727
728 let modified_chunk = DefaultSingleOwnerChunk::try_from(modified_bytes.as_slice()).unwrap();
729 prop_assert!(!modified_chunk.is_valid_replica());
730 prop_assert!(modified_chunk.verify(&replica_address).is_err());
731 }
732
733 #[test]
734 fn test_chunk_invalid_signature(id in arb::<B256>(), data in proptest::collection::vec(any::<u8>(), 1..DEFAULT_BODY_SIZE)) {
735 let wallet = get_test_wallet();
736
737 let chunk = DefaultSingleOwnerChunk::new(id, data, &wallet).unwrap();
739 let original_address = *chunk.address();
740
741 let bytes: Bytes = chunk.into();
743
744 let mut modified_bytes = bytes.to_vec();
747 modified_bytes[ID_SIZE..ID_SIZE + 65].copy_from_slice(&[0xff; 65]);
748
749 let modified_chunk = DefaultSingleOwnerChunk::try_from(modified_bytes.as_slice()).unwrap();
750 prop_assert!(modified_chunk.verify(&original_address).is_err());
751 prop_assert!(modified_chunk.owner().is_err());
753 }
754
755 #[test]
756 fn test_chunk_too_small(data in proptest::collection::vec(any::<u8>(), 1..MIN_SOC_FIELDS_SIZE)) {
757 let chunk = DefaultSingleOwnerChunk::try_from(data.as_slice());
759 prop_assert!(chunk.is_err());
760 }
761 }
762
763 #[test]
764 fn test_new() {
765 let id = B256::ZERO;
766 let data = b"foo".to_vec();
767 let wallet = get_test_wallet();
768
769 let chunk = DefaultSingleOwnerChunk::new(id, data.clone(), &wallet).unwrap();
770
771 assert_eq!(chunk.id(), id);
772 assert_eq!(chunk.data(), &data);
773 }
774
775 #[test]
776 fn test_new_signed() {
777 let id = B256::ZERO;
778 let data = b"foo".to_vec();
779
780 let sig = hex!(
782 "5acd384febc133b7b245e5ddc62d82d2cded9182d2716126cd8844509af65a053deb418208027f548e3e88343af6f84a8772fb3cebc0a1833a0ea7ec0c1348311b"
783 );
784 let signature = Signature::try_from(sig.as_slice()).unwrap();
785
786 let chunk = SingleOwnerChunkBuilderImpl::<DEFAULT_BODY_SIZE, Initial>::default()
787 .auto_from_data(data.clone())
788 .unwrap()
789 .with_id(id)
790 .with_signature(signature)
791 .unwrap()
792 .build()
793 .unwrap();
794
795 assert_eq!(chunk.id(), id);
796 assert_eq!(chunk.data(), &data);
797 assert_eq!(chunk.signature().as_bytes(), sig);
798
799 let expected_owner = address!("8d3766440f0d7b949a5e32995d09619a7f86e632");
801 assert_eq!(chunk.owner().unwrap(), expected_owner);
802 }
803
804 fn get_test_chunk_data() -> Vec<u8> {
805 hex!(
806 "000000000000000000000000000000000000000000000000000000000000000\
807 05acd384febc133b7b245e5ddc62d82d2cded9182d2716126cd8844509af65a05\
808 3deb418208027f548e3e88343af6f84a8772fb3cebc0a1833a0ea7ec0c134831\
809 1b0300000000000000666f6f"
810 )
811 .to_vec()
812 }
813
814 #[test]
815 fn test_chunk_address() {
816 let chunk = DefaultSingleOwnerChunk::try_from(get_test_chunk_data().as_slice()).unwrap();
818
819 let expected_owner = address!("8d3766440f0d7b949a5e32995d09619a7f86e632");
821 assert_eq!(chunk.owner().unwrap(), expected_owner);
822
823 let expected_address =
825 b256!("9d453ebb73b2fedaaf44ceddcf7a0aa37f3e3d6453fea5841c31f0ea6d61dc85");
826 assert_eq!(chunk.address().as_ref(), expected_address);
827 }
828
829 #[test]
830 fn test_invalid_dispersed_replica() -> Result<()> {
831 let test_data = b"test data".to_vec();
832 let dispersed_replica_wallet =
833 PrivateKeySigner::from_slice(DISPERSED_REPLICA_OWNER_PK.as_slice()).unwrap();
834
835 let chunk = SingleOwnerChunkBuilderImpl::<DEFAULT_BODY_SIZE, Initial>::default()
836 .with_body(
837 BmtBody::<DEFAULT_BODY_SIZE>::builder()
838 .auto_from_data(test_data)?
839 .build()?,
840 )
841 .with_id(B256::ZERO)
842 .with_signer(&dispersed_replica_wallet)?
843 .build()?;
844 let replica_address = chunk.address();
845
846 assert!(!chunk.is_valid_replica());
847 assert!(matches!(
848 chunk.verify(replica_address),
849 Err(PrimitivesError::Chunk(ChunkError::InvalidFormat { .. }))
850 ));
851
852 Ok(())
853 }
854}