@@ -70,9 +70,6 @@ impl AssetState {
7070 pub fn to_bytes ( & self ) -> Result < Vec < u8 > , io:: Error > {
7171 use std:: io:: Write ;
7272
73- // FIXME: Consider writing a leading version byte here so we can change AssetState's
74- // on-disk format without silently mis-parsing old DB entries during upgrades (and fix
75- // from_bytes accordingly).
7673 let mut bytes = Vec :: new ( ) ;
7774 bytes. write_all ( & self . 0 . amount . to_bytes ( ) ) ?;
7875 bytes. write_u8 ( self . 0 . is_finalized as u8 ) ?;
@@ -133,6 +130,9 @@ pub enum AssetStateError {
133130
134131 #[ error( "burn validation failed: {0}" ) ]
135132 Burn ( BurnError ) ,
133+
134+ #[ error( "invalid input: {0}" ) ]
135+ InvalidInput ( String ) ,
136136}
137137
138138/// A map of asset state changes for assets modified in a block or transaction set.
@@ -174,16 +174,14 @@ impl IssuedAssetChanges {
174174 transaction_sighashes : Option < & [ SigHash ] > ,
175175 get_state : impl Fn ( & AssetBase ) -> Option < AssetState > ,
176176 ) -> Result < Self , AssetStateError > {
177- // When sighashes are provided, transactions and sighashes must be equal length by design,
178- // so we use assert instead of returning error.
179177 if let Some ( sighashes) = transaction_sighashes {
180- assert_eq ! (
181- transactions . len ( ) ,
182- sighashes . len ( ) ,
183- "Bug in caller: {} transactions but {} sighashes. Caller must provide one sighash per transaction." ,
184- transactions . len( ) ,
185- sighashes . len ( )
186- ) ;
178+ if transactions . len ( ) != sighashes . len ( ) {
179+ return Err ( AssetStateError :: InvalidInput ( format ! (
180+ "transaction count ({}) does not match sighash count ({})" ,
181+ transactions. len ( ) ,
182+ sighashes . len( ) ,
183+ ) ) ) ;
184+ }
187185 }
188186
189187 // Track old and current states - old_state is None for newly created assets
@@ -206,18 +204,17 @@ impl IssuedAssetChanges {
206204 // ZIP-0227 defines issued-note rho as DeriveIssuedRho(nf_{0,0}, i_action, i_note),
207205 // so we must pass the first Action nullifier (nf_{0,0}). We rely on
208206 // `orchard_nullifiers()` preserving Action order, so `.next()` returns nf_{0,0}.
209- let first_nullifier =
210- // FIXME: For now, the only way to convert Zebra's nullifier type to Orchard's nullifier type
211- // is via bytes, although they both wrap pallas::Point. Consider a more direct conversion to
212- // avoid this round-trip, if possible.
213- & Nullifier :: from_bytes ( & <[ u8 ; 32 ] >:: from (
214- * tx. orchard_nullifiers ( )
215- . next ( )
216- // ZIP-0227 requires an issuance bundle to contain at least one OrchardZSA Action Group.
217- // `ShieldedData.actions` is `AtLeastOne<...>`, so nf_{0,0} must exist.
218- . expect ( "issuance must have at least one nullifier" ) ,
219- ) )
220- . expect ( "Bytes can be converted to Nullifier" ) ;
207+ // Nullifier type conversion via bytes: both types wrap pallas::Point
208+ // but lack a direct conversion path in the current orchard API.
209+ // TODO: Consider adding a test for the case where a V6 transaction has issuance data
210+ // but has no nullifiers (the test may require constructing a proper mock V6 transaction).
211+ let raw_nullifier = tx. orchard_nullifiers ( ) . next ( ) . ok_or_else ( || {
212+ AssetStateError :: InvalidInput (
213+ "issuance bundle has no orchard actions" . to_string ( ) ,
214+ )
215+ } ) ?;
216+ let first_nullifier = & Nullifier :: from_bytes ( & <[ u8 ; 32 ] >:: from ( * raw_nullifier) )
217+ . expect ( "valid zebra nullifier bytes convert to orchard nullifier" ) ;
221218
222219 let issue_records = match transaction_sighashes {
223220 Some ( sighashes) => {
0 commit comments