Skip to content

Commit 06eca6a

Browse files
authored
Adding test vectors and golden tests from the standard for both versions of VRF (#519)
* add vrf_ver13_2 * vrf_ver13_generated_3 * vrf_ver13_generated_4 * vrf_ver13_standard_11 * vrf_ver13_standard_12 * vrf_ver13_standard_10 * enable first Praos test * vrf_ver03_generated_2 * vrf_ver03_generated_3 * vrf_ver03_generated_4 * add two golden from standard * vrf_ver03_standard_12 * adjust Changelogs * handle empty message cases * use Paths_ * refactor outputFromBytes for both VRFs * populate data-files in cabal and data-files must end with extension or presented explicitly
1 parent 56ed3e3 commit 06eca6a

20 files changed

+363
-5
lines changed

cardano-crypto-praos/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## 2.2.0.1
44

5-
*
5+
* Add and expose `outputFromBytes` for both `Praos` and `PraosBatchCompat` modules
66

77
## 2.2.0.0
88

cardano-crypto-praos/src/Cardano/Crypto/VRF/Praos.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ module Cardano.Crypto.VRF.Praos (
3030

3131
-- * Conversions
3232
outputBytes,
33+
outputFromBytes,
34+
outputFromProof,
3335
proofBytes,
3436
skBytes,
3537
vkBytes,
@@ -376,6 +378,24 @@ vkFromBytes bs = do
376378
mkOutput :: IO Output
377379
mkOutput = fmap Output $ newForeignPtr finalizerFree =<< mallocBytes (fromIntegral crypto_vrf_outputbytes)
378380

381+
outputFromBytes :: MonadFail m => ByteString -> m Output
382+
outputFromBytes bs = do
383+
if bsLen /= fromIntegral @CSize @Int crypto_vrf_outputbytes
384+
then
385+
fail
386+
( "Invalid output length "
387+
<> show bsLen
388+
<> ", expecting "
389+
<> show crypto_vrf_outputbytes
390+
)
391+
else pure $! unsafePerformIO $ do
392+
output <- mkOutput
393+
withForeignPtr (unOutput output) $ \ptr ->
394+
copyFromByteString ptr bs bsLen
395+
pure output
396+
where
397+
bsLen = BS.length bs
398+
379399
-- | Derive a key pair (Sign + Verify) from a seed.
380400
keypairFromSeed :: Seed -> (VerKey, SignKey)
381401
keypairFromSeed seed =

cardano-crypto-praos/src/Cardano/Crypto/VRF/PraosBatchCompat.hs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,19 @@ module Cardano.Crypto.VRF.PraosBatchCompat (
4040
Seed,
4141
genSeed,
4242
keypairFromSeed,
43+
seedFromBytes,
4344

4445
-- * Conversions
4546
unsafeRawSeed,
4647
outputBytes,
48+
outputFromBytes,
49+
outputFromProof,
4750
proofBytes,
51+
proofFromBytes,
4852
skBytes,
53+
skFromBytes,
4954
vkBytes,
55+
vkFromBytes,
5056
skToVerKey,
5157
skToSeed,
5258

@@ -56,6 +62,8 @@ module Cardano.Crypto.VRF.PraosBatchCompat (
5662
SignKeyVRF (..),
5763
VerKeyVRF (..),
5864
CertVRF (..),
65+
Proof (..),
66+
Output (..),
5967
)
6068
where
6169

@@ -165,7 +173,8 @@ foreign import ccall "crypto_vrf_ietfdraft13_publickeybytes"
165173
crypto_vrf_ietfdraft13_publickeybytes :: CSize
166174
foreign import ccall "crypto_vrf_ietfdraft13_secretkeybytes"
167175
crypto_vrf_ietfdraft13_secretkeybytes :: CSize
168-
foreign import ccall "crypto_vrf_ietfdraft13_seedbytes" crypto_vrf_ietfdraft13_seedbytes :: CSize
176+
foreign import ccall "crypto_vrf_ietfdraft13_seedbytes"
177+
crypto_vrf_ietfdraft13_seedbytes :: CSize
169178
foreign import ccall "crypto_vrf_ietfdraft13_outputbytes"
170179
crypto_vrf_ietfdraft13_outputbytes :: CSize
171180

@@ -402,6 +411,24 @@ mkOutput =
402411
fmap Output $
403412
newForeignPtr finalizerFree =<< mallocBytes (fromIntegral crypto_vrf_ietfdraft13_outputbytes)
404413

414+
outputFromBytes :: MonadFail m => ByteString -> m Output
415+
outputFromBytes bs = do
416+
if bsLen /= fromIntegral @CSize @Int crypto_vrf_ietfdraft13_outputbytes
417+
then
418+
fail
419+
( "Invalid output length "
420+
<> show bsLen
421+
<> ", expecting "
422+
<> show crypto_vrf_ietfdraft13_outputbytes
423+
)
424+
else pure $! unsafePerformIO $ do
425+
output <- mkOutput
426+
withForeignPtr (unOutput output) $ \ptr ->
427+
copyFromByteString ptr bs bsLen
428+
pure output
429+
where
430+
bsLen = BS.length bs
431+
405432
-- | Derive a key pair (Sign + Verify) from a seed.
406433
keypairFromSeed :: Seed -> (VerKey, SignKey)
407434
keypairFromSeed seed =

cardano-crypto-tests/CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## 2.2.0.1
44

5-
*
5+
* Add tests using standard test vectors and generated ones for Praos and PraosBatchCompat
66

77
## 2.2.0.0
88

@@ -38,4 +38,3 @@
3838
## 2.0.0.1
3939

4040
* Initial release
41-

cardano-crypto-tests/cardano-crypto-tests.cabal

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ data-files:
2323
bls12-381-test-vectors/test_vectors/h2c_large_dst
2424
bls12-381-test-vectors/test_vectors/pairing_test_vectors
2525
bls12-381-test-vectors/test_vectors/serde_test_vectors
26+
test_vectors/vrf_ver03_generated_1
27+
test_vectors/vrf_ver03_generated_2
28+
test_vectors/vrf_ver03_generated_3
29+
test_vectors/vrf_ver03_generated_4
30+
test_vectors/vrf_ver03_standard_10
31+
test_vectors/vrf_ver03_standard_11
32+
test_vectors/vrf_ver03_standard_12
33+
test_vectors/vrf_ver13_generated_1
34+
test_vectors/vrf_ver13_generated_2
35+
test_vectors/vrf_ver13_generated_3
36+
test_vectors/vrf_ver13_generated_4
37+
test_vectors/vrf_ver13_standard_10
38+
test_vectors/vrf_ver13_standard_11
39+
test_vectors/vrf_ver13_standard_12
2640

2741
flag secp256k1-support
2842
description:
@@ -49,13 +63,13 @@ common project-config
4963
library
5064
import: base, project-config
5165
hs-source-dirs: src
66+
other-modules: Paths_cardano_crypto_tests
5267
exposed-modules:
5368
Bench.Crypto.BenchData
5469
Bench.Crypto.DSIGN
5570
Bench.Crypto.HASH
5671
Bench.Crypto.KES
5772
Bench.Crypto.VRF
58-
Paths_cardano_crypto_tests
5973
Test.Crypto.AllocLog
6074
Test.Crypto.DSIGN
6175
Test.Crypto.EllipticCurve
@@ -106,6 +120,7 @@ test-suite test-crypto
106120
import: base, project-config
107121
type: exitcode-stdio-1.0
108122
hs-source-dirs: test
123+
other-modules: Paths_cardano_crypto_tests
109124
main-is: Main.hs
110125
build-depends:
111126
base,

cardano-crypto-tests/src/Test/Crypto/VRF.hs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{-# LANGUAGE DataKinds #-}
22
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE NamedFieldPuns #-}
4+
{-# LANGUAGE RecordWildCards #-}
35
{-# LANGUAGE ScopedTypeVariables #-}
46
{-# LANGUAGE TypeApplications #-}
57
{-# LANGUAGE TypeFamilies #-}
@@ -15,12 +17,18 @@ where
1517
import Cardano.Crypto.Util
1618
import Cardano.Crypto.VRF
1719
import Cardano.Crypto.VRF.Praos
20+
import qualified Cardano.Crypto.VRF.Praos as Ver03
1821
import Cardano.Crypto.VRF.PraosBatchCompat
22+
import qualified Cardano.Crypto.VRF.PraosBatchCompat as Ver13
1923

2024
import qualified Data.ByteString as BS
25+
import qualified Data.Char as Char
2126
import Data.Proxy (Proxy (..))
2227
import Data.Word (Word64, Word8)
28+
import qualified Text.ParserCombinators.ReadP as Parse
29+
import qualified Text.Read as Read
2330

31+
import Paths_cardano_crypto_tests (getDataFileName)
2432
import Test.Crypto.Util
2533
import Test.QuickCheck (
2634
Arbitrary (..),
@@ -32,6 +40,7 @@ import Test.QuickCheck (
3240
(==>),
3341
)
3442
import Test.Tasty (TestTree, testGroup)
43+
import Test.Tasty.HUnit (Assertion, HasCallStack, assertBool, assertFailure, testCase, (@?=))
3544
import Test.Tasty.QuickCheck (testProperty, vectorOf)
3645

3746
{- HLINT IGNORE "Use <$>" -}
@@ -59,8 +68,184 @@ tests =
5968
, testProperty "compatibleVerKeyConversion" prop_verKeyValidConversion
6069
, testProperty "compatibleSignKeyConversion" prop_signKeyValidConversion
6170
]
71+
, testGroup
72+
"test vectors for Praos"
73+
[ testCase "generated golden test vector: vrf_ver03_generated_1" $
74+
checkVer03TestVector "vrf_ver03_generated_1"
75+
, testCase "generated golden test vector: vrf_ver03_generated_2" $
76+
checkVer03TestVector "vrf_ver03_generated_2"
77+
, testCase "generated golden test vector: vrf_ver03_generated_3" $
78+
checkVer03TestVector "vrf_ver03_generated_3"
79+
, testCase "generated golden test vector: vrf_ver03_generated_4" $
80+
checkVer03TestVector "vrf_ver03_generated_4"
81+
, -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/ - Section A.4.
82+
testCase "generated golden test vector: vrf_ver03_standard_10" $
83+
checkVer03TestVector "vrf_ver03_standard_10"
84+
, -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/ - Section A.4.
85+
testCase "generated golden test vector: vrf_ver03_standard_11" $
86+
checkVer03TestVector "vrf_ver03_standard_11"
87+
, -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/03/ - Section A.4.
88+
testCase "generated golden test vector: vrf_ver03_standard_12" $
89+
checkVer03TestVector "vrf_ver03_standard_12"
90+
]
91+
, testGroup
92+
"test vectors for PraosBatchCompat"
93+
[ testCase "generated golden test vector: vrf_ver13_generated_1" $
94+
checkVer13TestVector "vrf_ver13_generated_1"
95+
, testCase "generated golden test vector: vrf_ver13_generated_2" $
96+
checkVer13TestVector "vrf_ver13_generated_2"
97+
, testCase "generated golden test vector: vrf_ver13_generated_3" $
98+
checkVer13TestVector "vrf_ver13_generated_3"
99+
, testCase "generated golden test vector: vrf_ver13_generated_4" $
100+
checkVer13TestVector "vrf_ver13_generated_4"
101+
, -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/13/ - example 10
102+
-- pi = 7d9c633ffeee27349264cf5c667579fc583b4bda63ab71d001f89c10003ab46f14adf9a3cd8b8412d9038531e865c341cafa73589b023d14311c331a9ad15ff2fb37831e00f0acaa6d73bc9997b06501
103+
testCase "generated golden test vector: vrf_ver13_standard_10" $
104+
checkVer13TestVector "vrf_ver13_standard_10"
105+
, -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/13/ - example 11
106+
-- pi = 47b327393ff2dd81336f8a2ef10339112401253b3c714eeda879f12c509072ef055b48372bb82efbdce8e10c8cb9a2f9d60e93908f93df1623ad78a86a028d6bc064dbfc75a6a57379ef855dc6733801
107+
testCase "generated golden test vector: vrf_ver13_standard_11" $
108+
checkVer13TestVector "vrf_ver13_standard_11"
109+
, -- https://datatracker.ietf.org/doc/draft-irtf-cfrg-vrf/13/ - example 12
110+
-- pi = 926e895d308f5e328e7aa159c06eddbe56d06846abf5d98c2512235eaa57fdce35b46edfc655bc828d44ad09d1150f31374e7ef73027e14760d42e77341fe05467bb286cc2c9d7fde29120a0b2320d04
111+
testCase "generated golden test vector: vrf_ver13_standard_12" $
112+
checkVer13TestVector "vrf_ver13_standard_12"
113+
]
114+
]
115+
116+
bytesEq :: HasCallStack => (a -> BS.ByteString) -> Maybe a -> a -> Assertion
117+
bytesEq outputToBytes suppliedM expected = case suppliedM of
118+
Just supplied ->
119+
outputToBytes supplied @?= outputToBytes expected
120+
Nothing ->
121+
assertBool ("suppliedM in byteEq gave Nothing") False
122+
123+
checkVer03TestVector :: FilePath -> Assertion
124+
checkVer03TestVector file = do
125+
filename <- getDataFileName $ "test_vectors/" <> file
126+
str <- readFile filename
127+
let testVectorE = Read.readMaybe @VRFTestVector str
128+
VRFTestVector {..} <-
129+
maybe
130+
(assertFailure $ "parsing test vector: " <> file <> " not successful")
131+
pure
132+
testVectorE
133+
signKey <- Ver03.skFromBytes testVectorSigningKey
134+
verKey <- Ver03.vkFromBytes testVectorVerifyingKey
135+
testVectorName @?= algorithmNameVRF (Proxy :: Proxy PraosVRF)
136+
testVectorVersion @?= "ietfdraft03"
137+
testVectorCipherSuite @?= "ECVRF-ED25519-SHA512-Elligator2"
138+
proof' <- Ver03.proofFromBytes testVectorProof
139+
hash' <- Ver03.outputFromBytes testVectorHash
140+
-- prove signKey msg -> proof
141+
Ver03.prove signKey testVectorMessage @?= Just proof'
142+
-- signKey -> verKey
143+
Ver03.skToVerKey signKey @?= verKey
144+
-- proof -> hashed msg
145+
bytesEq Ver03.outputBytes (Ver03.outputFromProof proof') hash'
146+
-- verify verKey proof msg -> hashed msg
147+
bytesEq Ver03.outputBytes (Ver03.verify verKey proof' testVectorMessage) hash'
148+
149+
checkVer13TestVector :: FilePath -> Assertion
150+
checkVer13TestVector file = do
151+
filename <- getDataFileName $ "test_vectors/" <> file
152+
str <- readFile filename
153+
let testVectorE = Read.readMaybe @VRFTestVector str
154+
VRFTestVector {..} <-
155+
maybe
156+
(assertFailure $ "parsing test vector: " <> file <> " not successful")
157+
pure
158+
testVectorE
159+
let signKey = Ver13.skFromBytes testVectorSigningKey
160+
let verKey = Ver13.vkFromBytes testVectorVerifyingKey
161+
testVectorName @?= algorithmNameVRF (Proxy :: Proxy PraosBatchCompatVRF)
162+
testVectorVersion @?= "ietfdraft13"
163+
testVectorCipherSuite @?= "ECVRF-ED25519-SHA512-Elligator2"
164+
-- prove signKey msg -> proof
165+
let proof' = Ver13.proofFromBytes testVectorProof
166+
hash' <- Ver13.outputFromBytes testVectorHash
167+
Ver13.prove signKey testVectorMessage @?= Just proof'
168+
-- signKey -> verKey
169+
Ver13.skToVerKey signKey @?= verKey
170+
-- proof -> hashed msg
171+
bytesEq Ver13.outputBytes (Ver13.outputFromProof proof') hash'
172+
-- verify verKey proof msg -> hashed msg
173+
bytesEq Ver13.outputBytes (Ver13.verify verKey proof' testVectorMessage) hash'
174+
175+
data VRFTestVector = VRFTestVector
176+
{ testVectorName :: String
177+
, testVectorVersion :: String
178+
, testVectorCipherSuite :: String
179+
, testVectorSigningKey :: BS.ByteString
180+
, testVectorVerifyingKey :: BS.ByteString
181+
, testVectorMessage :: BS.ByteString
182+
, testVectorProof :: BS.ByteString
183+
, testVectorHash :: BS.ByteString
184+
}
185+
186+
data HexStringWithLength = HexStringWithLength
187+
{ hswlPayload :: String
188+
, hswExpectedLength :: Int
189+
}
190+
deriving (Show, Eq)
191+
192+
parserHex :: Maybe Int -> Parse.ReadP BS.ByteString
193+
parserHex lenM = do
194+
str <- parseString
195+
if str == "empty"
196+
then
197+
pure BS.empty
198+
else case lenM of
199+
Just len -> handleDecode str len
200+
Nothing -> handleDecode str (length str `div` 2)
201+
where
202+
handleDecode str size = case decodeHexString str size of
203+
Right bs -> pure bs
204+
Left err -> error err
205+
206+
parseKey :: String -> Parse.ReadP String
207+
parseKey key = do
208+
key' <- Parse.string key
209+
Parse.skipSpaces
210+
_ <- Parse.string ":"
211+
Parse.skipSpaces
212+
pure key'
213+
214+
parseEOL :: Parse.ReadP ()
215+
parseEOL =
216+
Parse.choice
217+
[ Parse.char '\n' >> return ()
218+
, Parse.eof
62219
]
63220

221+
parseContent :: String -> Parse.ReadP a -> Parse.ReadP a
222+
parseContent key parser =
223+
Parse.between (parseKey key) parseEOL parser
224+
225+
parseString :: Parse.ReadP String
226+
parseString = Parse.munch1 (\c -> Char.isAlphaNum c || c == '-')
227+
228+
parserVRFTestVector :: Parse.ReadP VRFTestVector
229+
parserVRFTestVector = do
230+
testVectorName <- parseContent "vrf" parseString
231+
testVectorVersion <- parseContent "ver" parseString
232+
testVectorCipherSuite <- parseContent "ciphersuite" parseString
233+
sk <- parseContent "sk" $ parserHex (Just 32)
234+
testVectorVerifyingKey <- parseContent "pk" $ parserHex (Just 32)
235+
let testVectorSigningKey = sk <> testVectorVerifyingKey
236+
testVectorMessage <- parseContent "alpha" (parserHex Nothing)
237+
testVectorProof <-
238+
if testVectorName == "PraosVRF"
239+
then
240+
parseContent "pi" (parserHex (Just 80))
241+
else
242+
parseContent "pi" (parserHex (Just 128))
243+
testVectorHash <- parseContent "beta" (parserHex (Just 64))
244+
pure VRFTestVector {..}
245+
246+
instance Read VRFTestVector where
247+
readsPrec _ = Parse.readP_to_S parserVRFTestVector
248+
64249
testVRFAlgorithm ::
65250
forall proxy v.
66251
( VRFAlgorithm v
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
vrf: PraosVRF
2+
ver: ietfdraft03
3+
ciphersuite: ECVRF-ED25519-SHA512-Elligator2
4+
sk: 0000000000000000000000000000000000000000000000000000000000000000
5+
pk: 3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29
6+
alpha: 00
7+
pi: 000f006e64c91f84212919fe0899970cd341206fc081fe599339c8492e2cea3299ae9de4b6ce21cda0a975f65f45b70f82b3952ba6d0dbe11a06716e67aca233c0d78f115a655aa1952ada9f3d692a0a
8+
beta: 9930b5dddc0938f01cf6f9746eded569ee676bd6ff3b4f19233d74b903ec53a45c5728116088b7c622b6d6c354f7125c7d09870b56ec6f1e4bf4970f607e04b2

0 commit comments

Comments
 (0)