@@ -712,3 +712,95 @@ func generateSphinxPacket(rt *route.Route, paymentHash []byte,
712712 PaymentPath : sphinxPath .NodeKeys (),
713713 }, nil
714714}
715+
716+ // verifyAttempt validates that a new HTLC attempt is compatible with the
717+ // existing payment and its in-flight HTLCs. This function checks:
718+ // 1. MPP (Multi-Path Payment) compatibility between attempts
719+ // 2. Blinded payment consistency
720+ // 3. Amount validation
721+ // 4. Total payment amount limits
722+ func verifyAttempt (payment * MPPayment , attempt * HTLCAttemptInfo ) error {
723+ // If the final hop has encrypted data, then we know this is a
724+ // blinded payment. In blinded payments, MPP records are not set
725+ // for split payments and the recipient is responsible for using
726+ // a consistent PathID across the various encrypted data
727+ // payloads that we received from them for this payment. All we
728+ // need to check is that the total amount field for each HTLC
729+ // in the split payment is correct.
730+ isBlinded := len (attempt .Route .FinalHop ().EncryptedData ) != 0
731+
732+ // Make sure any existing shards match the new one with regards
733+ // to MPP options.
734+ mpp := attempt .Route .FinalHop ().MPP
735+
736+ // MPP records should not be set for attempts to blinded paths.
737+ if isBlinded && mpp != nil {
738+ return ErrMPPRecordInBlindedPayment
739+ }
740+
741+ for _ , h := range payment .InFlightHTLCs () {
742+ hMpp := h .Route .FinalHop ().MPP
743+
744+ // If this is a blinded payment, then no existing HTLCs
745+ // should have MPP records.
746+ if isBlinded && hMpp != nil {
747+ return ErrMPPRecordInBlindedPayment
748+ }
749+
750+ // If this is a blinded payment, then we just need to
751+ // check that the TotalAmtMsat field for this shard
752+ // is equal to that of any other shard in the same
753+ // payment.
754+ if isBlinded {
755+ if attempt .Route .FinalHop ().TotalAmtMsat !=
756+ h .Route .FinalHop ().TotalAmtMsat {
757+
758+ return ErrBlindedPaymentTotalAmountMismatch
759+ }
760+
761+ continue
762+ }
763+
764+ switch {
765+ // We tried to register a non-MPP attempt for a MPP
766+ // payment.
767+ case mpp == nil && hMpp != nil :
768+ return ErrMPPayment
769+
770+ // We tried to register a MPP shard for a non-MPP
771+ // payment.
772+ case mpp != nil && hMpp == nil :
773+ return ErrNonMPPayment
774+
775+ // Non-MPP payment, nothing more to validate.
776+ case mpp == nil :
777+ continue
778+ }
779+
780+ // Check that MPP options match.
781+ if mpp .PaymentAddr () != hMpp .PaymentAddr () {
782+ return ErrMPPPaymentAddrMismatch
783+ }
784+
785+ if mpp .TotalMsat () != hMpp .TotalMsat () {
786+ return ErrMPPTotalAmountMismatch
787+ }
788+ }
789+
790+ // If this is a non-MPP attempt, it must match the total amount
791+ // exactly. Note that a blinded payment is considered an MPP
792+ // attempt.
793+ amt := attempt .Route .ReceiverAmt ()
794+ if ! isBlinded && mpp == nil && amt != payment .Info .Value {
795+ return ErrValueMismatch
796+ }
797+
798+ // Ensure we aren't sending more than the total payment amount.
799+ sentAmt , _ := payment .SentAmt ()
800+ if sentAmt + amt > payment .Info .Value {
801+ return fmt .Errorf ("%w: attempted=%v, payment amount=%v" ,
802+ ErrValueExceedsAmt , sentAmt + amt , payment .Info .Value )
803+ }
804+
805+ return nil
806+ }
0 commit comments