ppad-secp256k1-0.3.0: Schnorr signatures, ECDSA, and ECDH on the elliptic curve secp256k1
Copyright(c) 2024 Jared Tobin
LicenseMIT
MaintainerJared Tobin <jared@ppad.tech>
Safe HaskellNone
LanguageHaskell2010

Crypto.Curve.Secp256k1

Description

Pure BIP0340 Schnorr signatures, deterministic RFC6979 ECDSA (with BIP0146-style "low-S" signatures), and ECDH shared secret computation on the elliptic curve secp256k1.

Synopsis

Field and group parameters

_CURVE_Q :: Integer Source #

secp256k1 group order.

_CURVE_P :: Integer Source #

secp256k1 field prime.

remQ :: Integer -> Integer Source #

Division modulo secp256k1 group order, when argument is nonnegative.

modQ :: Integer -> Integer Source #

Division modulo secp256k1 group order.

secp256k1 points

type Pub = Projective Source #

A Schnorr and ECDSA-flavoured alias for a secp256k1 point.

derive_pub :: Integer -> Pub Source #

Derive a public key (i.e., a secp256k1 point) from the provided secret.

>>> import qualified System.Entropy as E
>>> sk <- fmap parse_int256 (E.getEntropy 32)
>>> derive_pub sk
"<secp256k1 point>"

derive_pub' :: Context -> Integer -> Pub Source #

The same as derive_pub, except uses a Context to optimise internal calculations.

>>> import qualified System.Entropy as E
>>> sk <- fmap parse_int256 (E.getEntropy 32)
>>> let !tex = precompute
>>> derive_pub' tex sk
"<secp256k1 point>"

_CURVE_G :: Projective Source #

secp256k1 generator point.

_CURVE_ZERO :: Projective Source #

secp256k1 zero point, point at infinity, or monoidal identity.

Parsing

parse_int256 :: ByteString -> Integer Source #

Parse a positive 256-bit Integer, e.g. a Schnorr or ECDSA secret key.

>>> import qualified Data.ByteString as BS
>>> parse_int256 (BS.replicate 32 0xFF)
<2^256 - 1>

parse_point :: ByteString -> Maybe Projective Source #

Parse compressed secp256k1 point (33 bytes), uncompressed point (65 bytes), or BIP0340-style point (32 bytes).

>>> parse_point <33-byte compressed point>
Just <Pub>
>>> parse_point <65-byte uncompressed point>
Just <Pub>
>>> parse_point <32-byte bip0340 public key>
Just <Pub>
>>> parse_point <anything else>
Nothing

parse_sig :: ByteString -> Maybe ECDSA Source #

Parse an ECDSA signature encoded in 64-byte "compact" form.

>>> parse_sig <64-byte compact signature>
"<ecdsa signature>"

Serializing

serialize_point :: Projective -> ByteString Source #

Serialize a secp256k1 point in 33-byte compressed form.

>>> serialize_point pub
"<33-byte compressed point>"

ECDH

ecdh Source #

Arguments

:: Projective

public key

-> Integer

secret key

-> ByteString

shared secret

Compute a shared secret, given a secret key and public secp256k1 point, via Elliptic Curve Diffie-Hellman (ECDH).

The shared secret is the SHA256 hash of the x-coordinate of the point obtained by scalar multiplication.

>>> let sec_alice = 0x03                   -- contrived
>>> let sec_bob   = 2 ^ 128 - 1            -- contrived
>>> let pub_alice = derive_pub sec_alice
>>> let pub_bob   = derive_pub sec_bob
>>> let secret_as_computed_by_alice = ecdh pub_bob sec_alice
>>> let secret_as_computed_by_bob   = ecdh pub_alice sec_bob
>>> secret_as_computed_by_alice == secret_as_computed_by_bob
True

BIP0340 Schnorr signatures

sign_schnorr Source #

Arguments

:: Integer

secret key

-> ByteString

message

-> ByteString

32 bytes of auxilliary random data

-> ByteString

64-byte Schnorr signature

Create a 64-byte Schnorr signature for the provided message, using the provided secret key.

BIP0340 recommends that 32 bytes of fresh auxiliary entropy be generated and added at signing time as additional protection against side-channel attacks (namely, to thwart so-called "fault injection" attacks). This entropy is supplemental to security, and the cryptographic security of the signature scheme itself does not rely on it, so it is not strictly required; 32 zero bytes can be used in its stead (and can be supplied via mempty).

>>> import qualified System.Entropy as E
>>> aux <- E.getEntropy 32
>>> sign_schnorr sec msg aux
"<64-byte schnorr signature>"

verify_schnorr Source #

Arguments

:: ByteString

message

-> Pub

public key

-> ByteString

64-byte Schnorr signature

-> Bool 

Verify a 64-byte Schnorr signature for the provided message with the supplied public key.

>>> verify_schnorr msg pub <valid signature>
True
>>> verify_schnorr msg pub <invalid signature>
False

RFC6979 ECDSA

data ECDSA Source #

An ECDSA signature.

Constructors

ECDSA 

Fields

Instances

Instances details
Generic ECDSA Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

Associated Types

type Rep ECDSA 
Instance details

Defined in Crypto.Curve.Secp256k1

type Rep ECDSA = D1 ('MetaData "ECDSA" "Crypto.Curve.Secp256k1" "ppad-secp256k1-0.3.0-2RiLeGP2i6sFDQENTGajra" 'False) (C1 ('MetaCons "ECDSA" 'PrefixI 'True) (S1 ('MetaSel ('Just "ecdsa_r") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Integer) :*: S1 ('MetaSel ('Just "ecdsa_s") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Integer)))

Methods

from :: ECDSA -> Rep ECDSA x #

to :: Rep ECDSA x -> ECDSA #

Show ECDSA Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

Methods

showsPrec :: Int -> ECDSA -> ShowS #

show :: ECDSA -> String #

showList :: [ECDSA] -> ShowS #

Eq ECDSA Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

Methods

(==) :: ECDSA -> ECDSA -> Bool #

(/=) :: ECDSA -> ECDSA -> Bool #

type Rep ECDSA Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

type Rep ECDSA = D1 ('MetaData "ECDSA" "Crypto.Curve.Secp256k1" "ppad-secp256k1-0.3.0-2RiLeGP2i6sFDQENTGajra" 'False) (C1 ('MetaCons "ECDSA" 'PrefixI 'True) (S1 ('MetaSel ('Just "ecdsa_r") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Integer) :*: S1 ('MetaSel ('Just "ecdsa_s") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 Integer)))

sign_ecdsa Source #

Arguments

:: Integer

secret key

-> ByteString

message

-> ECDSA 

Produce an ECDSA signature for the provided message, using the provided private key.

sign_ecdsa produces a "low-s" signature, as is commonly required in applications using secp256k1. If you need a generic ECDSA signature, use sign_ecdsa_unrestricted.

>>> sign_ecdsa sec msg
"<ecdsa signature>"

sign_ecdsa_unrestricted Source #

Arguments

:: Integer

secret key

-> ByteString

message

-> ECDSA 

Produce an ECDSA signature for the provided message, using the provided private key.

sign_ecdsa_unrestricted produces an unrestricted ECDSA signature, which is less common in applications using secp256k1 due to the signature's inherent malleability. If you need a conventional "low-s" signature, use sign_ecdsa.

>>> sign_ecdsa_unrestricted sec msg
"<ecdsa signature>"

verify_ecdsa Source #

Arguments

:: ByteString

message

-> Pub

public key

-> ECDSA

signature

-> Bool 

Verify a "low-s" ECDSA signature for the provided message and public key,

Fails to verify otherwise-valid "high-s" signatures. If you need to verify generic ECDSA signatures, use verify_ecdsa_unrestricted.

>>> verify_ecdsa msg pub valid_sig
True
>>> verify_ecdsa msg pub invalid_sig
False

verify_ecdsa_unrestricted Source #

Arguments

:: ByteString

message

-> Pub

public key

-> ECDSA

signature

-> Bool 

Verify an unrestricted ECDSA signature for the provided message and public key.

>>> verify_ecdsa_unrestricted msg pub valid_sig
True
>>> verify_ecdsa_unrestricted msg pub invalid_sig
False

Fast variants

data Context Source #

Precomputed multiples of the secp256k1 base or generator point.

Instances

Instances details
Generic Context Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

Associated Types

type Rep Context 
Instance details

Defined in Crypto.Curve.Secp256k1

type Rep Context = D1 ('MetaData "Context" "Crypto.Curve.Secp256k1" "ppad-secp256k1-0.3.0-2RiLeGP2i6sFDQENTGajra" 'False) (C1 ('MetaCons "Context" 'PrefixI 'True) (S1 ('MetaSel ('Just "ctxW") 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 Int) :*: S1 ('MetaSel ('Just "ctxArray") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (Array Projective))))

Methods

from :: Context -> Rep Context x #

to :: Rep Context x -> Context #

Show Context Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

Eq Context Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

Methods

(==) :: Context -> Context -> Bool #

(/=) :: Context -> Context -> Bool #

type Rep Context Source # 
Instance details

Defined in Crypto.Curve.Secp256k1

type Rep Context = D1 ('MetaData "Context" "Crypto.Curve.Secp256k1" "ppad-secp256k1-0.3.0-2RiLeGP2i6sFDQENTGajra" 'False) (C1 ('MetaCons "Context" 'PrefixI 'True) (S1 ('MetaSel ('Just "ctxW") 'SourceUnpack 'SourceStrict 'DecidedStrict) (Rec0 Int) :*: S1 ('MetaSel ('Just "ctxArray") 'NoSourceUnpackedness 'SourceStrict 'DecidedStrict) (Rec0 (Array Projective))))

precompute :: Context Source #

Create a secp256k1 context by precomputing multiples of the curve's generator point.

This should be used once to create a Context to be reused repeatedly afterwards.

>>> let !tex = precompute
>>> sign_ecdsa' tex sec msg
>>> sign_schnorr' tex sec msg aux

sign_schnorr' Source #

Arguments

:: Context

secp256k1 context

-> Integer

secret key

-> ByteString

message

-> ByteString

32 bytes of auxilliary random data

-> ByteString

64-byte Schnorr signature

The same as sign_schnorr, except uses a Context to optimise internal calculations.

You can expect about a 2x performance increase when using this function, compared to sign_schnorr.

>>> import qualified System.Entropy as E
>>> aux <- E.getEntropy 32
>>> let !tex = precompute
>>> sign_schnorr' tex sec msg aux
"<64-byte schnorr signature>"

verify_schnorr' Source #

Arguments

:: Context

secp256k1 context

-> ByteString

message

-> Pub

public key

-> ByteString

64-byte Schnorr signature

-> Bool 

The same as verify_schnorr, except uses a Context to optimise internal calculations.

You can expect about a 1.5x performance increase when using this function, compared to verify_schnorr.

>>> let !tex = precompute
>>> verify_schnorr' tex msg pub <valid signature>
True
>>> verify_schnorr' tex msg pub <invalid signature>
False

sign_ecdsa' Source #

Arguments

:: Context

secp256k1 context

-> Integer

secret key

-> ByteString

message

-> ECDSA 

The same as sign_ecdsa, except uses a Context to optimise internal calculations.

You can expect about a 10x performance increase when using this function, compared to sign_ecdsa.

>>> let !tex = precompute
>>> sign_ecdsa' tex sec msg
"<ecdsa signature>"

sign_ecdsa_unrestricted' Source #

Arguments

:: Context

secp256k1 context

-> Integer

secret key

-> ByteString

message

-> ECDSA 

The same as sign_ecdsa_unrestricted, except uses a Context to optimise internal calculations.

You can expect about a 10x performance increase when using this function, compared to sign_ecdsa_unrestricted.

>>> let !tex = precompute
>>> sign_ecdsa_unrestricted' tex sec msg
"<ecdsa signature>"

verify_ecdsa' Source #

Arguments

:: Context

secp256k1 context

-> ByteString

message

-> Pub

public key

-> ECDSA

signature

-> Bool 

The same as verify_ecdsa, except uses a Context to optimise internal calculations.

You can expect about a 2x performance increase when using this function, compared to verify_ecdsa.

>>> let !tex = precompute
>>> verify_ecdsa' tex msg pub valid_sig
True
>>> verify_ecdsa' tex msg pub invalid_sig
False

verify_ecdsa_unrestricted' Source #

Arguments

:: Context

secp256k1 context

-> ByteString

message

-> Pub

public key

-> ECDSA

signature

-> Bool 

The same as verify_ecdsa_unrestricted, except uses a Context to optimise internal calculations.

You can expect about a 2x performance increase when using this function, compared to verify_ecdsa_unrestricted.

>>> let !tex = precompute
>>> verify_ecdsa_unrestricted' tex msg pub valid_sig
True
>>> verify_ecdsa_unrestricted' tex msg pub invalid_sig
False