sui_rpc/proto/sui/rpc/v2alpha/
proof_service.rs1use super::*;
16use crate::proto::TryFromProtoError;
17
18use sui_sdk_types::Digest;
19use sui_sdk_types::ObjectReference;
20use sui_sdk_types::merkle::MerkleNonInclusionProof as SdkMerkleNonInclusionProof;
21use sui_sdk_types::merkle::Node;
22use sui_sdk_types::proof::OcsInclusionProof as SdkOcsInclusionProof;
23use sui_sdk_types::proof::OcsNonInclusionProof as SdkOcsNonInclusionProof;
24
25const DIGEST_LEN: usize = 32;
26
27fn try_digest_from_bytes(
30 bytes: &prost::bytes::Bytes,
31 field: &'static str,
32) -> Result<Digest, TryFromProtoError> {
33 let len = bytes.len();
34 let arr: [u8; DIGEST_LEN] = bytes.as_ref().try_into().map_err(|_| {
35 TryFromProtoError::invalid(field, format!("expected {DIGEST_LEN} bytes, got {len}"))
36 })?;
37 Ok(Digest::new(arr))
38}
39
40impl From<Node> for MerkleNode {
41 fn from(value: Node) -> Self {
42 let node = match value {
43 Node::Empty => merkle_node::Node::Empty(()),
44 Node::Digest(digest) => {
45 merkle_node::Node::Digest(prost::bytes::Bytes::copy_from_slice(&digest))
46 }
47 };
48 Self { node: Some(node) }
49 }
50}
51
52impl From<&Node> for MerkleNode {
53 fn from(value: &Node) -> Self {
54 (*value).into()
55 }
56}
57
58impl TryFrom<&MerkleNode> for Node {
59 type Error = TryFromProtoError;
60
61 fn try_from(value: &MerkleNode) -> Result<Self, Self::Error> {
62 match value.node.as_ref() {
63 Some(merkle_node::Node::Empty(_)) => Ok(Node::Empty),
64 Some(merkle_node::Node::Digest(bytes)) => {
65 let len = bytes.len();
66 let digest: [u8; DIGEST_LEN] = bytes.as_ref().try_into().map_err(|_| {
67 TryFromProtoError::invalid(
68 MerkleNode::DIGEST_FIELD.name,
69 format!("expected {DIGEST_LEN} bytes, got {len}"),
70 )
71 })?;
72 Ok(Node::Digest(digest))
73 }
74 None => Err(TryFromProtoError::missing("node")),
75 }
76 }
77}
78
79impl From<&sui_sdk_types::merkle::MerkleProof> for MerkleProof {
80 fn from(value: &sui_sdk_types::merkle::MerkleProof) -> Self {
81 Self {
82 path: value.path().iter().copied().map(MerkleNode::from).collect(),
83 }
84 }
85}
86
87impl From<sui_sdk_types::merkle::MerkleProof> for MerkleProof {
88 fn from(value: sui_sdk_types::merkle::MerkleProof) -> Self {
89 (&value).into()
90 }
91}
92
93impl TryFrom<&MerkleProof> for sui_sdk_types::merkle::MerkleProof {
94 type Error = TryFromProtoError;
95
96 fn try_from(value: &MerkleProof) -> Result<Self, Self::Error> {
97 let path = value
98 .path
99 .iter()
100 .enumerate()
101 .map(|(i, node)| {
102 Node::try_from(node).map_err(|e| e.nested_at(MerkleProof::PATH_FIELD.name, i))
103 })
104 .collect::<Result<Vec<_>, _>>()?;
105 Ok(sui_sdk_types::merkle::MerkleProof::new(path))
106 }
107}
108
109impl From<&(ObjectReference, sui_sdk_types::merkle::MerkleProof)> for MerkleNeighbourLeaf {
110 fn from(value: &(ObjectReference, sui_sdk_types::merkle::MerkleProof)) -> Self {
111 let (leaf, proof) = value;
112 Self {
113 leaf: Some(leaf.clone().into()),
114 merkle_proof: Some(proof.into()),
115 }
116 }
117}
118
119impl TryFrom<&MerkleNeighbourLeaf> for (ObjectReference, sui_sdk_types::merkle::MerkleProof) {
120 type Error = TryFromProtoError;
121
122 fn try_from(value: &MerkleNeighbourLeaf) -> Result<Self, Self::Error> {
123 let leaf_proto = value
124 .leaf
125 .as_ref()
126 .ok_or_else(|| TryFromProtoError::missing(MerkleNeighbourLeaf::LEAF_FIELD.name))?;
127 let leaf: ObjectReference = leaf_proto
128 .try_into()
129 .map_err(|e: TryFromProtoError| e.nested(MerkleNeighbourLeaf::LEAF_FIELD.name))?;
130 let proof_proto = value.merkle_proof.as_ref().ok_or_else(|| {
131 TryFromProtoError::missing(MerkleNeighbourLeaf::MERKLE_PROOF_FIELD.name)
132 })?;
133 let proof = sui_sdk_types::merkle::MerkleProof::try_from(proof_proto)
134 .map_err(|e| e.nested(MerkleNeighbourLeaf::MERKLE_PROOF_FIELD.name))?;
135 Ok((leaf, proof))
136 }
137}
138
139impl From<&SdkMerkleNonInclusionProof<ObjectReference>> for MerkleNonInclusionProof {
140 fn from(value: &SdkMerkleNonInclusionProof<ObjectReference>) -> Self {
141 Self {
142 index: Some(value.index() as u64),
143 left_leaf: value.left_leaf().map(MerkleNeighbourLeaf::from),
144 right_leaf: value.right_leaf().map(MerkleNeighbourLeaf::from),
145 }
146 }
147}
148
149impl From<SdkMerkleNonInclusionProof<ObjectReference>> for MerkleNonInclusionProof {
150 fn from(value: SdkMerkleNonInclusionProof<ObjectReference>) -> Self {
151 (&value).into()
152 }
153}
154
155impl TryFrom<&MerkleNonInclusionProof> for SdkMerkleNonInclusionProof<ObjectReference> {
156 type Error = TryFromProtoError;
157
158 fn try_from(value: &MerkleNonInclusionProof) -> Result<Self, Self::Error> {
159 let index_u64 = value
160 .index
161 .ok_or_else(|| TryFromProtoError::missing(MerkleNonInclusionProof::INDEX_FIELD.name))?;
162 let index = usize::try_from(index_u64).map_err(|e| {
163 TryFromProtoError::invalid(MerkleNonInclusionProof::INDEX_FIELD.name, e)
164 })?;
165 let left_leaf = value
166 .left_leaf
167 .as_ref()
168 .map(|l| {
169 <(ObjectReference, sui_sdk_types::merkle::MerkleProof)>::try_from(l)
170 .map_err(|e| e.nested(MerkleNonInclusionProof::LEFT_LEAF_FIELD.name))
171 })
172 .transpose()?;
173 let right_leaf = value
174 .right_leaf
175 .as_ref()
176 .map(|l| {
177 <(ObjectReference, sui_sdk_types::merkle::MerkleProof)>::try_from(l)
178 .map_err(|e| e.nested(MerkleNonInclusionProof::RIGHT_LEAF_FIELD.name))
179 })
180 .transpose()?;
181 Ok(SdkMerkleNonInclusionProof::new(
182 index, left_leaf, right_leaf,
183 ))
184 }
185}
186
187impl From<&SdkOcsInclusionProof> for OcsInclusionProof {
188 fn from(value: &SdkOcsInclusionProof) -> Self {
189 Self {
190 object_ref: None,
198 merkle_proof: Some((&value.merkle_proof).into()),
199 leaf_index: Some(value.leaf_index),
200 tree_root: Some(prost::bytes::Bytes::copy_from_slice(
201 value.tree_root.inner(),
202 )),
203 object_data: None,
204 }
205 }
206}
207
208impl From<SdkOcsInclusionProof> for OcsInclusionProof {
209 fn from(value: SdkOcsInclusionProof) -> Self {
210 (&value).into()
211 }
212}
213
214impl TryFrom<&OcsInclusionProof> for SdkOcsInclusionProof {
215 type Error = TryFromProtoError;
216
217 fn try_from(value: &OcsInclusionProof) -> Result<Self, Self::Error> {
218 let merkle_proof_proto = value.merkle_proof.as_ref().ok_or_else(|| {
219 TryFromProtoError::missing(OcsInclusionProof::MERKLE_PROOF_FIELD.name)
220 })?;
221 let merkle_proof = sui_sdk_types::merkle::MerkleProof::try_from(merkle_proof_proto)
222 .map_err(|e| e.nested(OcsInclusionProof::MERKLE_PROOF_FIELD.name))?;
223 let leaf_index = value
224 .leaf_index
225 .ok_or_else(|| TryFromProtoError::missing(OcsInclusionProof::LEAF_INDEX_FIELD.name))?;
226 let tree_root_bytes = value
227 .tree_root
228 .as_ref()
229 .ok_or_else(|| TryFromProtoError::missing(OcsInclusionProof::TREE_ROOT_FIELD.name))?;
230 let tree_root =
231 try_digest_from_bytes(tree_root_bytes, OcsInclusionProof::TREE_ROOT_FIELD.name)?;
232 Ok(SdkOcsInclusionProof {
233 merkle_proof,
234 leaf_index,
235 tree_root,
236 })
237 }
238}
239
240impl From<&SdkOcsNonInclusionProof> for OcsNonInclusionProof {
241 fn from(value: &SdkOcsNonInclusionProof) -> Self {
242 Self {
243 non_inclusion_proof: Some((&value.non_inclusion_proof).into()),
244 tree_root: Some(prost::bytes::Bytes::copy_from_slice(
245 value.tree_root.inner(),
246 )),
247 }
248 }
249}
250
251impl From<SdkOcsNonInclusionProof> for OcsNonInclusionProof {
252 fn from(value: SdkOcsNonInclusionProof) -> Self {
253 (&value).into()
254 }
255}
256
257impl TryFrom<&OcsNonInclusionProof> for SdkOcsNonInclusionProof {
258 type Error = TryFromProtoError;
259
260 fn try_from(value: &OcsNonInclusionProof) -> Result<Self, Self::Error> {
261 let inner_proto = value.non_inclusion_proof.as_ref().ok_or_else(|| {
262 TryFromProtoError::missing(OcsNonInclusionProof::NON_INCLUSION_PROOF_FIELD.name)
263 })?;
264 let non_inclusion_proof =
265 SdkMerkleNonInclusionProof::<ObjectReference>::try_from(inner_proto)
266 .map_err(|e| e.nested(OcsNonInclusionProof::NON_INCLUSION_PROOF_FIELD.name))?;
267 let tree_root_bytes = value.tree_root.as_ref().ok_or_else(|| {
268 TryFromProtoError::missing(OcsNonInclusionProof::TREE_ROOT_FIELD.name)
269 })?;
270 let tree_root =
271 try_digest_from_bytes(tree_root_bytes, OcsNonInclusionProof::TREE_ROOT_FIELD.name)?;
272 Ok(SdkOcsNonInclusionProof {
273 non_inclusion_proof,
274 tree_root,
275 })
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 use prost::bytes::Bytes;
284 use sui_sdk_types::merkle::MerkleTree;
285
286 #[test]
290 fn merkle_proof_round_trip_via_proto() {
291 const LEAVES: [&[u8]; 9] = [
292 b"foo", b"bar", b"fizz", b"baz", b"buzz", b"fizz", b"foobar", b"walrus", b"fizz",
293 ];
294
295 let tree = MerkleTree::build_from_serialized(LEAVES);
296 for (index, leaf) in LEAVES.iter().enumerate() {
297 let original = tree.get_proof(index).unwrap();
298
299 let proto: MerkleProof = (&original).into();
300 let round_tripped: sui_sdk_types::merkle::MerkleProof = (&proto).try_into().unwrap();
301
302 assert_eq!(round_tripped, original);
303 round_tripped
304 .verify_proof_with_leaf_bytes(&tree.root(), leaf, index)
305 .unwrap();
306 }
307 }
308
309 #[test]
310 fn merkle_node_empty_round_trip() {
311 let proto: MerkleNode = Node::Empty.into();
312 assert!(matches!(proto.node, Some(merkle_node::Node::Empty(_))));
313 let back: Node = (&proto).try_into().unwrap();
314 assert_eq!(back, Node::Empty);
315 }
316
317 #[test]
318 fn merkle_node_digest_round_trip() {
319 let raw = [0xab; DIGEST_LEN];
320 let proto: MerkleNode = Node::Digest(raw).into();
321 match &proto.node {
322 Some(merkle_node::Node::Digest(bytes)) => assert_eq!(bytes.as_ref(), raw),
323 other => panic!("expected Digest variant, got {other:?}"),
324 }
325 let back: Node = (&proto).try_into().unwrap();
326 assert_eq!(back, Node::Digest(raw));
327 }
328
329 #[test]
330 fn merkle_node_missing_oneof_rejected() {
331 let proto = MerkleNode { node: None };
332 let err = Node::try_from(&proto).unwrap_err();
333 assert_eq!(err.field_violation().field, "node");
334 }
335
336 #[test]
337 fn merkle_node_short_digest_rejected() {
338 let proto = MerkleNode {
339 node: Some(merkle_node::Node::Digest(Bytes::from_static(&[0u8; 16]))),
340 };
341 let err = Node::try_from(&proto).unwrap_err();
342 assert_eq!(err.field_violation().field, "digest");
343 assert!(
344 err.to_string().contains("expected 32 bytes"),
345 "error should mention expected length: {err}"
346 );
347 }
348
349 #[test]
350 fn merkle_node_long_digest_rejected() {
351 let proto = MerkleNode {
352 node: Some(merkle_node::Node::Digest(Bytes::from_static(&[0u8; 64]))),
353 };
354 let err = Node::try_from(&proto).unwrap_err();
355 assert_eq!(err.field_violation().field, "digest");
356 }
357
358 #[test]
361 fn malformed_inner_node_reports_path_index() {
362 let mut tree_proof: MerkleProof = (&MerkleTree::build_from_serialized([b"a", b"b"])
363 .get_proof(1)
364 .unwrap())
365 .into();
366 tree_proof.path[0].node = None;
368
369 let err = sui_sdk_types::merkle::MerkleProof::try_from(&tree_proof).unwrap_err();
370 let field = &err.field_violation().field;
371 assert!(
372 field.contains("path[0]") || field.contains("path.0"),
373 "field path should reference path index, got {field}"
374 );
375 }
376
377 fn synthetic_refs(count: u8) -> Vec<ObjectReference> {
380 (0..count)
381 .map(|i| {
382 let mut addr = [0u8; 32];
383 addr[31] = i;
384 let mut digest = [0u8; 32];
385 digest[0] = i ^ 0x42;
386 ObjectReference::new(
387 sui_sdk_types::Address::new(addr),
388 u64::from(i) + 1,
389 Digest::new(digest),
390 )
391 })
392 .collect()
393 }
394
395 #[test]
400 fn ocs_inclusion_proof_round_trip_via_proto() {
401 let refs = synthetic_refs(5);
402 let tree = MerkleTree::build_from_unserialized(&refs).unwrap();
403 let tree_root = Digest::new(tree.root().bytes());
404
405 for (index, _) in refs.iter().enumerate() {
406 let original = SdkOcsInclusionProof {
407 merkle_proof: tree.get_proof(index).unwrap(),
408 leaf_index: index as u64,
409 tree_root,
410 };
411 let proto: OcsInclusionProof = (&original).into();
412 let round_tripped: SdkOcsInclusionProof = (&proto).try_into().unwrap();
413 assert_eq!(round_tripped, original);
414 }
415 }
416
417 #[test]
424 fn ocs_non_inclusion_proof_round_trip_via_proto() {
425 let refs = synthetic_refs(5);
426 let tree = MerkleTree::build_from_unserialized(&refs).unwrap();
427 let tree_root = Digest::new(tree.root().bytes());
428
429 let interior = {
431 let mut addr = [0u8; 32];
432 addr[31] = 0x02;
433 ObjectReference::new(
434 sui_sdk_types::Address::new(addr),
435 10,
436 Digest::new([0xaa; 32]),
437 )
438 };
439 let before = ObjectReference::new(
441 sui_sdk_types::Address::new([0u8; 32]),
442 0,
443 Digest::new([0u8; 32]),
444 );
445 let after = {
447 let mut addr = [0u8; 32];
448 addr[31] = 0xff;
449 ObjectReference::new(
450 sui_sdk_types::Address::new(addr),
451 999,
452 Digest::new([0xff; 32]),
453 )
454 };
455
456 for target in [interior, before, after] {
457 assert!(!refs.contains(&target));
458 let inner = tree.compute_non_inclusion_proof(&refs, &target).unwrap();
459 let original = SdkOcsNonInclusionProof {
460 non_inclusion_proof: inner,
461 tree_root,
462 };
463 let proto: OcsNonInclusionProof = (&original).into();
464 let round_tripped: SdkOcsNonInclusionProof = (&proto).try_into().unwrap();
465 assert_eq!(round_tripped, original);
466 }
467 }
468
469 #[test]
472 fn merkle_neighbour_leaf_missing_leaf_rejected() {
473 let proto = MerkleNeighbourLeaf {
474 leaf: None,
475 merkle_proof: Some(MerkleProof::default()),
476 };
477 let err =
478 <(ObjectReference, sui_sdk_types::merkle::MerkleProof)>::try_from(&proto).unwrap_err();
479 assert_eq!(
480 err.field_violation().field,
481 MerkleNeighbourLeaf::LEAF_FIELD.name
482 );
483 }
484
485 #[test]
488 fn merkle_neighbour_leaf_missing_merkle_proof_rejected() {
489 let leaf_ref = synthetic_refs(1).pop().unwrap();
490 let proto = MerkleNeighbourLeaf {
491 leaf: Some(leaf_ref.into()),
492 merkle_proof: None,
493 };
494 let err =
495 <(ObjectReference, sui_sdk_types::merkle::MerkleProof)>::try_from(&proto).unwrap_err();
496 assert_eq!(
497 err.field_violation().field,
498 MerkleNeighbourLeaf::MERKLE_PROOF_FIELD.name,
499 );
500 }
501
502 #[test]
504 fn merkle_non_inclusion_proof_missing_index_rejected() {
505 let proto = MerkleNonInclusionProof::default();
506 let err = SdkMerkleNonInclusionProof::<ObjectReference>::try_from(&proto).unwrap_err();
507 assert_eq!(
508 err.field_violation().field,
509 MerkleNonInclusionProof::INDEX_FIELD.name,
510 );
511 }
512}