Как создать две строки байтов, вызывающие этот API внешней библиотеки?

В настоящее время я пишу привязки к криптографической библиотеке, которая предоставляет функцию для создания пар ключей:

const size_t PUBLICKEYBYTES = 32;
const size_t SECRETKEYBYTES = 32;
int random_keypair(unsigned char pk[PUBLICKEYBYTES],
                   unsigned char sk[SECRETKEYBYTES]);

Эта функция случайным образом генерирует секретный ключ, вычисляет соответствующий открытый ключ и помещает результаты в pk и sk.

Когда я только что вернул один ByteString, я обнаружил, что проще всего использовать create :: Int -> (Ptr Word8 -> IO ()) -> IO ByteString из Data.ByteString.Internal. Однако эта функция не может создать два ByteStrings одновременно.

Мой первый подход состоял в том, чтобы написать что-то вроде:

newtype PublicKey = PublicKey ByteString
newtype SecretKey = SecretKey ByteString
randomKeypair :: IO (PublicKey, SecretKey)
randomKeypair = do
    let pk = B.replicate 0 publicKeyBytes
        sk = B.replicate 0 secretKeyBytes
    B.unsafeUseAsCString pk $ \ppk ->
        B.unsafeUseAsCString sk $ \psk ->
        c_random_keypair ppk psk
    return (PublicKey pk, SecretKey sk)

Однако это не работает с GHC 7.10.2. При запуске набора тестов я обнаружил, что у меня, кажется, есть совместное использование ByteStrings между вызовами функций, что приводит к сбою шифрования/дешифрования и дает неверные результаты.

Мне удалось обойти проблему, определив свою собственную функцию:

createWithResult :: Int -> (Ptr Word8 -> IO a) -> IO (ByteString, a)
createWithResult i f = do
    fp <- B.mallocByteString i
    r <- withForeignPtr fp f
    return (B.fromForeignPtr fp 0 i, r)

и используя его как:

randomKeypair = fmap (PublicKey *** SecretKey) $
    createWithResult publicKeyBytes $ \ppk ->
    B.create secretKeyBytes $ \psk ->
    void $ c_random_keypair ppk psk

Вроде работает, все тесты проходят.

Мой вопрос в том, какова именно семантика, когда речь идет о совместном использовании и ссылочной прозрачности, когда речь идет о монаде IO?

Моя интуиция подсказывала мне (неверно), что я могу решить задачу первым способом, но, видимо, не смогла. Я полагаю, что произошло то, что оптимизатор увидел, что let-операторы могут быть помещены в определения верхнего уровня, и это было причиной, по которой у меня возникли эти проблемы.


person dnaq    schedule 25.08.2015    source источник


Ответы (2)


Проблема с вашим первым подходом заключается в том, что вы пытаетесь изменить неизменяемое значение (pk и sk в своей функции). документы для unsafeUseAsCString сказать:

изменение CString либо в C, либо с помощью poke приведет к изменению содержимого ByteString, нарушая ссылочную прозрачность

Монада IO не имеет другой семантики, когда речь идет о совместном использовании и ссылочной прозрачности. На самом деле let в блоке do никак не связано с монадой IO; ваш код эквивалентен:

randomKeypair :: IO (PublicKey, SecretKey)
randomKeypair =
    let pk = B.replicate 0 publicKeyBytes
        sk = B.replicate 0 secretKeyBytes
    in B.unsafeUseAsCString pk (\ppk ->
        B.unsafeUseAsCString sk $ \psk ->
        c_random_keypair ppk psk) >>
    return (PublicKey pk, SecretKey sk)

Теперь ясно видно, что pk и sk можно вывести на верхний уровень.

person Maciej Bielecki    schedule 25.08.2015
comment
Итак, если бы ByteString выставил функцию «replicateIO :: Int -> Word8 -> IO ByteString», которая не вызывает «unsafePerformIO», тогда это сработало бы? - person dnaq; 25.08.2015

Это не ответ на ваш вопрос, но это слишком долго, чтобы помещать его в комментарий.

В качестве хака, если вы хотите избежать ручного распределения, вы можете использовать два вложенных вызова create и IORef ByteString для хранения строки байтов, созданной самым внутренним create. Например. (псевдокод)

secRef <- newIORef ""
pubB <- create publicKeyBytes (\pub -> do
   secB <- create secretKeyBytes (\sec -> void $ c_random_keypair pub sec)
   writeIORef secRef secB)
secB <- readIORef secRef
return (pubB, secB)

Однако я предпочитаю ваш createWithResult этому подходу.

person chi    schedule 25.08.2015