bitcoin-keys-0.1: lib/Bitcoin/Keys/GHCJS.hs
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE JavaScriptFFI #-}
-- | Implementation to be used when compiled with GHCJS
module Bitcoin.Keys.GHCJS
( Prv
, parsePrv
, prvRaw
, prvToPub
, prvAddTweak
, Pub
, parsePubCompressed
, pubCompressed
, pubUncompressed
, pubAddTweak
, Tweak
, parseTweak
) where
import Control.Monad
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy.Char8 as BL8
import qualified Data.ByteString.Builder as BB
import qualified GHCJS.Buffer as Buf
import GHCJS.Types (JSVal)
import GHCJS.Nullable
import qualified JavaScript.TypedArray.ArrayBuffer as AB
import qualified JavaScript.TypedArray as TA
--------------------------------------------------------------------------------
-- | Private key.
--
-- Construct with 'parsePrv'.
newtype Prv = Prv B.ByteString
deriving newtype (Eq, Ord)
-- | Big-endian base-16.
instance Show Prv where
showsPrec n p = showParen (n > 10) $
showString "Prv " .
mappend (BL8.unpack (BB.toLazyByteString (BB.byteStringHex (prvRaw p))))
-- | Obtain the 32 raw bytes inside a 'Prv' (big-endian).
--
-- @
-- 'Just' == 'parsePrv' . 'prvRaw'
-- @
prvRaw :: Prv -> B.ByteString
{-# INLINE prvRaw #-}
prvRaw (Prv x) = x
-- | Construct a 'Prv' key from its raw 32 bytes (big-endian).
--
-- Returns 'Nothing' if something is not satisfied.
--
-- @
-- 'Just' == 'parsePrv' . 'prvRaw'
-- @
parsePrv :: B.ByteString -> Maybe Prv
parsePrv b = do
guard (B.length b == 32)
guard (js_prvVerify (bsToU8A b))
pure (Prv b)
foreign import javascript unsafe
"h$bitcoin_keys.prvVerify($1)"
js_prvVerify :: TA.Uint8Array -> Bool
-- | Obtain the 'Pub' key for 'Prv'.
prvToPub :: Prv -> Pub
{-# INLINE prvToPub #-}
prvToPub = Pub . bsFromU8A . js_prvToPub . bsToU8A . prvRaw
foreign import javascript unsafe "h$bitcoin_keys.prvToPub($1)"
js_prvToPub :: TA.Uint8Array -> TA.Uint8Array
-- | Tweak a 'Prv'ate key by adding 'Tweak' times the generator to it.
--
-- Returns 'Nothing' if the resulting 'Prv' would be invalid.
prvAddTweak :: Tweak -> Prv -> Maybe Prv
{-# INLINE prvAddTweak #-}
prvAddTweak (Tweak t) (Prv p) = do
jsv <- nullableToMaybe (js_prvAddTweak (bsToU8A p) (bsToU8A t))
pure $ Prv (bsFromU8A (js_unsafe_JSVal_to_UInt8Array jsv))
foreign import javascript unsafe "h$bitcoin_keys.prvAddTweak($1, $2)"
js_prvAddTweak :: TA.Uint8Array -- ^ Private key.
-> TA.Uint8Array -- ^ Tweak.
-> Nullable JSVal -- ^ Nullable Uint8Array
--------------------------------------------------------------------------------
-- | Public key.
--
-- Construct with 'Bitcoin.Keys.parsePub'.
newtype Pub = Pub B.ByteString
deriving newtype (Eq, Ord)
-- | SEC compressed base-16.
instance Show Pub where
showsPrec n p = showParen (n > 10) $
showString "Pub " .
mappend (BL8.unpack (BB.toLazyByteString (BB.byteStringHex (pubCompressed p))))
-- | Obtain the 33-bytes contatining the SEC compressed 'Pub'lic key.
--
-- @
-- 'Just' == 'Bitcoin.Keys.parsePub' . 'pubCompressed'
-- @
pubCompressed :: Pub -> B.ByteString
{-# INLINE pubCompressed #-}
pubCompressed (Pub x) = x
-- | Obtain the 65-bytes contatining the SEC uncompressed 'Pub'lic key.
--
-- @
-- 'Just' == 'Bitcoin.Keys.parsePub' . 'pubUncompressed'
-- @
pubUncompressed :: Pub -> B.ByteString
{-# INLINE pubUncompressed #-}
pubUncompressed = bsFromU8A . js_pubUncompressed . bsToU8A . pubCompressed
foreign import javascript unsafe "h$bitcoin_keys.pubUncompressed($1)"
js_pubUncompressed :: TA.Uint8Array -- ^ Compressed public key.
-> TA.Uint8Array -- ^ Uncompressed public key.
-- | Builds a public key from its compressed SEC-encoded bytes.
--
-- * Compressed keys are 33 bytes. The leftmost byte is @0x02@ if the @y@
-- coordinate is even, or @0x03@ if odd. The remaining 32 bytes
-- are the big-endian encoded @x@ coordinate.
--
-- @
-- 'Just' == 'Bitcoin.Keys.parsePub' . 'pubCompressed'
-- @
parsePubCompressed :: B.ByteString -> Maybe Pub
parsePubCompressed b = do
guard (B.length b == 33)
guard (js_pubVerify (bsToU8A b))
pure (Pub b)
foreign import javascript unsafe "h$bitcoin_keys.pubVerify($1)"
js_pubVerify :: TA.Uint8Array -- ^ Compressed public key.
-> Bool -- ^ Whether the key is valid.
-- | Tweak a 'Pub'lic key by adding 'Tweak' times the generator to it.
--
-- Returns 'Nothing' if the resulting 'Pub' would be invalid.
pubAddTweak :: Tweak -> Pub -> Maybe Pub
{-# INLINE pubAddTweak #-}
pubAddTweak (Tweak t) (Pub p) = do
jsv <- nullableToMaybe (js_pubAddTweak (bsToU8A p) (bsToU8A t))
pure $ Pub (bsFromU8A (js_unsafe_JSVal_to_UInt8Array jsv))
foreign import javascript unsafe "h$bitcoin_keys.pubAddTweak($1, $2)"
js_pubAddTweak :: TA.Uint8Array -- ^ Public key.
-> TA.Uint8Array -- ^ Tweak.
-> Nullable JSVal -- ^ Nullable Uint8Array
--------------------------------------------------------------------------------
-- | A 32-byte number used to modify a 'Pub' or 'Prv' using 'prvAddTweak'
-- or 'pubAddTweak'.
newtype Tweak = Tweak B.ByteString
deriving newtype (Eq, Ord)
-- | Big-endian base-16.
instance Show Tweak where
showsPrec n (Tweak b) = showParen (n > 10) $
showString "Tweak " .
mappend (BL8.unpack (BB.toLazyByteString (BB.byteStringHex b)))
-- | Construct a 'Tweak' from its raw 32 bytes (big-endian).
--
-- Returns 'Nothing' if something is not satisfied.
parseTweak :: B.ByteString -> Maybe Tweak
parseTweak b = do
guard (B.length b == 32)
pure $ Tweak b
--------------------------------------------------------------------------------
bsToU8A :: B.ByteString -> TA.Uint8Array
bsToU8A x
| B.length x == 0 = js_emptyUint8Array
| otherwise = let (buf, off, len) = Buf.fromByteString x
in js_newUint8Array (Buf.getArrayBuffer buf) off len
foreign import javascript unsafe "new Uint8Array($1, $2, $3)"
js_newUint8Array :: AB.ArrayBuffer
-> Int -- ^ Byte offset.
-> Int -- ^ Byte length.
-> TA.Uint8Array
foreign import javascript unsafe "new Uint8Array(0)"
js_emptyUint8Array :: TA.Uint8Array
--------------------------------------------------------------------------------
bsFromU8A :: TA.Uint8Array -> B.ByteString
bsFromU8A x = Buf.toByteString (TA.byteOffset x)
(Just (TA.byteLength x))
(Buf.createFromArrayBuffer (TA.buffer x))
--------------------------------------------------------------------------------
foreign import javascript unsafe "$r = $1;"
js_unsafe_JSVal_to_UInt8Array :: JSVal -> TA.Uint8Array