{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Lightning.Protocol.BOLT4.Process (
process
, RejectReason(..)
) where
import Data.Bits (xor)
import qualified Crypto.Curve.Secp256k1 as Secp256k1
import qualified Data.ByteString as BS
import Data.Word (Word8)
import GHC.Generics (Generic)
import Lightning.Protocol.BOLT4.Codec
import Lightning.Protocol.BOLT4.Prim
import Lightning.Protocol.BOLT4.Types
data RejectReason
= InvalidVersion !Word8
| InvalidEphemeralKey
| HmacMismatch
| InvalidPayload !String
deriving (RejectReason -> RejectReason -> Bool
(RejectReason -> RejectReason -> Bool)
-> (RejectReason -> RejectReason -> Bool) -> Eq RejectReason
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RejectReason -> RejectReason -> Bool
== :: RejectReason -> RejectReason -> Bool
$c/= :: RejectReason -> RejectReason -> Bool
/= :: RejectReason -> RejectReason -> Bool
Eq, Int -> RejectReason -> ShowS
[RejectReason] -> ShowS
RejectReason -> String
(Int -> RejectReason -> ShowS)
-> (RejectReason -> String)
-> ([RejectReason] -> ShowS)
-> Show RejectReason
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RejectReason -> ShowS
showsPrec :: Int -> RejectReason -> ShowS
$cshow :: RejectReason -> String
show :: RejectReason -> String
$cshowList :: [RejectReason] -> ShowS
showList :: [RejectReason] -> ShowS
Show, (forall x. RejectReason -> Rep RejectReason x)
-> (forall x. Rep RejectReason x -> RejectReason)
-> Generic RejectReason
forall x. Rep RejectReason x -> RejectReason
forall x. RejectReason -> Rep RejectReason x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. RejectReason -> Rep RejectReason x
from :: forall x. RejectReason -> Rep RejectReason x
$cto :: forall x. Rep RejectReason x -> RejectReason
to :: forall x. Rep RejectReason x -> RejectReason
Generic)
process
:: BS.ByteString
-> OnionPacket
-> BS.ByteString
-> Either RejectReason ProcessResult
process :: ByteString
-> OnionPacket -> ByteString -> Either RejectReason ProcessResult
process !ByteString
secKey !OnionPacket
packet !ByteString
assocData = do
OnionPacket -> Either RejectReason ()
validateVersion OnionPacket
packet
ephemeral <- OnionPacket -> Either RejectReason Projective
parseEphemeralKey OnionPacket
packet
ss <- case computeSharedSecret secKey ephemeral of
Maybe SharedSecret
Nothing -> RejectReason -> Either RejectReason SharedSecret
forall a b. a -> Either a b
Left RejectReason
InvalidEphemeralKey
Just SharedSecret
s -> SharedSecret -> Either RejectReason SharedSecret
forall a b. b -> Either a b
Right SharedSecret
s
let !muKey = SharedSecret -> DerivedKey
deriveMu SharedSecret
ss
!rhoKey = SharedSecret -> DerivedKey
deriveRho SharedSecret
ss
if not (verifyPacketHmac muKey packet assocData)
then Left HmacMismatch
else pure ()
let !decrypted = DerivedKey -> ByteString -> ByteString
decryptPayloads DerivedKey
rhoKey (OnionPacket -> ByteString
opHopPayloads OnionPacket
packet)
(payloadBytes, nextHmac, remaining) <- extractPayload decrypted
hopPayload <- case decodeHopPayload payloadBytes of
Maybe HopPayload
Nothing -> RejectReason -> Either RejectReason HopPayload
forall a b. a -> Either a b
Left (String -> RejectReason
InvalidPayload String
"failed to decode TLV")
Just HopPayload
hp -> HopPayload -> Either RejectReason HopPayload
forall a b. b -> Either a b
Right HopPayload
hp
let SharedSecret ssBytes = ss
if isFinalHop nextHmac
then Right $! Receive $! ReceiveInfo
{ riPayload = hopPayload
, riSharedSecret = ssBytes
}
else do
nextPacket <- case prepareForward ephemeral ss remaining nextHmac of
Maybe OnionPacket
Nothing -> RejectReason -> Either RejectReason OnionPacket
forall a b. a -> Either a b
Left RejectReason
InvalidEphemeralKey
Just OnionPacket
np -> OnionPacket -> Either RejectReason OnionPacket
forall a b. b -> Either a b
Right OnionPacket
np
Right $! Forward $! ForwardInfo
{ fiNextPacket = nextPacket
, fiPayload = hopPayload
, fiSharedSecret = ssBytes
}
validateVersion :: OnionPacket -> Either RejectReason ()
validateVersion :: OnionPacket -> Either RejectReason ()
validateVersion !OnionPacket
packet
| OnionPacket -> Word8
opVersion OnionPacket
packet Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
versionByte = () -> Either RejectReason ()
forall a b. b -> Either a b
Right ()
| Bool
otherwise = RejectReason -> Either RejectReason ()
forall a b. a -> Either a b
Left (Word8 -> RejectReason
InvalidVersion (OnionPacket -> Word8
opVersion OnionPacket
packet))
{-# INLINE validateVersion #-}
parseEphemeralKey :: OnionPacket -> Either RejectReason Secp256k1.Projective
parseEphemeralKey :: OnionPacket -> Either RejectReason Projective
parseEphemeralKey !OnionPacket
packet =
case ByteString -> Maybe Projective
Secp256k1.parse_point (OnionPacket -> ByteString
opEphemeralKey OnionPacket
packet) of
Maybe Projective
Nothing -> RejectReason -> Either RejectReason Projective
forall a b. a -> Either a b
Left RejectReason
InvalidEphemeralKey
Just Projective
pub -> Projective -> Either RejectReason Projective
forall a b. b -> Either a b
Right Projective
pub
{-# INLINE parseEphemeralKey #-}
decryptPayloads
:: DerivedKey
-> BS.ByteString
-> BS.ByteString
decryptPayloads :: DerivedKey -> ByteString -> ByteString
decryptPayloads !DerivedKey
rhoKey !ByteString
payloads =
let !streamLen :: Int
streamLen = Int
2 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
hopPayloadsSize
!stream :: ByteString
stream = DerivedKey -> Int -> ByteString
generateStream DerivedKey
rhoKey Int
streamLen
!extended :: ByteString
extended = ByteString
payloads ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Int -> Word8 -> ByteString
BS.replicate Int
hopPayloadsSize Word8
0
in ByteString -> ByteString -> ByteString
xorBytes ByteString
stream ByteString
extended
{-# INLINE decryptPayloads #-}
xorBytes :: BS.ByteString -> BS.ByteString -> BS.ByteString
xorBytes :: ByteString -> ByteString -> ByteString
xorBytes !ByteString
a !ByteString
b = [Word8] -> ByteString
BS.pack ((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 #-}
extractPayload
:: BS.ByteString
-> Either RejectReason (BS.ByteString, BS.ByteString, BS.ByteString)
!ByteString
decrypted = do
(len, afterLen) <- case ByteString -> Maybe (Word64, ByteString)
decodeBigSize ByteString
decrypted of
Maybe (Word64, ByteString)
Nothing -> RejectReason -> Either RejectReason (Int, ByteString)
forall a b. a -> Either a b
Left (String -> RejectReason
InvalidPayload String
"invalid length prefix")
Just (Word64
l, ByteString
r) -> (Int, ByteString) -> Either RejectReason (Int, ByteString)
forall a b. b -> Either a b
Right (Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
l :: Int, ByteString
r)
if len > BS.length afterLen
then Left (InvalidPayload "payload length exceeds buffer")
else if len == 0
then Left (InvalidPayload "zero-length payload")
else pure ()
let !payloadBytes = Int -> ByteString -> ByteString
BS.take Int
len ByteString
afterLen
!afterPayload = Int -> ByteString -> ByteString
BS.drop Int
len ByteString
afterLen
if BS.length afterPayload < hmacSize
then Left (InvalidPayload "insufficient bytes for HMAC")
else do
let !nextHmac = Int -> ByteString -> ByteString
BS.take Int
hmacSize ByteString
afterPayload
!remaining = Int -> ByteString -> ByteString
BS.drop Int
hmacSize ByteString
afterPayload
Right (payloadBytes, nextHmac, remaining)
verifyPacketHmac
:: DerivedKey
-> OnionPacket
-> BS.ByteString
-> Bool
verifyPacketHmac :: DerivedKey -> OnionPacket -> ByteString -> Bool
verifyPacketHmac !DerivedKey
muKey !OnionPacket
packet !ByteString
assocData =
let !computed :: ByteString
computed = DerivedKey -> ByteString -> ByteString -> ByteString
computeHmac DerivedKey
muKey (OnionPacket -> ByteString
opHopPayloads OnionPacket
packet) ByteString
assocData
in ByteString -> ByteString -> Bool
verifyHmac (OnionPacket -> ByteString
opHmac OnionPacket
packet) ByteString
computed
{-# INLINE verifyPacketHmac #-}
prepareForward
:: Secp256k1.Projective
-> SharedSecret
-> BS.ByteString
-> BS.ByteString
-> Maybe OnionPacket
prepareForward :: Projective
-> SharedSecret -> ByteString -> ByteString -> Maybe OnionPacket
prepareForward !Projective
ephemeral !SharedSecret
ss !ByteString
remaining !ByteString
nextHmac = do
let !bf :: BlindingFactor
bf = Projective -> SharedSecret -> BlindingFactor
computeBlindingFactor Projective
ephemeral SharedSecret
ss
newEphemeral <- Projective -> BlindingFactor -> Maybe Projective
blindPubKey Projective
ephemeral BlindingFactor
bf
let !newEphBytes = Projective -> ByteString
Secp256k1.serialize_point Projective
newEphemeral
let !newPayloads = Int -> ByteString -> ByteString
BS.take Int
hopPayloadsSize ByteString
remaining
pure $! OnionPacket
{ opVersion = versionByte
, opEphemeralKey = newEphBytes
, opHopPayloads = newPayloads
, opHmac = nextHmac
}
isFinalHop :: BS.ByteString -> Bool
isFinalHop :: ByteString -> Bool
isFinalHop !ByteString
hmac = ByteString
hmac ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> Word8 -> ByteString
BS.replicate Int
hmacSize Word8
0
{-# INLINE isFinalHop #-}