Skip to content

Build with libsecp256k1-haskell #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
name: Checkout bitcoin-core/secp256k1
with:
repository: bitcoin-core/secp256k1
ref: 694ce8fb2d1fd8a3d641d7c33705691d41a2a860
ref: 751c4354d51fb5b10a80764df627b84e1a5ccd4f
path: lib/secp256k1

- uses: haskell/actions/setup@f7b0997283589ea5a6b4f2ade6a239d70a412877
Expand Down Expand Up @@ -83,7 +83,7 @@ jobs:
working-directory: ./lib/secp256k1
run: |
./autogen.sh
./configure
./configure --enable-module-schnorrsig --enable-module-recovery
make
make check
sudo make install
Expand All @@ -97,7 +97,7 @@ jobs:
pacman --noconfirm -S mingw-w64-x86_64-pkg-config
pacman --noconfirm -S mingw-w64-x86_64-autotools
./autogen.sh
./configure --prefix=/mingw64
./configure --prefix=/mingw64 --enable-module-schnorrsig --enable-module-recovery
make
make check
make install
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ cabal.project.local~
.ghc.environment.*
TAGS
tags
.vscode
6 changes: 3 additions & 3 deletions bitcoin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ library
, entropy >=0.4.1.5
, hashable >=1.3.0.0
, hspec >=2.7.1
, libsecp256k1 >=0.1.0
, memory >=0.15.0
, murmur3 >=1.0.3
, network >=3.1.1.1
, safe >=0.3.18
, scientific >=0.3.6.2
, secp256k1-haskell >=0.4.0
, split >=0.2.3.3
, string-conversions >=0.4.0.1
, text >=1.2.3.0
Expand Down Expand Up @@ -165,12 +165,12 @@ test-suite spec
, entropy >=0.4.1.5
, hashable >=1.3.0.0
, hspec >=2.7.1
, libsecp256k1 >=0.1.0
, memory >=0.15.0
, murmur3 >=1.0.3
, network >=3.1.1.1
, safe >=0.3.18
, scientific >=0.3.6.2
, secp256k1-haskell >=0.4.0
, split >=0.2.3.3
, string-conversions >=0.4.0.1
, text >=1.2.3.0
Expand Down Expand Up @@ -202,12 +202,12 @@ benchmark benchmark
, entropy >=0.4.1.5
, hashable >=1.3.0.0
, hspec >=2.7.1
, libsecp256k1 >=0.1.0
, memory >=0.15.0
, murmur3 >=1.0.3
, network >=3.1.1.1
, safe >=0.3.18
, scientific >=0.3.6.2
, secp256k1-haskell >=0.4.0
, split >=0.2.3.3
, string-conversions >=0.4.0.1
, text >=1.2.3.0
Expand Down
2 changes: 1 addition & 1 deletion package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dependencies:
- split >= 0.2.3.3
- safe >= 0.3.18
- scientific >= 0.3.6.2
- secp256k1-haskell >= 0.4.0
- libsecp256k1 >= 0.1.0
- string-conversions >= 0.4.0.1
- text >= 1.2.3.0
- time >= 1.9.3
Expand Down
61 changes: 25 additions & 36 deletions src/Bitcoin/Crypto/Signature.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,44 @@ module Bitcoin.Crypto.Signature (
verifyHashSig,
isCanonicalHalfOrder,
decodeStrictSig,
exportSig,
) where

import Bitcoin.Crypto.Hash (Hash256)
import Bitcoin.Crypto.Hash (Hash256 (getHash256))
import qualified Bitcoin.Util as U
import Control.Monad (guard, unless, when)
import Crypto.Secp256k1 (
CompactSig (getCompactSig),
Msg,
PubKey,
PubKeyXY,
SecKey,
Sig,
exportCompactSig,
exportSig,
importSig,
msg,
normalizeSig,
signMsg,
verifySig,
Signature,
ecdsaSign,
ecdsaVerify,
exportSignatureCompact,
exportSignatureDer,
importSignatureDer,
normalizeSignature,
)
import Data.Binary.Get (Get, getByteString, getWord8, lookAhead)
import Data.Binary.Put (Put, putByteString)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Maybe (fromMaybe, isNothing)
import Data.ByteString.Short (fromShort)
import Numeric (showHex)


-- | Convert 256-bit hash into a 'Msg' for signing or verification.
hashToMsg :: Hash256 -> Msg
hashToMsg = fromMaybe e . msg . U.encodeS
where
e = error "Could not convert 32-byte hash to secp256k1 message"


-- | Sign a 256-bit hash using secp256k1 elliptic curve.
signHash :: SecKey -> Hash256 -> Sig
signHash k = signMsg k . hashToMsg
signHash :: SecKey -> Hash256 -> Maybe Signature
signHash k = ecdsaSign k . fromShort . getHash256


-- | Verify an ECDSA signature for a 256-bit hash.
verifyHashSig :: Hash256 -> Sig -> PubKey -> Bool
verifyHashSig h s p = verifySig p norm (hashToMsg h)
verifyHashSig :: Hash256 -> Signature -> PubKeyXY -> Bool
verifyHashSig h s p = ecdsaVerify (fromShort $ getHash256 h) p norm
where
norm = fromMaybe s (normalizeSig s)
norm = snd $ normalizeSignature s


-- | Deserialize an ECDSA signature as commonly encoded in Bitcoin.
getSig :: Get Sig
getSig :: Get Signature
getSig = do
l <-
lookAhead $ do
Expand All @@ -82,24 +71,24 @@ getSig = do


-- | Serialize an ECDSA signature for Bitcoin use.
putSig :: Sig -> Put
putSig s = putByteString $ exportSig s
putSig :: Signature -> Put
putSig s = putByteString $ exportSignatureDer s


-- | Is canonical half order.
isCanonicalHalfOrder :: Sig -> Bool
isCanonicalHalfOrder = isNothing . normalizeSig
isCanonicalHalfOrder :: Signature -> Bool
isCanonicalHalfOrder = not . fst . normalizeSignature


-- | Decode signature strictly.
decodeStrictSig :: ByteString -> Maybe Sig
decodeStrictSig :: ByteString -> Maybe Signature
decodeStrictSig bs = do
g <- importSig bs
g <- importSignatureDer bs
-- <http://www.secg.org/sec1-v2.pdf Section 4.1.4>
-- 4.1.4.1 (r and s can not be zero)
let compact = exportCompactSig g
let compact = exportSignatureCompact g
let zero = BS.replicate 32 0
guard $ BS.take 32 (getCompactSig compact) /= zero
guard $ BS.take 32 (BS.drop 32 (getCompactSig compact)) /= zero
guard $ BS.take 32 compact /= zero
guard $ BS.take 32 (BS.drop 32 compact) /= zero
guard $ isCanonicalHalfOrder g
return g
49 changes: 25 additions & 24 deletions src/Bitcoin/Keys/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ module Bitcoin.Keys.Common (
-- * Public & Private Keys
PubKeyI (..),
SecKeyI (..),
exportPubKey,
importPubKey,
exportPubKeyXY,
importPubKeyXY,
wrapPubKey,
derivePubKeyI,
wrapSecKey,
fromMiniKey,
tweakPubKey,
tweakSecKey,
getSecKey,
secKey,
exportSecKey,
importSecKey,

-- ** Private Key Wallet Import Format (WIF)
fromWif,
Expand All @@ -45,15 +45,16 @@ import Control.Monad (guard, mzero, (<=<))
import Crypto.Hash (hashWith)
import Crypto.Hash.Algorithms (SHA256 (SHA256))
import Crypto.Secp256k1 (
PubKey,
PubKeyXY,
SecKey (..),
derivePubKey,
exportPubKey,
importPubKey,
secKey,
tweak,
tweakAddPubKey,
tweakAddSecKey,
exportPubKeyXY,
exportSecKey,
importPubKeyXY,
importSecKey,
importTweak,
pubKeyTweakAdd,
secKeyTweakAdd,
)
import Data.Binary (Binary (..))
import Data.Binary.Get (getByteString, getWord8, lookAhead)
Expand All @@ -71,7 +72,7 @@ import GHC.Generics (Generic)

-- | Elliptic curve public key type with expected serialized compression flag.
data PubKeyI = PubKeyI
{ pubKeyPoint :: !PubKey
{ pubKeyPoint :: !PubKeyXY
, pubKeyCompressed :: !Bool
}
deriving (Generic, Eq, Show, Read, Hashable, NFData)
Expand Down Expand Up @@ -100,18 +101,18 @@ instance Binary PubKeyI where
c = do
bs <- getByteString 33
maybe (fail "Could not decode public key") return $
PubKeyI <$> importPubKey bs <*> pure True
PubKeyI <$> importPubKeyXY bs <*> pure True
u = do
bs <- getByteString 65
maybe (fail "Could not decode public key") return $
PubKeyI <$> importPubKey bs <*> pure False
PubKeyI <$> importPubKeyXY bs <*> pure False


put pk = putByteString $ (exportPubKey <$> pubKeyCompressed <*> pubKeyPoint) pk
put pk = putByteString $ (exportPubKeyXY <$> pubKeyCompressed <*> pubKeyPoint) pk


-- | Wrap a public key from secp256k1 library adding information about compression.
wrapPubKey :: Bool -> PubKey -> PubKeyI
wrapPubKey :: Bool -> PubKeyXY -> PubKeyI
wrapPubKey c p = PubKeyI p c


Expand All @@ -122,8 +123,8 @@ derivePubKeyI (SecKeyI d c) = PubKeyI (derivePubKey d) c


-- | Tweak a public key.
tweakPubKey :: PubKey -> Hash256 -> Maybe PubKey
tweakPubKey p = tweakAddPubKey p <=< tweak . U.encodeS
tweakPubKey :: PubKeyXY -> Hash256 -> Maybe PubKeyXY
tweakPubKey p = pubKeyTweakAdd p <=< importTweak . U.encodeS


-- | Elliptic curve private key type with expected public key compression
Expand All @@ -144,14 +145,14 @@ wrapSecKey c d = SecKeyI d c

-- | Tweak a private key.
tweakSecKey :: SecKey -> Hash256 -> Maybe SecKey
tweakSecKey key = tweakAddSecKey key <=< tweak . U.encodeS
tweakSecKey key = secKeyTweakAdd key <=< importTweak . U.encodeS


-- | Decode Casascius mini private keys (22 or 30 characters).
fromMiniKey :: ByteString -> Maybe SecKeyI
fromMiniKey bs = do
guard checkShortKey
wrapSecKey False <$> (secKey . BA.convert . hashWith SHA256) bs
wrapSecKey False <$> (importSecKey . BA.convert . hashWith SHA256) bs
where
checkHash = BA.convert . hashWith SHA256 $ bs `BS.append` "?"
checkShortKey = BS.length bs `elem` [22, 30] && BS.head checkHash == 0x00
Expand All @@ -165,11 +166,11 @@ fromWif net wif = do
guard (BSL.head bs == getSecretPrefix net)
case BSL.length bs of
-- Uncompressed format
33 -> wrapSecKey False <$> (secKey . BSL.toStrict) (BSL.tail bs)
33 -> wrapSecKey False <$> (importSecKey . BSL.toStrict) (BSL.tail bs)
-- Compressed format
34 -> do
guard $ BSL.last bs == 0x01
wrapSecKey True <$> (secKey . BS.tail . BS.init . BSL.toStrict) bs
wrapSecKey True <$> (importSecKey . BS.tail . BS.init . BSL.toStrict) bs
-- Bad length
_ -> Nothing

Expand All @@ -179,5 +180,5 @@ toWif :: Network -> SecKeyI -> Base58
toWif net (SecKeyI k c) =
encodeBase58Check . BSL.cons (getSecretPrefix net) . BSL.fromStrict $
if c
then getSecKey k `BS.snoc` 0x01
else getSecKey k
then exportSecKey k `BS.snoc` 0x01
else exportSecKey k
Loading