如何创建两个调用此外部库 API 的 ByteString?

How to create two ByteStrings calling this external library API?

我目前正在编写绑定到公开生成密钥对的函数的加密库:

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

该函数随机生成一个秘钥,计算出对应的public秘钥并将结果放入pksk.

当只返回一个 ByteString 时,我发现最简单的方法是使用 Data.ByteString.Internal 中的 create :: Int -> (Ptr Word8 -> IO ()) -> IO ByteString。但是,该函数不能同时创建两个 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。当 运行 测试套件时,我发现我似乎在函数调用之间共享 ByteString,导致 encryption/decryption 失败并给出不正确的结果。

我已经通过定义自己的函数解决了这个问题:

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 monad 时,共享和引用透明性的语义到底是什么?

我的直觉告诉我(错误地)我可以用第一种方式解决问题,但显然我做不到。我认为正在发生的事情是优化器看到 let 语句可以浮动到顶级定义中,这就是我遇到这些问题的原因。

这并没有回答你的问题,但它太长了,无法发表评论。

作为 hack,如果你想避免手动分配,你可以使用两个嵌套的 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 而不是这种方法。

第一种方法的问题是您试图修改不可变值(函数中的 pksk)。 docs for unsafeUseAsCString 说:

modifying the CString, either in C, or using poke, will cause the contents of the ByteString to change, breaking referential transparency

IO monad 在共享和引用透明性方面没有不同的语义。事实上,do 块中的 letIO monad 没有任何关系;您的代码相当于:

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)

现在可以清楚地看到pksk可以浮动到顶层了。