2015-08-25 9 views
5

Ich bin derzeit Bindungen zu einer Krypto-Bibliothek zu schreiben, die eine Funktion zum Erzeugen von Schlüsselpaaren aussetzt:Wie erstellt man zwei ByteStrings, die diese externe Bibliothek API aufrufen?

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

Diese Funktion generiert zufällig einen geheimen Schlüssel, berechnet die entsprechenden öffentlichen Schlüssel und stellt die Ergebnisse in pk und sk.

Wenn ich gerade eine ByteString zurückgegeben habe, habe ich festgestellt, dass der einfachste Weg ist, create :: Int -> (Ptr Word8 -> IO()) -> IO ByteString von Data.ByteString.Internal zu verwenden. Diese Funktion kann jedoch nicht zwei ByteStrings gleichzeitig erstellen.

Mein erster Ansatz war zu schreiben, so etwas wie:

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) 

Allerdings scheint dies nicht mit GHC 7.10.2 zu arbeiten. Beim Ausführen der Testsuite stelle ich fest, dass ich scheinbar die ByteString s zwischen Funktionsaufrufen teile, was dazu führt, dass die Verschlüsselung/Entschlüsselung fehlschlägt und falsche Ergebnisse liefert.

Ich habe es geschafft, durch Definieren meine eigene Funktion, um das Problem zu umgehen:

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) 

und dessen Verwendung mag:

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

Dies scheint zu funktionieren, alle Tests bestanden.

Meine Frage ist, was genau ist die Semantik, wenn es um die gemeinsame und referentielle Transparenz geht, wenn es um die IO Monade geht?

Meine Intuition sagte mir (falsch), dass ich das Problem auf die erste Weise lösen könnte, aber anscheinend konnte ich nicht. Was ich glaube, war, dass der Optimierer sah, dass die let -Statements in Top-Level-Definitionen fließen konnten, und das war der Grund, warum ich diese Probleme bekam.

Antwort

2

Das Problem mit Ihrem ersten Ansatz ist, dass Sie versuchen, einen unveränderlichen Wert (pk und sk in Ihrer Funktion) zu ändern. Die docs for unsafeUseAsCString sagen:

das CString ändern, entweder in C oder Poke verwenden, um den Inhalt des ByteString führen zu ändern, referentielle Transparenz brechen

Die IO Monade haben keine andere Semantik wenn es um das Teilen und die referentielle Transparenz geht.Tatsächlich ist der let im do Block in keiner Weise mit IO Monade verwandt; Ihr Code entspricht:

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) 

Jetzt ist es deutlich zu sehen, dass pk und sk kann auf oberster Ebene schwebte werden.

+0

Also, wenn ByteString eine 'replicateIO :: Int -> Word8 -> IO ByteString' Funktion, die 'unsafePerformIO' nicht aufgerufen hat, hätte es funktioniert? – dnaq

4

Dies beantwortet Ihre Frage nicht, aber es ist zu lang, um in einen Kommentar eingefügt zu werden.

Als Hack, wenn Sie die manuelle Zuordnungen zu tun vermeiden möchten, können Sie zwei verschachtelte create Anrufe verwenden könnte und eine IORef ByteString die bytestring durch die innerste create erstellt zu speichern. Z.B. (Pseudo-Code)

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) 

Allerdings ziehe ich Ihre createWithResult zu diesem Ansatz.