From e81d36a3118b4f03bc478741532c347b5da2e099 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 18 Sep 2024 08:08:10 -0700 Subject: [PATCH] friendly and idiomatic Haskell API (#16) * Add high level Haskell API * do not split parameters * provide separate rust function for hash parameters * address comments from review * fix typo in comment --- .github/workflows/ci.yml | 4 +- plonk-verify.cabal | 32 +++--- src/PlonkBn254/Utils/EmbedVMKeys.hs | 84 +++++++++++++++ src/PlonkBn254/Verify.hs | 157 ++++++++++++++++++++++++++++ src/PlonkVerify.hs | 27 ----- src/lib.rs | 83 +++++++++++---- test/app/Main.hs | 40 +++++-- test/test.cabal | 2 +- tests/Main.hs | 131 +++++++++++++++++------ 9 files changed, 446 insertions(+), 114 deletions(-) create mode 100644 src/PlonkBn254/Utils/EmbedVMKeys.hs create mode 100644 src/PlonkBn254/Verify.hs delete mode 100644 src/PlonkVerify.hs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8913bb3..bf2c9bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,8 @@ jobs: - name: Set up Haskell uses: haskell-actions/setup@v2 with: - ghc-version: '9.2.5' - cabal-version: '3.6.2.0' + ghc-version: '9.10.1' + cabal-version: 'latest' - name: Build with Cabal run: cabal build diff --git a/plonk-verify.cabal b/plonk-verify.cabal index 133b866..229ebb8 100644 --- a/plonk-verify.cabal +++ b/plonk-verify.cabal @@ -1,22 +1,7 @@ cabal-version: 3.0 name: plonk-verify version: 0.1.0 - --- A short (one-line) description of the package. --- synopsis: - --- A longer description of the package. --- description: - license: MIT - --- The package author(s). --- author: - --- An email address to which users can send suggestions, bug reports, and --- patches. --- maintainer: - copyright: Copyright (c) 2024 Argument Computer Corporation extra-doc-files: @@ -46,9 +31,17 @@ library default-language: Haskell2010 hs-source-dirs: src exposed-modules: - PlonkVerify + PlonkBn254.Verify + PlonkBn254.Utils.EmbedVMKeys build-depends: - base + , base + , base16-bytestring >=0.1.1.7 + , bytestring >=0.11.2 + , directory >=1.3 + , filepath >=1.4 + , template-haskell >=2.16 + , temporary >=1.3 + , vector >=0.12 frameworks: CoreFoundation Security @@ -63,7 +56,10 @@ test-suite tests main-is: Main.hs hs-source-dirs: tests build-depends: - , plonk-verify , aeson >=2.2 , base + , base16-bytestring >=0.1.1.7 + , bytestring >=0.11.2 , hspec >=2.11 + , plonk-verify + , text >=2.1 diff --git a/src/PlonkBn254/Utils/EmbedVMKeys.hs b/src/PlonkBn254/Utils/EmbedVMKeys.hs new file mode 100644 index 0000000..616c5ec --- /dev/null +++ b/src/PlonkBn254/Utils/EmbedVMKeys.hs @@ -0,0 +1,84 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE DeriveLift #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE TemplateHaskellQuotes #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} + +-- | +-- Module: PlonkBn254.Utils.EmbedVMKeys +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module PlonkBn254.Utils.EmbedVMKeys +( embedVMKeys +) where + +import Control.Monad + +import Data.ByteString qualified as B +import Data.Functor +import Data.List qualified as L +import Data.Vector qualified as V + +import Language.Haskell.TH +import Language.Haskell.TH.Syntax + +import System.Directory +import System.FilePath + +-- internal modules + +import PlonkBn254.Verify + +-- -------------------------------------------------------------------------- -- +-- Embed VM Keys + +-- | Running this slice produces @[(FilePath, VMKey)]@. +-- +-- It does not recurse into subdirectories and ignores any files that do +-- not have the suffix @.bin@. +-- +-- The file path is the (relative) file name within the given directory. +-- +embedVMKeys :: String -> FilePath -> Code Q [(FilePath, VMKey)] +embedVMKeys suffix fp = embedIO $ readVMKeyDir suffix fp + +readVMKeyDir :: String -> FilePath -> IO [(FilePath, VMKey)] +readVMKeyDir suffix fp = do + paths <- listFiles suffix fp + forM paths $ \p -> do + vk <- VMKey <$> B.readFile (fp p) + return (stripSuffix p, vk) + where + stripSuffix x = take (length x - length suffix - 1) x + +-- | The returned paths are relative to the given directory +-- +listFiles :: String -> FilePath -> IO [FilePath] +listFiles suffix r = listDirectory r + >>= filterM (doesFileExist . (r )) + >>= filterM (fmap readable . getPermissions . (r )) + <&> filter (L.isSuffixOf ("." <> suffix)) + +-- -------------------------------------------------------------------------- -- +-- File embedding + +embedIO :: Lift a => IO a -> Code Q a +embedIO action = runIO action `bindCode` liftTyped + +-- -------------------------------------------------------------------------- -- +-- Orphan Lift instances +-- +-- Requires template-haskell >=2.16 + +instance (Lift a) => Lift (V.Vector a) where + lift v = [| V.fromListN n' v' |] + where + n' = V.length v + v' = V.toList v + liftTyped = Code . unsafeTExpCoerce . lift + diff --git a/src/PlonkBn254/Verify.hs b/src/PlonkBn254/Verify.hs new file mode 100644 index 0000000..5952d6e --- /dev/null +++ b/src/PlonkBn254/Verify.hs @@ -0,0 +1,157 @@ +{-# LANGUAGE DeriveLift #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE TemplateHaskell #-} + +-- | This library is not tied to a particular VM key. But it provides tools that +-- help users to manage VM keys in their applications. + +module PlonkBn254.Verify +( VMKey(..) +, Proof(..) +, ProgramId(..) +, PublicParameter(..) +, PublicParameterHash(..) +, mkPublicParameterHash +, verify +, verifyPrehashed +) where + +import Data.ByteString qualified as B +import Data.ByteString.Base16 qualified as B16 +import Data.ByteString.Short qualified as BS + +import Foreign.C.String +import Foreign.C.Types + +import Language.Haskell.TH.Syntax (Lift) + +import System.IO.Temp +import Data.Bits + +-- -------------------------------------------------------------------------- -- +-- Plonk Verifier FFI + +foreign import ccall safe "__c_verify_plonk_bn254" + verify_plonk_bn254 :: CString -> CString -> CString -> CString -> IO (CUInt) + +foreign import ccall safe "__c_verify_plonk_bn254_prehashed" + verify_plonk_bn254_prehashed :: CString -> CString -> CString -> CString -> IO (CUInt) + +-- -------------------------------------------------------------------------- -- +-- API Types + +-- | The VM key identifies the circuit of the VM that is used to execute and +-- proof the program. Intuitively, one can think of the key as a version +-- identifier of the VM. +-- +-- When the VM circuit is updated the key changes. Applications should record +-- what key a proofs depends on for verification. The module "EmbedVMKeys" +-- provides tools for embedding existing keys from files into applications +-- binaries. +-- +newtype VMKey = VMKey + { _vmKey :: B.ByteString } + deriving (Show, Eq, Ord) + deriving (Lift) + +-- | The actual proof. This is opaque cryptographic object that allows the +-- verifier to establish that a program was executed with a particular list of +-- public parameters in the context of the VM that is idendified with a +-- particular VM key. +-- +newtype Proof = Proof + { _proof :: BS.ShortByteString } + deriving (Show, Eq, Ord) + +-- | A unique identifier of a program. +-- +newtype ProgramId = ProgramId + { _programId :: BS.ShortByteString } + deriving (Show, Eq, Ord) + +-- | The public parameters of a program invocation. +-- +newtype PublicParameter = PublicParameter + { _publicParameter :: BS.ShortByteString } + deriving (Show, Eq, Ord) + +-- | The hash of the public parameters of a program invocation. +-- +newtype PublicParameterHash = PublicParameterHash + { _publicParameterHash :: BS.ShortByteString } + deriving (Show, Eq, Ord) + +-- | Helper function that checks some invariants on the format of public +-- parameters digest. It is valid only for Plonk over curve BN254. +-- +mkPublicParameterHash :: BS.ShortByteString -> Either String PublicParameterHash +mkPublicParameterHash bytes + | BS.length bytes /= 32 = Left $ "wrong length; expected: 32, actual: " <> show (BS.length bytes) + | BS.head bytes .&. 0xe0 /= 0 = Left "first three bit are not set to 0" + | otherwise = Right $ PublicParameterHash bytes + +-- -------------------------------------------------------------------------- -- +-- Plonk Verifier API + +-- | Verify the claim that the program with the given id was invoked with the +-- given list of public parameters. +-- +verify + :: VMKey + -- ^ The VM key in bytes. This key represents the particular version of + -- the verifier. It must match the respective version of the prover that + -- was used to generate the proof. Otherwise verification fails. + -> Proof + -- ^ The proof object for the invocation of program with the respective + -- public parameters. + -> ProgramId + -- ^ The program identifier. A program is valid only in the context of a + -- particular VM key. It also the number and types of public parameters + -- are well defined. + -> PublicParameter + -- ^ The public parameters of the program execution. The encoding of the + -- parameters depends on the program. + -> IO Bool +verify (VMKey vk) (Proof proof) (ProgramId pid) (PublicParameter params) = do + withSystemTempDirectory "plonk-verifier" $ \path -> do + B.writeFile (path <> "/" <> "vk.bin") vk + withCString path $ \cpath -> + useAsHexCString (proof) $ \cproof -> + useAsHexCString pid $ \cpid -> + useAsHexCString params $ \cparams -> + (== 1) <$> verify_plonk_bn254 cpath cproof cpid cparams + where + useAsHexCString = B.useAsCString . B16.encode . BS.fromShort + +-- | Verify the claim that the program with the given id was invoked with the +-- list of public parameters with the given digest. +-- +verifyPrehashed + :: VMKey + -- ^ The VM key in bytes. This key represents the particular version of + -- the verifier. It must match the respective version of the prover that + -- was used to generate the proof. Otherwise verification fails. + -> Proof + -- ^ The proof object for the invocation of program with the respective + -- public parameters. + -> ProgramId + -- ^ The program identifier. A program is valid only in the context of a + -- particular VM key. It also the number and types of public parameters + -- are well defined. + -> PublicParameterHash + -- ^ The digest of the public parameters as computed by + -- 'hashPublicParameters'. + -> IO Bool +verifyPrehashed (VMKey vk) (Proof proof) (ProgramId pid) (PublicParameterHash paramHash) = do + withSystemTempDirectory "plonk-verifier" $ \path -> do + B.writeFile (path <> "/" <> "vk.bin") vk + withCString path $ \cpath -> + useAsHexCString (proof) $ \cproof -> + useAsHexCString pid $ \cpid -> + useAsHexCString paramHash $ \cparamHash -> + (== 1) <$> verify_plonk_bn254_prehashed cpath cproof cpid cparamHash + where + useAsHexCString = B.useAsCString . B16.encode . BS.fromShort + diff --git a/src/PlonkVerify.hs b/src/PlonkVerify.hs deleted file mode 100644 index 6a3449e..0000000 --- a/src/PlonkVerify.hs +++ /dev/null @@ -1,27 +0,0 @@ --- This file was generated by `hs-bindgen` crate and contains C FFI bindings --- wrappers for every Rust function annotated with `#[hs_bindgen]` - -{-# LANGUAGE ForeignFunctionInterface #-} - --- Why not rather using `{-# LANGUAGE CApiFFI #-}` language extension? --- --- * Because it's GHC specific and not part of the Haskell standard: --- https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/ffi.html ; --- --- * Because the capabilities it gave (by rather works on top of symbols of a C --- header file) can't work in our case. Maybe we want a future with an --- {-# LANGUAGE RustApiFFI #-} language extension that would enable us to --- work on top of a `.rs` source file (or a `.rlib`, but this is unlikely as --- this format has purposely no public specification). - -{-# OPTIONS_GHC -Wno-unused-imports #-} - -module PlonkVerify (verify_plonk_bn254) where - -import Data.Int -import Data.Word -import Foreign.C.String -import Foreign.C.Types -import Foreign.Ptr - -foreign import ccall safe "__c_verify_plonk_bn254" verify_plonk_bn254 :: CString -> CString -> CString -> CString -> IO (CUInt) \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b5d9809..7630cce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ use num_bigint::BigUint; use sha2::{Digest as _, Sha256}; use std::ffi::CStr; - use sphinx_recursion_gnark_ffi::ffi; // Helper function to remove "0x" if present @@ -16,24 +15,62 @@ pub extern "C" fn __c_verify_plonk_bn254( cvkeydir: *const core::ffi::c_char, cproof: *const core::ffi::c_char, cpkey: *const core::ffi::c_char, - cparams: *const core::ffi::c_char, + ccommitted_values: *const core::ffi::c_char, ) -> core::ffi::c_uint { let vkeydir = unsafe { CStr::from_ptr(cvkeydir) }; let proof = unsafe { CStr::from_ptr(cproof) }; let pkey = unsafe { CStr::from_ptr(cpkey) }; - let params = unsafe { CStr::from_ptr(cparams) }; + let committed_values = unsafe { CStr::from_ptr(ccommitted_values) }; return verify_plonk_bn254( vkeydir.to_str().expect("verifying key directory name is not a valid string"), proof.to_str().expect("proof is not a value string"), pkey.to_str().expect("program key is not a valid string"), - params.to_str().expect("public params is not a valid string"), + committed_values.to_str().expect("committed values are not a valid string"), + ); +} + +#[no_mangle] +pub extern "C" fn __c_verify_plonk_bn254_prehashed( + cvkeydir: *const core::ffi::c_char, + cproof: *const core::ffi::c_char, + cpkey: *const core::ffi::c_char, + ccommitted_values_hash: *const core::ffi::c_char, +) -> core::ffi::c_uint +{ + let vkeydir = unsafe { CStr::from_ptr(cvkeydir) }; + let proof = unsafe { CStr::from_ptr(cproof) }; + let pkey = unsafe { CStr::from_ptr(cpkey) }; + let committed_values_hash = unsafe { CStr::from_ptr(ccommitted_values_hash) }; + + return verify_plonk_bn254_prehashed( + vkeydir.to_str().expect("verifying key directory name is not a valid string"), + proof.to_str().expect("proof is not a value string"), + pkey.to_str().expect("program key is not a valid string"), + committed_values_hash.to_str().expect("committed values digest is not a valid string"), ); } -// Facade exposing a bindgen anchor pub fn verify_plonk_bn254( + build_dir_str: &str, + proof_str: &str, + vkey_hash_str: &str, + committed_values_str: &str, +) -> u32 { + let committed_values_str = strip_hex_prefix(committed_values_str); + // Decode the hex-encoded string for public values + let decoded_bytes = hex::decode(committed_values_str).expect("Invalid committed values field"); + // Hash the value using SHA-256 + let mut hash: [u8; 32] = Sha256::digest(decoded_bytes).into(); + // Truncate to 253 bits by clearing the top 3 bits of the first byte + hash[0] &= 0x1F; // 0x1F is 00011111 in binary, which clears the top 3 bits + // Re-encode the truncated hash in hex + let committed_values_digest_str: String = hex::encode(hash); + verify_plonk_bn254_prehashed(build_dir_str, proof_str, vkey_hash_str, &committed_values_digest_str) +} + +pub fn verify_plonk_bn254_prehashed( build_dir_str: &str, proof_str: &str, vkey_hash_str: &str, @@ -50,17 +87,9 @@ pub fn verify_plonk_bn254( // Check the bit length (bytes * 8 should be 256 bits), hash if necessary let bit_length = decoded_bytes.len() * 8; - let public_inputs: String = if bit_length > 256 { - // The user has provided the committed values rather than the digest! - // Let's reproduce the digest using the committed values - // - // Hash the value using SHA-256 - let mut hash: [u8; 32] = Sha256::digest(decoded_bytes).into(); - // Truncate to 253 bits by clearing the top 3 bits of the first byte - hash[0] &= 0x1F; // 0x1F is 00011111 in binary, which clears the top 3 bits - - // Re-encode the truncated hash in hex - hex::encode(hash) + let public_inputs: String = if bit_length != 256 { + eprintln!("Error in verify_plonk_bn254: wrong length of the committed values digest"); + return 0u32; // Return 0 for failure } else { committed_values_digest_str.to_string() }; @@ -152,12 +181,22 @@ mod tests { // Fetch the prover's asset directory let build_dir = plonk_bn254_artifacts_dev_dir(); - let result = super::verify_plonk_bn254( - build_dir.to_str().unwrap(), - &fixture.proof, - &fixture.vkey, - &fixture.public_values, - ); + + let result = if strip_hex_prefix(&fixture.public_values).len() > 64 { + super::verify_plonk_bn254( + build_dir.to_str().unwrap(), + &fixture.proof, + &fixture.vkey, + &fixture.public_values, + ) + } else { + super::verify_plonk_bn254_prehashed( + build_dir.to_str().unwrap(), + &fixture.proof, + &fixture.vkey, + &fixture.public_values, + ) + }; // Push the result of this verification to the results list match result { diff --git a/test/app/Main.hs b/test/app/Main.hs index e317ccd..db0f118 100644 --- a/test/app/Main.hs +++ b/test/app/Main.hs @@ -1,22 +1,42 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedStrings #-} + module Main where -import Foreign.C.String -import PlonkVerify +import Data.ByteString qualified as B +import Data.ByteString.Char8 qualified as B8 +import Data.ByteString.Base16 qualified as B16 +import Data.ByteString.Short qualified as BS +import Data.List (stripPrefix) +import Data.Maybe + +import PlonkBn254.Verify main :: IO () main = do -- This directory contains only the necessary artifacts for verification -- Alternatively, the parameters might also be present at `~/.sp1/circuits/plonk_bn254/v1.0.8-testnet` let circuitPathStr = "./verifier-assets/v1.0.8-testnet" - + vk <- VMKey <$> B.readFile (circuitPathStr <> "/vk.bin") + -- Convert Haskell strings to CStrings - circuitPath <- newCString circuitPathStr - proof <- newCString "0x070ebe597360e09c92562b810cfd411a4cadac13904a6f5ef80597d31992b55611cc3d8557ad399cb5153cf87460901db1fd8dc92f16c5ea2ba1e8f357a1c5dd192120e7e19011e6e8028677870b4d59c1688fc0e729a547691ff7b9760aefbf242cfd52dbf3b98ddc003d31c8b86f2acf5f02ac891f990accba9a9c8477eb701d9f43bbd2c0566e7f48469c84a1769c17e74b8ed53602ce12f7f9a0181e9ab70548028c15e5a49611db64135b80545bf38afa4195e95d1289e20b945d7139ef1a94705f2ddbd481817316ef262053e9ee6a2419c63de7e3f339cac54bfa8c222e740ee8470ead68edb38aae2e9519fe01ce2f20707d6f81bc8643f0306c61ed1d37a65d87c876e13b3353bb42f02ab86423b4da43341e7bc5461b185a3946691d1015cb04fd6ffc38f9e7f52c92cffc3c44de7c58c9728d71e9f4c8266a831c2484b36f3ea92dd827d668dcf59ab7a0dee33f46fe289b90df06b15e4d288a5f1bee1bc0214e6a77647db6a2f4f51df8b100b1ddaef494618b3a3b0369ca18631f2d81effd2f9185afed9a0712d302b1d439c31d44a2ca3f20fd5f70a95eb30c132f39448df14f1e5311b906d22068ca160ac7d574df55605555df41208c9ab41634986f88e8af3b475ca428eb66e17f35c5d5808f358ec9884317db04d11a2d07200c82c257f1fdf6de2265f8cfd280c9728303f36dd0af7fc797b5ac232ae60000000720e4c44119a39f2303913d419ccff44f27639a1efe8634c93d82e581a4af1dee26b61ebb2d1eac5fe7e918786ced3a2a6b441dee0e8574341bb2e5bcbb27dc4f294ecca8cf684af42cecc403954747d3fb9b8ffb2a8081f2a809d3315f96bfca10d8e46e711998c3cd612849af775cb156caebcd3e2ce2bc7f6c7825dc536e310d69f3e8a506635f24f681b32fc30435c93d82ee0f93c83d7daf695d33acd87e1dbd2d55668d9a07c45d1f261173e9155bf35ce61b73becdc86ea76ca228f45a264f228a3b4f5b02022cd03bd8604a5ad62fd3a3706713bbc4f9846b31856b9608e7cdef78fc19f96cf6d5ada170bd3332f797f3a67756b83fa53e628f1db9a121042af248f08e8df4c9b046dc8807f7b514eb1dd7e6e2b6b80d060097a296cf22b56debdb623976250d281713f6e34b96ef5f71e92e748106d84675e30f9dc6000000010a0b17cbd37bd5c99d4ee00bf0a469c39494d8df9bbdbf57342e5d43e167dba20b659c2534a428edb9063ad60ddfb4867608b0520457348b535d1e5c1d040704" - vk <- newCString "0x2acfe95638a7aa71feaa167c1d53339708571ba2082f000d39bedebac6c872" - publicInputs <- newCString "0x190e5a3ed690bd3b533c087ef7339d00e25113d217f7dac4575a01b79dacd553" - -- Call the FFI function - result <- verify_plonk_bn254 circuitPath proof vk publicInputs - + result <- verifyPrehashed vk proof progId publicInputs + -- Print the result putStrLn ("Verification result: " ++ show result) + + where + + proof = Proof $ fromHex $ + "0x070ebe597360e09c92562b810cfd411a4cadac13904a6f5ef80597d31992b55611cc3d8557ad399cb5153cf87460901db1fd8dc92f16c5ea2ba1e8f357a1c5dd192120e7e19011e6e8028677870b4d59c1688fc0e729a547691ff7b9760aefbf242cfd52dbf3b98ddc003d31c8b86f2acf5f02ac891f990accba9a9c8477eb701d9f43bbd2c0566e7f48469c84a1769c17e74b8ed53602ce12f7f9a0181e9ab70548028c15e5a49611db64135b80545bf38afa4195e95d1289e20b945d7139ef1a94705f2ddbd481817316ef262053e9ee6a2419c63de7e3f339cac54bfa8c222e740ee8470ead68edb38aae2e9519fe01ce2f20707d6f81bc8643f0306c61ed1d37a65d87c876e13b3353bb42f02ab86423b4da43341e7bc5461b185a3946691d1015cb04fd6ffc38f9e7f52c92cffc3c44de7c58c9728d71e9f4c8266a831c2484b36f3ea92dd827d668dcf59ab7a0dee33f46fe289b90df06b15e4d288a5f1bee1bc0214e6a77647db6a2f4f51df8b100b1ddaef494618b3a3b0369ca18631f2d81effd2f9185afed9a0712d302b1d439c31d44a2ca3f20fd5f70a95eb30c132f39448df14f1e5311b906d22068ca160ac7d574df55605555df41208c9ab41634986f88e8af3b475ca428eb66e17f35c5d5808f358ec9884317db04d11a2d07200c82c257f1fdf6de2265f8cfd280c9728303f36dd0af7fc797b5ac232ae60000000720e4c44119a39f2303913d419ccff44f27639a1efe8634c93d82e581a4af1dee26b61ebb2d1eac5fe7e918786ced3a2a6b441dee0e8574341bb2e5bcbb27dc4f294ecca8cf684af42cecc403954747d3fb9b8ffb2a8081f2a809d3315f96bfca10d8e46e711998c3cd612849af775cb156caebcd3e2ce2bc7f6c7825dc536e310d69f3e8a506635f24f681b32fc30435c93d82ee0f93c83d7daf695d33acd87e1dbd2d55668d9a07c45d1f261173e9155bf35ce61b73becdc86ea76ca228f45a264f228a3b4f5b02022cd03bd8604a5ad62fd3a3706713bbc4f9846b31856b9608e7cdef78fc19f96cf6d5ada170bd3332f797f3a67756b83fa53e628f1db9a121042af248f08e8df4c9b046dc8807f7b514eb1dd7e6e2b6b80d060097a296cf22b56debdb623976250d281713f6e34b96ef5f71e92e748106d84675e30f9dc6000000010a0b17cbd37bd5c99d4ee00bf0a469c39494d8df9bbdbf57342e5d43e167dba20b659c2534a428edb9063ad60ddfb4867608b0520457348b535d1e5c1d040704" + progId = ProgramId $ fromHex $ + "0x2acfe95638a7aa71feaa167c1d53339708571ba2082f000d39bedebac6c872" + publicInputs = PublicParameterHash $ fromHex + "0x190e5a3ed690bd3b533c087ef7339d00e25113d217f7dac4575a01b79dacd553" + +fromHex :: String -> BS.ShortByteString +fromHex s = case B16.decode $ B8.pack $ fromMaybe s $ stripPrefix "0x" s of + Left e -> error $ "failed to decode hex string: " <> e + Right r -> BS.toShort r + diff --git a/test/test.cabal b/test/test.cabal index 89b1b4d..2b31779 100644 --- a/test/test.cabal +++ b/test/test.cabal @@ -51,7 +51,7 @@ executable test -- Import common warning flags. import: warnings - build-depends: base, plonk-verify + build-depends: base, plonk-verify, bytestring, base16-bytestring -- .hs or .lhs file containing the Main module. main-is: Main.hs diff --git a/tests/Main.hs b/tests/Main.hs index 31625bc..fff1a10 100644 --- a/tests/Main.hs +++ b/tests/Main.hs @@ -1,5 +1,13 @@ -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DerivingVia #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} + -- | -- Module: Main -- Copyright: Copyright © 2024 Kadena LLC. @@ -11,65 +19,120 @@ module Main ( main ) where -import Data.Aeson +import Control.Applicative +import Control.Monad -import Foreign.C.String +import Data.Aeson +import Data.ByteString.Base16 qualified as B16 +import Data.ByteString.Short qualified as BS +import Data.Maybe +import Data.Text qualified as T +import Data.Text.Encoding qualified as T import Test.Hspec -- internal modules -import PlonkVerify +import PlonkBn254.Verify +import PlonkBn254.Utils.EmbedVMKeys -- -------------------------------------------------------------------------- -- -- main +vmKeyName :: String +vmKeyName = "vk" + +vmKeys :: [(FilePath, VMKey)] +vmKeys = $$(embedVMKeys "bin" "verifier-assets/v1.0.8-testnet") + main :: IO () main = hspec $ describe "examples" $ do testExample "fibonacci_fixture" testExample "epoch_change" testExample "inclusion_fixture" --- -------------------------------------------------------------------------- --- Run Example +-- -------------------------------------------------------------------------- -- +-- Orphans -testExample :: String -> SpecWith () -testExample name = it name $ do - r <- runExample name - shouldNotBe r 0 - -runExample :: String -> IO Int -runExample name = do - p <- readProof name - dataDir <- newCString "./verifier-assets/v1.0.8-testnet" - proof <- newCString (_proofProof p) - vkeyHash <- newCString (_proofVKey p) - committedValuesDigest <- newCString (_proofPublicValues p) - - fromIntegral <$> verify_plonk_bn254 - dataDir - proof - vkeyHash - committedValuesDigest +deriving via HexEncoded instance FromJSON Proof +deriving via HexEncoded instance FromJSON ProgramId +deriving via HexEncoded instance FromJSON PublicParameter --- -------------------------------------------------------------------------- -- --- Utils +instance FromJSON PublicParameterHash where + parseJSON = parseJSON >=> \x -> case mkPublicParameterHash (_hexEncoded x) of + Right r -> return r + Left e -> fail $ "invalid public parameter hash bytes: " <> e -data Proof = Proof - { _proofVKey :: !String - , _proofPublicValues :: !String - , _proofProof :: !String +-- -------------------------------------------------------------------------- -- +-- Test Proof Claims + +data ProofClaim = ProofClaim + { _claimProgramId :: !ProgramId + -- ^ Identifies the RISC-V program that is proven. A program is valid + -- only in the context of a particular vm key. Each program has a well + -- defined set of public parameters. + + , _claimPublicParameters :: !(Either PublicParameterHash PublicParameter) + -- ^ The public parameters of the respective program. For verification + -- the parameters are encoded and hashes. + -- + -- In the context of this test suite a the length of used as heuristics + -- for whether the value is a digest or a list. + + , _claimProof :: !Proof + -- ^ The actual proof object. } -instance FromJSON Proof where - parseJSON = withObject "Proof" $ \o -> Proof +instance FromJSON ProofClaim where + parseJSON = withObject "ProofClaim" $ \o -> ProofClaim <$> o .: "vkey" - <*> o .: "publicValues" + <*> parseParameters o <*> o .: "proof" + where + parseParameters o + = (Left <$> o .: "publicValues") + <|> (Right <$> o .: "publicValues") -readProof :: String -> IO Proof +readProof :: String -> IO ProofClaim readProof name = eitherDecodeFileStrict' ("./assets/" <> name <> ".json") >>= \case Left e -> fail $ "failed to load proof " <> name <> ": " <> e Right p -> return p +-- -------------------------------------------------------------------------- +-- Run Example + +testExample :: String -> SpecWith () +testExample name = it name $ do + r <- runExample name + shouldBe r True + +runExample :: String -> IO Bool +runExample name = case lookup vmKeyName vmKeys of + Just vk -> do + p <- readProof name + case _claimPublicParameters p of + Left pp -> verifyPrehashed vk + (_claimProof p) + (_claimProgramId p) + pp + Right pp -> verify vk + (_claimProof p) + (_claimProgramId p) + pp + Nothing -> error $ "missing VM key: " <> vmKeyName + +-- -------------------------------------------------------------------------- -- +-- Utils + +-- | Helper for hex encodings +-- +newtype HexEncoded = HexEncoded { _hexEncoded :: BS.ShortByteString } + deriving (Show, Eq, Ord) + +instance FromJSON HexEncoded where + parseJSON = withText "HexEncoded" $ \str -> do + let sstr = fromMaybe str $ T.stripPrefix "0x" str + case B16.decode (T.encodeUtf8 sstr) of + Left e -> fail $ "decodeing hex string failed: " <> e + Right bytes -> return (HexEncoded $ BS.toShort bytes)