| Copyright | (c) 2025 Jared Tobin |
|---|---|
| License | MIT |
| Maintainer | Jared Tobin <jared@ppad.tech> |
| Safe Haskell | None |
| Language | Haskell2010 |
Lightning.Protocol.BOLT8.Internal
Description
Internal module exporting all constructors for testing and benchmarking. Prefer Lightning.Protocol.BOLT8 for general use.
Synopsis
- newtype Sec = Sec ByteString
- newtype Pub = Pub Projective
- keypair :: ByteString -> Maybe (Sec, Pub)
- parse_pub :: ByteString -> Maybe Pub
- serialize_pub :: Pub -> ByteString
- newtype Key32 = Key32 {}
- key32 :: ByteString -> Maybe Key32
- unsafeKey32 :: ByteString -> Key32
- newtype SessionNonce = SessionNonce {}
- newtype MessagePayload = MessagePayload {}
- mkMessagePayload :: ByteString -> Either Error MessagePayload
- data Initiator
- data Responder
- data HandshakeFor a = HandshakeFor {}
- act1 :: Sec -> Pub -> Pub -> ByteString -> Either Error (ByteString, HandshakeFor Initiator)
- act3 :: HandshakeFor Initiator -> ByteString -> Either Error (ByteString, Handshake)
- act2 :: Sec -> Pub -> ByteString -> ByteString -> Either Error (ByteString, HandshakeFor Responder)
- finalize :: HandshakeFor Responder -> ByteString -> Either Error Handshake
- data Session = Session {}
- data HandshakeState = HandshakeState {}
- data Handshake = Handshake {
- session :: !Session
- remote_static :: !Pub
- encrypt :: Session -> ByteString -> Either Error (ByteString, Session)
- decrypt :: Session -> ByteString -> Either Error (ByteString, Session)
- decrypt_frame :: Session -> ByteString -> Either Error (ByteString, ByteString, Session)
- decrypt_frame_partial :: Session -> ByteString -> FrameResult
- data FrameResult
- = NeedMore !Int
- | FrameOk !ByteString !ByteString !Session
- | FrameError !Error
- data Error
Keys
Secret key (32 bytes).
The Eq instance compares in constant time.
Constructors
| Sec ByteString |
Instances
| Generic Sec Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
| |||||
| Eq Sec Source # | |||||
| type Rep Sec Source # | |||||
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))) | |||||
Compressed public key.
Constructors
| Pub Projective |
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 lengthNothing
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 lengthNothing
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
A 32-byte key, validated at construction.
The Eq instance compares in constant time.
Constructors
| Key32 | |
Fields | |
Instances
| Generic Key32 Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
| |||||
| Eq Key32 Source # | |||||
| type Rep Key32 Source # | |||||
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 #
newtype SessionNonce Source #
Session nonce, distinguishing send from receive direction.
Constructors
| SessionNonce | |
Fields | |
Instances
| Generic SessionNonce Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
| |||||
| Eq SessionNonce Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal | |||||
| type Rep SessionNonce Source # | |||||
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.
Constructors
| MessagePayload | |
Fields | |
Instances
mkMessagePayload :: ByteString -> Either Error MessagePayload Source #
Construct a MessagePayload from a ByteString.
Returns Left if the payload exceeds 65535 bytes.
Handshake roles
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 | |
Fields | |
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
Post-handshake session state.
Constructors
| Session | |
Instances
| Generic Session Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
| |||||
| type Rep Session Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal type Rep Session = D1 ('MetaData "Session" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'False) (C1 ('MetaCons "Session" 'PrefixI 'True) ((S1 ('MetaSel ('Just "sess_sk") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Key32) :*: (S1 ('MetaSel ('Just "sess_sn") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 SessionNonce) :*: S1 ('MetaSel ('Just "sess_sck") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Key32))) :*: (S1 ('MetaSel ('Just "sess_rk") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Key32) :*: (S1 ('MetaSel ('Just "sess_rn") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 SessionNonce) :*: S1 ('MetaSel ('Just "sess_rck") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Key32))))) | |||||
data HandshakeState Source #
Internal handshake state (exported for benchmarking).
Constructors
| HandshakeState | |
Fields
| |
Instances
| Generic HandshakeState Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
Methods from :: HandshakeState -> Rep HandshakeState x # to :: Rep HandshakeState x -> HandshakeState # | |||||
| type Rep HandshakeState Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal type Rep HandshakeState = D1 ('MetaData "HandshakeState" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'False) (C1 ('MetaCons "HandshakeState" 'PrefixI 'True) (((S1 ('MetaSel ('Just "hs_h") 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 ByteString) :*: S1 ('MetaSel ('Just "hs_ck") 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 ByteString)) :*: (S1 ('MetaSel ('Just "hs_temp_k") 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 ByteString) :*: S1 ('MetaSel ('Just "hs_e_sec") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Sec))) :*: ((S1 ('MetaSel ('Just "hs_e_pub") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Pub) :*: S1 ('MetaSel ('Just "hs_s_sec") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Sec)) :*: (S1 ('MetaSel ('Just "hs_s_pub") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Pub) :*: (S1 ('MetaSel ('Just "hs_re") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (Maybe Pub)) :*: S1 ('MetaSel ('Just "hs_rs") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (Maybe Pub))))))) | |||||
Result of a successful handshake.
Constructors
| Handshake | |
Fields
| |
Instances
| Generic Handshake Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
| |||||
| type Rep Handshake Source # | |||||
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
whereNeedMorennis the bytes still needed. - If the length header is complete but the body is
incomplete, returns
with bytes needed for the full frame.NeedMoren - MAC or decryption failures return
FrameError. - A complete, valid frame returns
FrameOkwith 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 |
| FrameOk !ByteString !ByteString !Session | Successfully decrypted: plaintext, remainder, updated session. |
| FrameError !Error | Decryption failed with the given error. |
Instances
| Generic FrameResult Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
| |||||
| type Rep FrameResult Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal type Rep FrameResult = D1 ('MetaData "FrameResult" "Lightning.Protocol.BOLT8.Internal" "ppad-bolt8-0.0.1-kgcD9OTXZh1niNa1K8fy4" 'False) (C1 ('MetaCons "NeedMore" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 Int)) :+: (C1 ('MetaCons "FrameOk" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 ByteString) :*: (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 ByteString) :*: S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Session))) :+: C1 ('MetaCons "FrameError" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Error)))) | |||||
Errors
Handshake errors.
Instances
| Generic Error Source # | |||||
Defined in Lightning.Protocol.BOLT8.Internal Associated Types
| |||||
| Show Error Source # | |||||
| Eq Error Source # | |||||
| type Rep Error Source # | |||||
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)))) | |||||