@@ -32,6 +32,40 @@ struct Body {
3232}
3333
3434impl NonInclusionCertificate {
35+ /// Construct the distinguished certificate for an empty tree.
36+ pub fn empty_tree ( ) -> Self {
37+ Self { body : None }
38+ }
39+
40+ /// Construct a non-empty certificate from its typed components.
41+ ///
42+ /// Siblings are ordered root-to-leaf and their count must equal the bitmap
43+ /// population count. The fixed terminal-value width is a Unicity profile
44+ /// restriction; the generic RSMT format permits arbitrary byte strings.
45+ pub fn from_parts (
46+ bitmap : [ u8 ; BITMAP_SIZE ] ,
47+ siblings : Vec < [ u8 ; HASH_SIZE ] > ,
48+ terminal_key : [ u8 ; TERMINAL_KEY_SIZE ] ,
49+ terminal_value : [ u8 ; AGGREGATION_TREE_VALUE_SIZE ] ,
50+ ) -> Result < Self , Error > {
51+ let expected = bitmap. iter ( ) . map ( |byte| byte. count_ones ( ) ) . sum :: < u32 > ( ) as usize ;
52+ if siblings. len ( ) != expected {
53+ return Err ( Error :: InvalidLength {
54+ what : "NonInclusionCertificate siblings" ,
55+ expected,
56+ actual : siblings. len ( ) ,
57+ } ) ;
58+ }
59+ Ok ( Self {
60+ body : Some ( Body {
61+ bitmap,
62+ siblings,
63+ terminal_key,
64+ terminal_value,
65+ } ) ,
66+ } )
67+ }
68+
3569 /// Decode the canonical raw-byte representation.
3670 ///
3771 /// The empty byte string is the distinguished certificate for an empty
@@ -40,7 +74,7 @@ impl NonInclusionCertificate {
4074 /// where `n` is the bitmap population count.
4175 pub fn decode ( bytes : & [ u8 ] ) -> Result < Self , Error > {
4276 if bytes. is_empty ( ) {
43- return Ok ( Self { body : None } ) ;
77+ return Ok ( Self :: empty_tree ( ) ) ;
4478 }
4579 if bytes. len ( ) < BITMAP_SIZE {
4680 return Err ( Error :: InvalidLength {
@@ -92,14 +126,7 @@ impl NonInclusionCertificate {
92126 . try_into ( )
93127 . expect ( "length checked" ) ;
94128
95- Ok ( Self {
96- body : Some ( Body {
97- bitmap,
98- siblings,
99- terminal_key,
100- terminal_value,
101- } ) ,
102- } )
129+ Self :: from_parts ( bitmap, siblings, terminal_key, terminal_value)
103130 }
104131
105132 /// Encode to the canonical raw-byte representation.
@@ -123,10 +150,26 @@ impl NonInclusionCertificate {
123150 }
124151
125152 /// Whether this is the distinguished certificate for an empty tree.
126- pub fn is_empty ( & self ) -> bool {
153+ pub fn is_empty_tree ( & self ) -> bool {
127154 self . body . is_none ( )
128155 }
129156
157+ /// The authenticated terminal key, or `None` for an empty-tree certificate.
158+ ///
159+ /// Treat this as untrusted data until certificate or proof verification has
160+ /// succeeded.
161+ pub fn terminal_key ( & self ) -> Option < & [ u8 ; TERMINAL_KEY_SIZE ] > {
162+ self . body . as_ref ( ) . map ( |body| & body. terminal_key )
163+ }
164+
165+ /// The authenticated terminal value, or `None` for an empty-tree certificate.
166+ ///
167+ /// Treat this as untrusted data until certificate or proof verification has
168+ /// succeeded.
169+ pub fn terminal_value ( & self ) -> Option < & [ u8 ; AGGREGATION_TREE_VALUE_SIZE ] > {
170+ self . body . as_ref ( ) . map ( |body| & body. terminal_value )
171+ }
172+
130173 /// Verify both the authenticated path and the non-inclusion relation.
131174 /// `expected_root` is `None` only for an empty certified tree.
132175 ///
@@ -167,10 +210,6 @@ impl NonInclusionCertificate {
167210 . as_ref ( )
168211 . is_some_and ( |body| & body. terminal_key == target. bytes ( ) )
169212 }
170-
171- pub ( crate ) fn terminal_key ( & self ) -> Option < & [ u8 ; TERMINAL_KEY_SIZE ] > {
172- self . body . as_ref ( ) . map ( |body| & body. terminal_key )
173- }
174213}
175214
176215#[ cfg( test) ]
@@ -196,9 +235,9 @@ mod tests {
196235
197236 #[ test]
198237 fn empty_certificate_only_verifies_empty_root ( ) {
199- let certificate = NonInclusionCertificate :: decode ( & [ ] ) . unwrap ( ) ;
238+ let certificate = NonInclusionCertificate :: empty_tree ( ) ;
200239 let target = state_id ( [ 7u8 ; 32 ] ) ;
201- assert ! ( certificate. is_empty ( ) ) ;
240+ assert ! ( certificate. is_empty_tree ( ) ) ;
202241 assert_eq ! ( certificate. verify( & target, None ) , Ok ( ( ) ) ) ;
203242 assert_eq ! (
204243 certificate. verify( & target, Some ( & leaf_root( & [ 1 ; 32 ] , & [ 2 ; 32 ] ) ) ) ,
@@ -211,10 +250,16 @@ mod tests {
211250 fn singleton_terminal_proves_another_key_absent ( ) {
212251 let terminal_key = [ 1u8 ; 32 ] ;
213252 let terminal_value = [ 2u8 ; 32 ] ;
253+ let certificate = NonInclusionCertificate :: from_parts (
254+ [ 0u8 ; BITMAP_SIZE ] ,
255+ vec ! [ ] ,
256+ terminal_key,
257+ terminal_value,
258+ )
259+ . unwrap ( ) ;
214260 let mut encoded = vec ! [ 0u8 ; BITMAP_SIZE ] ;
215261 encoded. extend_from_slice ( & terminal_key) ;
216262 encoded. extend_from_slice ( & terminal_value) ;
217- let certificate = NonInclusionCertificate :: decode ( & encoded) . unwrap ( ) ;
218263 let root = leaf_root ( & terminal_key, & terminal_value) ;
219264
220265 assert_eq ! (
@@ -225,6 +270,8 @@ mod tests {
225270 certificate. verify( & state_id( terminal_key) , Some ( & root) ) ,
226271 Err ( VerificationError :: StateIncluded )
227272 ) ;
273+ assert_eq ! ( certificate. terminal_key( ) , Some ( & terminal_key) ) ;
274+ assert_eq ! ( certificate. terminal_value( ) , Some ( & terminal_value) ) ;
228275 assert_eq ! ( certificate. encode( ) , encoded) ;
229276 }
230277
@@ -245,12 +292,15 @@ mod tests {
245292 . update ( right_hash. data ( ) )
246293 . finalize ( ) ;
247294
248- let mut encoded = vec ! [ 0u8 ; BITMAP_SIZE ] ;
249- encoded[ 0 ] = 0x80 ;
250- encoded. extend_from_slice ( right_hash. data ( ) ) ;
251- encoded. extend_from_slice ( & left_key) ;
252- encoded. extend_from_slice ( & left_value) ;
253- let certificate = NonInclusionCertificate :: decode ( & encoded) . unwrap ( ) ;
295+ let mut bitmap = [ 0u8 ; BITMAP_SIZE ] ;
296+ bitmap[ 0 ] = 0x80 ;
297+ let certificate = NonInclusionCertificate :: from_parts (
298+ bitmap,
299+ vec ! [ right_hash. data( ) . try_into( ) . expect( "SHA-256 length" ) ] ,
300+ left_key,
301+ left_value,
302+ )
303+ . unwrap ( ) ;
254304
255305 let mut target_left = [ 0u8 ; 32 ] ;
256306 target_left[ 31 ] = 1 ;
@@ -270,6 +320,16 @@ mod tests {
270320
271321 #[ test]
272322 fn decoder_rejects_wrong_terminal_or_sibling_lengths ( ) {
323+ let mut bitmap = [ 0u8 ; BITMAP_SIZE ] ;
324+ bitmap[ 0 ] = 0x80 ;
325+ assert ! ( NonInclusionCertificate :: from_parts(
326+ bitmap,
327+ vec![ ] ,
328+ [ 1u8 ; 32 ] ,
329+ [ 2u8 ; AGGREGATION_TREE_VALUE_SIZE ] ,
330+ )
331+ . is_err( ) ) ;
332+
273333 assert ! ( NonInclusionCertificate :: decode( & [ 0u8 ; 32 ] ) . is_err( ) ) ;
274334
275335 let mut one_sibling_without_terminal = vec ! [ 0u8 ; 64 ] ;
0 commit comments