diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..ced9d3a2af --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "cbits/libbbs"] + path = cbits/libbbs + url = https://github.com/simplex-chat/libbbs.git +[submodule "cbits/blst"] + path = cbits/blst + url = https://github.com/supranational/blst.git diff --git a/cbits/blst b/cbits/blst new file mode 160000 index 0000000000..db3defd0d5 --- /dev/null +++ b/cbits/blst @@ -0,0 +1 @@ +Subproject commit db3defd0d501cc2bb1cdd59cb955d1a421821441 diff --git a/cbits/libbbs b/cbits/libbbs new file mode 160000 index 0000000000..ac7f5c711a --- /dev/null +++ b/cbits/libbbs @@ -0,0 +1 @@ +Subproject commit ac7f5c711a2a2ab08a0d36260c795f94e1501e41 diff --git a/plans/2026-06-01-bbs-bindings.md b/plans/2026-06-01-bbs-bindings.md new file mode 100644 index 0000000000..1ebff46406 --- /dev/null +++ b/plans/2026-06-01-bbs-bindings.md @@ -0,0 +1,127 @@ +# BBS+ Bindings for simplexmq + +Haskell FFI bindings to libbbs for BBS+ signatures. General-purpose - the module knows nothing about specific applications. + +## How BBS+ works + +BBS+ signs a fixed list of N messages. Each message is an arbitrary byte array. The signer signs all N messages at once with one signature. + +The holder of the signature can then generate a proof that selectively discloses some messages and hides others. The verifier learns the disclosed messages and confirms they were signed by the signer, but learns nothing about the hidden messages. Different proofs from the same signature are unlinkable. + +Key constraint: the total number of messages N is fixed at signing time. The verifier must know N. A proof generated from a 3-message signature cannot be verified as a 2-message proof. + +## Types + +```haskell +newtype BBSSecretKey = BBSSecretKey ByteString -- 32 bytes +newtype BBSPublicKey = BBSPublicKey ByteString -- 96 bytes (BLS12-381 G2 point) +newtype BBSSignature = BBSSignature ByteString -- 80 bytes +newtype BBSProof = BBSProof ByteString -- 272 + 32 * numUndisclosed bytes +newtype BBSHeader = BBSHeader ByteString -- always-disclosed context (e.g. protocol identifier) +newtype BBSPresHeader = BBSPresHeader ByteString -- random nonce for proof unlinkability +``` + +All newtypes get StrEncoding (base64url), ToJSON/FromJSON (via strToJSON/strParseJSON), Eq, Show. + +## Functions + +```haskell +bbsKeyGen :: IO (BBSSecretKey, BBSPublicKey) + +-- C order: sk, pk, header, messages +bbsSign + :: BBSSecretKey + -> BBSPublicKey + -> BBSHeader -- always-disclosed context + -> [ByteString] -- all N messages + -> IO (Either String BBSSignature) + +-- C order: pk, signature, header, presentation_header, disclosed_indexes, messages +bbsProofGen + :: BBSPublicKey + -> BBSSignature + -> BBSHeader -- must match what was signed + -> BBSPresHeader -- random nonce bound into the proof + -> [Int] -- disclosed indexes (0-based) + -> [ByteString] -- all N messages (needed internally, hidden ones not revealed in proof) + -> IO (Either String BBSProof) + +-- C order: pk, proof, header, presentation_header, disclosed_indexes, n, messages +bbsProofVerify + :: BBSPublicKey + -> BBSProof + -> BBSHeader -- must match what was signed + -> BBSPresHeader -- must match what was used in bbsProofGen + -> [Int] -- disclosed indexes + -> Int -- total message count N + -> [ByteString] -- disclosed messages only + -> IO Bool +``` + +## How applications use it + +An application defines: +- A message layout: which index means what +- Which indexes are disclosed vs hidden +- How to encode application values as ByteString messages + +### Badge example (in simplex-chat, not in this module) + +Message layout (always 3 messages): +- Index 0: master secret (32 random bytes) - HIDDEN +- Index 1: expiry (UTF-8 encoded timestamp string) - DISCLOSED +- Index 2: badge type (UTF-8 encoded, e.g. "supporter") - DISCLOSED + +Signing (v2, on the server): +``` +bbsSign sk pk header [ms, encodeUtf8 "2026-07-31", encodeUtf8 "supporter"] +``` + +Proof generation (v2, on the client): +``` +bbsProofGen pk sig header presHeader [1, 2] [ms, encodeUtf8 "2026-07-31", encodeUtf8 "supporter"] +``` + +Proof verification (v1, on the recipient): +``` +bbsProofVerify pk proof header presHeader 3 [1, 2] [encodeUtf8 "2026-07-31", encodeUtf8 "supporter"] +``` + +The recipient only sees the proof, presentationHeader, expiry string, and badge type string. They verify these were signed by the server (pk is hardcoded). They never see the master secret. + +Expiry is always present as a string. Monthly badges use a date like `"2026-07-31"`, lifetime badges use `"lifetime"`. BBS+ doesn't interpret the bytes - expiry semantics are the application's responsibility. This keeps the message count fixed at 3 for all badge types. + +## libbbs C API mapping + +```c +int bbs_keygen_full(ciphersuite, sk, pk) +int bbs_sign(ciphersuite, sk, pk, signature, header, header_len, n, messages, message_lens) +int bbs_proof_gen(ciphersuite, pk, signature, proof, header, header_len, presentation_header, presentation_header_len, disclosed_indexes, disclosed_indexes_len, n, messages, message_lens) +int bbs_proof_verify(ciphersuite, pk, proof, proof_len, header, header_len, presentation_header, presentation_header_len, disclosed_indexes, disclosed_indexes_len, n, messages, message_lens) +``` + +We use `bbs_sha256_ciphersuite`. The header parameter is exposed in all Haskell functions - the application decides what to put there. Tests use `"SimpleX"` as header. + +The `presentation_header` parameter is what we call `presentationHeader`. + +In `bbs_proof_verify`, the `n` parameter is the total number of messages (not the number of disclosed messages). The `messages` array contains only the disclosed messages, and `disclosed_indexes` maps each to its position in the original message list. + +## Build + +Submodules in cbits/: +- `cbits/libbbs` - https://github.com/Fraunhofer-AISEC/libbbs +- `cbits/blst` - https://github.com/supranational/blst (libbbs dependency) + +C sources in cabal: `cbits/blst/src/server.c`, `cbits/blst/build/assembly.S`, libbbs source files. +Include dirs: `cbits/blst/bindings/`, `cbits/blst/src/`, `cbits/libbbs/include/`, `cbits/libbbs/src/`. +C flags: `-D__BLST_PORTABLE__` for cross-CPU-generation compatibility. + +## Tests + +- Keygen produces keys of correct size +- Sign + proofGen + proofVerify roundtrip succeeds +- Tampered proof fails verification +- Tampered disclosed message fails verification +- Wrong public key fails verification +- Two proofs from same credential with different nonces both verify +- Proof size matches expected (272 + 32 * numUndisclosed) diff --git a/simplexmq.cabal b/simplexmq.cabal index df093a49df..9790fb898d 100644 --- a/simplexmq.cabal +++ b/simplexmq.cabal @@ -1,4 +1,4 @@ -cabal-version: 1.12 +cabal-version: 3.0 name: simplexmq version: 6.5.3.0 @@ -16,7 +16,7 @@ homepage: https://github.com/simplex-chat/simplexmq#readme author: simplex.chat maintainer: chat@simplex.chat copyright: 2020-2022 simplex.chat -license: AGPL-3 +license: AGPL-3.0-only license-file: LICENSE build-type: Simple extra-source-files: @@ -24,6 +24,13 @@ extra-source-files: CHANGELOG.md cbits/sha512.h cbits/sntrup761.h + cbits/blst/**/*.c + cbits/blst/**/*.h + cbits/blst/**/*.s + cbits/blst/**/*.S + cbits/blst/**/*.asm + cbits/libbbs/**/*.c + cbits/libbbs/**/*.h apps/common/Web/static/index.html apps/common/Web/static/link.html apps/common/Web/static/media/apk_icon.png @@ -122,6 +129,7 @@ library Simplex.Messaging.Crypto.File Simplex.Messaging.Crypto.Lazy Simplex.Messaging.Crypto.Ratchet + Simplex.Messaging.Crypto.BBS Simplex.Messaging.Crypto.SNTRUP761 Simplex.Messaging.Crypto.SNTRUP761.Bindings Simplex.Messaging.Crypto.SNTRUP761.Bindings.Defines @@ -298,9 +306,23 @@ library ghc-options: -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=incomplete-uni-patterns -Werror=missing-home-modules -Werror=missing-methods -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns -O2 include-dirs: cbits + cbits/blst/bindings + cbits/blst/src + cbits/libbbs/include + cbits/libbbs/src + cc-options: -D__BLST_PORTABLE__ c-sources: cbits/sha512.c cbits/sntrup761.c + cbits/blst/src/server.c + cbits/libbbs/src/bbs.c + cbits/libbbs/src/bbs_ciphersuites.c + cbits/libbbs/src/bbs_util.c + cbits/libbbs/src/compat-string.c + cbits/libbbs/src/sha256.c + cbits/libbbs/src/shake256.c + asm-sources: + cbits/blst/build/assembly.S extra-libraries: crypto build-depends: diff --git a/src/Simplex/Messaging/Crypto/BBS.hs b/src/Simplex/Messaging/Crypto/BBS.hs new file mode 100644 index 0000000000..095c605434 --- /dev/null +++ b/src/Simplex/Messaging/Crypto/BBS.hs @@ -0,0 +1,243 @@ +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module Simplex.Messaging.Crypto.BBS + ( BBSSecretKey (..), + BBSPublicKey (..), + BBSSignature (..), + BBSProof (..), + BBSHeader (..), + BBSPresHeader (..), + bbsKeyGen, + bbsSign, + bbsVerify, + bbsProofGen, + bbsProofVerify, + ) where + +import Data.Aeson (FromJSON (..), ToJSON (..)) +import Data.ByteString (ByteString) +import qualified Data.ByteString as B +import qualified Data.ByteString.Internal as BI +import Foreign +import Foreign.C +import Simplex.Messaging.Encoding.String + +newtype BBSSecretKey = BBSSecretKey ByteString + deriving newtype (Eq, Show, StrEncoding) + +instance ToJSON BBSSecretKey where + toJSON = strToJSON + toEncoding = strToJEncoding + +instance FromJSON BBSSecretKey where + parseJSON = strParseJSON "BBSSecretKey" + +newtype BBSPublicKey = BBSPublicKey ByteString + deriving newtype (Eq, Show, StrEncoding) + deriving (ToJSON, FromJSON) via BBSSecretKey + +newtype BBSSignature = BBSSignature ByteString + deriving newtype (Eq, Show, StrEncoding) + deriving (ToJSON, FromJSON) via BBSSecretKey + +newtype BBSProof = BBSProof ByteString + deriving newtype (Eq, Show, StrEncoding) + deriving (ToJSON, FromJSON) via BBSSecretKey + +newtype BBSHeader = BBSHeader ByteString + deriving newtype (Eq, Show, StrEncoding) + deriving (ToJSON, FromJSON) via BBSSecretKey + +newtype BBSPresHeader = BBSPresHeader ByteString + deriving newtype (Eq, Show, StrEncoding) + deriving (ToJSON, FromJSON) via BBSSecretKey + +-- Constants + +bbsSkLen, bbsPkLen, bbsSigLen, bbsProofBaseLen, bbsProofUdElemLen :: Int +bbsSkLen = 32 +bbsPkLen = 96 +bbsSigLen = 80 +bbsProofBaseLen = 272 +bbsProofUdElemLen = 32 + +bbsProofLen :: Int -> Int +bbsProofLen numUndisclosed = bbsProofBaseLen + numUndisclosed * bbsProofUdElemLen + +-- FFI + +data BBS_Ciphersuite + +foreign import ccall "bbs_keygen_full" + c_bbs_keygen_full :: Ptr BBS_Ciphersuite -> Ptr Word8 -> Ptr Word8 -> IO CInt + +foreign import ccall "bbs_sign" + c_bbs_sign :: + Ptr BBS_Ciphersuite -> + Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> + Ptr Word8 -> CSize -> + CSize -> Ptr (Ptr Word8) -> Ptr CSize -> + IO CInt + +foreign import ccall "bbs_verify" + c_bbs_verify :: + Ptr BBS_Ciphersuite -> + Ptr Word8 -> Ptr Word8 -> + Ptr Word8 -> CSize -> + CSize -> Ptr (Ptr Word8) -> Ptr CSize -> + IO CInt + +foreign import ccall "bbs_proof_gen" + c_bbs_proof_gen :: + Ptr BBS_Ciphersuite -> + Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> + Ptr Word8 -> CSize -> + Ptr Word8 -> CSize -> + Ptr CSize -> CSize -> + CSize -> Ptr (Ptr Word8) -> Ptr CSize -> + IO CInt + +foreign import ccall "bbs_proof_verify" + c_bbs_proof_verify :: + Ptr BBS_Ciphersuite -> + Ptr Word8 -> + Ptr Word8 -> CSize -> + Ptr Word8 -> CSize -> + Ptr Word8 -> CSize -> + Ptr CSize -> CSize -> + CSize -> Ptr (Ptr Word8) -> Ptr CSize -> + IO CInt + +foreign import ccall "&bbs_sha256_ciphersuite" + c_bbs_sha256_ciphersuite :: Ptr (Ptr BBS_Ciphersuite) + +getCiphersuite :: IO (Ptr BBS_Ciphersuite) +getCiphersuite = peek c_bbs_sha256_ciphersuite + +-- Helpers + +withBS :: ByteString -> (Ptr Word8 -> CSize -> IO a) -> IO a +withBS bs f = + let (fptr, off, len) = BI.toForeignPtr bs + in withForeignPtr fptr $ \ptr -> f (ptr `plusPtr` off) (fromIntegral len) + +packPtr :: Ptr Word8 -> Int -> IO ByteString +packPtr ptr len = B.packCStringLen (castPtr ptr, len) + +withMessages :: [ByteString] -> (Ptr (Ptr Word8) -> Ptr CSize -> CSize -> IO a) -> IO a +withMessages msgs f = do + let n = length msgs + allocaArray n $ \msgsPtr -> + allocaArray n $ \lensPtr -> do + pokeMessages msgs msgsPtr lensPtr 0 + f msgsPtr lensPtr (fromIntegral n) + where + pokeMessages [] _ _ _ = pure () + pokeMessages (m : ms) msgsPtr lensPtr i = do + let (fptr, off, len) = BI.toForeignPtr m + withForeignPtr fptr $ \ptr -> do + pokeElemOff msgsPtr i (ptr `plusPtr` off) + pokeElemOff lensPtr i (fromIntegral len) + pokeMessages ms msgsPtr lensPtr (i + 1) + +withIndexes :: [Int] -> (Ptr CSize -> CSize -> IO a) -> IO a +withIndexes idxs f = do + let n = length idxs + allocaArray n $ \ptr -> do + pokeArray ptr (map fromIntegral idxs) + f ptr (fromIntegral n) + +-- Public API + +bbsKeyGen :: IO (Either String (BBSSecretKey, BBSPublicKey)) +bbsKeyGen = do + cs <- getCiphersuite + allocaBytes bbsSkLen $ \skPtr -> + allocaBytes bbsPkLen $ \pkPtr -> do + rc <- c_bbs_keygen_full cs skPtr pkPtr + if rc /= 0 + then pure $ Left "bbsKeyGen failed" + else do + sk <- packPtr skPtr bbsSkLen + pk <- packPtr pkPtr bbsPkLen + pure $ Right (BBSSecretKey sk, BBSPublicKey pk) + +bbsSign :: + BBSSecretKey -> + BBSPublicKey -> + BBSHeader -> + [ByteString] -> + IO (Either String BBSSignature) +bbsSign (BBSSecretKey sk) (BBSPublicKey pk) (BBSHeader header) msgs = do + cs <- getCiphersuite + allocaBytes bbsSigLen $ \sigPtr -> + withBS sk $ \skPtr _ -> + withBS pk $ \pkPtr _ -> + withBS header $ \hdrPtr hdrLen -> + withMessages msgs $ \msgsPtr lensPtr n -> do + rc <- c_bbs_sign cs skPtr pkPtr sigPtr hdrPtr hdrLen n msgsPtr lensPtr + if rc /= 0 + then pure $ Left "bbsSign failed" + else Right . BBSSignature <$> packPtr sigPtr bbsSigLen + +bbsVerify :: + BBSPublicKey -> + BBSSignature -> + BBSHeader -> + [ByteString] -> + IO Bool +bbsVerify (BBSPublicKey pk) (BBSSignature sig) (BBSHeader header) msgs = do + cs <- getCiphersuite + withBS pk $ \pkPtr _ -> + withBS sig $ \sigPtr _ -> + withBS header $ \hdrPtr hdrLen -> + withMessages msgs $ \msgsPtr lensPtr n -> do + rc <- c_bbs_verify cs pkPtr sigPtr hdrPtr hdrLen n msgsPtr lensPtr + pure (rc == 0) + +bbsProofGen :: + BBSPublicKey -> + BBSSignature -> + BBSHeader -> + BBSPresHeader -> + [Int] -> + [ByteString] -> + IO (Either String BBSProof) +bbsProofGen (BBSPublicKey pk) (BBSSignature sig) (BBSHeader header) (BBSPresHeader ph) disclosedIdxs msgs = do + cs <- getCiphersuite + let numUndisclosed = length msgs - length disclosedIdxs + proofSz = bbsProofLen numUndisclosed + allocaBytes proofSz $ \proofPtr -> + withBS pk $ \pkPtr _ -> + withBS sig $ \sigPtr _ -> + withBS header $ \hdrPtr hdrLen -> + withBS ph $ \phPtr phLen -> + withIndexes disclosedIdxs $ \idxsPtr idxsLen -> + withMessages msgs $ \msgsPtr lensPtr n -> do + rc <- c_bbs_proof_gen cs pkPtr sigPtr proofPtr hdrPtr hdrLen phPtr phLen idxsPtr idxsLen n msgsPtr lensPtr + if rc /= 0 + then pure $ Left "bbsProofGen failed" + else Right . BBSProof <$> packPtr proofPtr proofSz + +bbsProofVerify :: + BBSPublicKey -> + BBSProof -> + BBSHeader -> + BBSPresHeader -> + [Int] -> + Int -> + [ByteString] -> + IO Bool +bbsProofVerify (BBSPublicKey pk) (BBSProof proof) (BBSHeader header) (BBSPresHeader ph) disclosedIdxs numMessages disclosedMsgs = do + cs <- getCiphersuite + withBS pk $ \pkPtr _ -> + withBS proof $ \proofPtr proofLen -> + withBS header $ \hdrPtr hdrLen -> + withBS ph $ \phPtr phLen -> + withIndexes disclosedIdxs $ \idxsPtr idxsLen -> + withMessages disclosedMsgs $ \msgsPtr lensPtr _ -> do + rc <- c_bbs_proof_verify cs pkPtr proofPtr proofLen hdrPtr hdrLen phPtr phLen idxsPtr idxsLen (fromIntegral numMessages) msgsPtr lensPtr + pure (rc == 0) diff --git a/tests/CoreTests/CryptoTests.hs b/tests/CoreTests/CryptoTests.hs index 8e4d9a2582..8d67c6a4cf 100644 --- a/tests/CoreTests/CryptoTests.hs +++ b/tests/CoreTests/CryptoTests.hs @@ -22,6 +22,7 @@ import qualified Data.X509.Validation as XV import qualified SMPClient import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto.Lazy as LC +import Simplex.Messaging.Crypto.BBS import Simplex.Messaging.Crypto.SNTRUP761.Bindings import Simplex.Messaging.Transport.Client import Test.Hspec hiding (fit, it) @@ -101,6 +102,14 @@ cryptoTests = do it "should validate certificates" testValidateX509 describe "sntrup761" $ it "should enc/dec key" testSNTRUP761 + describe "BBS+" $ do + it "should sign and verify" testBBSSignVerify + it "should generate and verify proof" testBBSProofRoundtrip + it "should reject tampered proof" testBBSTamperedProof + it "should reject wrong disclosed message" testBBSWrongMessage + it "should reject wrong public key" testBBSWrongKey + it "should produce unlinkable proofs" testBBSUnlinkable + it "should produce proof of expected size" testBBSProofSize instance Eq C.APublicKey where C.APublicKey a k == C.APublicKey a' k' = case testEquality a a' of @@ -271,3 +280,88 @@ testSNTRUP761 = do (c, KEMSharedKey k) <- sntrup761Enc drg pk KEMSharedKey k' <- sntrup761Dec c sk k' `shouldBe` k + +-- BBS+ tests + +bbsHeader :: BBSHeader +bbsHeader = BBSHeader "SimpleX" + +bbsMessages :: [B.ByteString] +bbsMessages = ["secret_master_key", "2026-07-31", "supporter"] + +bbsDisclosedIdxs :: [Int] +bbsDisclosedIdxs = [1, 2] + +bbsDisclosedMsgs :: [B.ByteString] +bbsDisclosedMsgs = ["2026-07-31", "supporter"] + +testBBSSignVerify :: IO () +testBBSSignVerify = do + Right (sk, pk) <- bbsKeyGen + let BBSSecretKey skBs = sk + BBSPublicKey pkBs = pk + B.length skBs `shouldBe` 32 + B.length pkBs `shouldBe` 96 + Right sig <- bbsSign sk pk bbsHeader bbsMessages + let BBSSignature sigBs = sig + B.length sigBs `shouldBe` 80 + bbsVerify pk sig bbsHeader bbsMessages >>= (`shouldBe` True) + bbsVerify pk sig bbsHeader ["wrong", "2026-07-31", "supporter"] >>= (`shouldBe` False) + +testBBSProofRoundtrip :: IO () +testBBSProofRoundtrip = do + Right (sk, pk) <- bbsKeyGen + Right sig <- bbsSign sk pk bbsHeader bbsMessages + let ph = BBSPresHeader "test-nonce-1" + Right proof <- bbsProofGen pk sig bbsHeader ph bbsDisclosedIdxs bbsMessages + result <- bbsProofVerify pk proof bbsHeader ph bbsDisclosedIdxs 3 bbsDisclosedMsgs + result `shouldBe` True + +testBBSTamperedProof :: IO () +testBBSTamperedProof = do + Right (sk, pk) <- bbsKeyGen + Right sig <- bbsSign sk pk bbsHeader bbsMessages + let ph = BBSPresHeader "test-nonce-2" + Right (BBSProof proofBs) <- bbsProofGen pk sig bbsHeader ph bbsDisclosedIdxs bbsMessages + let tampered = BBSProof $ B.take 10 proofBs <> "\xff" <> B.drop 11 proofBs + result <- bbsProofVerify pk tampered bbsHeader ph bbsDisclosedIdxs 3 bbsDisclosedMsgs + result `shouldBe` False + +testBBSWrongMessage :: IO () +testBBSWrongMessage = do + Right (sk, pk) <- bbsKeyGen + Right sig <- bbsSign sk pk bbsHeader bbsMessages + let ph = BBSPresHeader "test-nonce-3" + Right proof <- bbsProofGen pk sig bbsHeader ph bbsDisclosedIdxs bbsMessages + result <- bbsProofVerify pk proof bbsHeader ph bbsDisclosedIdxs 3 ["2026-07-31", "business"] + result `shouldBe` False + +testBBSWrongKey :: IO () +testBBSWrongKey = do + Right (sk, pk) <- bbsKeyGen + Right (_, pk2) <- bbsKeyGen + Right sig <- bbsSign sk pk bbsHeader bbsMessages + let ph = BBSPresHeader "test-nonce-4" + Right proof <- bbsProofGen pk sig bbsHeader ph bbsDisclosedIdxs bbsMessages + result <- bbsProofVerify pk2 proof bbsHeader ph bbsDisclosedIdxs 3 bbsDisclosedMsgs + result `shouldBe` False + +testBBSUnlinkable :: IO () +testBBSUnlinkable = do + Right (sk, pk) <- bbsKeyGen + Right sig <- bbsSign sk pk bbsHeader bbsMessages + let ph1 = BBSPresHeader "nonce-contact-1" + ph2 = BBSPresHeader "nonce-contact-2" + Right (BBSProof proof1) <- bbsProofGen pk sig bbsHeader ph1 bbsDisclosedIdxs bbsMessages + Right (BBSProof proof2) <- bbsProofGen pk sig bbsHeader ph2 bbsDisclosedIdxs bbsMessages + proof1 `shouldNotBe` proof2 + bbsProofVerify pk (BBSProof proof1) bbsHeader ph1 bbsDisclosedIdxs 3 bbsDisclosedMsgs >>= (`shouldBe` True) + bbsProofVerify pk (BBSProof proof2) bbsHeader ph2 bbsDisclosedIdxs 3 bbsDisclosedMsgs >>= (`shouldBe` True) + +testBBSProofSize :: IO () +testBBSProofSize = do + Right (sk, pk) <- bbsKeyGen + Right sig <- bbsSign sk pk bbsHeader bbsMessages + let ph = BBSPresHeader "test-nonce-size" + Right (BBSProof proofBs) <- bbsProofGen pk sig bbsHeader ph bbsDisclosedIdxs bbsMessages + B.length proofBs `shouldBe` 304 -- 272 + 32 * 1 undisclosed