Skip to content

Commit f481dc3

Browse files
add an add-on package for recoverable signatures
1 parent 8df0b17 commit f481dc3

File tree

13 files changed

+537
-0
lines changed

13 files changed

+537
-0
lines changed

scripts/format

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ REPO_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )
44

55
find $REPO_DIR/secp256k1-haskell/src -type f -name "*.hs" | xargs ormolu -i
66
find $REPO_DIR/secp256k1-haskell/test -type f -name "*.hs" | xargs ormolu -i
7+
find $REPO_DIR/secp256k1-haskell-recovery/src -type f -name "*.hs" | xargs ormolu -i
8+
find $REPO_DIR/secp256k1-haskell-recovery/test -type f -name "*.hs" | xargs ormolu -i
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.1.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: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: secp256k1-haskell-recovery
2+
version: 0.1.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+
- entropy >=0.3.8 && <0.5
21+
- deepseq >=1.4.2 && <1.5
22+
- hashable >=1.2.6 && <1.5
23+
- QuickCheck >=2.9.2 && <2.15
24+
- secp256k1-haskell
25+
- string-conversions >=0.4 && <0.5
26+
- unliftio-core >=0.1.0 && <0.3
27+
library:
28+
source-dirs: src
29+
tests:
30+
spec:
31+
main: Spec.hs
32+
source-dirs: test
33+
ghc-options:
34+
- -threaded
35+
- -rtsopts
36+
- -with-rtsopts=-N
37+
verbatim:
38+
build-tool-depends:
39+
hspec-discover:hspec-discover
40+
dependencies:
41+
- hspec
42+
- secp256k1-haskell-recovery
43+
- monad-par
44+
- mtl
45+
- HUnit
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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.1.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+
, deepseq >=1.4.2 && <1.5
43+
, entropy >=0.3.8 && <0.5
44+
, hashable >=1.2.6 && <1.5
45+
, secp256k1-haskell
46+
, string-conversions ==0.4.*
47+
, unliftio-core >=0.1.0 && <0.3
48+
default-language: Haskell2010
49+
50+
test-suite spec
51+
type: exitcode-stdio-1.0
52+
main-is: Spec.hs
53+
other-modules:
54+
Crypto.Secp256k1.RecoverySpec
55+
Paths_secp256k1_haskell_recovery
56+
hs-source-dirs:
57+
test
58+
ghc-options: -threaded -rtsopts -with-rtsopts=-N
59+
build-depends:
60+
HUnit
61+
, QuickCheck >=2.9.2 && <2.15
62+
, base >=4.9 && <5
63+
, base16 >=1.0
64+
, bytestring >=0.10.8 && <0.12
65+
, deepseq >=1.4.2 && <1.5
66+
, entropy >=0.3.8 && <0.5
67+
, hashable >=1.2.6 && <1.5
68+
, hspec
69+
, monad-par
70+
, mtl
71+
, secp256k1-haskell
72+
, secp256k1-haskell-recovery
73+
, string-conversions ==0.4.*
74+
, unliftio-core >=0.1.0 && <0.3
75+
default-language: Haskell2010
76+
build-tool-depends: hspec-discover:hspec-discover
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
{-# LANGUAGE DeriveGeneric #-}
2+
{-# LANGUAGE DuplicateRecordFields #-}
3+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
{-# LANGUAGE ImportQualifiedPost #-}
5+
{-# LANGUAGE OverloadedRecordDot #-}
6+
{-# LANGUAGE NoFieldSelectors #-}
7+
8+
-- |
9+
-- Module : Crypto.Secp256k1.Internal.Recovery
10+
-- License : UNLICENSE
11+
-- Maintainer : Jean-Pierre Rupp <[email protected]>
12+
-- Stability : experimental
13+
-- Portability : POSIX
14+
--
15+
-- Crytpographic functions related to recoverable signatures from Bitcoin’s secp256k1 library.
16+
--
17+
-- The API for this module may change at any time. This is an internal module only
18+
-- exposed for hacking and experimentation.
19+
module Crypto.Secp256k1.Internal.Recovery where
20+
21+
import Control.DeepSeq (NFData)
22+
import Control.Monad (unless, (<=<))
23+
import Crypto.Secp256k1.Internal.Base (Msg (..), PubKey (..), SecKey (..), Sig (..))
24+
import Crypto.Secp256k1.Internal.Context (Ctx (..))
25+
import Crypto.Secp256k1.Internal.ForeignTypes
26+
( isSuccess,
27+
)
28+
import Crypto.Secp256k1.Internal.RecoveryOps
29+
( ecdsaRecover,
30+
ecdsaRecoverableSignatureConvert,
31+
ecdsaRecoverableSignatureParseCompact,
32+
ecdsaRecoverableSignatureSerializeCompact,
33+
ecdsaSignRecoverable,
34+
)
35+
import Crypto.Secp256k1.Internal.Util
36+
( decodeHex,
37+
showsHex,
38+
unsafePackByteString,
39+
unsafeUseByteString,
40+
)
41+
import Data.ByteString (ByteString)
42+
import Data.ByteString qualified as BS
43+
import Data.Hashable (Hashable (..))
44+
import Data.Maybe (fromMaybe)
45+
import Data.String (IsString (..))
46+
import Data.Word (Word8)
47+
import Foreign
48+
( alloca,
49+
free,
50+
mallocBytes,
51+
nullFunPtr,
52+
nullPtr,
53+
peek,
54+
)
55+
import GHC.Generics (Generic)
56+
import System.IO.Unsafe (unsafePerformIO)
57+
import Text.Read
58+
( Lexeme (String),
59+
lexP,
60+
parens,
61+
pfail,
62+
readPrec,
63+
)
64+
65+
newtype RecSig = RecSig {get :: ByteString}
66+
deriving (Eq, Generic, NFData)
67+
68+
data CompactRecSig = CompactRecSig
69+
{ rs :: !ByteString,
70+
v :: {-# UNPACK #-} !Word8
71+
}
72+
deriving (Eq, Generic)
73+
74+
instance NFData CompactRecSig
75+
76+
compactRecSig :: ByteString -> Maybe CompactRecSig
77+
compactRecSig bs
78+
| BS.length bs == 65,
79+
BS.last bs <= 3 =
80+
Just (CompactRecSig (BS.take 64 bs) (BS.last bs))
81+
| otherwise = Nothing
82+
83+
serializeCompactRecSig :: CompactRecSig -> ByteString
84+
serializeCompactRecSig (CompactRecSig bs v) =
85+
BS.snoc bs v
86+
87+
compactRecSigFromString :: String -> Maybe CompactRecSig
88+
compactRecSigFromString = compactRecSig <=< decodeHex
89+
90+
instance Read CompactRecSig where
91+
readPrec = parens $ do
92+
String str <- lexP
93+
maybe pfail return $ compactRecSigFromString str
94+
95+
instance IsString CompactRecSig where
96+
fromString = fromMaybe e . compactRecSigFromString
97+
where
98+
e = error "Could not decode signature from hex string"
99+
100+
instance Show CompactRecSig where
101+
showsPrec _ = showsHex . serializeCompactRecSig
102+
103+
-- | Parse a compact ECDSA signature (64 bytes + recovery id).
104+
importCompactRecSig :: Ctx -> CompactRecSig -> Maybe RecSig
105+
importCompactRecSig (Ctx ctx) (CompactRecSig sig_rs sig_v)
106+
| BS.length sig_rs == 64,
107+
sig_v <= 3 = unsafePerformIO $
108+
unsafeUseByteString sig_rs $ \(sig_rs_ptr, _) -> do
109+
out_rec_sig_ptr <- mallocBytes 65
110+
ret <-
111+
ecdsaRecoverableSignatureParseCompact
112+
ctx
113+
out_rec_sig_ptr
114+
sig_rs_ptr
115+
(fromIntegral sig_v)
116+
if isSuccess ret
117+
then do
118+
out_bs <- unsafePackByteString (out_rec_sig_ptr, 65)
119+
return (Just (RecSig out_bs))
120+
else do
121+
free out_rec_sig_ptr
122+
return Nothing
123+
| otherwise = Nothing
124+
125+
-- | Serialize an ECDSA signature in compact format (64 bytes + recovery id).
126+
exportCompactRecSig :: Ctx -> RecSig -> CompactRecSig
127+
exportCompactRecSig (Ctx ctx) (RecSig rec_sig_bs) = unsafePerformIO $
128+
unsafeUseByteString rec_sig_bs $ \(rec_sig_ptr, _) ->
129+
alloca $ \out_v_ptr -> do
130+
out_sig_ptr <- mallocBytes 64
131+
ret <-
132+
ecdsaRecoverableSignatureSerializeCompact
133+
ctx
134+
out_sig_ptr
135+
out_v_ptr
136+
rec_sig_ptr
137+
unless (isSuccess ret) $ do
138+
free out_sig_ptr
139+
error "Could not obtain compact signature"
140+
out_bs <- unsafePackByteString (out_sig_ptr, 64)
141+
out_v <- peek out_v_ptr
142+
return $ CompactRecSig out_bs (fromIntegral out_v)
143+
144+
-- | Convert a recoverable signature into a normal signature.
145+
convertRecSig :: Ctx -> RecSig -> Sig
146+
convertRecSig (Ctx ctx) (RecSig rec_sig_bs) = unsafePerformIO $
147+
unsafeUseByteString rec_sig_bs $ \(rec_sig_ptr, _) -> do
148+
out_ptr <- mallocBytes 64
149+
ret <- ecdsaRecoverableSignatureConvert ctx out_ptr rec_sig_ptr
150+
unless (isSuccess ret) $
151+
error "Could not convert a recoverable signature"
152+
out_bs <- unsafePackByteString (out_ptr, 64)
153+
return $ Sig out_bs
154+
155+
-- | Create a recoverable ECDSA signature.
156+
signRecMsg :: Ctx -> SecKey -> Msg -> RecSig
157+
signRecMsg (Ctx ctx) (SecKey sec_key) (Msg m) = unsafePerformIO $
158+
unsafeUseByteString sec_key $ \(sec_key_ptr, _) ->
159+
unsafeUseByteString m $ \(msg_ptr, _) -> do
160+
rec_sig_ptr <- mallocBytes 65
161+
ret <- ecdsaSignRecoverable ctx rec_sig_ptr msg_ptr sec_key_ptr nullFunPtr nullPtr
162+
unless (isSuccess ret) $ do
163+
free rec_sig_ptr
164+
error "could not sign message"
165+
RecSig <$> unsafePackByteString (rec_sig_ptr, 65)
166+
167+
-- | Recover an ECDSA public key from a signature.
168+
recover :: Ctx -> RecSig -> Msg -> Maybe PubKey
169+
recover (Ctx ctx) (RecSig rec_sig) (Msg m) = unsafePerformIO $
170+
unsafeUseByteString rec_sig $ \(rec_sig_ptr, _) ->
171+
unsafeUseByteString m $ \(msg_ptr, _) -> do
172+
pub_key_ptr <- mallocBytes 64
173+
ret <- ecdsaRecover ctx pub_key_ptr rec_sig_ptr msg_ptr
174+
if isSuccess ret
175+
then do
176+
pub_key_bs <- unsafePackByteString (pub_key_ptr, 64)
177+
return (Just (PubKey pub_key_bs))
178+
else do
179+
free pub_key_ptr
180+
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, LCtx, 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+
Ptr LCtx ->
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+
Ptr LCtx ->
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+
Ptr LCtx ->
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+
Ptr LCtx ->
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+
Ptr LCtx ->
55+
Ptr PubKey64 ->
56+
Ptr RecSig65 ->
57+
Ptr Msg32 ->
58+
IO Ret

0 commit comments

Comments
 (0)