{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module Lightning.Protocol.BOLT4.Error (
ErrorPacket(..)
, AttributionResult(..)
, minErrorPacketSize
, constructError
, wrapError
, unwrapError
) where
import Data.Bits (xor)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as BL
import qualified Crypto.Hash.SHA256 as SHA256
import Data.Word (Word8, Word16)
import Lightning.Protocol.BOLT4.Codec (encodeFailureMessage, decodeFailureMessage)
import Lightning.Protocol.BOLT4.Prim
import Lightning.Protocol.BOLT4.Types (FailureMessage)
newtype ErrorPacket = ErrorPacket BS.ByteString
deriving (ErrorPacket -> ErrorPacket -> Bool
(ErrorPacket -> ErrorPacket -> Bool)
-> (ErrorPacket -> ErrorPacket -> Bool) -> Eq ErrorPacket
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ErrorPacket -> ErrorPacket -> Bool
== :: ErrorPacket -> ErrorPacket -> Bool
$c/= :: ErrorPacket -> ErrorPacket -> Bool
/= :: ErrorPacket -> ErrorPacket -> Bool
Eq, Int -> ErrorPacket -> ShowS
[ErrorPacket] -> ShowS
ErrorPacket -> String
(Int -> ErrorPacket -> ShowS)
-> (ErrorPacket -> String)
-> ([ErrorPacket] -> ShowS)
-> Show ErrorPacket
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ErrorPacket -> ShowS
showsPrec :: Int -> ErrorPacket -> ShowS
$cshow :: ErrorPacket -> String
show :: ErrorPacket -> String
$cshowList :: [ErrorPacket] -> ShowS
showList :: [ErrorPacket] -> ShowS
Show)
data AttributionResult
= Attributed {-# UNPACK #-} !Int !FailureMessage
| UnknownOrigin !BS.ByteString
deriving (AttributionResult -> AttributionResult -> Bool
(AttributionResult -> AttributionResult -> Bool)
-> (AttributionResult -> AttributionResult -> Bool)
-> Eq AttributionResult
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AttributionResult -> AttributionResult -> Bool
== :: AttributionResult -> AttributionResult -> Bool
$c/= :: AttributionResult -> AttributionResult -> Bool
/= :: AttributionResult -> AttributionResult -> Bool
Eq, Int -> AttributionResult -> ShowS
[AttributionResult] -> ShowS
AttributionResult -> String
(Int -> AttributionResult -> ShowS)
-> (AttributionResult -> String)
-> ([AttributionResult] -> ShowS)
-> Show AttributionResult
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AttributionResult -> ShowS
showsPrec :: Int -> AttributionResult -> ShowS
$cshow :: AttributionResult -> String
show :: AttributionResult -> String
$cshowList :: [AttributionResult] -> ShowS
showList :: [AttributionResult] -> ShowS
Show)
minErrorPacketSize :: Int
minErrorPacketSize :: Int
minErrorPacketSize = Int
256
{-# INLINE minErrorPacketSize #-}
constructError
:: SharedSecret
-> FailureMessage
-> ErrorPacket
constructError :: SharedSecret -> FailureMessage -> ErrorPacket
constructError !SharedSecret
ss !FailureMessage
failure =
let !um :: DerivedKey
um = SharedSecret -> DerivedKey
deriveUm SharedSecret
ss
!ammag :: DerivedKey
ammag = SharedSecret -> DerivedKey
deriveAmmag SharedSecret
ss
!inner :: ByteString
inner = DerivedKey -> FailureMessage -> ByteString
buildErrorMessage DerivedKey
um FailureMessage
failure
!obfuscated :: ByteString
obfuscated = DerivedKey -> ByteString -> ByteString
obfuscateError DerivedKey
ammag ByteString
inner
in ByteString -> ErrorPacket
ErrorPacket ByteString
obfuscated
{-# INLINE constructError #-}
wrapError
:: SharedSecret
-> ErrorPacket
-> ErrorPacket
wrapError :: SharedSecret -> ErrorPacket -> ErrorPacket
wrapError !SharedSecret
ss (ErrorPacket !ByteString
packet) =
let !ammag :: DerivedKey
ammag = SharedSecret -> DerivedKey
deriveAmmag SharedSecret
ss
!wrapped :: ByteString
wrapped = DerivedKey -> ByteString -> ByteString
obfuscateError DerivedKey
ammag ByteString
packet
in ByteString -> ErrorPacket
ErrorPacket ByteString
wrapped
{-# INLINE wrapError #-}
unwrapError
:: [SharedSecret]
-> ErrorPacket
-> AttributionResult
unwrapError :: [SharedSecret] -> ErrorPacket -> AttributionResult
unwrapError [SharedSecret]
secrets (ErrorPacket !ByteString
initialPacket) = Int -> ByteString -> [SharedSecret] -> AttributionResult
go Int
0 ByteString
initialPacket [SharedSecret]
secrets
where
go :: Int -> BS.ByteString -> [SharedSecret] -> AttributionResult
go :: Int -> ByteString -> [SharedSecret] -> AttributionResult
go !Int
_ !ByteString
packet [] = ByteString -> AttributionResult
UnknownOrigin ByteString
packet
go !Int
idx !ByteString
packet (SharedSecret
ss:[SharedSecret]
rest) =
let !ammag :: DerivedKey
ammag = SharedSecret -> DerivedKey
deriveAmmag SharedSecret
ss
!um :: DerivedKey
um = SharedSecret -> DerivedKey
deriveUm SharedSecret
ss
!deobfuscated :: ByteString
deobfuscated = DerivedKey -> ByteString -> ByteString
deobfuscateError DerivedKey
ammag ByteString
packet
in if DerivedKey -> ByteString -> Bool
verifyErrorHmac DerivedKey
um ByteString
deobfuscated
then case ByteString -> Maybe FailureMessage
parseErrorMessage (Int -> ByteString -> ByteString
BS.drop Int
32 ByteString
deobfuscated) of
Just FailureMessage
msg -> Int -> FailureMessage -> AttributionResult
Attributed Int
idx FailureMessage
msg
Maybe FailureMessage
Nothing -> ByteString -> AttributionResult
UnknownOrigin ByteString
deobfuscated
else Int -> ByteString -> [SharedSecret] -> AttributionResult
go (Int
idx Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) ByteString
deobfuscated [SharedSecret]
rest
buildErrorMessage
:: DerivedKey
-> FailureMessage
-> BS.ByteString
buildErrorMessage :: DerivedKey -> FailureMessage -> ByteString
buildErrorMessage (DerivedKey !ByteString
umKey) !FailureMessage
failure =
let !encoded :: ByteString
encoded = FailureMessage -> ByteString
encodeFailureMessage FailureMessage
failure
!msgLen :: Int
msgLen = ByteString -> Int
BS.length ByteString
encoded
!padLen :: Int
padLen = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int
minErrorPacketSize Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
32 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
msgLen Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2)
!padding :: ByteString
padding = Int -> Word8 -> ByteString
BS.replicate Int
padLen Word8
0
!payload :: ByteString
payload = Builder -> ByteString
toStrict (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$
Word16 -> Builder
B.word16BE (Int -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
msgLen) Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<>
ByteString -> Builder
B.byteString ByteString
encoded Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<>
Word16 -> Builder
B.word16BE (Int -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
padLen) Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<>
ByteString -> Builder
B.byteString ByteString
padding
SHA256.MAC !ByteString
hmac = ByteString -> ByteString -> MAC
SHA256.hmac ByteString
umKey ByteString
payload
in ByteString
hmac ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
payload
{-# INLINE buildErrorMessage #-}
obfuscateError
:: DerivedKey
-> BS.ByteString
-> BS.ByteString
obfuscateError :: DerivedKey -> ByteString -> ByteString
obfuscateError !DerivedKey
ammag !ByteString
packet =
let !stream :: ByteString
stream = DerivedKey -> Int -> ByteString
generateStream DerivedKey
ammag (ByteString -> Int
BS.length ByteString
packet)
in ByteString -> ByteString -> ByteString
xorBytes ByteString
packet ByteString
stream
{-# INLINE obfuscateError #-}
deobfuscateError
:: DerivedKey
-> BS.ByteString
-> BS.ByteString
deobfuscateError :: DerivedKey -> ByteString -> ByteString
deobfuscateError = DerivedKey -> ByteString -> ByteString
obfuscateError
{-# INLINE deobfuscateError #-}
verifyErrorHmac
:: DerivedKey
-> BS.ByteString
-> Bool
verifyErrorHmac :: DerivedKey -> ByteString -> Bool
verifyErrorHmac (DerivedKey !ByteString
umKey) !ByteString
packet
| ByteString -> Int
BS.length ByteString
packet Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
32 = Bool
False
| Bool
otherwise =
let !receivedHmac :: ByteString
receivedHmac = Int -> ByteString -> ByteString
BS.take Int
32 ByteString
packet
!payload :: ByteString
payload = Int -> ByteString -> ByteString
BS.drop Int
32 ByteString
packet
SHA256.MAC !ByteString
computedHmac = ByteString -> ByteString -> MAC
SHA256.hmac ByteString
umKey ByteString
payload
in ByteString -> ByteString -> Bool
constantTimeEq ByteString
receivedHmac ByteString
computedHmac
{-# INLINE verifyErrorHmac #-}
parseErrorMessage
:: BS.ByteString
-> Maybe FailureMessage
parseErrorMessage :: ByteString -> Maybe FailureMessage
parseErrorMessage !ByteString
bs
| ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
4 = Maybe FailureMessage
forall a. Maybe a
Nothing
| Bool
otherwise =
let !msgLen :: Int
msgLen = Word16 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Word16
word16BE (Int -> ByteString -> ByteString
BS.take Int
2 ByteString
bs))
in if ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
msgLen
then Maybe FailureMessage
forall a. Maybe a
Nothing
else ByteString -> Maybe FailureMessage
decodeFailureMessage (Int -> ByteString -> ByteString
BS.take Int
msgLen (Int -> ByteString -> ByteString
BS.drop Int
2 ByteString
bs))
{-# INLINE parseErrorMessage #-}
xorBytes :: BS.ByteString -> BS.ByteString -> BS.ByteString
xorBytes :: ByteString -> ByteString -> ByteString
xorBytes !ByteString
a !ByteString
b = [Word8] -> ByteString
BS.pack ([Word8] -> ByteString) -> [Word8] -> ByteString
forall a b. (a -> b) -> a -> b
$ (Word8 -> Word8 -> Word8) -> ByteString -> ByteString -> [Word8]
forall a. (Word8 -> Word8 -> a) -> ByteString -> ByteString -> [a]
BS.zipWith Word8 -> Word8 -> Word8
forall a. Bits a => a -> a -> a
xor ByteString
a ByteString
b
{-# INLINE xorBytes #-}
constantTimeEq :: BS.ByteString -> BS.ByteString -> Bool
constantTimeEq :: ByteString -> ByteString -> Bool
constantTimeEq !ByteString
a !ByteString
b
| ByteString -> Int
BS.length ByteString
a Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= ByteString -> Int
BS.length ByteString
b = Bool
False
| Bool
otherwise = Word8 -> [(Word8, Word8)] -> Bool
go Word8
0 (ByteString -> ByteString -> [(Word8, Word8)]
BS.zip ByteString
a ByteString
b)
where
go :: Word8 -> [(Word8, Word8)] -> Bool
go :: Word8 -> [(Word8, Word8)] -> Bool
go !Word8
acc [] = Word8
acc Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0
go !Word8
acc ((Word8
x, Word8
y):[(Word8, Word8)]
rest) = Word8 -> [(Word8, Word8)] -> Bool
go (Word8
acc Word8 -> Word8 -> Word8
forall a. Bits a => a -> a -> a
`xor` (Word8
x Word8 -> Word8 -> Word8
forall a. Bits a => a -> a -> a
`xor` Word8
y)) [(Word8, Word8)]
rest
{-# INLINE constantTimeEq #-}
word16BE :: BS.ByteString -> Word16
word16BE :: ByteString -> Word16
word16BE !ByteString
bs =
let !b0 :: Word16
b0 = Word8 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
0) :: Word16
!b1 :: Word16
b1 = Word8 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
1) :: Word16
in (Word16
b0 Word16 -> Word16 -> Word16
forall a. Num a => a -> a -> a
* Word16
256) Word16 -> Word16 -> Word16
forall a. Num a => a -> a -> a
+ Word16
b1
{-# INLINE word16BE #-}
toStrict :: B.Builder -> BS.ByteString
toStrict :: Builder -> ByteString
toStrict = LazyByteString -> ByteString
BL.toStrict (LazyByteString -> ByteString)
-> (Builder -> LazyByteString) -> Builder -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
B.toLazyByteString
{-# INLINE toStrict #-}