Skip to content

Commit ff6d748

Browse files
committed
Implement custom serialization for Opening
Resolves: #55
1 parent 004068d commit ff6d748

File tree

4 files changed

+250
-0
lines changed

4 files changed

+250
-0
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
## Added
11+
12+
- Add more comprehensive benchmarks for `blake3` tree [#54]
13+
- Add `to_var_bytes` and `from_slice` to `Opening` [#55]
14+
815
## [0.4.0] - 2023-06-07
916

1017
### Added
@@ -68,6 +75,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6875
- Fix `CheckBytes` derivation in `Node` [#15]
6976

7077
<!-- ISSUES -->
78+
[#55]: https://github.com/dusk-network/merkle/issues/55
79+
[#54]: https://github.com/dusk-network/merkle/issues/54
7180
[#49]: https://github.com/dusk-network/merkle/issues/49
7281
[#44]: https://github.com/dusk-network/merkle/issues/44
7382
[#38]: https://github.com/dusk-network/merkle/issues/38

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ edition = "2021"
1515
license = "MPL-2.0"
1616

1717
[dependencies]
18+
dusk-bytes = "0.1"
1819
rkyv = { version = "0.7", optional = true, default-features = false }
1920
bytecheck = { version = "0.6", optional = true, default-features = false }
2021
blake3 = { version = "1", optional = true, default-features = false }

src/opening.rs

+88
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
use crate::{init_array, Aggregate, Node, Tree};
88

9+
use alloc::vec::Vec;
10+
911
#[cfg(feature = "rkyv-impl")]
1012
use bytecheck::CheckBytes;
13+
use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable};
1114
#[cfg(feature = "rkyv-impl")]
1215
use rkyv::{Archive, Deserialize, Serialize};
1316

@@ -91,6 +94,91 @@ where
9194

9295
self.root == item
9396
}
97+
98+
/// Serialize an [`Opening`] to a vector of bytes.
99+
// Once the new implementation of the `Serializable` trait becomes
100+
// available, we will want that instead, but for the time being we use
101+
// this implementation.
102+
pub fn to_var_bytes<const T_SIZE: usize>(&self) -> Vec<u8>
103+
where
104+
T: Serializable<T_SIZE>,
105+
{
106+
let mut bytes = Vec::with_capacity(
107+
(1 + H * A) * T_SIZE + H * (usize::BITS as usize / 8),
108+
);
109+
110+
// serialize root
111+
bytes.extend(&self.root.to_bytes());
112+
113+
// serialize branch
114+
for level in self.branch.iter() {
115+
for item in level.iter() {
116+
bytes.extend(&item.to_bytes());
117+
}
118+
}
119+
120+
// serialize positions
121+
for pos in self.positions.iter() {
122+
// the positions will be in the range [0..A[, so casting to u32
123+
// is never going to be a problem
124+
#[allow(clippy::cast_possible_truncation)]
125+
bytes.extend(&(*pos as u32).to_bytes());
126+
}
127+
128+
bytes
129+
}
130+
131+
/// Deserialize an [`Opening`] from a slice of bytes.
132+
///
133+
/// # Errors
134+
///
135+
/// Will return [`dusk_bytes::Error`] in case of a deserialization error.
136+
// Once the new implementation of the `Serializable` trait becomes
137+
// available, we will want that instead, but for the time being we use
138+
// this implementation.
139+
pub fn from_slice<const T_SIZE: usize>(
140+
buf: &[u8],
141+
) -> Result<Self, BytesError>
142+
where
143+
T: Serializable<T_SIZE>,
144+
<T as Serializable<T_SIZE>>::Error: dusk_bytes::BadLength,
145+
dusk_bytes::Error: From<<T as Serializable<T_SIZE>>::Error>,
146+
{
147+
let expected_len =
148+
(1 + H * A) * T_SIZE + H * (usize::BITS as usize / 8);
149+
if buf.len() != expected_len {
150+
return Err(BytesError::BadLength {
151+
found: (buf.len()),
152+
expected: (expected_len),
153+
});
154+
}
155+
156+
let mut bytes = buf;
157+
158+
// deserialize root
159+
let root = T::from_reader(&mut bytes)?;
160+
161+
// deserialize branch
162+
let mut branch: [[T; A]; H] =
163+
init_array(|_| init_array(|_| T::EMPTY_SUBTREE));
164+
for level in branch.iter_mut() {
165+
for item in level.iter_mut() {
166+
*item = T::from_reader(&mut bytes)?;
167+
}
168+
}
169+
170+
// deserialize positions
171+
let mut positions = [0usize; H];
172+
for pos in positions.iter_mut() {
173+
*pos = u32::from_reader(&mut bytes)? as usize;
174+
}
175+
176+
Ok(Self {
177+
root,
178+
branch,
179+
positions,
180+
})
181+
}
94182
}
95183

96184
fn fill_opening<T, const H: usize, const A: usize>(

tests/serializable_opening.rs

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
//
5+
// Copyright (c) DUSK NETWORK. All rights reserved.
6+
7+
use std::cmp;
8+
9+
use dusk_bls12_381::BlsScalar;
10+
use dusk_bytes::{DeserializableSlice, Error, Serializable};
11+
use dusk_merkle::{Aggregate, Opening, Tree};
12+
13+
use rand::rngs::StdRng;
14+
use rand::{RngCore, SeedableRng};
15+
16+
const H: usize = 17;
17+
const A: usize = 4;
18+
19+
#[derive(Clone, Copy, PartialEq, Debug)]
20+
struct Range {
21+
start: u64,
22+
end: u64,
23+
}
24+
25+
#[derive(Clone, Copy, PartialEq, Debug)]
26+
struct Item {
27+
hash: BlsScalar,
28+
bh_range: Option<Range>,
29+
}
30+
31+
const EMPTY_ITEM: Item = Item {
32+
hash: BlsScalar::zero(),
33+
bh_range: None,
34+
};
35+
36+
impl Aggregate<A> for Item {
37+
const EMPTY_SUBTREE: Self = EMPTY_ITEM;
38+
39+
fn aggregate(items: [&Self; A]) -> Self {
40+
let mut bh_range = None;
41+
let rng = &mut StdRng::seed_from_u64(0xbeef);
42+
43+
for item in items {
44+
bh_range = match (bh_range, item.bh_range.as_ref()) {
45+
(None, None) => None,
46+
(None, Some(r)) => Some(*r),
47+
(Some(r), None) => Some(r),
48+
(Some(bh_range), Some(item_bh_range)) => {
49+
let start = cmp::min(item_bh_range.start, bh_range.start);
50+
let end = cmp::max(item_bh_range.end, bh_range.end);
51+
Some(Range { start, end })
52+
}
53+
};
54+
}
55+
56+
Self {
57+
hash: BlsScalar::random(rng),
58+
bh_range,
59+
}
60+
}
61+
}
62+
63+
// scalar + bh_range
64+
// 32 + 2 + 8 + 8
65+
const ITEM_SIZE: usize = 50;
66+
67+
impl Serializable<ITEM_SIZE> for Item {
68+
type Error = Error;
69+
70+
fn from_bytes(buf: &[u8; ITEM_SIZE]) -> Result<Self, Self::Error> {
71+
let mut bytes = &buf[..];
72+
73+
// deserialize hash
74+
let hash = BlsScalar::from_reader(&mut bytes)?;
75+
76+
// deserialize bh_range
77+
let bh_range = match u16::from_reader(&mut bytes)? {
78+
0 => None,
79+
1 => {
80+
let start = u64::from_reader(&mut bytes)?;
81+
let end = u64::from_reader(&mut bytes)?;
82+
Some(Range { start, end })
83+
}
84+
_ => {
85+
return Err(Error::InvalidData);
86+
}
87+
};
88+
89+
Ok(Item { hash, bh_range })
90+
}
91+
92+
fn to_bytes(&self) -> [u8; ITEM_SIZE] {
93+
let mut buf = [0u8; ITEM_SIZE];
94+
95+
// serialize hash
96+
buf[0..BlsScalar::SIZE].copy_from_slice(&self.hash.to_bytes());
97+
98+
// serialize bh_range
99+
match self.bh_range {
100+
// the buffer was initialized with zeros so there is nothing to be
101+
// done for None
102+
None => {}
103+
Some(bh_range) => {
104+
buf[BlsScalar::SIZE..BlsScalar::SIZE + 2]
105+
.copy_from_slice(&1u16.to_bytes());
106+
buf[BlsScalar::SIZE + 2..BlsScalar::SIZE + 10]
107+
.copy_from_slice(&bh_range.start.to_bytes());
108+
buf[BlsScalar::SIZE + 10..]
109+
.copy_from_slice(&bh_range.end.to_bytes());
110+
}
111+
};
112+
113+
buf
114+
}
115+
}
116+
117+
type MerkleTree = Tree<Item, H, A>;
118+
119+
#[test]
120+
fn serialize_deserialize() {
121+
let tree = &mut MerkleTree::new();
122+
let rng = &mut StdRng::seed_from_u64(0xbeef);
123+
124+
const LEAVES: usize = 1000000;
125+
126+
let mut i = 0;
127+
let (pos, leaf) = loop {
128+
i += 1;
129+
let block_height = rng.next_u64() % 1000;
130+
let bh_range = Some(Range {
131+
start: block_height,
132+
end: block_height,
133+
});
134+
let leaf = Item {
135+
hash: BlsScalar::random(rng),
136+
bh_range,
137+
};
138+
let pos = rng.next_u64() % tree.capacity();
139+
tree.insert(pos, leaf);
140+
if i == LEAVES {
141+
break (pos, leaf);
142+
}
143+
};
144+
145+
let opening = tree.opening(pos).unwrap();
146+
147+
let serialized = opening.to_var_bytes();
148+
let deserialized = Opening::<Item, H, A>::from_slice(&serialized).unwrap();
149+
150+
assert!(deserialized.verify(leaf));
151+
assert_eq!(opening, deserialized);
152+
}

0 commit comments

Comments
 (0)