Error.hs (4278B)
1 {-# OPTIONS_HADDOCK hide #-} 2 {-# LANGUAGE DeriveGeneric #-} 3 4 -- | 5 -- Module: Lightning.Protocol.BOLT4.Error 6 -- Copyright: (c) 2025 Jared Tobin 7 -- License: MIT 8 -- Maintainer: Jared Tobin <jared@ppad.tech> 9 -- 10 -- Returning errors: construction, wrapping and attribution of return 11 -- packets. 12 13 module Lightning.Protocol.BOLT4.Error ( 14 ErrorPacket(..) 15 , Attribution(..) 16 , construct_error 17 , wrap_error 18 , unwrap_error 19 ) where 20 21 import Control.DeepSeq (NFData) 22 import qualified Data.ByteString as BS 23 import GHC.Generics (Generic) 24 import qualified Lightning.Protocol.BOLT1 as BOLT1 25 import Lightning.Protocol.BOLT4.Codec 26 import Lightning.Protocol.BOLT4.Prim 27 import Lightning.Protocol.BOLT4.Types 28 29 -- | An obfuscated return packet (the @reason@ of @update_fail_htlc@). 30 newtype ErrorPacket = ErrorPacket BS.ByteString 31 deriving (Eq, Show, Generic) 32 33 instance NFData ErrorPacket 34 35 -- | The origin of a return packet, as found by 'unwrap_error'. 36 data Attribution 37 = Attributed {-# UNPACK #-} !Int !FailureMessage 38 -- ^ the hop with this index (from 0, in route order) returned the 39 -- failure 40 | MalformedFailure {-# UNPACK #-} !Int 41 -- ^ the hop with this index returned the packet, but its failure 42 -- message can't be decoded 43 | UnknownOrigin 44 -- ^ no hop's HMAC matches 45 deriving (Eq, Show, Generic) 46 47 instance NFData Attribution 48 49 -- | Construct a return packet at the failing node, given the shared 50 -- secret from processing the onion and the failure. 51 -- 52 -- The failure message is padded so that its length plus that of the 53 -- padding is at least 256 bytes. Fails if the failure message can't 54 -- be encoded, or exceeds 65535 bytes. 55 -- 56 -- >>> let Just ss = shared_secret (BS.replicate 32 0x01) 57 -- >>> let fm = FailureMessage TemporaryNodeFailure BOLT1.empty_tlv_stream 58 -- >>> let Right (ErrorPacket pkt) = construct_error ss fm 59 -- >>> BS.length pkt 60 -- 292 61 construct_error 62 :: SharedSecret 63 -> FailureMessage 64 -> Either EncodeError ErrorPacket 65 construct_error ss fm = do 66 msg <- encode_failure_message fm 67 let len = BS.length msg 68 if len > 65535 69 then Left FieldTooLong 70 else do 71 let pad = max 0 (256 - len) 72 body = BS.concat 73 [ BOLT1.encode_u16 (fromIntegral len), msg 74 , BOLT1.encode_u16 (fromIntegral pad), BS.replicate pad 0 ] 75 mac = hmac (derive_um ss) body 76 pure (wrap_error ss (ErrorPacket (mac <> body))) 77 78 -- | Wrap a return packet from downstream at an intermediate node, given 79 -- the shared secret from processing the onion. 80 -- 81 -- >>> let Just ss = shared_secret (BS.replicate 32 0x01) 82 -- >>> wrap_error ss (wrap_error ss (ErrorPacket "packet")) 83 -- ErrorPacket "packet" 84 wrap_error :: SharedSecret -> ErrorPacket -> ErrorPacket 85 wrap_error ss (ErrorPacket p) = 86 ErrorPacket (xor_bytes p (keystream (derive_ammag ss) (BS.length p))) 87 88 -- | Find the origin of a return packet at the origin node, given the 89 -- shared secrets from 'Lightning.Protocol.BOLT4.construct', in route 90 -- order. 91 -- 92 -- >>> let Just ss0 = shared_secret (BS.replicate 32 0x01) 93 -- >>> let Just ss1 = shared_secret (BS.replicate 32 0x02) 94 -- >>> let fm = FailureMessage TemporaryNodeFailure BOLT1.empty_tlv_stream 95 -- >>> let Right pkt = construct_error ss1 fm 96 -- >>> unwrap_error [ss0, ss1] (wrap_error ss0 pkt) == Attributed 1 fm 97 -- True 98 -- >>> unwrap_error [ss0] (wrap_error ss0 pkt) 99 -- UnknownOrigin 100 unwrap_error :: [SharedSecret] -> ErrorPacket -> Attribution 101 unwrap_error = go 0 102 where 103 go _ [] _ = UnknownOrigin 104 go i (ss : rest) p = 105 let ErrorPacket q = wrap_error ss p 106 (mac, body) = BS.splitAt 32 q 107 in if ct_eq mac (hmac (derive_um ss) body) 108 then either (const (MalformedFailure i)) (Attributed i) 109 (failure_message body) 110 else go (i + 1) rest (ErrorPacket q) 111 112 -- The failure message of a return packet (after its HMAC). 113 failure_message :: BS.ByteString -> Either DecodeError FailureMessage 114 failure_message body = case BOLT1.decode_u16 body of 115 Just (len, r0) 116 | BS.length r0 >= fromIntegral len 117 , let (msg, r1) = BS.splitAt (fromIntegral len) r0 118 , Just (pad, r2) <- BOLT1.decode_u16 r1 119 , BS.length r2 >= fromIntegral pad 120 -> decode_failure_message msg 121 _ -> Left InvalidLength