Skip to content

Commit

Permalink
Simplify the VRF mkSeed
Browse files Browse the repository at this point in the history
This makes three actual changes:
1. change the hash function from SHA256
2. don't bother using xor, just include things into what we hash
3. serialise the slot number using raw serialisation, not CBOR

On 1, SHA256 was just the default suggestion, not a requirement. It's
simpler to simply use the same 256bit hash as we use everywhere else.

On 2, the original suggestion was to xor the universal constant into the
seed. But this suggestion of xor was as a minimum way to mix it in, not
a specific requirement. Since at the point where we are mixing it in we
are already hashing things, then its simpler to just include it into the
hash.

So instead of  a `xor` hash (b <> c) we can just use hash (a <> b <> c).

This also avoids using a slow xor implementation.

On 3, we should avoid CBOR serialisation for internal hashing, signing
etc. For one thing it's not quick.
  • Loading branch information
dcoutts committed Jul 14, 2020
1 parent 6611345 commit 67c842e
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ interval1 = UnsafeUnitInterval 1

-- | Evolving nonce type.
data Nonce
= Nonce !(Hash SHA256 Nonce)
= Nonce !(Hash Blake2b_256 Nonce)
| -- | Identity element
NeutralNonce
deriving (Eq, Generic, Ord, Show, NFData)
Expand Down Expand Up @@ -188,29 +188,30 @@ deriving anyclass instance FromJSON Nonce

-- | Evolve the nonce
(⭒) :: Nonce -> Nonce -> Nonce
(Nonce a) (Nonce b) = Nonce . coerce $ hashRaw @SHA256 id (getHash a <> getHash b)
Nonce a Nonce b = Nonce . castHash $
hashRaw id (getHash a <> getHash b)
x NeutralNonce = x
NeutralNonce x = x

-- | Make a nonce from the VRF output bytes
mkNonceFromOutputVRF :: VRF.OutputVRF v -> Nonce
mkNonceFromOutputVRF =
Nonce
. (castHash :: Hash SHA256 (VRF.OutputVRF v) -> Hash SHA256 Nonce)
. (castHash :: Hash Blake2b_256 (VRF.OutputVRF v) -> Hash Blake2b_256 Nonce)
. hashRaw VRF.getOutputVRFBytes

-- | Make a nonce from a number.
mkNonceFromNumber :: Word64 -> Nonce
mkNonceFromNumber =
Nonce
. (castHash :: Hash SHA256 Word64 -> Hash SHA256 Nonce)
. (castHash :: Hash Blake2b_256 Word64 -> Hash Blake2b_256 Nonce)
. hashRaw (BSL.toStrict . B.runPut . B.putWord64be)

-- | Seed to the verifiable random function.
--
-- We do not expose the constructor to `Seed`. Instead, a `Seed` should be
-- created using `mkSeed` for a VRF calculation.
newtype Seed = Seed (Hash SHA256 Seed)
newtype Seed = Seed (Hash Blake2b_256 Seed)
deriving (Eq, Ord, Show, Generic)
deriving newtype (NoUnexpectedThunks, ToCBOR)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import Cardano.Binary
withSlice,
withWordSize,
)
import Cardano.Crypto.Hash (SHA256)
import Cardano.Crypto.Hash (Blake2b_256)
import qualified Cardano.Crypto.Hash.Class as Hash
import qualified Cardano.Crypto.KES as KES
import qualified Cardano.Crypto.VRF as VRF
Expand All @@ -85,6 +85,8 @@ import Cardano.Prelude
import Cardano.Slotting.Slot (WithOrigin (..))
import Control.Monad (unless)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Builder as BS
import qualified Data.ByteString.Builder.Extra as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Coerce (coerce)
import Data.Map.Strict (Map)
Expand Down Expand Up @@ -133,6 +135,7 @@ import Shelley.Spec.Ledger.Serialization
decodeSeq,
encodeFoldableEncoder,
encodeFoldableMapEncoder,
runByteBuilder,
)
import Shelley.Spec.Ledger.Slot (BlockNo (..), SlotNo (..))
import Shelley.Spec.Ledger.Tx (Tx (..), decodeWits, segwitTx, txWitsBytes)
Expand Down Expand Up @@ -596,21 +599,28 @@ hsig ::
hsig (BHeader _ s) = s

-- | Construct a seed to use in the VRF computation.
--
-- We concatenate and then hash the slot, epoch nonce and universal constant.
--
mkSeed ::
-- | Universal constant
Nonce ->
SlotNo ->
-- | Epoch nonce
Nonce ->
Seed
mkSeed uc slot nonce = Seed . coerce $ case uc of
NeutralNonce -> seed
Nonce ucNonce -> ucNonce `Hash.xor` seed
where
seed = coerce $ Hash.hashRaw @SHA256 id (serialize' slot <> nonceBytes)
nonceBytes = case nonce of
NeutralNonce -> mempty
Nonce n -> Hash.getHash n
mkSeed ucNonce (SlotNo slot) eNonce =
Seed
. Hash.castHash
. Hash.hashRaw id
. runByteBuilder (8+32+32) $
BS.word64BE slot
<> (case eNonce of
NeutralNonce -> mempty
Nonce h -> BS.byteStringCopy (Hash.getHash h))
<> (case ucNonce of
NeutralNonce -> mempty
Nonce h -> BS.byteStringCopy (Hash.getHash h))

-- | Check that the certified input natural is valid for being slot leader. This
-- means we check that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ module Shelley.Spec.Ledger.Serialization
ipv6FromBytes,
ipv6ToCBOR,
ipv6FromCBOR,
-- Raw
runByteBuilder,
)
where

Expand Down Expand Up @@ -74,6 +76,8 @@ import Data.Binary.Get (Get, getWord32le, runGetOrFail)
import Data.Binary.Put (putWord32le, runPut)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Builder as BS
import qualified Data.ByteString.Builder.Extra as BS
import Data.Foldable (foldl')
import Data.Functor.Compose (Compose (..))
import Data.IP
Expand Down Expand Up @@ -333,3 +337,22 @@ ipv6ToCBOR = toCBOR . ipv6ToBytes

ipv6FromCBOR :: Decoder s IPv6
ipv6FromCBOR = byteDecoderToDecoder "IPv6" ipv6FromBytes

--
-- Raw serialisation
--

-- | Run a ByteString 'BS.Builder' using a strategy aimed at making smaller
-- things efficiently.
--
-- It takes a size hint and produces a strict 'ByteString'. This will be fast
-- when the size hint is the same or slightly bigger than the true size.
--
runByteBuilder :: Int -> BS.Builder -> BS.ByteString
runByteBuilder !sizeHint =
BSL.toStrict
. BS.toLazyByteStringWith
(BS.safeStrategy sizeHint (2 * sizeHint))
mempty
{-# NOINLINE runByteBuilder #-}

0 comments on commit 67c842e

Please sign in to comment.