-- | Signed Envelopes (RFC 0002) for domain-separated signed payloads.
--
-- A signed envelope wraps a payload with:
--   - Public key of the signer
--   - Domain string (prevents cross-protocol signature reuse)
--   - Payload type (multicodec)
--   - Payload bytes
--   - Signature over domain-separated content
--
-- Signing content (RFC 0002): [varint(len(domain))][domain][varint(len(payload_type))][payload_type][varint(len(payload))][payload]
-- Wire format (protobuf):
--   field 1: public_key (bytes, protobuf-encoded PublicKey)
--   field 2: payload_type (bytes, multicodec)
--   field 3: payload (bytes)
--   field 5: signature (bytes)
module Network.LibP2P.Crypto.SignedEnvelope
  ( SignedEnvelope (..)
  , createEnvelope
  , verifyEnvelope
  , encodeSignedEnvelope
  , decodeSignedEnvelope
  , buildSigningContent  -- exported for testing
  ) where

import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Data.Word (Word64)
import Network.LibP2P.Core.Varint (encodeUvarint)
import Network.LibP2P.Crypto.Key (PublicKey (..), PrivateKey, sign, verify)
import Network.LibP2P.Crypto.Protobuf (encodePublicKey, decodePublicKey)
import qualified Proto3.Wire.Decode as Decode
import Proto3.Wire.Decode (Parser, RawMessage, at, one, parse)
import qualified Proto3.Wire.Encode as Encode
import Proto3.Wire.Types (FieldNumber (..))

-- | A signed envelope containing a domain-separated signed payload.
data SignedEnvelope = SignedEnvelope
  { SignedEnvelope -> PublicKey
sePublicKey   :: !PublicKey     -- ^ Signer's public key
  , SignedEnvelope -> ByteString
seDomain      :: !ByteString    -- ^ Domain separation string
  , SignedEnvelope -> ByteString
sePayloadType :: !ByteString    -- ^ Payload type (multicodec bytes)
  , SignedEnvelope -> ByteString
sePayload     :: !ByteString    -- ^ Payload bytes
  , SignedEnvelope -> ByteString
seSignature   :: !ByteString    -- ^ Ed25519 signature
  } deriving (Int -> SignedEnvelope -> ShowS
[SignedEnvelope] -> ShowS
SignedEnvelope -> [Char]
(Int -> SignedEnvelope -> ShowS)
-> (SignedEnvelope -> [Char])
-> ([SignedEnvelope] -> ShowS)
-> Show SignedEnvelope
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SignedEnvelope -> ShowS
showsPrec :: Int -> SignedEnvelope -> ShowS
$cshow :: SignedEnvelope -> [Char]
show :: SignedEnvelope -> [Char]
$cshowList :: [SignedEnvelope] -> ShowS
showList :: [SignedEnvelope] -> ShowS
Show, SignedEnvelope -> SignedEnvelope -> Bool
(SignedEnvelope -> SignedEnvelope -> Bool)
-> (SignedEnvelope -> SignedEnvelope -> Bool) -> Eq SignedEnvelope
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SignedEnvelope -> SignedEnvelope -> Bool
== :: SignedEnvelope -> SignedEnvelope -> Bool
$c/= :: SignedEnvelope -> SignedEnvelope -> Bool
/= :: SignedEnvelope -> SignedEnvelope -> Bool
Eq)

-- | Build the content that gets signed (RFC 0002).
-- Format: [varint(len(domain))][domain][varint(len(payload_type))][payload_type][varint(len(payload))][payload]
-- Each field is independently varint-length-prefixed.
buildSigningContent :: ByteString -> ByteString -> ByteString -> ByteString
buildSigningContent :: ByteString -> ByteString -> ByteString -> ByteString
buildSigningContent ByteString
domain ByteString
payloadType ByteString
payload =
  let lenPrefix :: ByteString -> ByteString
lenPrefix ByteString
bs = Word64 -> ByteString
encodeUvarint (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
BS.length ByteString
bs) :: Word64) ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
bs
  in ByteString -> ByteString
lenPrefix ByteString
domain ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteString
lenPrefix ByteString
payloadType ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteString
lenPrefix ByteString
payload

-- | Create a signed envelope.
createEnvelope :: PrivateKey -> PublicKey -> ByteString -> ByteString -> ByteString -> Either String SignedEnvelope
createEnvelope :: PrivateKey
-> PublicKey
-> ByteString
-> ByteString
-> ByteString
-> Either [Char] SignedEnvelope
createEnvelope PrivateKey
privKey PublicKey
pubKey ByteString
domain ByteString
payloadType ByteString
payload = do
  let content :: ByteString
content = ByteString -> ByteString -> ByteString -> ByteString
buildSigningContent ByteString
domain ByteString
payloadType ByteString
payload
  sig <- PrivateKey -> ByteString -> Either [Char] ByteString
sign PrivateKey
privKey ByteString
content
  Right SignedEnvelope
    { sePublicKey   = pubKey
    , seDomain      = domain
    , sePayloadType = payloadType
    , sePayload     = payload
    , seSignature   = sig
    }

-- | Verify a signed envelope against an expected domain.
-- Reconstructs the signing content and verifies the signature.
verifyEnvelope :: SignedEnvelope -> ByteString -> Bool
verifyEnvelope :: SignedEnvelope -> ByteString -> Bool
verifyEnvelope SignedEnvelope
env ByteString
expectedDomain =
  let content :: ByteString
content = ByteString -> ByteString -> ByteString -> ByteString
buildSigningContent ByteString
expectedDomain (SignedEnvelope -> ByteString
sePayloadType SignedEnvelope
env) (SignedEnvelope -> ByteString
sePayload SignedEnvelope
env)
  in PublicKey -> ByteString -> ByteString -> Bool
verify (SignedEnvelope -> PublicKey
sePublicKey SignedEnvelope
env) ByteString
content (SignedEnvelope -> ByteString
seSignature SignedEnvelope
env)

-- | Encode a signed envelope to protobuf wire format.
-- Note: domain is NOT included in the wire format (it's implicit from protocol context).
-- Fields: 1=public_key, 2=payload_type, 3=payload, 5=signature
encodeSignedEnvelope :: SignedEnvelope -> ByteString
encodeSignedEnvelope :: SignedEnvelope -> ByteString
encodeSignedEnvelope SignedEnvelope
env = LazyByteString -> ByteString
BL.toStrict (LazyByteString -> ByteString) -> LazyByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ MessageBuilder -> LazyByteString
Encode.toLazyByteString (MessageBuilder -> LazyByteString)
-> MessageBuilder -> LazyByteString
forall a b. (a -> b) -> a -> b
$
     FieldNumber -> ByteString -> MessageBuilder
Encode.byteString (Word64 -> FieldNumber
FieldNumber Word64
1) (PublicKey -> ByteString
encodePublicKey (SignedEnvelope -> PublicKey
sePublicKey SignedEnvelope
env))
  MessageBuilder -> MessageBuilder -> MessageBuilder
forall a. Semigroup a => a -> a -> a
<> FieldNumber -> ByteString -> MessageBuilder
Encode.byteString (Word64 -> FieldNumber
FieldNumber Word64
2) (SignedEnvelope -> ByteString
sePayloadType SignedEnvelope
env)
  MessageBuilder -> MessageBuilder -> MessageBuilder
forall a. Semigroup a => a -> a -> a
<> FieldNumber -> ByteString -> MessageBuilder
Encode.byteString (Word64 -> FieldNumber
FieldNumber Word64
3) (SignedEnvelope -> ByteString
sePayload SignedEnvelope
env)
  MessageBuilder -> MessageBuilder -> MessageBuilder
forall a. Semigroup a => a -> a -> a
<> FieldNumber -> ByteString -> MessageBuilder
Encode.byteString (Word64 -> FieldNumber
FieldNumber Word64
5) (SignedEnvelope -> ByteString
seSignature SignedEnvelope
env)

-- | Decode a signed envelope from protobuf wire format.
-- Note: domain is NOT in the wire format — caller must know it.
decodeSignedEnvelope :: ByteString -> Either String SignedEnvelope
decodeSignedEnvelope :: ByteString -> Either [Char] SignedEnvelope
decodeSignedEnvelope ByteString
bs =
  case Parser RawMessage (ByteString, ByteString, ByteString, ByteString)
-> ByteString
-> Either
     ParseError (ByteString, ByteString, ByteString, ByteString)
forall a. Parser RawMessage a -> ByteString -> Either ParseError a
parse Parser RawMessage (ByteString, ByteString, ByteString, ByteString)
signedEnvelopeParser ByteString
bs of
    Left ParseError
err -> [Char] -> Either [Char] SignedEnvelope
forall a b. a -> Either a b
Left ([Char] -> Either [Char] SignedEnvelope)
-> [Char] -> Either [Char] SignedEnvelope
forall a b. (a -> b) -> a -> b
$ [Char]
"SignedEnvelope decode error: " [Char] -> ShowS
forall a. [a] -> [a] -> [a]
++ ParseError -> [Char]
forall a. Show a => a -> [Char]
show ParseError
err
    Right (ByteString
pkBytes, ByteString
ptBytes, ByteString
payload, ByteString
sig) -> do
      pk <- ByteString -> Either [Char] PublicKey
decodePublicKey ByteString
pkBytes
      Right SignedEnvelope
        { sePublicKey   = pk
        , seDomain      = BS.empty  -- domain not stored on wire
        , sePayloadType = ptBytes
        , sePayload     = payload
        , seSignature   = sig
        }

signedEnvelopeParser :: Parser RawMessage (ByteString, ByteString, ByteString, ByteString)
signedEnvelopeParser :: Parser RawMessage (ByteString, ByteString, ByteString, ByteString)
signedEnvelopeParser = (,,,)
  (ByteString
 -> ByteString
 -> ByteString
 -> ByteString
 -> (ByteString, ByteString, ByteString, ByteString))
-> Parser RawMessage ByteString
-> Parser
     RawMessage
     (ByteString
      -> ByteString
      -> ByteString
      -> (ByteString, ByteString, ByteString, ByteString))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Parser RawField ByteString
-> FieldNumber -> Parser RawMessage ByteString
forall a. Parser RawField a -> FieldNumber -> Parser RawMessage a
at (Parser RawPrimitive ByteString
-> ByteString -> Parser RawField ByteString
forall a. Parser RawPrimitive a -> a -> Parser RawField a
one Parser RawPrimitive ByteString
Decode.byteString ByteString
BS.empty) (Word64 -> FieldNumber
FieldNumber Word64
1)  -- public_key (protobuf-encoded)
  Parser
  RawMessage
  (ByteString
   -> ByteString
   -> ByteString
   -> (ByteString, ByteString, ByteString, ByteString))
-> Parser RawMessage ByteString
-> Parser
     RawMessage
     (ByteString
      -> ByteString -> (ByteString, ByteString, ByteString, ByteString))
forall a b.
Parser RawMessage (a -> b)
-> Parser RawMessage a -> Parser RawMessage b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Parser RawField ByteString
-> FieldNumber -> Parser RawMessage ByteString
forall a. Parser RawField a -> FieldNumber -> Parser RawMessage a
at (Parser RawPrimitive ByteString
-> ByteString -> Parser RawField ByteString
forall a. Parser RawPrimitive a -> a -> Parser RawField a
one Parser RawPrimitive ByteString
Decode.byteString ByteString
BS.empty) (Word64 -> FieldNumber
FieldNumber Word64
2)  -- payload_type
  Parser
  RawMessage
  (ByteString
   -> ByteString -> (ByteString, ByteString, ByteString, ByteString))
-> Parser RawMessage ByteString
-> Parser
     RawMessage
     (ByteString -> (ByteString, ByteString, ByteString, ByteString))
forall a b.
Parser RawMessage (a -> b)
-> Parser RawMessage a -> Parser RawMessage b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Parser RawField ByteString
-> FieldNumber -> Parser RawMessage ByteString
forall a. Parser RawField a -> FieldNumber -> Parser RawMessage a
at (Parser RawPrimitive ByteString
-> ByteString -> Parser RawField ByteString
forall a. Parser RawPrimitive a -> a -> Parser RawField a
one Parser RawPrimitive ByteString
Decode.byteString ByteString
BS.empty) (Word64 -> FieldNumber
FieldNumber Word64
3)  -- payload
  Parser
  RawMessage
  (ByteString -> (ByteString, ByteString, ByteString, ByteString))
-> Parser RawMessage ByteString
-> Parser
     RawMessage (ByteString, ByteString, ByteString, ByteString)
forall a b.
Parser RawMessage (a -> b)
-> Parser RawMessage a -> Parser RawMessage b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Parser RawField ByteString
-> FieldNumber -> Parser RawMessage ByteString
forall a. Parser RawField a -> FieldNumber -> Parser RawMessage a
at (Parser RawPrimitive ByteString
-> ByteString -> Parser RawField ByteString
forall a. Parser RawPrimitive a -> a -> Parser RawField a
one Parser RawPrimitive ByteString
Decode.byteString ByteString
BS.empty) (Word64 -> FieldNumber
FieldNumber Word64
5)  -- signature