【问题标题】:How to create two ByteStrings calling this external library API?如何创建两个调用此外部库 API 的 ByteString?
【发布时间】:2015-11-18 19:14:51
【问题描述】:

我目前正在编写与加密库的绑定,该库公开了用于生成密钥对的函数:

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

该函数随机生成一个秘钥,计算对应的公钥,并将结果放入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。在运行测试套件时,我发现我似乎在函数调用之间共享了 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 monad 而言,共享和引用透明的语义到底是什么?

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

【问题讨论】:

    标签: haskell ffi bytestring


    【解决方案1】:

    这并不能回答您的问题,但是太长了,无法发表评论。

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

    【讨论】:

      【解决方案2】:

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

      修改 CString,无论是在 C 中,还是使用 poke,都会导致 ByteString 的内容发生变化,破坏引用透明度

      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 可以浮动到顶层。

      【讨论】:

      • 所以如果 ByteString 暴露了一个没有调用 'unsafePerformIO' 的 'replicateIO :: Int -> Word8 -> IO ByteString' 函数,那么它会起作用吗?
      猜你喜欢
      • 2018-03-05
      • 1970-01-01
      • 2015-12-08
      • 1970-01-01
      • 2019-07-02
      • 2020-07-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多