secp256k1-haskell-2.0.0: src/Crypto/Secp256k1/Internal/Base.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE NoFieldSelectors #-}
-- | Internal module. Everything is exported, including the raw constructors
-- that "Crypto.Secp256k1" hides, and there is no API stability guarantee.
--
-- A value built with a raw constructor skips validation. Every function here
-- still checks the length of its bytes before calling the C library and
-- raises an 'Control.Exception.ErrorCall' when the length is wrong, but it does not check the
-- content. A raw 'PubKey' or 'Sig' with invalid content makes functions
-- return 'Nothing', 'False' or throw, never abort or read outside a buffer.
module Crypto.Secp256k1.Internal.Base where
import Control.DeepSeq (NFData)
import Control.Exception (evaluate, finally)
import Control.Monad (guard, join, replicateM, unless, (<=<))
import Control.Monad.IO.Class (MonadIO, liftIO)
import Crypto.Secp256k1.Internal.BaseOps
( ecPubKeyCombine,
ecPubKeyCreate,
ecPubKeyParse,
ecPubKeySerialize,
ecPubKeyTweakAdd,
ecPubKeyTweakMul,
ecSecKeyTweakAdd,
ecSecKeyTweakMul,
ecTweakNegate,
ecdsaSign,
ecdsaSignatureNormalize,
ecdsaSignatureParseCompact,
ecdsaSignatureParseDer,
ecdsaSignatureSerializeCompact,
ecdsaSignatureSerializeDer,
ecdsaVerify,
keyPairCreate,
keyPairSec,
keyPairXOnlyPub,
keyPairXOnlyTweakAdd,
schnorrSigSign32,
schnorrSigVerify,
xOnlyPubKeyFromPubKey,
xOnlyPubKeySerialize,
xOnlyPubKeyTweakAdd,
)
import Crypto.Secp256k1.Internal.Context (Ctx (..), validationContext)
import Crypto.Secp256k1.Internal.ForeignTypes
( KeyPair96,
LCtx,
SchnorrSig64,
XOnlyPubKey64,
compressed,
isSuccess,
uncompressed,
)
import Crypto.Secp256k1.Internal.Util
( decodeHex,
fillOutput,
packByteString,
showsHex,
unsafeUseByteString,
useFixedByteString,
withCopy,
withOutput,
wrongLength,
)
import Data.Bits (shiftR)
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.Hashable (Hashable (..))
import Data.Maybe (fromJust, fromMaybe, isJust)
import Data.String (IsString (..))
import Data.Word (Word32)
import Foreign
( Ptr,
alloca,
allocaArray,
allocaBytes,
castPtr,
fillBytes,
nullFunPtr,
nullPtr,
peek,
poke,
pokeArray,
withForeignPtr,
)
import Foreign.C (CUChar)
import GHC.Generics (Generic)
import System.Entropy (getEntropy)
import System.IO.Unsafe (unsafePerformIO)
import Test.QuickCheck
( Arbitrary (..),
arbitraryBoundedRandom,
suchThat,
)
import Text.Read
( Lexeme (String),
lexP,
parens,
pfail,
readPrec,
)
-- | Public key in the internal representation of libsecp256k1 (64 bytes).
--
-- The bytes in @get@ are opaque. They are not a standard encoding, and they
-- are not portable between platforms or library versions. Use 'exportPubKey'
-- to serialize a key and 'importPubKey' to parse one. 'pubKey' only exists to
-- rebuild a key from the bytes of @get@ on the same platform.
--
-- 'Show', 'Read' and 'IsString' use the compressed SEC1 encoding in hex.
-- 'Read' and 'IsString' also accept the uncompressed and hybrid encodings.
newtype PubKey = PubKey {get :: ByteString}
deriving (Eq, Generic, Hashable, NFData)
-- | 32-byte message hash to sign or verify.
--
-- This must be the output of a cryptographic hash function chosen by the
-- verifier. Verifying a value that an attacker can choose makes it easy to
-- produce a signature that verifies without knowing the secret key.
--
-- 'Show', 'Read' and 'IsString' use hex.
newtype Msg = Msg {get :: ByteString}
deriving (Eq, Generic, Hashable, NFData)
-- | ECDSA signature in the internal representation of libsecp256k1 (64
-- bytes). Both components of a valid 'Sig' are in the range 1 to n−1, where n
-- is the order of the curve.
--
-- The bytes in @get@ are opaque and not portable. Use 'exportSig' (DER) or
-- 'exportCompactSig' (64-byte r‖s) to serialize. 'sig' only exists to rebuild
-- a signature from the bytes of @get@ on the same platform.
--
-- 'Show', 'Read' and 'IsString' use the compact encoding in hex: r and s as
-- 32-byte big-endian numbers.
newtype Sig = Sig {get :: ByteString}
deriving (Eq, Generic, Hashable, NFData)
-- | 32-byte secret key as a big-endian number in the range 1 to n−1, where n
-- is the order of the curve.
--
-- 'Show', 'Read' and 'IsString' use hex. 'Show' prints the key, so showing a
-- record that holds a key, in a log line or an error message for instance,
-- reveals it. The bytes live in an ordinary 'ByteString', so the garbage
-- collector copies them and nothing ever clears them from memory.
newtype SecKey = SecKey {get :: ByteString}
deriving (Eq, Generic, Hashable, NFData)
-- | 32-byte scalar as a big-endian number in the range 0 to n−1, to add to or
-- multiply with a key. 'Show', 'Read' and 'IsString' use hex.
newtype Tweak = Tweak {get :: ByteString}
deriving (Eq, Generic, Hashable, NFData)
-- | Compact signature encoding: r and s as 32-byte big-endian numbers.
-- 'compactSig' only checks the length. 'importCompactSig' checks the values.
-- 'Show', 'Read' and 'IsString' use hex.
newtype CompactSig = CompactSig {get :: ByteString}
deriving (Eq, Generic, Hashable, NFData)
-- | BIP-340 Schnorr signature. Unlike 'Sig', the 64 bytes in @get@ are the
-- serialized form that BIP-340 defines, r‖s as 32-byte big-endian numbers,
-- which is the same on every platform. 'schnorrSig' only checks the length.
-- 'verifySchnorr' checks the values. 'Show', 'Read' and 'IsString' use hex.
newtype SchnorrSig = SchnorrSig {get :: ByteString}
deriving (Eq, Generic, Hashable, NFData)
instance Read PubKey where
readPrec = parens $ do
String str <- lexP
maybe pfail return $ importPubKey validationContext =<< decodeHex str
instance IsString PubKey where
fromString = fromMaybe e . (importPubKey validationContext <=< decodeHex)
where
e = error "Could not decode public key from hex string"
instance Show PubKey where
showsPrec _ = showsHex . exportPubKey validationContext True
instance Read Msg where
readPrec = parens $ do
String str <- lexP
maybe pfail return $ msg =<< decodeHex str
instance IsString Msg where
fromString = fromMaybe e . (msg <=< decodeHex)
where
e = error "Could not decode message from hex string"
instance Show Msg where
showsPrec _ = showsHex . (.get)
instance Read Sig where
readPrec = parens $ do
String str <- lexP
maybe pfail return $ sigFromCompactBytes =<< decodeHex str
instance IsString Sig where
fromString = fromMaybe e . (sigFromCompactBytes <=< decodeHex)
where
e = error "Could not decode signature from hex string"
instance Show Sig where
showsPrec _ = showsHex . (.get) . exportCompactSig validationContext
instance Read SecKey where
readPrec = parens $ do
String str <- lexP
maybe pfail return $ secKey =<< decodeHex str
instance IsString SecKey where
fromString = fromMaybe e . (secKey <=< decodeHex)
where
e = error "Could not decode secret key from hex string"
instance Show SecKey where
showsPrec _ = showsHex . (.get)
instance Read Tweak where
readPrec = parens $ do
String str <- lexP
maybe pfail return $ tweak =<< decodeHex str
instance IsString Tweak where
fromString = fromMaybe e . (tweak <=< decodeHex)
where
e = error "Could not decode tweak from hex string"
instance Show Tweak where
showsPrec _ = showsHex . (.get)
instance Read CompactSig where
readPrec = parens $ do
String str <- lexP
maybe pfail return $ compactSig =<< decodeHex str
instance IsString CompactSig where
fromString = fromMaybe e . (compactSig <=< decodeHex)
where
e = error "Could not decode compact signature from hex string"
instance Show CompactSig where
showsPrec _ = showsHex . (.get)
instance Read SchnorrSig where
readPrec = parens $ do
String str <- lexP
maybe pfail return $ schnorrSig =<< decodeHex str
instance IsString SchnorrSig where
fromString = fromMaybe e . (schnorrSig <=< decodeHex)
where
e = error "Could not decode Schnorr signature from hex string"
instance Show SchnorrSig where
showsPrec _ = showsHex . (.get)
-- | Parse the 64-byte compact encoding into a 'Sig' without a caller-provided
-- context. Used by the 'Read' and 'IsString' instances.
sigFromCompactBytes :: ByteString -> Maybe Sig
sigFromCompactBytes = importCompactSig validationContext <=< compactSig
-- | Order of the secp256k1 group, n, as a 32-byte big-endian number.
curveOrder :: ByteString
curveOrder =
BS.pack
[ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41
]
zero32 :: ByteString
zero32 = BS.replicate 32 0
-- | True when the bytes are a 32-byte big-endian number lower than the curve
-- order. 'ByteString' comparison is lexicographic, which for two strings of
-- the same length is the numeric order of the big-endian numbers.
belowCurveOrder :: ByteString -> Bool
belowCurveOrder bs = BS.length bs == 32 && bs < curveOrder
-- | Import a signature from the bytes of its internal representation, as
-- found in the @get@ field of a 'Sig' on the same platform. 'Nothing' unless
-- the bytes are the canonical representation of a signature whose components
-- are both in the range 1 to n−1. Signatures from other sources go through
-- 'importSig' or 'importCompactSig'.
sig :: ByteString -> Maybe Sig
sig bs
| isCanonicalSig bs = Just (Sig bs)
| otherwise = Nothing
-- | Serialize the internal bytes to the compact encoding, parse that back and
-- compare. Parsing checks that both components are below the curve order.
-- Comparing the bytes rejects non-canonical forms, so that each signature has
-- one representation and 'Eq' is meaningful. Zero components are rejected
-- too, because such a signature can never verify.
isCanonicalSig :: ByteString -> Bool
isCanonicalSig bs =
BS.length bs == 64
&& unsafePerformIO
( withForeignPtr fctx $ \ctx ->
unsafeUseByteString bs $ \(sig_ptr, _) ->
allocaBytes 64 $ \compact_ptr -> do
ret <- ecdsaSignatureSerializeCompact ctx compact_ptr sig_ptr
if not (isSuccess ret)
then return False
else do
compact <- packByteString (compact_ptr, 64)
parsed <- withOutput 64 $ \out ->
isSuccess <$> ecdsaSignatureParseCompact ctx out compact_ptr
return (parsed == Just bs && hasNonZeroComponents compact)
)
where
Ctx fctx = validationContext
-- | True when neither r nor s of a compact-encoded signature is zero.
hasNonZeroComponents :: ByteString -> Bool
hasNonZeroComponents compact =
BS.take 32 compact /= zero32 && BS.drop 32 compact /= zero32
-- | Import a public key from the bytes of its internal representation, as
-- found in the @get@ field of a 'PubKey' on the same platform. 'Nothing'
-- unless the bytes are the canonical representation of a point on the curve.
-- Keys from other sources go through 'importPubKey'.
pubKey :: ByteString -> Maybe PubKey
pubKey bs
| isCanonicalPubKey bs = Just (PubKey bs)
| otherwise = Nothing
-- | Serialize the internal bytes as an uncompressed key, parse that back and
-- compare. Parsing checks that the point is on the curve. Comparing the bytes
-- rejects non-canonical forms, so that each point has one representation and
-- 'Eq' is meaningful.
--
-- The serialization must be uncompressed. A compressed serialization drops
-- the y coordinate, so invalid bytes with a valid x would be accepted as a
-- different point.
isCanonicalPubKey :: ByteString -> Bool
isCanonicalPubKey bs =
BS.length bs == 64
&& unsafePerformIO
( withForeignPtr fctx $ \ctx ->
unsafeUseByteString bs $ \(pub_key_ptr, _) ->
alloca $ \len_ptr ->
allocaBytes 65 $ \ser_ptr -> do
poke len_ptr 65
ret <-
ecPubKeySerialize ctx ser_ptr len_ptr pub_key_ptr uncompressed
if not (isSuccess ret)
then return False
else do
len <- peek len_ptr
parsed <- withOutput 64 $ \out ->
isSuccess <$> ecPubKeyParse ctx out ser_ptr len
return (parsed == Just bs)
)
where
Ctx fctx = validationContext
-- | Import a 32-byte message hash.
msg :: ByteString -> Maybe Msg
msg bs
| BS.length bs == 32 = Just (Msg bs)
| otherwise = Nothing
-- | Import a 32-byte big-endian secret key. 'Nothing' unless the number is in
-- the range 1 to n−1, which is what @secp256k1_ec_seckey_verify@ requires.
secKey :: ByteString -> Maybe SecKey
secKey bs
| belowCurveOrder bs && bs /= zero32 = Just (SecKey bs)
| otherwise = Nothing
-- | Import a 32-byte big-endian tweak. 'Nothing' unless the number is in the
-- range 0 to n−1. Zero is allowed because adding it is harmless, but
-- multiplying by it fails.
tweak :: ByteString -> Maybe Tweak
tweak bs
| belowCurveOrder bs = Just (Tweak bs)
| otherwise = Nothing
-- | Import a 64-byte compact signature encoding. Only the length is checked.
-- 'importCompactSig' checks the values.
compactSig :: ByteString -> Maybe CompactSig
compactSig bs
| BS.length bs == 64 = Just (CompactSig bs)
| otherwise = Nothing
-- | Convert a signature to its lower-S form. A signature that is already in
-- that form is returned unchanged. 'verifySig' only accepts lower-S
-- signatures, so apply this first to signatures that may come from software
-- that does not produce them.
normalizeSig :: Ctx -> Sig -> Sig
normalizeSig (Ctx fctx) (Sig sig_bs) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Sig" 64 sig_bs $ \(sig_in, _) ->
-- The return value only says whether s was high. The output is written
-- in both cases.
Sig <$> fillOutput 64 (\sig_out -> () <$ ecdsaSignatureNormalize ctx sig_out sig_in)
-- | True when the s component is not greater than n/2. This is the form that
-- 'signMsg' produces and the only form that 'verifySig' accepts.
isLowS :: Ctx -> Sig -> Bool
isLowS (Ctx fctx) (Sig sig_bs) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Sig" 64 sig_bs $ \(sig_in, _) -> do
-- With a null output pointer the C function only reports whether s is
-- high: 1 when it is, 0 when it is not.
was_high <- ecdsaSignatureNormalize ctx nullPtr sig_in
return (not (isSuccess was_high))
-- | True when the r component is lower than 2^255, which makes the DER
-- encoding at most 70 bytes. 'signMsgLowR' produces such signatures.
isLowR :: Ctx -> Sig -> Bool
isLowR ctx s = BS.index (exportCompactSig ctx s).get 0 < 0x80
-- | Parse a public key in SEC1 encoding: 33 bytes compressed (prefix 0x02 or
-- 0x03), 65 bytes uncompressed (prefix 0x04) or 65 bytes hybrid (prefix 0x06
-- or 0x07). 'Nothing' when the encoding is wrong or the point is not on the
-- curve.
importPubKey :: Ctx -> ByteString -> Maybe PubKey
importPubKey (Ctx fctx) bs
| BS.null bs = Nothing
| otherwise =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
unsafeUseByteString bs $ \(input, len) ->
(fmap . fmap) PubKey . withOutput 64 $ \pub_key ->
isSuccess <$> ecPubKeyParse ctx pub_key input len
-- | Serialize a public key in SEC1 encoding. 'True' for the 33-byte
-- compressed form, 'False' for the 65-byte uncompressed form.
exportPubKey :: Ctx -> Bool -> PubKey -> ByteString
exportPubKey (Ctx fctx) compress (PubKey in_bs) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "PubKey" 64 in_bs $ \(in_ptr, _) ->
alloca $ \len_ptr ->
allocaBytes len $ \out_ptr -> do
poke len_ptr $ fromIntegral len
ret <- ecPubKeySerialize ctx out_ptr len_ptr in_ptr flags
unless (isSuccess ret) $ error "could not serialize public key"
final_len <- peek len_ptr
packByteString (out_ptr, final_len)
where
len = if compress then 33 else 65 :: Int
flags = if compress then compressed else uncompressed
-- | Serialize a signature in the compact encoding.
exportCompactSig :: Ctx -> Sig -> CompactSig
exportCompactSig (Ctx fctx) (Sig sig_bs) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Sig" 64 sig_bs $ \(sig_ptr, _) -> do
out <- withOutput 64 $ \out_ptr ->
isSuccess <$> ecdsaSignatureSerializeCompact ctx out_ptr sig_ptr
maybe
(error "could not serialize compact signature")
(return . CompactSig)
out
-- | Parse a signature from the compact encoding. 'Nothing' unless both
-- components are in the range 1 to n−1. The signature may be in high-S form.
importCompactSig :: Ctx -> CompactSig -> Maybe Sig
importCompactSig (Ctx fctx) (CompactSig compact) = do
guard (hasNonZeroComponents compact)
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "CompactSig" 64 compact $ \(compact_ptr, _) ->
(fmap . fmap) Sig . withOutput 64 $ \out_sig ->
isSuccess <$> ecdsaSignatureParseCompact ctx out_sig compact_ptr
-- | Parse a signature in strict DER encoding. 'Nothing' when the encoding is
-- not strict DER or when a component is outside the range 1 to n−1. The
-- signature may be in high-S form.
importSig :: Ctx -> ByteString -> Maybe Sig
importSig ctx@(Ctx fctx) bs
| BS.null bs = Nothing
| otherwise = do
parsed <-
unsafePerformIO $ withForeignPtr fctx $ \ctx_ptr ->
unsafeUseByteString bs $ \(in_ptr, in_len) ->
(fmap . fmap) Sig . withOutput 64 $ \out_sig ->
isSuccess <$> ecdsaSignatureParseDer ctx_ptr out_sig in_ptr in_len
-- libsecp256k1 parses a component that is not below the curve order as
-- zero and still reports success. Such a signature can never verify.
guard (hasNonZeroComponents (exportCompactSig ctx parsed).get)
return parsed
-- | Serialize a signature in strict DER encoding (8 to 72 bytes).
exportSig :: Ctx -> Sig -> ByteString
exportSig (Ctx fctx) (Sig in_sig) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Sig" 64 in_sig $ \(in_ptr, _) ->
alloca $ \out_len ->
allocaBytes 72 $ \out_ptr -> do
poke out_len 72
ret <- ecdsaSignatureSerializeDer ctx out_ptr out_len in_ptr
unless (isSuccess ret) $ error "could not serialize signature"
final_len <- peek out_len
packByteString (out_ptr, final_len)
-- | Verify a signature over a message hash. 'True' means the signature is
-- valid for the key and the hash and is in lower-S form. A valid signature
-- in high-S form gives 'False'. Apply 'normalizeSig' first to accept it.
verifySig :: Ctx -> PubKey -> Sig -> Msg -> Bool
verifySig (Ctx fctx) (PubKey pub_key) (Sig sig_bs) (Msg m) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "PubKey" 64 pub_key $ \(pub_key_ptr, _) ->
useFixedByteString "Sig" 64 sig_bs $ \(sig_ptr, _) ->
useFixedByteString "Msg" 32 m $ \(msg_ptr, _) ->
isSuccess <$> ecdsaVerify ctx sig_ptr msg_ptr pub_key_ptr
-- | Sign a message hash. The nonce comes from RFC 6979, so the same key and
-- hash always give the same signature. The signature is in lower-S form.
signMsg :: Ctx -> SecKey -> Msg -> Sig
signMsg ctx key m = signMsgWith ctx key m Nothing
-- | Sign a message hash with RFC 6979 and 32 bytes of extra nonce data (RFC
-- 6979 section 3.6), or without extra data for 'Nothing', which is the same as
-- 'signMsg'. The data must be exactly 32 bytes long. This is the primitive
-- behind 'signMsgLowR'.
signMsgWith :: Ctx -> SecKey -> Msg -> Maybe ByteString -> Sig
signMsgWith (Ctx fctx) (SecKey sec_key) (Msg m) nonce_data =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "SecKey" 32 sec_key $ \(sec_key_ptr, _) ->
useFixedByteString "Msg" 32 m $ \(msg_ptr, _) ->
withNonceData $ \data_ptr -> do
-- A null nonce function selects the RFC 6979 default, which still
-- receives the extra data.
out <- withOutput 64 $ \sig_ptr ->
isSuccess
<$> ecdsaSign ctx sig_ptr msg_ptr sec_key_ptr nullFunPtr data_ptr
maybe (error "could not sign message") (return . Sig) out
where
withNonceData f = case nonce_data of
Nothing -> f nullPtr
Just d -> useFixedByteString "nonce data" 32 d $ \(p, _) -> f p
-- | Sign like 'signMsg', then retry with extra nonce data until the r
-- component is lower than 2^255, so that the DER encoding is at most 70
-- bytes. This is what Bitcoin Core does since version 0.17, with the same
-- nonce data (a 32-bit little-endian counter padded to 32 bytes), so the
-- signatures are the same as Bitcoin Core's. On average it takes two
-- attempts. The result is still deterministic.
signMsgLowR :: Ctx -> SecKey -> Msg -> Sig
signMsgLowR ctx key m = go 0 (signMsg ctx key m)
where
go :: Word32 -> Sig -> Sig
go counter s
| isLowR ctx s = s
| otherwise =
let next = counter + 1
in go next (signMsgWith ctx key m (Just (counterData next)))
counterData c =
BS.pack (map (fromIntegral . (c `shiftR`)) [0, 8, 16, 24])
<> BS.replicate 28 0
-- | Compute the public key of a secret key.
derivePubKey :: Ctx -> SecKey -> PubKey
derivePubKey (Ctx fctx) (SecKey sec_key) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "SecKey" 32 sec_key $ \(sec_key_ptr, _) -> do
out <- withOutput 64 $ \pub_key_ptr ->
isSuccess <$> ecPubKeyCreate ctx pub_key_ptr sec_key_ptr
maybe (error "could not compute public key") (return . PubKey) out
-- | Add a tweak to a secret key: @key + tweak mod n@. 'Nothing' when the
-- result would be zero, which happens when the tweak is the negation of the
-- key.
tweakAddSecKey :: Ctx -> SecKey -> Tweak -> Maybe SecKey
tweakAddSecKey (Ctx fctx) (SecKey sec_key) (Tweak t) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Tweak" 32 t $ \(tweak_ptr, _) ->
(fmap . fmap) SecKey . withCopy "SecKey" 32 sec_key $ \sec_key_ptr ->
isSuccess <$> ecSecKeyTweakAdd ctx sec_key_ptr tweak_ptr
-- | Multiply a secret key by a tweak: @key * tweak mod n@. 'Nothing' when the
-- tweak is zero.
tweakMulSecKey :: Ctx -> SecKey -> Tweak -> Maybe SecKey
tweakMulSecKey (Ctx fctx) (SecKey sec_key) (Tweak t) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Tweak" 32 t $ \(tweak_ptr, _) ->
(fmap . fmap) SecKey . withCopy "SecKey" 32 sec_key $ \sec_key_ptr ->
isSuccess <$> ecSecKeyTweakMul ctx sec_key_ptr tweak_ptr
-- | Add a tweak to a public key: @key + tweak * G@. 'Nothing' when the result
-- would be the point at infinity, which happens when the tweak is the negation
-- of the secret key.
tweakAddPubKey :: Ctx -> PubKey -> Tweak -> Maybe PubKey
tweakAddPubKey (Ctx fctx) (PubKey pub_key) (Tweak t) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Tweak" 32 t $ \(tweak_ptr, _) ->
(fmap . fmap) PubKey . withCopy "PubKey" 64 pub_key $ \pub_key_ptr ->
isSuccess <$> ecPubKeyTweakAdd ctx pub_key_ptr tweak_ptr
-- | Multiply a public key by a tweak: @tweak * key@. 'Nothing' when the tweak
-- is zero.
tweakMulPubKey :: Ctx -> PubKey -> Tweak -> Maybe PubKey
tweakMulPubKey (Ctx fctx) (PubKey pub_key) (Tweak t) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Tweak" 32 t $ \(tweak_ptr, _) ->
(fmap . fmap) PubKey . withCopy "PubKey" 64 pub_key $ \pub_key_ptr ->
isSuccess <$> ecPubKeyTweakMul ctx pub_key_ptr tweak_ptr
-- | Add public keys together. 'Nothing' for an empty list, when a key is not
-- a valid point, or when the sum is the point at infinity.
combinePubKeys :: Ctx -> [PubKey] -> Maybe PubKey
combinePubKeys _ [] = Nothing
combinePubKeys (Ctx fctx) pubs
-- @secp256k1_ec_pubkey_combine@ ignores whether each key loads, so an
-- invalid key would be added as if it were a point.
| not (all isValidPoint pubs) = Nothing
| otherwise =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
withPointers pubs $ \ps ->
allocaArray (length ps) $ \arr -> do
pokeArray arr ps
(fmap . fmap) PubKey . withOutput 64 $ \out ->
isSuccess <$> ecPubKeyCombine ctx out arr (fromIntegral (length ps))
where
-- A raw key of the wrong length is a programming error, like everywhere
-- else. A raw key of the right length that is not a point gives Nothing.
isValidPoint (PubKey bs)
| BS.length bs /= 64 = wrongLength "PubKey" 64 bs
| otherwise = isCanonicalPubKey bs
withPointers [] f = f []
withPointers (PubKey pub_key : rest) f =
useFixedByteString "PubKey" 64 pub_key $ \(p, _) ->
withPointers rest $ \ps -> f (p : ps)
-- | Negate a tweak modulo the curve order: @n − tweak@, or zero for zero.
-- Adding a tweak and then its negation gives the original key back.
-- 'Nothing' only for a raw 'Tweak' that is not below the curve order.
tweakNegate :: Ctx -> Tweak -> Maybe Tweak
tweakNegate (Ctx fctx) (Tweak t)
| t == zero32 = Just (Tweak t)
| otherwise =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
(fmap . fmap) Tweak . withCopy "Tweak" 32 t $ \out ->
isSuccess <$> ecTweakNegate ctx out
-- * Schnorr signatures (BIP-340)
-- | Import a 64-byte BIP-340 Schnorr signature. Only the length is checked.
-- 'verifySchnorr' rejects a signature whose components are out of range.
schnorrSig :: ByteString -> Maybe SchnorrSig
schnorrSig bs
| BS.length bs == 64 = Just (SchnorrSig bs)
| otherwise = Nothing
-- | Run an action with the x-only form of a public key, which keeps the x
-- coordinate and drops the parity of y. 'Nothing' if the key does not load
-- as a point.
withXOnlyPubKey ::
Ptr LCtx -> ByteString -> (Ptr XOnlyPubKey64 -> IO a) -> IO (Maybe a)
withXOnlyPubKey ctx pub_key f =
useFixedByteString "PubKey" 64 pub_key $ \(pub_key_ptr, _) ->
allocaBytes 64 $ \xonly_ptr -> do
ret <- xOnlyPubKeyFromPubKey ctx xonly_ptr nullPtr pub_key_ptr
if isSuccess ret then Just <$> f xonly_ptr else return Nothing
-- | Run an action with the key pair of a secret key. 'Nothing' if the secret
-- key is not valid. The key pair holds the secret key, so its buffer is
-- zeroed when the action is done, whether it returns or throws.
withKeyPair ::
Ptr LCtx -> ByteString -> (Ptr KeyPair96 -> IO a) -> IO (Maybe a)
withKeyPair ctx sec_key f =
useFixedByteString "SecKey" 32 sec_key $ \(sec_key_ptr, _) ->
allocaBytes 96 $ \key_pair -> do
let create = do
ret <- keyPairCreate ctx key_pair sec_key_ptr
if isSuccess ret then Just <$> f key_pair else return Nothing
create `finally` fillBytes key_pair 0 96
-- | Import a 32-byte BIP-340 x-only public key: the x coordinate of a point.
-- The result is the point with that x coordinate and an even y coordinate,
-- as BIP-340 defines. 'Nothing' if no point has that x coordinate.
importXOnlyPubKey :: Ctx -> ByteString -> Maybe PubKey
importXOnlyPubKey ctx bs
| BS.length bs == 32 = importPubKey ctx (BS.cons 0x02 bs)
| otherwise = Nothing
-- | Export a public key as a 32-byte BIP-340 x-only public key: its x
-- coordinate. A key and its negation have the same x-only form.
exportXOnlyPubKey :: Ctx -> PubKey -> ByteString
exportXOnlyPubKey (Ctx fctx) (PubKey pub_key) =
unsafePerformIO $ withForeignPtr fctx $ \ctx -> do
out <- withXOnlyPubKey ctx pub_key $ \xonly_ptr ->
fillOutput 32 $ \out_ptr -> do
ret <- xOnlyPubKeySerialize ctx out_ptr xonly_ptr
unless (isSuccess ret) $ error "could not serialize x-only public key"
maybe (error "could not serialize x-only public key") return out
-- | Sign a message hash with BIP-340 and no auxiliary randomness. The
-- signature is deterministic, and the same as with 32 zero bytes of auxiliary
-- randomness. BIP-340 recommends fresh randomness for every signature, which
-- protects the key against side-channel and fault attacks; 'signSchnorrIO'
-- provides it.
--
-- As BIP-340 requires, the signature is verified before it is returned, and
-- an error is raised if that fails.
signSchnorr :: Ctx -> SecKey -> Msg -> SchnorrSig
signSchnorr ctx key m = signSchnorrInternal ctx key m Nothing
-- | Sign a message hash with BIP-340 and the given 32 bytes of auxiliary
-- randomness. 'Nothing' if the randomness is not 32 bytes long. The
-- signature is verified before it is returned, as in 'signSchnorr'.
signSchnorrWithAux :: Ctx -> SecKey -> Msg -> ByteString -> Maybe SchnorrSig
signSchnorrWithAux ctx key m aux
| BS.length aux == 32 = Just (signSchnorrInternal ctx key m (Just aux))
| otherwise = Nothing
-- | Sign a message hash with BIP-340 and fresh auxiliary randomness from the
-- system, as BIP-340 recommends. The signature is different each time. It is
-- verified before it is returned, as in 'signSchnorr'.
signSchnorrIO :: (MonadIO m) => Ctx -> SecKey -> Msg -> m SchnorrSig
signSchnorrIO ctx key m = liftIO $ do
aux <- getEntropy 32
evaluate (signSchnorrInternal ctx key m (Just aux))
-- | Sign with optional auxiliary randomness, then verify the signature with
-- the key pair that made it. BIP-340 makes that check the last step of
-- signing, so that a signature corrupted by a fault in the signer never
-- leaves it. Bitcoin Core does the same.
signSchnorrInternal :: Ctx -> SecKey -> Msg -> Maybe ByteString -> SchnorrSig
signSchnorrInternal (Ctx fctx) (SecKey sec_key) (Msg m) aux =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Msg" 32 m $ \(msg_ptr, _) ->
withAux $ \aux_ptr -> do
out <- withKeyPair ctx sec_key $ \key_pair ->
withOutput 64 $ \sig_ptr -> do
ret <- schnorrSigSign32 ctx sig_ptr msg_ptr key_pair aux_ptr
if isSuccess ret
then verifyFreshSignature ctx key_pair sig_ptr (castPtr msg_ptr)
else return False
case out of
Nothing -> error "could not sign message"
Just Nothing -> error "could not sign message: the signature does not verify"
Just (Just s) -> return (SchnorrSig s)
where
withAux f = case aux of
Nothing -> f nullPtr
Just bs -> useFixedByteString "auxiliary randomness" 32 bs $ \(p, _) -> f p
-- | Verify a signature that a key pair just made, with the x-only public key
-- of that key pair. The message is 32 bytes.
verifyFreshSignature ::
Ptr LCtx -> Ptr KeyPair96 -> Ptr SchnorrSig64 -> Ptr CUChar -> IO Bool
verifyFreshSignature ctx key_pair sig_ptr msg_ptr =
allocaBytes 64 $ \xonly_ptr -> do
ret <- keyPairXOnlyPub ctx xonly_ptr nullPtr key_pair
if isSuccess ret
then isSuccess <$> schnorrSigVerify ctx sig_ptr msg_ptr 32 xonly_ptr
else return False
-- | Verify a BIP-340 signature of a message hash. Only the x coordinate of
-- the public key is used, so a key and its negation verify the same
-- signatures. 'True' means that the signature is correct.
verifySchnorr :: Ctx -> PubKey -> SchnorrSig -> Msg -> Bool
verifySchnorr (Ctx fctx) (PubKey pub_key) (SchnorrSig sig_bs) (Msg m) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "SchnorrSig" 64 sig_bs $ \(sig_ptr, _) ->
useFixedByteString "Msg" 32 m $ \(msg_ptr, msg_len) ->
fmap (fromMaybe False) . withXOnlyPubKey ctx pub_key $ \xonly_ptr ->
isSuccess <$> schnorrSigVerify ctx sig_ptr msg_ptr msg_len xonly_ptr
-- | Add a tweak to the x-only form of a public key: @P + t·G@, where @P@ is
-- the point with the key's x coordinate and an even y coordinate. This is
-- how BIP-341 derives a Taproot output key from an internal key. The result
-- is a full point: its x coordinate is the tweaked x-only key, and the parity
-- of its y coordinate goes into the control block of a script path spend.
-- 'Nothing' if the result is the point at infinity, or for a raw 'Tweak' that
-- is not below the curve order.
tweakAddXOnlyPubKey :: Ctx -> PubKey -> Tweak -> Maybe PubKey
tweakAddXOnlyPubKey (Ctx fctx) (PubKey pub_key) (Tweak t) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Tweak" 32 t $ \(tweak_ptr, _) ->
fmap join . withXOnlyPubKey ctx pub_key $ \xonly_ptr ->
(fmap . fmap) PubKey . withOutput 64 $ \out ->
isSuccess <$> xOnlyPubKeyTweakAdd ctx out xonly_ptr tweak_ptr
-- | Add a tweak to a secret key the way 'tweakAddXOnlyPubKey' adds it to the
-- public key: the secret key is negated first when its public key has an odd
-- y coordinate, then the tweak is added. The result signs for the Taproot
-- output of 'tweakAddXOnlyPubKey' in a key path spend. 'Nothing' if the
-- result is zero, or for a raw 'Tweak' that is not below the curve order.
tweakAddXOnlySecKey :: Ctx -> SecKey -> Tweak -> Maybe SecKey
tweakAddXOnlySecKey (Ctx fctx) (SecKey sec_key) (Tweak t) =
unsafePerformIO $ withForeignPtr fctx $ \ctx ->
useFixedByteString "Tweak" 32 t $ \(tweak_ptr, _) ->
fmap join . withKeyPair ctx sec_key $ \key_pair -> do
ret <- keyPairXOnlyTweakAdd ctx key_pair tweak_ptr
if isSuccess ret
then
(fmap . fmap) SecKey . withOutput 32 $ \out ->
isSuccess <$> keyPairSec ctx out key_pair
else return Nothing
instance Arbitrary Msg where
arbitrary = fromJust <$> (gen `suchThat` isJust)
where
gen = msg . BS.pack <$> replicateM 32 arbitraryBoundedRandom
instance Arbitrary SecKey where
arbitrary = fromJust <$> (gen `suchThat` isJust)
where
gen = secKey . BS.pack <$> replicateM 32 arbitraryBoundedRandom
instance Arbitrary Tweak where
arbitrary = fromJust <$> (gen `suchThat` isJust)
where
gen = tweak . BS.pack <$> replicateM 32 arbitraryBoundedRandom