packages feed

bip32-0.1.1: lib/BIP32/GHCJS.hs

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE JavaScriptFFI #-}

-- | Implementation to be used when compiled with GHCJS
module BIP32.GHCJS
  ( Prv
  , prv
  , unPrv
  , prvToPub
  , addPrvTweak

  , Pub
  , pub
  , unPub
  , addPubTweak

  , Tweak
  , tweak

  , hmacSHA512
  , ripemd160
  , sha256
  ) where

import Control.Monad
import qualified Data.ByteString as B
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 'prv'.
newtype Prv = Prv TA.Uint8Array

instance Eq Prv where
  Prv a == Prv b =
    byteStringFromUint8Array a == byteStringFromUint8Array b

instance Show Prv where
  showsPrec n (Prv u) = showParen (n > 10) $
    let b = byteStringFromUint8Array u
    in showString "Prv " .
       showsPrec 0 (BB.toLazyByteString (BB.byteStringHex b))


-- | Obtain the 32 raw bytes inside a 'Prv'. See 'prv'.
unPrv :: Prv -> B.ByteString
{-# INLINE unPrv #-}
unPrv (Prv x) = byteStringFromUint8Array x

-- | Construct a 'Prv' key from its raw bytes.
--
-- * 32 bytes containing \(ser_{256}(k)\).
--
-- See Bitcoin's [BIP-0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
-- for details.
--
-- 'Nothing' if something is not satisfied.
prv :: B.ByteString -> Maybe Prv
prv b = do
  guard (B.length b == 32)
  let u = byteStringToUint8Array b
  guard (js_secp256k1_verifyPrv u)
  pure (Prv u)

foreign import javascript unsafe
  "h$bip32.secp256k1.verifyPrv($1)"
  js_secp256k1_verifyPrv :: TA.Uint8Array -> Bool

-- | Obtain the 'Pub' key for 'Prv'.
prvToPub :: Prv -> Pub
{-# INLINE prvToPub #-}
prvToPub (Prv x) = Pub (js_secp256k1_prvToPub x)

foreign import javascript unsafe
  "h$bip32.secp256k1.prvToPub($1)"
  js_secp256k1_prvToPub :: TA.Uint8Array -> TA.Uint8Array

-- | Tweak a 'Prv'ate key by adding 'Tweak' times the generator to it.
addPrvTweak :: Prv -> Tweak -> Maybe Prv
{-# INLINE addPrvTweak #-}
addPrvTweak (Prv p) (Tweak t) = do
  jsv <- nullableToMaybe (js_secp256k1_addPrvTweak p t)
  pure $ Prv (js_unsafe_JSVal_to_UInt8Array jsv)

foreign import javascript unsafe
  "h$bip32.secp256k1.addPrvTweak($1, $2)"
  js_secp256k1_addPrvTweak :: TA.Uint8Array  -- ^ Private key.
                           -> TA.Uint8Array  -- ^ Tweak.
                           -> Nullable JSVal -- ^ Nullable Uint8Array

--------------------------------------------------------------------------------

-- | Public key.
--
-- Construct with 'pub'.
newtype Pub = Pub TA.Uint8Array

instance Eq Pub where
  Pub a == Pub b =
    byteStringFromUint8Array a == byteStringFromUint8Array b

instance Show Pub where
  showsPrec n (Pub u) = showParen (n > 10) $
    let b = byteStringFromUint8Array u
    in showString "Pub " .
       showsPrec 0 (BB.toLazyByteString (BB.byteStringHex b))

-- | Obtain the 33 raw bytes inside a 'Pub'. See 'pub'.
--
-- Corresponds to BIP-0032's \(ser_{P}(P)\).
unPub :: Pub -> B.ByteString
{-# INLINE unPub #-}
unPub (Pub x) = byteStringFromUint8Array x

-- | Construct a 'Pub' key from its raw bytes.
--
-- * 33 bytes in total, containing \(ser_{P}(P)\).
--
-- * The leftmost byte is either @0x02@ or @0x03@, depending on the parity
-- of the omitted @y@ coordinate.
--
-- * The remaining 32 bytes are \(ser_{256}(x)\).
--
-- See Bitcoin's [BIP-0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
-- for details.
--
-- 'Nothing' if something is not satisfied.
pub :: B.ByteString -> Maybe Pub
pub b = do
  guard (B.length b == 33)
  let u = byteStringToUint8Array b
  guard (js_secp256k1_verifyPub u)
  pure (Pub u)

foreign import javascript unsafe
  "h$bip32.secp256k1.verifyPub($1)"
  js_secp256k1_verifyPub :: TA.Uint8Array -> Bool

-- | Tweak a 'Pub'lic key by adding 'Tweak' times the generator to it.
addPubTweak :: Pub -> Tweak -> Maybe Pub
{-# INLINE addPubTweak #-}
addPubTweak (Pub p) (Tweak t) = do
  jsv <- nullableToMaybe (js_secp256k1_addPubTweak p t)
  pure $ Pub (js_unsafe_JSVal_to_UInt8Array jsv)

foreign import javascript unsafe
  "h$bip32.secp256k1.addPubTweak($1, $2)"
  js_secp256k1_addPubTweak :: TA.Uint8Array  -- ^ Public key.
                           -> TA.Uint8Array  -- ^ Tweak.
                           -> Nullable JSVal -- ^ Nullable Uint8Array

--------------------------------------------------------------------------------

newtype Tweak = Tweak TA.Uint8Array

-- | Convert a 32-Byte 'B.ByteString' to a 'Tweak'.
tweak :: B.ByteString -> Maybe Tweak
tweak b = do
  guard (B.length b == 32)
  pure $ Tweak (byteStringToUint8Array b)

--------------------------------------------------------------------------------

-- | RIPEMD-160
ripemd160
  :: B.ByteString  -- ^ Data to hash.
  -> B.ByteString  -- ^ 20-byte long digest.
{-# INLINE ripemd160 #-}
ripemd160 = byteStringFromUint8Array . js_ripemd160 . byteStringToUint8Array

foreign import javascript unsafe
  "h$bip32.ripemd160($1)"
  js_ripemd160 :: TA.Uint8Array -- ^ Data to hash.
               -> TA.Uint8Array -- ^ 20-bytes long digest.

--------------------------------------------------------------------------------

-- | HMAC-SHA512
hmacSHA512
  :: B.ByteString  -- ^ Arbitrary length key.
  -> B.ByteString  -- ^ Data to hash.
  -> B.ByteString  -- ^ 64-byte long digest.
{-# INLINE hmacSHA512 #-}
hmacSHA512 k d =
  byteStringFromUint8Array $ js_hmacSHA512 (byteStringToUint8Array k)
                                           (byteStringToUint8Array d)

foreign import javascript unsafe
  "h$bip32.hmacSHA512($1, $2)"
  js_hmacSHA512 :: TA.Uint8Array  -- ^ Key.
                -> TA.Uint8Array  -- ^ Data to hash.
                -> TA.Uint8Array  -- ^ 64-bytes long digest.

--------------------------------------------------------------------------------

-- | SHA256
sha256
  :: B.ByteString  -- ^ Data to hash.
  -> B.ByteString  -- ^ 32-byte long digest.
{-# INLINE sha256 #-}
sha256 = byteStringFromUint8Array . js_sha256 . byteStringToUint8Array

foreign import javascript unsafe
  "h$bip32.sha256($1)"
  js_sha256 :: TA.Uint8Array   -- ^ Data to hash.
            -> TA.Uint8Array   -- ^ 32-bytes long digest.

--------------------------------------------------------------------------------

byteStringToUint8Array :: B.ByteString -> TA.Uint8Array
byteStringToUint8Array 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

--------------------------------------------------------------------------------

byteStringFromUint8Array :: TA.Uint8Array -> B.ByteString
byteStringFromUint8Array 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