Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cabal-build-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ jobs:
uses: actions/checkout@v6.0.1

- name: Cold Build / ${{ matrix.ghc }} / x86_64-linux
run: |
run: |
nix develop --no-warn-dirty --accept-flake-config .#${{ matrix.ghc }} \
--command bash -c 'cabal clean && cabal update && cabal build all --ghc-options=-Werror'
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### Added

- A new `with-crypto` Cabal flag (enabled by default) for `plutus-core`. When
disabled (`-with-crypto`), `plutus-core` links no system cryptography C library
(libsodium / libblst / libsecp256k1): the hash builtins are computed by
`crypton` (byte-identical — see the `crypto-hash-parity-test`), the BLS12-381
constants stay as their real hardcoded values, and the signature-verification
and BLS12-381 group/pairing operations become compile-only stubs. This lets
Plinth/Plutus scripts *compile* in environments where those C libraries are not
installed (Cabal otherwise cannot even solve, because `cardano-crypto-class`
declares them as `pkgconfig-depends`). Those builtins can still be compiled and
serialised but not *evaluated*; the default build is unaffected.
58 changes: 57 additions & 1 deletion plutus-core/plutus-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@ flag with-cert
manual: True
default: False

-- The with-crypto flag (enabled by default) selects Plutus Core's cryptography
-- backend, and is the single control for building without the system C crypto
-- libraries.
--
-- Enabled: the hash, signature-verification and BLS12-381 builtins are backed by
-- cardano-crypto-class, which binds the system libraries libsodium, libblst and
-- libsecp256k1.
--
-- Disabled (-with-crypto): Plutus Core links no system cryptography library, so
-- it builds -- and Plinth/Plutus scripts compile -- where those libraries are
-- not installed. (Otherwise Cabal cannot even solve, because cardano-crypto-class
-- declares them as pkgconfig-depends: https://github.com/haskell/cabal/issues/4087.)
-- In that build the modules under PlutusCore.Crypto are compiled C-free: the
-- hashes are computed by crypton (which vendors its C sources, needs no system
-- library, and is byte-identical -- see crypto-hash-parity-test); the BLS12-381
-- constants stay real hardcoded values; and the signature-verification and
-- BLS12-381 group/pairing operations become compile-only stubs that error if
-- evaluated. The components that link cardano-crypto-class directly are disabled
-- (buildable: False) so the whole package solves C-free. Scripts still COMPILE;
-- those builtins simply cannot be EVALUATED. The default build is unaffected.
flag with-crypto
manual: True
default: True

common lang
default-language: Haskell2010
default-extensions:
Expand Down Expand Up @@ -320,7 +344,6 @@ library
, base64-bytestring
, bimap
, bytestring
, cardano-crypto-class
, cassava
, cborg
, composition-prelude >=1.1.0.1
Expand Down Expand Up @@ -368,6 +391,29 @@ library
, unordered-containers
, vector ^>=0.13.2

if flag(with-crypto)
build-depends: cardano-crypto-class >=2.3.1.0
cpp-options: -DWITH_CRYPTO

else
build-depends:
, base16-bytestring ^>=1.0
, crypton ^>=1.0
, memory

test-suite crypto-hash-parity-test
import: lang
type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs: test-crypto-hash-parity
build-depends:
, base
, base16-bytestring
, bytestring
, plutus-core
, tasty
, tasty-golden

test-suite plutus-core-test
import: lang

Expand Down Expand Up @@ -435,6 +481,9 @@ test-suite untyped-plutus-core-test
if (os(windows) || arch(wasm32))
buildable: False

if !flag(with-crypto)
buildable: False

type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs: untyped-plutus-core/test
Expand All @@ -455,6 +504,9 @@ library untyped-plutus-core-testlib
if (os(windows) || arch(wasm32))
buildable: False

if !flag(with-crypto)
buildable: False

exposed-modules:
Analysis.Lib
Analysis.Spec
Expand Down Expand Up @@ -814,6 +866,10 @@ library plutus-ir-cert
executable cost-model-budgeting-bench
import: lang
main-is: Main.hs

if !flag(with-crypto)
buildable: False

other-modules:
Benchmarks.Arrays
Benchmarks.Bitwise
Expand Down
25 changes: 25 additions & 0 deletions plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/Error.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
module PlutusCore.Crypto.BLS12_381.Error
where

import Data.Bits (testBit)
import Data.ByteString qualified as BS

data BLS12_381_Error
= HashToCurveDstTooBig -- DSTs can be at most 255 bytes long.
| BadCompressedData -- A compressed point failed structural (length/flag) validation.
deriving stock (Show)

{-| Structural validation of a compressed BLS12-381 point, shared by the C-free
G1 and G2 `uncompress` stubs (@expectedLen@ is 48 for G1, 96 for G2). It checks
exactly what can be checked without field arithmetic: the length, and the three
metadata bits of the leading byte -- the compression bit must be set, and if the
infinity bit is set the encoding must be the canonical @0xc0 00..00@. It
deliberately does NOT verify that the bytes encode a point on the curve or in the
subgroup (that needs blst), so it is weaker than the real `blsUncompress`; but it
rejects the malformed inputs the real decoder rejects on structure alone, rather
than accepting anything. -}
checkCompressed :: Int -> BS.ByteString -> Either BLS12_381_Error BS.ByteString

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not have this duplicate validation logic that's originally in the blst c library. \_ _ -> Left (CryptoDisabled :: BLS12_381_Error) would be sufficient

checkCompressed expectedLen bs
| BS.length bs /= expectedLen = Left BadCompressedData
| not (testBit b0 7) = Left BadCompressedData -- must be the compressed form
| testBit b0 6 = -- point at infinity: must be exactly 0xc0 00..00
if b0 == 0xc0 && BS.all (== 0) (BS.drop 1 bs)
then Right bs
else Left BadCompressedData
| otherwise = Right bs
where
b0 = BS.head bs
80 changes: 69 additions & 11 deletions plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G1.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
-- editorconfig-checker-disable
{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
Expand All @@ -20,12 +21,7 @@ module PlutusCore.Crypto.BLS12_381.G1
, multiScalarMul
) where

import Cardano.Crypto.EllipticCurve.BLS12_381 qualified as BlstBindings
import Cardano.Crypto.EllipticCurve.BLS12_381.Internal qualified as BlstBindings.Internal

import PlutusCore.Builtin.Result (BuiltinResult (..))
import PlutusCore.Crypto.BLS12_381.Bounds (msmScalarOutOfBounds)
import PlutusCore.Crypto.BLS12_381.Error (BLS12_381_Error (..))
import PlutusCore.Crypto.Utils (byteStringAsHex)
import PlutusCore.Pretty.PrettyConst (ConstConfig)
import Text.PrettyBy (PrettyBy)
Expand All @@ -35,16 +31,25 @@ import Control.DeepSeq
, rnf
, rwhnf
)
import Data.ByteString
( ByteString
, length
)
import Data.Coerce (coerce)
import Data.ByteString (ByteString)
import Data.Hashable
import Data.Proxy (Proxy (..))
import PlutusCore.Flat
import Prettyprinter

#ifdef WITH_CRYPTO
import Cardano.Crypto.EllipticCurve.BLS12_381 qualified as BlstBindings
import Cardano.Crypto.EllipticCurve.BLS12_381.Internal qualified as BlstBindings.Internal
import Data.ByteString (length)
import Data.Coerce (coerce)
import Data.Proxy (Proxy (..))
import PlutusCore.Crypto.BLS12_381.Bounds (msmScalarOutOfBounds)
import PlutusCore.Crypto.BLS12_381.Error (BLS12_381_Error (..))
#else
import Data.ByteString.Base16 qualified as Base16
import PlutusCore.Crypto.BLS12_381.Error (BLS12_381_Error, checkCompressed)
import PlutusCore.Crypto.Utils (cryptoDisabled)
#endif

{- Note [Wrapping the BLS12-381 types in Plutus Core]. In the Haskell bindings
to the `blst` library in cardano-crypto-class, points in G1 and G2 are
represented as ForeignPtrs pointing to C objects, with a phantom type
Expand All @@ -60,8 +65,14 @@ functions at the appropriate phantom types.

See also Note [Wrapping the BLS12-381 types in PlutusTx].
-}
#ifdef WITH_CRYPTO
newtype Element = Element {unElement :: BlstBindings.Point1}
deriving newtype (Eq)
#else
newtype Element = Element {unElement :: ByteString}
deriving newtype (Eq)
#endif

instance Show Element where
show = byteStringAsHex . compress
instance Pretty Element where
Expand Down Expand Up @@ -91,6 +102,8 @@ instance NFData Element where
instance Hashable Element where
hashWithSalt salt = hashWithSalt salt . compress

#ifdef WITH_CRYPTO

-- | Add two G1 group elements
add :: Element -> Element -> Element
add = coerce (BlstBindings.blsAddOrDouble @BlstBindings.Curve1)
Expand Down Expand Up @@ -199,3 +212,48 @@ multiScalarMul :: [Integer] -> [Element] -> BuiltinResult Element
multiScalarMul ss p
| any msmScalarOutOfBounds ss = fail "Scalar exceeds 512-byte bound for G1.multiScalarMul"
| otherwise = pure . coerce $ BlstBindings.blsMSM @BlstBindings.Curve1 (zip ss (coerce p))

#else

add :: Element -> Element -> Element
add = cryptoDisabled "bls12_381_G1_add"

neg :: Element -> Element
neg = cryptoDisabled "bls12_381_G1_neg"

scalarMul :: Integer -> Element -> Element
scalarMul = cryptoDisabled "bls12_381_G1_scalarMul"

scalarMulE :: Integer -> Element -> BuiltinResult Element
scalarMulE = cryptoDisabled "bls12_381_G1_scalarMul"

compress :: Element -> ByteString
compress = unElement

uncompress :: ByteString -> Either BLS12_381_Error Element
uncompress = fmap Element . checkCompressed compressedSizeBytes

hashToGroup :: ByteString -> ByteString -> Either BLS12_381_Error Element
hashToGroup = cryptoDisabled "bls12_381_G1_hashToGroup"

offchain_zero :: Element
offchain_zero = Element compressed_zero

compressed_zero :: ByteString
compressed_zero =
Base16.decodeLenient "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

compressed_generator :: ByteString
compressed_generator =
Base16.decodeLenient "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"

memSizeBytes :: Int
memSizeBytes = 144

compressedSizeBytes :: Int
compressedSizeBytes = 48

multiScalarMul :: [Integer] -> [Element] -> BuiltinResult Element
multiScalarMul = cryptoDisabled "bls12_381_G1_multiScalarMul"

#endif
79 changes: 68 additions & 11 deletions plutus-core/plutus-core/src/PlutusCore/Crypto/BLS12_381/G2.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
-- editorconfig-checker-disable
{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
Expand All @@ -20,12 +21,7 @@ module PlutusCore.Crypto.BLS12_381.G2
, multiScalarMul
) where

import Cardano.Crypto.EllipticCurve.BLS12_381 qualified as BlstBindings
import Cardano.Crypto.EllipticCurve.BLS12_381.Internal qualified as BlstBindings.Internal

import PlutusCore.Builtin.Result (BuiltinResult (..))
import PlutusCore.Crypto.BLS12_381.Bounds (msmScalarOutOfBounds)
import PlutusCore.Crypto.BLS12_381.Error (BLS12_381_Error (..))
import PlutusCore.Crypto.Utils (byteStringAsHex)
import PlutusCore.Pretty.PrettyConst (ConstConfig)
import Text.PrettyBy (PrettyBy)
Expand All @@ -35,19 +31,33 @@ import Control.DeepSeq
, rnf
, rwhnf
)
import Data.ByteString
( ByteString
, length
)
import Data.Coerce (coerce)
import Data.ByteString (ByteString)
import Data.Hashable
import Data.Proxy (Proxy (..))
import PlutusCore.Flat
import Prettyprinter

#ifdef WITH_CRYPTO
import Cardano.Crypto.EllipticCurve.BLS12_381 qualified as BlstBindings
import Cardano.Crypto.EllipticCurve.BLS12_381.Internal qualified as BlstBindings.Internal
import Data.ByteString (length)
import Data.Coerce (coerce)
import Data.Proxy (Proxy (..))
import PlutusCore.Crypto.BLS12_381.Bounds (msmScalarOutOfBounds)
import PlutusCore.Crypto.BLS12_381.Error (BLS12_381_Error (..))
#else
import Data.ByteString.Base16 qualified as Base16
import PlutusCore.Crypto.BLS12_381.Error (BLS12_381_Error, checkCompressed)
import PlutusCore.Crypto.Utils (cryptoDisabled)
#endif

-- | See Note [Wrapping the BLS12-381 types in Plutus Core].
#ifdef WITH_CRYPTO
newtype Element = Element {unElement :: BlstBindings.Point2}
deriving newtype (Eq)
#else
newtype Element = Element {unElement :: ByteString}
deriving newtype (Eq)
#endif

instance Show Element where
show = byteStringAsHex . compress
Expand Down Expand Up @@ -78,6 +88,8 @@ instance NFData Element where
instance Hashable Element where
hashWithSalt salt = hashWithSalt salt . compress

#ifdef WITH_CRYPTO

-- | Add two G2 group elements
add :: Element -> Element -> Element
add = coerce (BlstBindings.blsAddOrDouble @BlstBindings.Curve2)
Expand Down Expand Up @@ -157,3 +169,48 @@ multiScalarMul :: [Integer] -> [Element] -> BuiltinResult Element
multiScalarMul ss p
| any msmScalarOutOfBounds ss = fail "Scalar exceeds 512-byte bound for G2.multiScalarMul"
| otherwise = pure . coerce $ BlstBindings.blsMSM @BlstBindings.Curve2 (zip ss (coerce p))

#else

add :: Element -> Element -> Element
add = cryptoDisabled "bls12_381_G2_add"

neg :: Element -> Element
neg = cryptoDisabled "bls12_381_G2_neg"

scalarMul :: Integer -> Element -> Element
scalarMul = cryptoDisabled "bls12_381_G2_scalarMul"

scalarMulE :: Integer -> Element -> BuiltinResult Element
scalarMulE = cryptoDisabled "bls12_381_G2_scalarMul"

compress :: Element -> ByteString
compress = unElement

uncompress :: ByteString -> Either BLS12_381_Error Element
uncompress = fmap Element . checkCompressed compressedSizeBytes

hashToGroup :: ByteString -> ByteString -> Either BLS12_381_Error Element
hashToGroup = cryptoDisabled "bls12_381_G2_hashToGroup"

offchain_zero :: Element
offchain_zero = Element compressed_zero

compressed_zero :: ByteString
compressed_zero =
Base16.decodeLenient "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

compressed_generator :: ByteString
compressed_generator =
Base16.decodeLenient "93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"

memSizeBytes :: Int
memSizeBytes = 288

compressedSizeBytes :: Int
compressedSizeBytes = 96

multiScalarMul :: [Integer] -> [Element] -> BuiltinResult Element
multiScalarMul = cryptoDisabled "bls12_381_G2_multiScalarMul"

#endif
Loading
Loading