ppad-bolt8-0.0.1: Encrypted and authenticated transport per BOLT #8
Copyright(c) 2025 Jared Tobin
LicenseMIT
MaintainerJared Tobin <jared@ppad.tech>
Safe HaskellNone
LanguageHaskell2010

Lightning.Protocol.BOLT8.Internal

Description

Internal module exporting all constructors for testing and benchmarking. Prefer Lightning.Protocol.BOLT8 for general use.

Synopsis

Keys

newtype Sec Source #

Secret key (32 bytes).

The Eq instance compares in constant time.

Constructors

Sec ByteString 

Instances

Instances details
Generic Sec Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Associated Types

type Rep Sec 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Sec = D1 ('MetaData "Sec" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "Sec" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))

Methods

from :: Sec -> Rep Sec x #

to :: Rep Sec x -> Sec #

Eq Sec Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Methods

(==) :: Sec -> Sec -> Bool #

(/=) :: Sec -> Sec -> Bool #

type Rep Sec Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Sec = D1 ('MetaData "Sec" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "Sec" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))

newtype Pub Source #

Compressed public key.

Constructors

Pub Projective 

Instances

Instances details
Show Pub Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Methods

showsPrec :: Int -> Pub -> ShowS #

show :: Pub -> String #

showList :: [Pub] -> ShowS #

Eq Pub Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Methods

(==) :: Pub -> Pub -> Bool #

(/=) :: Pub -> Pub -> Bool #

keypair :: ByteString -> Maybe (Sec, Pub) Source #

Derive a keypair from 32 bytes of entropy.

Returns Nothing if the entropy is invalid (zero or >= curve order).

>>> let ent = BS.replicate 32 0x11
>>> case keypair ent of
...   Just _ -> "ok"
...   Nothing -> "fail"
"ok"
>>> keypair (BS.replicate 31 0x11) -- wrong length
Nothing

parse_pub :: ByteString -> Maybe Pub Source #

Parse a 33-byte compressed public key.

>>> let Just (_, pub) = keypair (BS.replicate 32 0x11)
>>> let bytes = serialize_pub pub
>>> case parse_pub bytes of
...   Just _ -> "ok"
...   Nothing -> "fail"
"ok"
>>> parse_pub (BS.replicate 32 0x00) -- wrong length
Nothing

serialize_pub :: Pub -> ByteString Source #

Serialize a public key to 33-byte compressed form.

>>> let Just (_, pub) = keypair (BS.replicate 32 0x11)
>>> BS.length (serialize_pub pub)
33

Newtypes

newtype Key32 Source #

A 32-byte key, validated at construction.

The Eq instance compares in constant time.

Constructors

Key32 

Fields

Instances

Instances details
Generic Key32 Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Associated Types

type Rep Key32 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Key32 = D1 ('MetaData "Key32" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "Key32" 'PrefixI 'True) (S1 ('MetaSel ('Just "unKey32") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))

Methods

from :: Key32 -> Rep Key32 x #

to :: Rep Key32 x -> Key32 #

Eq Key32 Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Methods

(==) :: Key32 -> Key32 -> Bool #

(/=) :: Key32 -> Key32 -> Bool #

type Rep Key32 Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Key32 = D1 ('MetaData "Key32" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "Key32" 'PrefixI 'True) (S1 ('MetaSel ('Just "unKey32") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))

key32 :: ByteString -> Maybe Key32 Source #

Construct a Key32 from a 32-byte ByteString.

Returns Nothing if the input is not exactly 32 bytes.

>>> key32 (BS.replicate 32 0x00)
Just (Key32 {unKey32 = ...})
>>> key32 (BS.replicate 31 0x00)
Nothing

unsafeKey32 :: ByteString -> Key32 Source #

Construct a Key32 without validation.

For test and benchmark use only; prefer key32.

newtype SessionNonce Source #

Session nonce, distinguishing send from receive direction.

Constructors

SessionNonce 

Instances

Instances details
Generic SessionNonce Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Associated Types

type Rep SessionNonce 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep SessionNonce = D1 ('MetaData "SessionNonce" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "SessionNonce" 'PrefixI 'True) (S1 ('MetaSel ('Just "unSessionNonce") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Word64)))
Eq SessionNonce Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep SessionNonce Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep SessionNonce = D1 ('MetaData "SessionNonce" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "SessionNonce" 'PrefixI 'True) (S1 ('MetaSel ('Just "unSessionNonce") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Word64)))

newtype MessagePayload Source #

Message payload (max 65535 bytes), validated at construction.

Instances

Instances details
Generic MessagePayload Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Associated Types

type Rep MessagePayload 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep MessagePayload = D1 ('MetaData "MessagePayload" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "MessagePayload" 'PrefixI 'True) (S1 ('MetaSel ('Just "unMessagePayload") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))
Eq MessagePayload Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep MessagePayload Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep MessagePayload = D1 ('MetaData "MessagePayload" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'True) (C1 ('MetaCons "MessagePayload" 'PrefixI 'True) (S1 ('MetaSel ('Just "unMessagePayload") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))

mkMessagePayload :: ByteString -> Either Error MessagePayload Source #

Construct a MessagePayload from a ByteString.

Returns Left if the payload exceeds 65535 bytes.

Handshake roles

data Initiator Source #

Phantom type for initiator role.

data Responder Source #

Phantom type for responder role.

data HandshakeFor a Source #

Role-indexed handshake state.

The phantom type parameter prevents passing an initiator's state to a responder function and vice versa.

Constructors

HandshakeFor 

Handshake (initiator)

act1 :: Sec -> Pub -> Pub -> ByteString -> Either Error (ByteString, HandshakeFor Initiator) Source #

Initiator: generate Act 1 message (50 bytes).

Takes local static key, remote static pubkey, and 32 bytes of entropy for ephemeral key generation.

Returns the 50-byte Act 1 message and handshake state for Act 3.

>>> let Just (i_sec, i_pub) = keypair (BS.replicate 32 0x11)
>>> let Just (r_sec, r_pub) = keypair (BS.replicate 32 0x21)
>>> let eph_ent = BS.replicate 32 0x12
>>> case act1 i_sec i_pub r_pub eph_ent of { Right (msg, _) -> BS.length msg; Left _ -> 0 }
50

act3 :: HandshakeFor Initiator -> ByteString -> Either Error (ByteString, Handshake) Source #

Initiator: process Act 2 and generate Act 3 (66 bytes), completing the handshake.

Returns the 66-byte Act 3 message and the handshake result.

>>> let Just (i_sec, i_pub) = keypair (BS.replicate 32 0x11)
>>> let Just (r_sec, r_pub) = keypair (BS.replicate 32 0x21)
>>> let Right (msg1, i_hs) = act1 i_sec i_pub r_pub (BS.replicate 32 0x12)
>>> let Right (msg2, _) = act2 r_sec r_pub (BS.replicate 32 0x22) msg1
>>> case act3 i_hs msg2 of { Right (msg, _) -> BS.length msg; Left _ -> 0 }
66

Handshake (responder)

act2 :: Sec -> Pub -> ByteString -> ByteString -> Either Error (ByteString, HandshakeFor Responder) Source #

Responder: process Act 1 and generate Act 2 message (50 bytes).

Takes local static key and 32 bytes of entropy for ephemeral key, plus the 50-byte Act 1 message from initiator.

Returns the 50-byte Act 2 message and handshake state for finalize.

>>> let Just (i_sec, i_pub) = keypair (BS.replicate 32 0x11)
>>> let Just (r_sec, r_pub) = keypair (BS.replicate 32 0x21)
>>> let Right (msg1, _) = act1 i_sec i_pub r_pub (BS.replicate 32 0x12)
>>> case act2 r_sec r_pub (BS.replicate 32 0x22) msg1 of { Right (msg, _) -> BS.length msg; Left _ -> 0 }
50

finalize :: HandshakeFor Responder -> ByteString -> Either Error Handshake Source #

Responder: process Act 3 (66 bytes) and complete the handshake.

Returns the handshake result with authenticated remote static pubkey.

>>> let Just (i_sec, i_pub) = keypair (BS.replicate 32 0x11)
>>> let Just (r_sec, r_pub) = keypair (BS.replicate 32 0x21)
>>> let Right (msg1, i_hs) = act1 i_sec i_pub r_pub (BS.replicate 32 0x12)
>>> let Right (msg2, r_hs) = act2 r_sec r_pub (BS.replicate 32 0x22) msg1
>>> let Right (msg3, _) = act3 i_hs msg2
>>> case finalize r_hs msg3 of { Right _ -> "ok"; Left e -> show e }
"ok"

Session

data Session Source #

Post-handshake session state.

Constructors

Session 

Fields

Instances

Instances details
Generic Session Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Associated Types

type Rep Session 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Methods

from :: Session -> Rep Session x #

to :: Rep Session x -> Session #

type Rep Session Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

data HandshakeState Source #

Internal handshake state (exported for benchmarking).

Constructors

HandshakeState 

Fields

Instances

Instances details
Generic HandshakeState Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep HandshakeState Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

data Handshake Source #

Result of a successful handshake.

Constructors

Handshake 

Fields

Instances

Instances details
Generic Handshake Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Associated Types

type Rep Handshake 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Handshake = D1 ('MetaData "Handshake" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'False) (C1 ('MetaCons "Handshake" 'PrefixI 'True) (S1 ('MetaSel ('Just "session") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Session) :*: S1 ('MetaSel ('Just "remote_static") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Pub)))
type Rep Handshake Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Handshake = D1 ('MetaData "Handshake" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'False) (C1 ('MetaCons "Handshake" 'PrefixI 'True) (S1 ('MetaSel ('Just "session") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Session) :*: S1 ('MetaSel ('Just "remote_static") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Pub)))

encrypt :: Session -> ByteString -> Either Error (ByteString, Session) Source #

Encrypt a message (max 65535 bytes).

Returns the encrypted packet and updated session. Key rotation is handled automatically at nonce 1000.

Wire format: encrypted_length (2) || MAC (16) || encrypted_body || MAC (16)

>>> let Just (i_sec, i_pub) = keypair (BS.replicate 32 0x11)
>>> let Just (r_sec, r_pub) = keypair (BS.replicate 32 0x21)
>>> let Right (msg1, i_hs) = act1 i_sec i_pub r_pub (BS.replicate 32 0x12)
>>> let Right (msg2, _) = act2 r_sec r_pub (BS.replicate 32 0x22) msg1
>>> let Right (_, i_result) = act3 i_hs msg2
>>> let sess = session i_result
>>> case encrypt sess "hello" of { Right (ct, _) -> BS.length ct; Left _ -> 0 }
39

decrypt :: Session -> ByteString -> Either Error (ByteString, Session) Source #

Decrypt a message, requiring an exact packet with no trailing bytes.

Returns the plaintext and updated session. Key rotation is handled automatically at nonce 1000.

This is a strict variant that rejects any trailing data. For streaming use cases where you need to handle multiple frames in a buffer, use decrypt_frame instead.

>>> let Just (i_sec, i_pub) = keypair (BS.replicate 32 0x11)
>>> let Just (r_sec, r_pub) = keypair (BS.replicate 32 0x21)
>>> let Right (msg1, i_hs) = act1 i_sec i_pub r_pub (BS.replicate 32 0x12)
>>> let Right (msg2, r_hs) = act2 r_sec r_pub (BS.replicate 32 0x22) msg1
>>> let Right (msg3, i_result) = act3 i_hs msg2
>>> let Right r_result = finalize r_hs msg3
>>> let Right (ct, _) = encrypt (session i_result) "hello"
>>> case decrypt (session r_result) ct of { Right (pt, _) -> pt; Left _ -> "fail" }
"hello"

decrypt_frame :: Session -> ByteString -> Either Error (ByteString, ByteString, Session) Source #

Decrypt a single frame from a buffer, returning the remainder.

Returns the plaintext, any unconsumed bytes, and the updated session. Key rotation is handled automatically every 1000 messages.

This is useful for streaming scenarios where multiple messages may be buffered together. The remainder can be passed to the next call to decrypt_frame.

Wire format consumed: encrypted_length (18) || encrypted_body (len + 16)

>>> let Just (i_sec, i_pub) = keypair (BS.replicate 32 0x11)
>>> let Just (r_sec, r_pub) = keypair (BS.replicate 32 0x21)
>>> let Right (msg1, i_hs) = act1 i_sec i_pub r_pub (BS.replicate 32 0x12)
>>> let Right (msg2, r_hs) = act2 r_sec r_pub (BS.replicate 32 0x22) msg1
>>> let Right (msg3, i_result) = act3 i_hs msg2
>>> let Right r_result = finalize r_hs msg3
>>> let Right (ct, _) = encrypt (session i_result) "hello"
>>> case decrypt_frame (session r_result) ct of { Right (pt, rem, _) -> (pt, BS.null rem); Left _ -> ("fail", False) }
("hello",True)

decrypt_frame_partial :: Session -> ByteString -> FrameResult Source #

Decrypt a frame from a partial buffer, indicating when more data needed.

Unlike decrypt_frame, this function handles incomplete buffers gracefully by returning NeedMore with the number of additional bytes required to make progress.

  • If the buffer has fewer than 18 bytes (encrypted length + MAC), returns NeedMore n where n is the bytes still needed.
  • If the length header is complete but the body is incomplete, returns NeedMore n with bytes needed for the full frame.
  • MAC or decryption failures return FrameError.
  • A complete, valid frame returns FrameOk with plaintext, remainder, and updated session.

This is useful for non-blocking I/O where data arrives incrementally.

data FrameResult Source #

Result of attempting to decrypt a frame from a partial buffer.

Constructors

NeedMore !Int

More bytes needed; the Int is the minimum additional bytes required.

FrameOk !ByteString !ByteString !Session

Successfully decrypted: plaintext, remainder, updated session.

FrameError !Error

Decryption failed with the given error.

Instances

Instances details
Generic FrameResult Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep FrameResult Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Errors

data Error Source #

Handshake errors.

Instances

Instances details
Generic Error Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Associated Types

type Rep Error 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Error = D1 ('MetaData "Error" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'False) ((C1 ('MetaCons "InvalidKey" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "InvalidPub" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "InvalidMAC" 'PrefixI 'False) (U1 :: Type -> Type))) :+: (C1 ('MetaCons "InvalidVersion" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "InvalidLength" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "DecryptionFailed" 'PrefixI 'False) (U1 :: Type -> Type))))

Methods

from :: Error -> Rep Error x #

to :: Rep Error x -> Error #

Show Error Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Methods

showsPrec :: Int -> Error -> ShowS #

show :: Error -> String #

showList :: [Error] -> ShowS #

Eq Error Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

Methods

(==) :: Error -> Error -> Bool #

(/=) :: Error -> Error -> Bool #

type Rep Error Source # 
Instance details

Defined in Lightning.Protocol.BOLT8.Internal

type Rep Error = D1 ('MetaData "Error" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'False) ((C1 ('MetaCons "InvalidKey" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "InvalidPub" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "InvalidMAC" 'PrefixI 'False) (U1 :: Type -> Type))) :+: (C1 ('MetaCons "InvalidVersion" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "InvalidLength" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "DecryptionFailed" 'PrefixI 'False) (U1 :: Type -> Type))))