{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module PlutusCore.Crypto.Secp256k1
  ( verifyEcdsaSecp256k1Signature
  , verifySchnorrSecp256k1Signature
  ) where

import PlutusCore.Builtin.Result
import PlutusCore.Crypto.Utils

import Cardano.Crypto.DSIGN.Class qualified as DSIGN
import Cardano.Crypto.DSIGN.EcdsaSecp256k1 (EcdsaSecp256k1DSIGN, toMessageHash)
import Cardano.Crypto.DSIGN.SchnorrSecp256k1 (SchnorrSecp256k1DSIGN)
import Data.ByteString qualified as BS
import Data.Text (Text)

{-| Verify an ECDSA signature made using the SECP256k1 curve.

= Note

There are additional well-formation requirements for the arguments beyond
their length:

* The first byte of the public key must correspond to the sign of the /y/
coordinate: this is @0x02@ if /y/ is even, and @0x03@ otherwise.
* The remaining bytes of the public key must correspond to the /x/
coordinate, as a big-endian integer.
* The first 32 bytes of the signature must correspond to the big-endian
integer representation of _r_.
* The last 32 bytes of the signature must correspond to the big-endian
integer representation of _s_.

While this primitive /accepts/ a hash, any caller should only pass it hashes
that they computed themselves: specifically, they should receive the
/message/ from a sender and hash it, rather than receiving the /hash/ from
said sender. Failure to do so can be
[dangerous](https://bitcoin.stackexchange.com/a/81116/35586). Other than
length, we make no requirements of what hash gets used. -}
verifyEcdsaSecp256k1Signature
  :: BS.ByteString
  -- ^ Public key   (33 bytes)
  -> BS.ByteString
  -- ^ Message hash (32 bytes)
  -> BS.ByteString
  -- ^ Signature    (64 bytes)
  -> BuiltinResult Bool
verifyEcdsaSecp256k1Signature :: ByteString -> ByteString -> ByteString -> BuiltinResult Bool
verifyEcdsaSecp256k1Signature ByteString
pk ByteString
msg ByteString
sig =
  case forall v. DSIGNAlgorithm v => ByteString -> Maybe (VerKeyDSIGN v)
DSIGN.rawDeserialiseVerKeyDSIGN @EcdsaSecp256k1DSIGN ByteString
pk of
    Maybe (VerKeyDSIGN EcdsaSecp256k1DSIGN)
Nothing -> Text -> Text -> BuiltinResult Bool
forall a. Text -> Text -> BuiltinResult a
failWithMessage Text
loc Text
"Invalid verification key."
    Just VerKeyDSIGN EcdsaSecp256k1DSIGN
pk' -> case forall v. DSIGNAlgorithm v => ByteString -> Maybe (SigDSIGN v)
DSIGN.rawDeserialiseSigDSIGN @EcdsaSecp256k1DSIGN ByteString
sig of
      Maybe (SigDSIGN EcdsaSecp256k1DSIGN)
Nothing -> Text -> Text -> BuiltinResult Bool
forall a. Text -> Text -> BuiltinResult a
failWithMessage Text
loc Text
"Invalid signature."
      Just SigDSIGN EcdsaSecp256k1DSIGN
sig' -> case ByteString -> Maybe MessageHash
toMessageHash ByteString
msg of
        Maybe MessageHash
Nothing -> Text -> Text -> BuiltinResult Bool
forall a. Text -> Text -> BuiltinResult a
failWithMessage Text
loc Text
"Invalid message hash."
        Just MessageHash
msg' -> Bool -> BuiltinResult Bool
forall a. a -> BuiltinResult a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Bool -> BuiltinResult Bool) -> Bool -> BuiltinResult Bool
forall a b. (a -> b) -> a -> b
$ case ContextDSIGN EcdsaSecp256k1DSIGN
-> VerKeyDSIGN EcdsaSecp256k1DSIGN
-> MessageHash
-> SigDSIGN EcdsaSecp256k1DSIGN
-> Either String ()
forall v a.
(DSIGNAlgorithm v, Signable v a, HasCallStack) =>
ContextDSIGN v
-> VerKeyDSIGN v -> a -> SigDSIGN v -> Either String ()
forall a.
(Signable EcdsaSecp256k1DSIGN a, HasCallStack) =>
ContextDSIGN EcdsaSecp256k1DSIGN
-> VerKeyDSIGN EcdsaSecp256k1DSIGN
-> a
-> SigDSIGN EcdsaSecp256k1DSIGN
-> Either String ()
DSIGN.verifyDSIGN () VerKeyDSIGN EcdsaSecp256k1DSIGN
pk' MessageHash
msg' SigDSIGN EcdsaSecp256k1DSIGN
sig' of
          Left String
_ -> Bool
False
          Right () -> Bool
True
  where
    loc :: Text
    loc :: Text
loc = Text
"ECDSA SECP256k1 signature verification"

{-| Verify a Schnorr signature made using the SECP256k1 curve.

= Note

There are additional well-formation requirements for the arguments beyond
their length. Throughout, we refer to co-ordinates of the point @R@.

* The bytes of the public key must correspond to the /x/ coordinate, as a
big-endian integer, as specified in BIP-340.
* The first 32 bytes of the signature must correspond to the /x/ coordinate,
as a big-endian integer, as specified in BIP-340.
* The last 32 bytes of the signature must correspond to the bytes of /s/, as
a big-endian integer, as specified in BIP-340.

= See also

* [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) -}
verifySchnorrSecp256k1Signature
  :: BS.ByteString
  -- ^ Public key (32 bytes)
  -> BS.ByteString
  -- ^ Message    (arbitrary length)
  -> BS.ByteString
  -- ^ Signature  (64 bytes)
  -> BuiltinResult Bool
verifySchnorrSecp256k1Signature :: ByteString -> ByteString -> ByteString -> BuiltinResult Bool
verifySchnorrSecp256k1Signature ByteString
pk ByteString
msg ByteString
sig =
  case forall v. DSIGNAlgorithm v => ByteString -> Maybe (VerKeyDSIGN v)
DSIGN.rawDeserialiseVerKeyDSIGN @SchnorrSecp256k1DSIGN ByteString
pk of
    Maybe (VerKeyDSIGN SchnorrSecp256k1DSIGN)
Nothing -> Text -> Text -> BuiltinResult Bool
forall a. Text -> Text -> BuiltinResult a
failWithMessage Text
loc Text
"Invalid verification key."
    Just VerKeyDSIGN SchnorrSecp256k1DSIGN
pk' -> case forall v. DSIGNAlgorithm v => ByteString -> Maybe (SigDSIGN v)
DSIGN.rawDeserialiseSigDSIGN @SchnorrSecp256k1DSIGN ByteString
sig of
      Maybe (SigDSIGN SchnorrSecp256k1DSIGN)
Nothing -> Text -> Text -> BuiltinResult Bool
forall a. Text -> Text -> BuiltinResult a
failWithMessage Text
loc Text
"Invalid signature."
      Just SigDSIGN SchnorrSecp256k1DSIGN
sig' -> Bool -> BuiltinResult Bool
forall a. a -> BuiltinResult a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Bool -> BuiltinResult Bool) -> Bool -> BuiltinResult Bool
forall a b. (a -> b) -> a -> b
$ case ContextDSIGN SchnorrSecp256k1DSIGN
-> VerKeyDSIGN SchnorrSecp256k1DSIGN
-> ByteString
-> SigDSIGN SchnorrSecp256k1DSIGN
-> Either String ()
forall v a.
(DSIGNAlgorithm v, Signable v a, HasCallStack) =>
ContextDSIGN v
-> VerKeyDSIGN v -> a -> SigDSIGN v -> Either String ()
forall a.
(Signable SchnorrSecp256k1DSIGN a, HasCallStack) =>
ContextDSIGN SchnorrSecp256k1DSIGN
-> VerKeyDSIGN SchnorrSecp256k1DSIGN
-> a
-> SigDSIGN SchnorrSecp256k1DSIGN
-> Either String ()
DSIGN.verifyDSIGN () VerKeyDSIGN SchnorrSecp256k1DSIGN
pk' ByteString
msg SigDSIGN SchnorrSecp256k1DSIGN
sig' of
        Left String
_ -> Bool
False
        Right () -> Bool
True
  where
    loc :: Text
    loc :: Text
loc = Text
"Schnorr SECP256k1 signature verification"