module Tezos.Crypto.Secp256k1
(
PublicKey (..)
, SecretKey
, Signature (..)
, detSecretKey
, toPublic
, publicKeyToBytes
, mkPublicKey
, publicKeyLengthBytes
, signatureToBytes
, mkSignature
, signatureLengthBytes
, formatPublicKey
, mformatPublicKey
, parsePublicKey
, formatSignature
, mformatSignature
, parseSignature
, sign
, checkSignature
) where
import Crypto.Hash (Blake2b_256(..))
import Crypto.Number.Serialize (i2ospOf_, os2ip)
import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
import qualified Crypto.PubKey.ECC.Generate as ECC.Generate
import Crypto.PubKey.ECC.Types (Curve, CurveName(..), Point(..), curveSizeBits, getCurveByName)
import Crypto.Random (MonadRandom, drgNewSeed, seedFromInteger, withDRG)
import Data.ByteArray (ByteArray, ByteArrayAccess)
import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS
import Fmt (Buildable, build)
import Test.QuickCheck (Arbitrary(..), vector)
import Michelson.Text
import Tezos.Crypto.Util
curve :: Curve
curve = getCurveByName SEC_p256k1
curveSizeBytes :: Int
curveSizeBytes = curveSizeBits curve `div` 8
data PublicKey = PublicKey
{ unPublicKey :: ECDSA.PublicKey
, pkBytes :: Maybe ByteString
} deriving stock (Show)
instance Eq PublicKey where
pk1 == pk2 = publicKeyToBytes @ByteString pk1 == publicKeyToBytes pk2
instance Arbitrary PublicKey where
arbitrary = toPublic <$> arbitrary
newtype SecretKey = SecretKey
{ unSecretKey :: ECDSA.KeyPair
} deriving stock (Show, Eq)
detSecretKey :: ByteString -> SecretKey
detSecretKey seed = deterministic seed $ detSecretKeyDo
detSecretKeyDo :: MonadRandom m => m SecretKey
detSecretKeyDo = SecretKey <$> do
(publicKey, privateKey) <- ECC.Generate.generate curve
return $
ECDSA.KeyPair curve (ECDSA.public_q publicKey) (ECDSA.private_d privateKey)
instance Arbitrary SecretKey where
arbitrary = detSecretKey . BS.pack <$> vector 32
toPublic :: SecretKey -> PublicKey
toPublic =
flip PublicKey Nothing .
ECDSA.PublicKey curve . (\(ECDSA.KeyPair _ pp _) -> pp) . unSecretKey
newtype Signature = Signature
{ unSignature :: ECDSA.Signature
} deriving stock (Show, Eq)
instance Arbitrary Signature where
arbitrary = do
seed <- drgNewSeed . seedFromInteger <$> arbitrary
byteToSign <- arbitrary
return $ fst $ withDRG seed $ do
sk <- detSecretKeyDo
sign sk (one byteToSign)
publicKeyToBytes :: forall ba. ByteArray ba => PublicKey -> ba
publicKeyToBytes (PublicKey _ (Just bytes)) = BA.convert bytes
publicKeyToBytes (PublicKey (ECDSA.PublicKey _ publicPoint) Nothing) =
case publicPoint of
Point x y -> prefix y `BA.append` coordToBytes x
PointO -> error "PublicKey somehow contains infinity point"
where
prefix :: Integer -> ba
prefix y
| odd y = BA.singleton 0x03
| otherwise = BA.singleton 0x02
mkPublicKey :: ByteArrayAccess ba => ba -> Either CryptoParseError PublicKey
mkPublicKey ba
| l == publicKeyLengthBytes =
Right $ PublicKey (ECDSA.PublicKey curve $ Point 11 12) (Just $ BA.convert ba)
| otherwise =
Left $ CryptoParseUnexpectedLength "public key" l
where
l = BA.length ba
publicKeyLengthBytes :: Integral n => n
publicKeyLengthBytes = fromIntegral $ 1 + curveSizeBytes
signatureToBytes :: ByteArray ba => Signature -> ba
signatureToBytes (Signature (ECDSA.Signature r s)) =
coordToBytes r <> coordToBytes s
mkSignature :: ByteArray ba => ba -> Either CryptoParseError Signature
mkSignature ba
| l == signatureLengthBytes
, (rBytes, sBytes) <- BA.splitAt curveSizeBytes ba =
Right $ Signature (ECDSA.Signature (os2ip rBytes) (os2ip sBytes))
| otherwise =
Left $ CryptoParseUnexpectedLength "signature" l
where
l = BA.length ba
signatureLengthBytes :: Integral n => n
signatureLengthBytes = fromIntegral $ curveSizeBytes + curveSizeBytes
coordToBytes :: ByteArray ba => Integer -> ba
coordToBytes = i2ospOf_ curveSizeBytes
publicKeyTag :: ByteString
publicKeyTag = "\003\254\226\086"
signatureTag :: ByteString
signatureTag = "\013\115\101\019\063"
formatPublicKey :: PublicKey -> Text
formatPublicKey = formatImpl @ByteString publicKeyTag . publicKeyToBytes
mformatPublicKey :: PublicKey -> MText
mformatPublicKey = mkMTextUnsafe . formatPublicKey
instance Buildable PublicKey where
build = build . formatPublicKey
parsePublicKey :: Text -> Either CryptoParseError PublicKey
parsePublicKey = parseImpl publicKeyTag mkPublicKey
formatSignature :: Signature -> Text
formatSignature = formatImpl @ByteString signatureTag . signatureToBytes
mformatSignature :: Signature -> MText
mformatSignature = mkMTextUnsafe . formatSignature
instance Buildable Signature where
build = build . formatSignature
parseSignature :: Text -> Either CryptoParseError Signature
parseSignature = parseImpl signatureTag mkSignature
sign :: MonadRandom m => SecretKey -> ByteString -> m Signature
sign (SecretKey keyPair) =
fmap Signature . ECDSA.sign (ECDSA.toPrivateKey keyPair) Blake2b_256
checkSignature :: PublicKey -> Signature -> ByteString -> Bool
checkSignature (PublicKey pk _) (Signature sig) =
ECDSA.verify Blake2b_256 pk sig