@@ -147,7 +147,7 @@ impl BundlePoolRestrictions {
147147/// The two formats use different commitment personalization strings and include the bundle's
148148/// anchor in different digests: v5 includes the anchor in the transaction-ID digest, while v6
149149/// includes it in the authorizing digest. Ironwood bundles exist only in v6 transactions, so
150- /// their commitment format ignores this value .
150+ /// attempting to compute an Ironwood commitment for a v5 transaction returns an error .
151151///
152152/// This is independent of the [`BundlePoolRestrictions`] that govern construction: the same
153153/// Orchard bundle can be committed under either version, and the caller must pass the one
@@ -659,15 +659,15 @@ impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
659659 /// # Errors
660660 ///
661661 /// Returns [`CommitmentError::UnrepresentableFlags`] if the flags cannot
662- /// be encoded under the given pool restrictions.
662+ /// be encoded under the given pool restrictions, or
663+ /// [`CommitmentError::InvalidTransactionVersion`] if `tx_version` is not
664+ /// valid for `pool_restrictions`.
663665 pub fn commitment (
664666 & self ,
665667 pool_restrictions : BundlePoolRestrictions ,
666668 tx_version : TxVersion ,
667669 ) -> Result < BundleCommitment , CommitmentError > {
668- hash_bundle_txid_data ( self , pool_restrictions, tx_version)
669- . map ( BundleCommitment )
670- . ok_or ( CommitmentError :: UnrepresentableFlags )
670+ hash_bundle_txid_data ( self , pool_restrictions, tx_version) . map ( BundleCommitment )
671671 }
672672
673673 /// Returns the transaction binding validating key for this bundle.
@@ -789,6 +789,13 @@ pub enum CommitmentError {
789789 /// * cross-address transfers are enabled but `pool_restrictions` specifies a post-NU6.3
790790 /// Orchard pool (where cross-address transfers are forbidden).
791791 UnrepresentableFlags ,
792+ /// The requested transaction version is not valid for the requested pool
793+ /// restrictions.
794+ ///
795+ /// Ironwood bundles exist only in v6 transactions, so
796+ /// `BundlePoolRestrictions::IronwoodNu6_3Onward` cannot be committed with
797+ /// `TxVersion::V5`.
798+ InvalidTransactionVersion ,
792799}
793800
794801impl fmt:: Display for CommitmentError {
@@ -798,6 +805,10 @@ impl fmt::Display for CommitmentError {
798805 f,
799806 "bundle flags are not representable according to the requested pool restrictions" ,
800807 ) ,
808+ CommitmentError :: InvalidTransactionVersion => write ! (
809+ f,
810+ "Ironwood bundles can only be committed in a v6 transaction" ,
811+ ) ,
801812 }
802813 }
803814}
@@ -849,12 +860,17 @@ impl<V> Bundle<Authorized, V> {
849860 /// `tx_version` select the commitment personalization; in a v6 transaction this digest also
850861 /// includes the bundle anchor bytes (in a v5 transaction they are included by
851862 /// [`Bundle::commitment`] instead).
863+ ///
864+ /// # Errors
865+ ///
866+ /// Returns [`CommitmentError::InvalidTransactionVersion`] if `tx_version`
867+ /// is not valid for `pool_restrictions`.
852868 pub fn authorizing_commitment (
853869 & self ,
854870 pool_restrictions : BundlePoolRestrictions ,
855871 tx_version : TxVersion ,
856- ) -> BundleAuthorizingCommitment {
857- BundleAuthorizingCommitment ( hash_bundle_auth_data ( self , pool_restrictions, tx_version) )
872+ ) -> Result < BundleAuthorizingCommitment , CommitmentError > {
873+ hash_bundle_auth_data ( self , pool_restrictions, tx_version) . map ( BundleAuthorizingCommitment )
858874 }
859875
860876 /// Verifies the proof for this bundle.
@@ -1325,15 +1341,24 @@ pub(crate) mod tests {
13251341 let ( pi, ti) = formats[ i] ;
13261342 let ( pj, tj) = formats[ j] ;
13271343 assert_ne ! (
1328- hash_bundle_txid_empty( pi, ti) . as_bytes( ) ,
1329- hash_bundle_txid_empty( pj, tj) . as_bytes( )
1344+ hash_bundle_txid_empty( pi, ti) . unwrap ( ) . as_bytes( ) ,
1345+ hash_bundle_txid_empty( pj, tj) . unwrap ( ) . as_bytes( )
13301346 ) ;
13311347 assert_ne ! (
1332- hash_bundle_auth_empty( pi, ti) . as_bytes( ) ,
1333- hash_bundle_auth_empty( pj, tj) . as_bytes( )
1348+ hash_bundle_auth_empty( pi, ti) . unwrap ( ) . as_bytes( ) ,
1349+ hash_bundle_auth_empty( pj, tj) . unwrap ( ) . as_bytes( )
13341350 ) ;
13351351 }
13361352 }
1353+
1354+ assert ! ( matches!(
1355+ hash_bundle_txid_empty( BundlePoolRestrictions :: IronwoodNu6_3Onward , TxVersion :: V5 ) ,
1356+ Err ( CommitmentError :: InvalidTransactionVersion )
1357+ ) ) ;
1358+ assert ! ( matches!(
1359+ hash_bundle_auth_empty( BundlePoolRestrictions :: IronwoodNu6_3Onward , TxVersion :: V5 ) ,
1360+ Err ( CommitmentError :: InvalidTransactionVersion )
1361+ ) ) ;
13371362 }
13381363
13391364 proptest ! {
@@ -1420,6 +1445,29 @@ pub(crate) mod tests {
14201445 ) ) ;
14211446 }
14221447
1448+ #[ test]
1449+ fn ironwood_rejects_v5_commitment_version( bundle in arb_bundle( 3 ) ) {
1450+ let bundle_i64 = Bundle :: from_parts_unchecked(
1451+ bundle. actions( ) . clone( ) ,
1452+ * bundle. flags( ) ,
1453+ 0i64 ,
1454+ * bundle. anchor( ) ,
1455+ bundle. authorization( ) . clone( ) ,
1456+ ) ;
1457+
1458+ prop_assert!( matches!(
1459+ bundle_i64. commitment( BundlePoolRestrictions :: IronwoodNu6_3Onward , TxVersion :: V5 ) ,
1460+ Err ( CommitmentError :: InvalidTransactionVersion )
1461+ ) ) ;
1462+ prop_assert!( matches!(
1463+ bundle. authorizing_commitment(
1464+ BundlePoolRestrictions :: IronwoodNu6_3Onward ,
1465+ TxVersion :: V5
1466+ ) ,
1467+ Err ( CommitmentError :: InvalidTransactionVersion )
1468+ ) ) ;
1469+ }
1470+
14231471 /// The anchor bytes are included in the transaction-ID digest for the v5 format and in
14241472 /// the authorizing digest for the v6 format, so changing only the anchor moves exactly
14251473 /// one of the two digests. The v5 and v6 Orchard formats are also domain-separated, so
@@ -1450,8 +1498,8 @@ pub(crate) mod tests {
14501498 ] {
14511499 let txid_a: [ u8 ; 32 ] = a. commitment( pool_restrictions, tx) . unwrap( ) . into( ) ;
14521500 let txid_b: [ u8 ; 32 ] = b. commitment( pool_restrictions, tx) . unwrap( ) . into( ) ;
1453- let auth_a = a. authorizing_commitment( pool_restrictions, tx) . 0 ;
1454- let auth_b = b. authorizing_commitment( pool_restrictions, tx) . 0 ;
1501+ let auth_a = a. authorizing_commitment( pool_restrictions, tx) . unwrap ( ) . 0 ;
1502+ let auth_b = b. authorizing_commitment( pool_restrictions, tx) . unwrap ( ) . 0 ;
14551503 if anchor_in_txid_digest {
14561504 prop_assert_ne!( txid_a, txid_b) ;
14571505 prop_assert_eq!( auth_a. as_bytes( ) , auth_b. as_bytes( ) ) ;
0 commit comments