|
| 1 | +{-# LANGUAGE CApiFFI #-} |
1 | 2 | {-# LANGUAGE CPP #-} |
2 | 3 |
|
3 | 4 | module Cardano.Wasm.Internal.JavaScript.Random (getRandomBytes) where |
4 | 5 |
|
5 | | -import Data.ByteString (ByteString) |
6 | | - |
7 | 6 | #if !defined(wasm32_HOST_ARCH) |
8 | 7 |
|
| 8 | +import Data.ByteString (ByteString) |
| 9 | + |
9 | 10 | getRandomBytes :: Word -> IO ByteString |
10 | 11 | getRandomBytes _ = error "getRandomBytes is not implemented for non-WASM targets" |
11 | 12 |
|
12 | 13 | #else |
13 | 14 |
|
14 | | -import Cardano.Wasm.Internal.ExceptionHandling (rightOrError) |
15 | | - |
16 | | -import Data.ByteString.Base16 qualified as Base16 |
17 | | -import Data.ByteString.Char8 qualified as BSC |
18 | | -import GHC.Wasm.Prim |
19 | | - |
20 | | --- | Create a GRPC-web client for the Cardano API. |
21 | | -foreign import javascript safe "{ let numBytes = $1; \ |
22 | | - let randomBytes = new Uint8ClampedArray(numBytes); \ |
23 | | - crypto.getRandomValues(randomBytes); \ |
24 | | - return (randomBytes.reduce((acc, next) => acc.concat(next.toString(16).padStart(2, '0')), '')); \ |
25 | | - }" |
26 | | - js_generateRandomBytes :: Int -> IO JSString |
27 | | - |
| 15 | +import Control.Exception (throwIO) |
| 16 | +import Data.ByteString (ByteString, packCStringLen) |
| 17 | +import Data.Word (Word8) |
| 18 | +import System.IO.Error (mkIOError, userErrorType) |
| 19 | + |
| 20 | +import Foreign.C.Types (CInt (..), CSize (..)) |
| 21 | +import Foreign.Marshal.Alloc (allocaBytes) |
| 22 | +import Foreign.Ptr (Ptr, castPtr) |
| 23 | + |
| 24 | +-- | Import the 'random_get' function from wasi |
| 25 | +-- module provided by the WASI runtime. |
| 26 | +-- |
| 27 | +-- It takes a raw pointer to a u8 buffer (Ptr Word8) and the buffer's |
| 28 | +-- length (CSize) and returns a CInt representing the error code number. |
| 29 | +-- A return value of 0 means success. |
| 30 | +foreign import ccall "__wasi_random_get" |
| 31 | + wasi_random_get |
| 32 | + :: Ptr Word8 |
| 33 | + -- ^ Pointer to the buffer to fill |
| 34 | + -> CSize |
| 35 | + -- ^ Number of bytes to write |
| 36 | + -> IO CInt |
| 37 | + -- ^ Returns 0 on success, or an errno code |
| 38 | + |
| 39 | +-- | A safe Haskell wrapper around 'random_get' function from wasi. |
| 40 | +-- It requests the given number of cryptographically secure random |
| 41 | +-- bytes and returns them as a ByteString. |
28 | 42 | getRandomBytes :: Word -> IO ByteString |
29 | | -getRandomBytes n = do |
30 | | - hexRandomBytes <- fromJSString <$> js_generateRandomBytes (fromIntegral n) |
31 | | - rightOrError $ Base16.decode $ BSC.pack hexRandomBytes |
| 43 | +getRandomBytes n = |
| 44 | + allocaBytes (fromIntegral n) $ \ptr -> do |
| 45 | + -- Get pointer to allocated buffer |
| 46 | + errno <- wasi_random_get ptr (fromIntegral n) |
| 47 | + if errno == 0 |
| 48 | + -- If successful, pack the bytes from the pointer into a Haskell managed ByteString |
| 49 | + then packCStringLen (castPtr ptr, fromIntegral n) |
| 50 | + else -- Otherwise, throw we throw |
| 51 | + throwIO $ |
| 52 | + mkIOError |
| 53 | + userErrorType |
| 54 | + ("wasi_random_get failed with errno: " ++ show errno) |
| 55 | + Nothing |
| 56 | + Nothing |
32 | 57 |
|
33 | 58 | #endif |
0 commit comments