{-| The Pedersen commitment scheme has three operations: - Setup - Commit - Open -} {-# LANGUAGE NoImplicitPrelude #-} module Pedersen ( -- ** Safe Prime Field Pedersen Commitments Pedersen(..), CommitParams(..), Commitment(..), Reveal(..), setup, commit, open, addCommitments, verifyAddCommitments, verifyCommitParams, -- ** Elliptic Curve Pedersen Commitments ECPedersen(..), ECCommitParams(..), ECCommitment(..), ECReveal(..), ecSetup, ecCommit, ecOpen, ecAddCommitments, ecVerifyAddCommitments, ecAddInteger, ecVerifyAddInteger, verifyECCommitParams ) where import Protolude import qualified Prelude import Crypto.Hash import Crypto.Number.Serialize (os2ip) import Crypto.Random.Types (MonadRandom(..)) import qualified Crypto.PubKey.ECC.Prim as ECC import qualified Crypto.PubKey.ECC.Types as ECC import Data.Bits (xor, popCount) import qualified Data.ByteArray as BA import qualified Data.Map as Map import PrimeField ------------------------------------------------------------------------------- -- Pedersen Commitment Scheme ------------------------------------------------------------------------------- data CommitParams = CommitParams { pedersenSPF :: SPF -- ^ Safe prime field for pedersen commitment , pedersenH :: Integer -- ^ \(h = g^a \mod p\) where a is random } newtype Commitment = Commitment { unCommitment :: Integer } deriving (Eq) data Reveal = Reveal { revealVal :: Integer -- ^ Original value comitted , revealExp :: Integer -- ^ random exponent r, \(g^x \cdot h^r\) } data Pedersen = Pedersen { commitment :: Commitment , reveal :: Reveal } -- | Generates a Safe Prime Field (p,q,g) and a random value -- \(a \in Z_q\) such that \(g^a = h\), where g and h are the bases -- to be used in the pedersen commit function. setup :: MonadRandom m => Int -> m (Integer, CommitParams) setup nbits = do spf <- mkSPF nbits (a,h) <- runSPFT spf $ do a <- randomInZqM h <- gexpSafeSPFM a return (a,h) return (a, CommitParams spf h) -- | Commit a value by generating a random number \(r \in Z_q\) -- and computing \(C(x) = g^x \cdot h^r\) where x is the value to commit commit :: MonadRandom m => Integer -> CommitParams -> m Pedersen commit x (CommitParams spf h) = do (r,c) <- runSPFT spf $ do r <- randomInZqM c <- gexpSafeSPFM x |*| expSafeSPFM h r return (r,c) return $ Pedersen (Commitment c) (Reveal x r) -- | Open the commit by supplying the value commited, `x`, the -- random value `r` and the pedersen bases `g` and `h`, and -- verifying that \(C(x) \overset{!}{=} g^x * h^r\) open :: CommitParams -> Commitment -> Reveal -> Bool open (CommitParams spf h) (Commitment c) (Reveal x r) = resCommit == c where resCommit = runSPFM spf $ gexpSafeSPFM x |*| expSafeSPFM h r -- | This addition should be recorded as the previous commits are unable -- to be extracted from this new commitment. The only way to open this commiment -- is to tell the committing party the two commitments that were added so that the -- commitment can be validated and opening parameters can be created. addCommitments :: CommitParams -> Commitment -> Commitment -> Commitment addCommitments cp c1 c2 = Commitment $ modp (pedersenSPF cp) $ unCommitment c1 * unCommitment c2 -- | This function validates a homomorphic addition of two commitments using the -- original pedersen commits and reveals to compute the new commitment without -- homomorphic addition. verifyAddCommitments :: CommitParams -> Pedersen -> Pedersen -> Pedersen verifyAddCommitments (CommitParams spf h) p1 p2 = Pedersen newCommitment $ Reveal newVal newExp where (Reveal x r) = reveal p1 (Reveal y r') = reveal p2 newVal = modp spf $ x + y newExp = modp spf $ r + r' newCommitment = Commitment $ runSPFM spf $ gexpSafeSPFM newVal |*| expSafeSPFM h newExp -- | Check that `g^a = h` to verify integrity of a counterparty's commitment verifyCommitParams :: Integer -> CommitParams -> Bool verifyCommitParams a (CommitParams spf h) = runSPFM spf $ do h' <- gexpSafeSPFM a return $ h' == h ------------------------------------------------------------------------------- -- Pedersen Commitment Scheme - Elliptic Curve (SECP256k1) ------------------------------------------------------------------------------- secp256k1 :: ECC.Curve secp256k1 = ECC.getCurveByName ECC.SEC_p256k1 data ECCommitParams = ECCommitParams { ecCurve :: ECC.Curve , ecH :: ECC.Point } data ECCommitment = ECCommitment { unECCommitment :: ECC.Point } deriving Eq data ECReveal = ECReveal { ecRevealVal :: Integer , ecRevealScalar :: Integer } data ECPedersen = ECPedersen { ecCommitment :: ECCommitment , ecReveal :: ECReveal } -- | Setup EC Pedersen commit params, defaults to curve secp256k1 ecSetup :: MonadRandom m => Maybe ECC.CurveName -> m ECCommitParams ecSetup mCurveName = do a <- ECC.scalarGenerate curve let h = ECC.pointBaseMul curve a return $ ECCommitParams curve h where curve = case mCurveName of Nothing -> secp256k1 Just cn' -> ECC.getCurveByName cn' ecCommit :: MonadRandom m => Integer -> ECCommitParams -> m ECPedersen ecCommit x (ECCommitParams curve h) = do r <- ECC.scalarGenerate curve let xG = ECC.pointBaseMul curve x let rH = ECC.pointMul curve r h let commitment = ECCommitment $ ECC.pointAdd curve xG rH let reveal = ECReveal x r return $ ECPedersen commitment reveal ecOpen :: ECCommitParams -> ECCommitment -> ECReveal -> Bool ecOpen (ECCommitParams curve h) (ECCommitment c) (ECReveal x r) = c == ECC.pointAdd curve xG rH where xG = ECC.pointBaseMul curve x rH = ECC.pointMul curve r h -- | In order for this resulting commitment to be opened, the commiter -- must construct a new set of reveal parameters. The new reveal is then -- sent to the counterparty to open the homomorphically added commitment. ecAddCommitments :: ECCommitParams -> ECCommitment -> ECCommitment -> ECCommitment ecAddCommitments ecp (ECCommitment c1) (ECCommitment c2) = ECCommitment $ ECC.pointAdd (ecCurve ecp) c1 c2 -- | Verify the addition of two EC Pedersen Commitments by constructing -- the new Pedersen commitment on the uncommitted values. ecVerifyAddCommitments :: ECCommitParams -> ECPedersen -> ECPedersen -> ECPedersen ecVerifyAddCommitments (ECCommitParams curve h) p1 p2 = ECPedersen newCommitment newReveal where ECReveal x1 r1 = ecReveal p1 ECReveal x2 r2 = ecReveal p2 newVal = x1 + x2 newScalar = r1 + r2 xG = ECC.pointBaseMul curve newVal rH = ECC.pointMul curve newScalar h newCommitment = ECCommitment $ ECC.pointAdd curve xG rH newReveal = ECReveal newVal newScalar -- | Add an integer to the committed value. The committer should be informed -- of the integer added to the commitment so that a valid pedersen reveal -- can be constructed and the resulting commitment can be opened ecAddInteger :: ECCommitParams -> ECCommitment -> Integer -> ECCommitment ecAddInteger (ECCommitParams curve h) (ECCommitment c) n = ECCommitment $ ECC.pointAdd curve nG c where nG = ECC.pointBaseMul curve n ecVerifyAddInteger :: ECCommitParams -> ECPedersen -> Integer -> ECPedersen ecVerifyAddInteger (ECCommitParams curve h) p n = ECPedersen newCommitment newReveal where ECReveal x r = ecReveal p newVal = x + n xG = ECC.pointBaseMul curve newVal rH = ECC.pointMul curve r h -- rH doesn't change newCommitment = ECCommitment $ ECC.pointAdd curve xG rH newReveal = ECReveal newVal r -- r doesn't change verifyECCommitParams :: Integer -> ECCommitParams -> Bool verifyECCommitParams a (ECCommitParams curve h) = h == ECC.pointBaseMul curve a