@@ -22,6 +22,7 @@ import (
2222)
2323
2424var UtxoValidationRules = []common.UtxoValidationRuleFunc {
25+ UtxoValidateMetadata ,
2526 UtxoValidateOutsideValidityIntervalUtxo ,
2627 UtxoValidateInputSetEmptyUtxo ,
2728 UtxoValidateFeeTooSmallUtxo ,
@@ -167,3 +168,50 @@ func UtxoValidateMaxTxSizeUtxo(
167168 tmpPparams ,
168169 )
169170}
171+
172+ // UtxoValidateMetadata validates that auxiliary data (metadata) matches the hash in transaction body
173+ // This is a cheap structural check that should run before expensive ledger lookups
174+ func UtxoValidateMetadata (
175+ tx common.Transaction ,
176+ slot uint64 ,
177+ ls common.LedgerState ,
178+ pp common.ProtocolParameters ,
179+ ) error {
180+ bodyAuxDataHash := tx .AuxDataHash ()
181+ txAuxData := tx .Metadata ()
182+ rawAuxData := tx .RawAuxiliaryData ()
183+
184+ // Case 1: Neither body hash nor aux data present - OK
185+ if bodyAuxDataHash == nil && txAuxData == nil && len (rawAuxData ) == 0 {
186+ return nil
187+ }
188+
189+ // Case 2: Body has hash but no aux data provided - error
190+ if bodyAuxDataHash != nil && txAuxData == nil && len (rawAuxData ) == 0 {
191+ return MissingTransactionMetadataError {
192+ Hash : * bodyAuxDataHash ,
193+ }
194+ }
195+
196+ // Case 3: Aux data provided but body has no hash - error
197+ if bodyAuxDataHash == nil && len (rawAuxData ) > 0 {
198+ actualHash := common .Blake2b256Hash (rawAuxData )
199+ return MissingTransactionAuxiliaryDataHashError {
200+ Hash : actualHash ,
201+ }
202+ }
203+
204+ // Case 4: Both present - verify hash matches
205+ // Use raw auxiliary data (includes scripts) for hashing, not just metadata
206+ if bodyAuxDataHash != nil && len (rawAuxData ) > 0 {
207+ actualHash := common .Blake2b256Hash (rawAuxData )
208+ if * bodyAuxDataHash != actualHash {
209+ return ConflictingMetadataHashError {
210+ Supplied : * bodyAuxDataHash ,
211+ Expected : actualHash ,
212+ }
213+ }
214+ }
215+
216+ return nil
217+ }
0 commit comments