module Bitcoin.Crypto.EC.Key where
import Control.Monad
import Prelude hiding ( sqrt )
import Data.Char
import Data.Bits
import Data.Word
import Data.Maybe
import qualified Data.ByteString as B
import System.Random
import Bitcoin.Misc.HexString
import Bitcoin.Misc.BigInt
import Bitcoin.Misc.OctetStream
import Bitcoin.Misc.BigInt
import Bitcoin.Protocol.Hash
import Bitcoin.Crypto.FiniteField.Fast.Fp hiding ( secp256k1_p )
import Bitcoin.Crypto.FiniteField.Naive.Fn hiding ( secp256k1_n )
import Bitcoin.Crypto.EC.Curve
import Bitcoin.Crypto.EC.Projective
newtype PrivKey = PrivKey { fromPrivKey :: Integer } deriving (Eq,Show)
data PubKey
= FullPubKey !Integer !Integer
| ComprPubKey !Word8 !Integer
deriving (Eq,Show)
data PubKeyFormat = Uncompressed | Compressed deriving (Eq,Show)
generatePrivKeyIO :: IO PrivKey
generatePrivKeyIO = getStdRandom generatePrivKey
generatePrivKey :: RandomGen gen => gen -> (PrivKey,gen)
generatePrivKey = go where
go gen = if ( priv > 0 && priv < secp256k1_n ) then (PrivKey priv, gen'') else go gen'' where
(priv0,gen' ) = randomR (1, secp256k1_n 1) gen
(priv1,gen'') = randomR (1, secp256k1_n 1) gen'
priv = toIntegerBE
$ doHash256
$ (fromIntegerLE priv0 ++ fromIntegerLE priv1 ++ [0x12::Word8,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0])
pubKeyFormat :: PubKey -> PubKeyFormat
pubKeyFormat pk = case pk of
FullPubKey {} -> Uncompressed
ComprPubKey {} -> Compressed
computePubKey :: PubKeyFormat -> PrivKey -> PubKey
computePubKey fmt priv =
case fmt of
Uncompressed -> full
Compressed -> compressPubKey full
where
full = computeFullPubKey priv
computeFullPubKey :: PrivKey -> PubKey
computeFullPubKey (PrivKey da)
| da < 1 && da >= secp256k1_n = error "computePubKey: invalid private key"
| otherwise = case fromECProj (mulECP secp256k1_G_proj da) of
ECPoint x y -> FullPubKey (fromFp x) (fromFp y) where
ECInfinity -> error "computePubKey: invalid private key"
isValidPubKey :: PubKey -> Bool
isValidPubKey pub = case uncompressPubKey pub of
Nothing -> False
Just (ComprPubKey _ _ ) -> error "isValidPubKey: this shouldn't happen"
Just (FullPubKey x0 y0) -> (y /= 0) && (y*y == x*x*x + 7) && (mulECP (toECProj ep) secp256k1_n =~= ecpInfinity) where
x = fromInteger x0 :: Fp
y = fromInteger y0 :: Fp
ep = ECPoint x y
formatPubKey :: PubKeyFormat -> PubKey -> PubKey
formatPubKey fmt pk = case fmt of
Compressed -> compressPubKey pk
Uncompressed -> case uncompressPubKey pk of
Just new -> new
Nothing -> error "formatPubKey: cannot expand compressed pubkey"
uncompressPubKey :: PubKey -> Maybe PubKey
uncompressPubKey full@(FullPubKey _ _) = Just full
uncompressPubKey (ComprPubKey evenOdd x) =
case my of
Just y -> let yi = fromFp y
in if even (evenOdd) == even yi
then Just ( FullPubKey x yi )
else Just ( FullPubKey x (secp256k1_p yi) )
Nothing -> Nothing
where
x1 = fromInteger x :: Fp
x3 = x1*x1*x1
y2 = x3 + 7
my = sqrt_p y2
compressPubKey :: PubKey -> PubKey
compressPubKey compr@(ComprPubKey _ _) = compr
compressPubKey (FullPubKey x y) = ComprPubKey (if even y then 2 else 3) x