Skip to content

Commit bdb4e19

Browse files
add an add-on package for recoverable signatures
1 parent 9036e7a commit bdb4e19

File tree

12 files changed

+536
-0
lines changed

12 files changed

+536
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6+
7+
## 0.7.0
8+
Initial version

secp256k1-haskell-recovery/LICENSE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../LICENSE
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../README.md
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: secp256k1-haskell-recovery
2+
version: 0.7.0
3+
synopsis: Bindings for recoverable signatures feature of secp256k1
4+
description: Sign and verify recoverable signatures using the secp256k1 library.
5+
category: Crypto
6+
author: Jean-Pierre Rupp
7+
maintainer: [email protected]
8+
copyright: (c) 2017 Jean-Pierre Rupp
9+
license: MIT
10+
license-file: LICENSE
11+
github: haskoin/secp256k1-haskell
12+
homepage: http://github.com/haskoin/secp256k1-haskell#readme
13+
extra-source-files:
14+
- CHANGELOG.md
15+
- README.md
16+
dependencies:
17+
- base >=4.9 && <5
18+
- base16 >=1.0
19+
- bytestring >=0.10.8 && <0.12
20+
- cereal >=0.5.4 && <0.6
21+
- entropy >=0.3.8 && <0.5
22+
- deepseq >=1.4.2 && <1.5
23+
- hashable >=1.2.6 && <1.5
24+
- QuickCheck >=2.9.2 && <2.15
25+
- secp256k1-haskell
26+
- string-conversions >=0.4 && <0.5
27+
- unliftio-core >=0.1.0 && <0.3
28+
library:
29+
source-dirs: src
30+
tests:
31+
spec:
32+
main: Spec.hs
33+
source-dirs: test
34+
ghc-options:
35+
- -threaded
36+
- -rtsopts
37+
- -with-rtsopts=-N
38+
verbatim:
39+
build-tool-depends:
40+
hspec-discover:hspec-discover
41+
dependencies:
42+
- hspec
43+
- secp256k1-haskell-recovery
44+
- monad-par
45+
- mtl
46+
- HUnit
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
cabal-version: 1.12
2+
3+
-- This file has been generated from package.yaml by hpack version 0.35.2.
4+
--
5+
-- see: https://github.com/sol/hpack
6+
7+
name: secp256k1-haskell-recovery
8+
version: 0.7.0
9+
synopsis: Bindings for recoverable signatures feature of secp256k1
10+
description: Sign and verify recoverable signatures using the secp256k1 library.
11+
category: Crypto
12+
homepage: http://github.com/haskoin/secp256k1-haskell#readme
13+
bug-reports: https://github.com/haskoin/secp256k1-haskell/issues
14+
author: Jean-Pierre Rupp
15+
maintainer: [email protected]
16+
copyright: (c) 2017 Jean-Pierre Rupp
17+
license: MIT
18+
license-file: LICENSE
19+
build-type: Simple
20+
extra-source-files:
21+
CHANGELOG.md
22+
README.md
23+
24+
source-repository head
25+
type: git
26+
location: https://github.com/haskoin/secp256k1-haskell
27+
28+
library
29+
exposed-modules:
30+
Crypto.Secp256k1.Internal.Recovery
31+
Crypto.Secp256k1.Internal.RecoveryOps
32+
Crypto.Secp256k1.Recovery
33+
other-modules:
34+
Paths_secp256k1_haskell_recovery
35+
hs-source-dirs:
36+
src
37+
build-depends:
38+
QuickCheck >=2.9.2 && <2.15
39+
, base >=4.9 && <5
40+
, base16 >=1.0
41+
, bytestring >=0.10.8 && <0.12
42+
, cereal >=0.5.4 && <0.6
43+
, deepseq >=1.4.2 && <1.5
44+
, entropy >=0.3.8 && <0.5
45+
, hashable >=1.2.6 && <1.5
46+
, secp256k1-haskell
47+
, string-conversions ==0.4.*
48+
, unliftio-core >=0.1.0 && <0.3
49+
default-language: Haskell2010
50+
51+
test-suite spec
52+
type: exitcode-stdio-1.0
53+
main-is: Spec.hs
54+
other-modules:
55+
Crypto.Secp256k1.RecoverySpec
56+
Paths_secp256k1_haskell_recovery
57+
hs-source-dirs:
58+
test
59+
ghc-options: -threaded -rtsopts -with-rtsopts=-N
60+
build-depends:
61+
HUnit
62+
, QuickCheck >=2.9.2 && <2.15
63+
, base >=4.9 && <5
64+
, base16 >=1.0
65+
, bytestring >=0.10.8 && <0.12
66+
, cereal >=0.5.4 && <0.6
67+
, deepseq >=1.4.2 && <1.5
68+
, entropy >=0.3.8 && <0.5
69+
, hashable >=1.2.6 && <1.5
70+
, hspec
71+
, monad-par
72+
, mtl
73+
, secp256k1-haskell
74+
, secp256k1-haskell-recovery
75+
, string-conversions ==0.4.*
76+
, unliftio-core >=0.1.0 && <0.3
77+
default-language: Haskell2010
78+
build-tool-depends: hspec-discover:hspec-discover
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
{-# LANGUAGE DeriveGeneric #-}
2+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+
4+
-- |
5+
-- Module : Crypto.Secp256k1.Internal.Recovery
6+
-- License : UNLICENSE
7+
-- Maintainer : Jean-Pierre Rupp <[email protected]>
8+
-- Stability : experimental
9+
-- Portability : POSIX
10+
--
11+
-- Crytpographic functions related to recoverable signatures from Bitcoin’s secp256k1 library.
12+
--
13+
-- The API for this module may change at any time. This is an internal module only
14+
-- exposed for hacking and experimentation.
15+
module Crypto.Secp256k1.Internal.Recovery where
16+
17+
import Control.DeepSeq (NFData)
18+
import Control.Monad (unless)
19+
import Crypto.Secp256k1.Internal.Base (Msg (..), PubKey (..), SecKey (..), Sig (..))
20+
import Crypto.Secp256k1.Internal.Context (ctx)
21+
import Crypto.Secp256k1.Internal.ForeignTypes
22+
( isSuccess,
23+
)
24+
import Crypto.Secp256k1.Internal.RecoveryOps
25+
( ecdsaRecover,
26+
ecdsaRecoverableSignatureConvert,
27+
ecdsaRecoverableSignatureParseCompact,
28+
ecdsaRecoverableSignatureSerializeCompact,
29+
ecdsaSignRecoverable,
30+
)
31+
import Crypto.Secp256k1.Internal.Util
32+
( decodeHex,
33+
showsHex,
34+
unsafePackByteString,
35+
unsafeUseByteString,
36+
)
37+
import Data.ByteString (ByteString)
38+
import Data.Hashable (Hashable (..))
39+
import Data.Maybe (fromMaybe)
40+
import Data.Serialize
41+
( Serialize (..),
42+
decode,
43+
encode,
44+
getByteString,
45+
getWord8,
46+
putByteString,
47+
putWord8,
48+
)
49+
import Data.String (IsString (..))
50+
import Data.Word (Word8)
51+
import Foreign
52+
( alloca,
53+
free,
54+
mallocBytes,
55+
nullFunPtr,
56+
nullPtr,
57+
peek,
58+
)
59+
import GHC.Generics (Generic)
60+
import System.IO.Unsafe (unsafePerformIO)
61+
import Text.Read
62+
( Lexeme (String),
63+
lexP,
64+
parens,
65+
pfail,
66+
readPrec,
67+
)
68+
69+
newtype RecSig = RecSig {getRecSig :: ByteString}
70+
deriving (Eq, Generic, NFData)
71+
72+
data CompactRecSig = CompactRecSig
73+
{ getCompactRecSigRS :: !ByteString,
74+
getCompactRecSigV :: {-# UNPACK #-} !Word8
75+
}
76+
deriving (Eq, Generic)
77+
78+
instance NFData CompactRecSig
79+
80+
instance Serialize RecSig where
81+
put (RecSig bs) = putByteString bs
82+
get = RecSig <$> getByteString 65
83+
84+
instance Serialize CompactRecSig where
85+
put (CompactRecSig bs v) = putByteString bs <> putWord8 v
86+
get = CompactRecSig <$> getByteString 64 <*> getWord8
87+
88+
instance Hashable RecSig where
89+
i `hashWithSalt` s = i `hashWithSalt` encode (exportCompactRecSig s)
90+
91+
recSigFromString :: String -> Maybe RecSig
92+
recSigFromString str = do
93+
bs <- decodeHex str
94+
rs <- either (const Nothing) Just $ decode bs
95+
importCompactRecSig rs
96+
97+
instance Read RecSig where
98+
readPrec = parens $ do
99+
String str <- lexP
100+
maybe pfail return $ recSigFromString str
101+
102+
instance IsString RecSig where
103+
fromString = fromMaybe e . recSigFromString
104+
where
105+
e = error "Could not decode signature from hex string"
106+
107+
instance Show RecSig where
108+
showsPrec _ = showsHex . encode . exportCompactRecSig
109+
110+
-- | Parse a compact ECDSA signature (64 bytes + recovery id).
111+
importCompactRecSig :: CompactRecSig -> Maybe RecSig
112+
importCompactRecSig (CompactRecSig sig_rs sig_v)
113+
| sig_v `notElem` [0, 1, 2, 3] = Nothing
114+
| otherwise = unsafePerformIO $
115+
unsafeUseByteString sig_rs $ \(sig_rs_ptr, _) -> do
116+
out_rec_sig_ptr <- mallocBytes 65
117+
ret <-
118+
ecdsaRecoverableSignatureParseCompact
119+
ctx
120+
out_rec_sig_ptr
121+
sig_rs_ptr
122+
(fromIntegral sig_v)
123+
if isSuccess ret
124+
then do
125+
out_bs <- unsafePackByteString (out_rec_sig_ptr, 65)
126+
return (Just (RecSig out_bs))
127+
else do
128+
free out_rec_sig_ptr
129+
return Nothing
130+
131+
-- | Serialize an ECDSA signature in compact format (64 bytes + recovery id).
132+
exportCompactRecSig :: RecSig -> CompactRecSig
133+
exportCompactRecSig (RecSig rec_sig_bs) = unsafePerformIO $
134+
unsafeUseByteString rec_sig_bs $ \(rec_sig_ptr, _) ->
135+
alloca $ \out_v_ptr -> do
136+
out_sig_ptr <- mallocBytes 64
137+
ret <-
138+
ecdsaRecoverableSignatureSerializeCompact
139+
ctx
140+
out_sig_ptr
141+
out_v_ptr
142+
rec_sig_ptr
143+
unless (isSuccess ret) $ do
144+
free out_sig_ptr
145+
error "Could not obtain compact signature"
146+
out_bs <- unsafePackByteString (out_sig_ptr, 64)
147+
out_v <- peek out_v_ptr
148+
return $ CompactRecSig out_bs (fromIntegral out_v)
149+
150+
-- | Convert a recoverable signature into a normal signature.
151+
convertRecSig :: RecSig -> Sig
152+
convertRecSig (RecSig rec_sig_bs) = unsafePerformIO $
153+
unsafeUseByteString rec_sig_bs $ \(rec_sig_ptr, _) -> do
154+
out_ptr <- mallocBytes 64
155+
ret <- ecdsaRecoverableSignatureConvert ctx out_ptr rec_sig_ptr
156+
unless (isSuccess ret) $
157+
error "Could not convert a recoverable signature"
158+
out_bs <- unsafePackByteString (out_ptr, 64)
159+
return $ Sig out_bs
160+
161+
-- | Create a recoverable ECDSA signature.
162+
signRecMsg :: SecKey -> Msg -> RecSig
163+
signRecMsg (SecKey sec_key) (Msg m) = unsafePerformIO $
164+
unsafeUseByteString sec_key $ \(sec_key_ptr, _) ->
165+
unsafeUseByteString m $ \(msg_ptr, _) -> do
166+
rec_sig_ptr <- mallocBytes 65
167+
ret <- ecdsaSignRecoverable ctx rec_sig_ptr msg_ptr sec_key_ptr nullFunPtr nullPtr
168+
unless (isSuccess ret) $ do
169+
free rec_sig_ptr
170+
error "could not sign message"
171+
RecSig <$> unsafePackByteString (rec_sig_ptr, 65)
172+
173+
-- | Recover an ECDSA public key from a signature.
174+
recover :: RecSig -> Msg -> Maybe PubKey
175+
recover (RecSig rec_sig) (Msg m) = unsafePerformIO $
176+
unsafeUseByteString rec_sig $ \(rec_sig_ptr, _) ->
177+
unsafeUseByteString m $ \(msg_ptr, _) -> do
178+
pub_key_ptr <- mallocBytes 64
179+
ret <- ecdsaRecover ctx pub_key_ptr rec_sig_ptr msg_ptr
180+
if isSuccess ret
181+
then do
182+
pub_key_bs <- unsafePackByteString (pub_key_ptr, 64)
183+
return (Just (PubKey pub_key_bs))
184+
else do
185+
free pub_key_ptr
186+
return Nothing
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
-- |
2+
-- Module : Crypto.Secp256k1.Internal.RecoveryOps
3+
-- License : UNLICENSE
4+
-- Maintainer : Jean-Pierre Rupp <[email protected]>
5+
-- Stability : experimental
6+
-- Portability : POSIX
7+
--
8+
-- The API for this module may change at any time. This is an internal module only
9+
-- exposed for hacking and experimentation.
10+
module Crypto.Secp256k1.Internal.RecoveryOps where
11+
12+
import Crypto.Secp256k1.Internal.ForeignTypes (Compact64, Ctx, Msg32, NonceFun, PubKey64, Ret, SecKey32, Sig64)
13+
import Foreign (FunPtr, Ptr)
14+
import Foreign.C (CInt (..))
15+
16+
data RecSig65
17+
18+
foreign import ccall safe "secp256k1_recovery.h secp256k1_ecdsa_recoverable_signature_parse_compact"
19+
ecdsaRecoverableSignatureParseCompact ::
20+
Ctx ->
21+
Ptr RecSig65 ->
22+
Ptr Compact64 ->
23+
CInt ->
24+
IO Ret
25+
26+
foreign import ccall safe "secp256k1_recovery.h secp256k1_ecdsa_recoverable_signature_convert"
27+
ecdsaRecoverableSignatureConvert ::
28+
Ctx ->
29+
Ptr Sig64 ->
30+
Ptr RecSig65 ->
31+
IO Ret
32+
33+
foreign import ccall safe "secp256k1_recovery.h secp256k1_ecdsa_recoverable_signature_serialize_compact"
34+
ecdsaRecoverableSignatureSerializeCompact ::
35+
Ctx ->
36+
Ptr Compact64 ->
37+
Ptr CInt ->
38+
Ptr RecSig65 ->
39+
IO Ret
40+
41+
foreign import ccall safe "secp256k1_recovery.h secp256k1_ecdsa_sign_recoverable"
42+
ecdsaSignRecoverable ::
43+
Ctx ->
44+
Ptr RecSig65 ->
45+
Ptr Msg32 ->
46+
Ptr SecKey32 ->
47+
FunPtr (NonceFun a) ->
48+
-- | nonce data
49+
Ptr a ->
50+
IO Ret
51+
52+
foreign import ccall safe "secp256k1_recovery.h secp256k1_ecdsa_recover"
53+
ecdsaRecover ::
54+
Ctx ->
55+
Ptr PubKey64 ->
56+
Ptr RecSig65 ->
57+
Ptr Msg32 ->
58+
IO Ret

0 commit comments

Comments
 (0)