You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Consistent naming conventions aid discoverability and reduce surprise. Opinionated APIs can encourage consistency of practice and help prevent user error.
Executive Summary
Define a naming convention for common functions (e.g., serde, construction)
Define a consistent API for transmission/display (i.e., raw bytes, String, Text, Show, Display, etc.)
Define a part of speech for "accessors" (e.g., to retrieve the signature on a signed message, should we say signature or getSignature?) and how to report access failure
{-# LANGUAGE NoFieldSelectors #-}
dataKeyPair=KeyPair{public::PublicKey, secret::SecretKey}-- Encourage `OverloadedRecordDot` for access, but we can also export
public = (.public)
secret = (.secret)
-- for qualified use.---- This bit is substantially optional, and is mainly suggested to avoid silly-- things like `signaturePublicKey someKeyPair` to avoid name collisions
and a constructor keyPair :: IO KeyPair.
Encourage qualified imports and OverloadedRecordDot so one writes
{-# LANGUAGE ImportQualifiedPost, OverloadedRecordDot #-}
importSel.PublicKey.CipherqualifiedasCipherimportSel.PublicKey.SealqualifiedasSealimportSel.PublicKey.SignaturequalifiedasSignaturefoo::IO()
foo =do
c <-Cipher.keyPair
s <-Seal.keyPair
sig <-Signature.keyPair
print$ c.public
print$ s.secret
print$Signature.public sig
Tuples make sense when there's no name for a thing, but keypairs definitely have a name. The author, at least, is more likely to remember someKeyPair.public before fst someKeyPair.
Define a consistent vocabulary for serde functions, and expose them uniformly. In particular, expose at least separate encoding and decoding functions for each half of a keypair. Public keys especially are shared often, so it's unusual that there's no way to recover a Cipher.PublicKey without bytes for a Cipher.SecretKey on hand.
Re-export the Cipher functions and types in Seal for consistency. It feels weird to import a seemingly separate module to get at "core" functions and types of the module at hand.
With an expressed recommendation for qualified imports, shorter names would also be agreeable, e.g., Signature.encodePublicKey, Cipher.decodeSecretKey, etc.
Goofier option: Virtual record fields via HasField like hexBytes, hexString, bytes. Not an especially serious suggestion, just covering options.
Use a richer type for reporting errors in decoding, either specific to each module or a catch-all "all the sorts of problems we run into when decoding bytestrings" sum type. This is exposed in feat(Signature): add serde functions, hide internals #173 as KeyMaterialDecodingError, but could be renamed to simply DecodingError and cover the failure modes of, e.g.,
The identified common failure modes in Sel.PublicKey are:
Bytes are not hex-encoded
Byte array length mismatch (e.g., the target pointer has size n, but the byte array being decoded has length /= n); there are some more unusual situations that still fit largely into this case, such as around cryptoBoxMACBytes for cipher text, but these could have their own cases if necessary
Transmission/Display
Cipher:
ShowCipher.PublicKey-- Display via ShowInstanceShowCipher.SecretKey-- Display via OpaqueInstance-- butShowCipher.CipherText-- via binary -> Base16 -> ByteString -> StringDisplayCipher.CipherText-- via binary -> Base16 -> TextcipherTextToBinary::CipherText->StrictByteStringcipherTextToHexByteString::CipherText->StrictByteStringcipherTextToHexText::CipherText->Text
Seal: None, implication is to import PublicKey.Cipher and use those functions
ShowSignature.PublicKey-- encodePublicKeyHexByteString, unpackChars-- Display via ShowInstanceShowSignature.SecretKey-- Display via OpaqueInstance-- No functions for transmitting raw binary data
Proposed Changes
If transmitting raw binary data is a desirable property, expose it for all types, e.g., encodePublicKeyBinary, encodeCipherTextBinary or whatever conventional name.
I'm not entirely sure if this is an expected usecase or if the cipherTextToBinary is just there for the implementation of the other two. If it's just a shared bit of code, recommend dropping it from the exports and recommending hexadecimal encoding everywhere.
Choose one of Show or Display to implement for each type, and reuse that definition for the other instance; suggest display for Text output rather than multiple functions.
Prefer descriptive nouns over verbs for "pure" computations. E.g., decryptedMessage, verifiedMessage, signature, unverifiedMessage, signedMessage. (EDIT: Elsewhere, it was suggested that a more precise (though still informal) rule of thumb might be "use nouns when there is no rejection/failure case" or when the function "looks like" a Church encoding)
The main thing I'd like to avoid is prefix noise like get and mk. We do not say getLength, why say getMessage?
In the particular case of mkSignature, the name is just a bit wrong since it's actually building a SignedMessage.
Verbs seem reasonable for IO actions since they "do" things.
If consistency with the libsodium vocabulary is desirable, verbs make more sense. In that case, consider identifiers like seal and openSeal, though this is more complicated in Cipher since it uses crypto_box_easy and crypto_box_open_easy, which are not exactly nice to rephrase.
Use a more descriptive type for reporting failure for convenient tracking of status.
Example: feat(Signature): add serde functions, hide internals #173 defines SignatureVerification internally so that (if added to the public API) a user could chain computations dependent on the signature being valid for the message and keep track of that information directly rather than the arguably lossy Maybe.
This is dependent on how these accessors are meant to be used. I'm sympathetic to the idea that we'd rather lose specificity of failures via Maybe to allow simpler chaining across modules. That means I can chain Cipher.decrypt and Seal.open and Signature.openMessage without intentionally flattening their failure modes to Maybe as might be required with a more descriptive type.
Alternatively, we could get away with some Either e with the right choice of e.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Sel.PublicKey
Motivation
Consistent naming conventions aid discoverability and reduce surprise. Opinionated APIs can encourage consistency of practice and help prevent user error.
Executive Summary
String
,Text
,Show
,Display
, etc.)signature
orgetSignature
?) and how to report access failureExample -
Sel.PublicKey
Key generation
Cipher
:Seal
: None, implication is to importPublicKey.Cipher
and use those functionsSignature
:Proposed Changes
In each module, define
and a constructor
keyPair :: IO KeyPair
.Encourage qualified imports and
OverloadedRecordDot
so one writesTuples make sense when there's no name for a thing, but keypairs definitely have a name. The author, at least, is more likely to remember
someKeyPair.public
beforefst someKeyPair
.De/serialization
Cipher
:Seal
: None, implication is to importPublicKey.Cipher
and use those functionsSignature
: None in current code, but #173 exportsProposed Changes
Define a consistent vocabulary for serde functions, and expose them uniformly. In particular, expose at least separate encoding and decoding functions for each half of a keypair. Public keys especially are shared often, so it's unusual that there's no way to recover a
Cipher.PublicKey
without bytes for aCipher.SecretKey
on hand.Re-export the
Cipher
functions and types inSeal
for consistency. It feels weird to import a seemingly separate module to get at "core" functions and types of the module at hand.With an expressed recommendation for qualified imports, shorter names would also be agreeable, e.g.,
Signature.encodePublicKey
,Cipher.decodeSecretKey
, etc.HasField
likehexBytes
,hexString
,bytes
. Not an especially serious suggestion, just covering options.Use a richer type for reporting errors in decoding, either specific to each module or a catch-all "all the sorts of problems we run into when decoding bytestrings" sum type. This is exposed in feat(Signature): add serde functions, hide internals #173 as
KeyMaterialDecodingError
, but could be renamed to simplyDecodingError
and cover the failure modes of, e.g.,The identified common failure modes in
Sel.PublicKey
are:n
, but the byte array being decoded has length/= n
); there are some more unusual situations that still fit largely into this case, such as aroundcryptoBoxMACBytes
for cipher text, but these could have their own cases if necessaryTransmission/Display
Cipher
:Seal
: None, implication is to importPublicKey.Cipher
and use those functionsSignature
: None in current code, but #173 exportsProposed Changes
encodePublicKeyBinary
,encodeCipherTextBinary
or whatever conventional name.cipherTextToBinary
is just there for the implementation of the other two. If it's just a shared bit of code, recommend dropping it from the exports and recommending hexadecimal encoding everywhere.Show
orDisplay
to implement for each type, and reuse that definition for the other instance; suggestdisplay
forText
output rather than multiple functions.Accessors and constructors
Cipher
:Seal
:Signature
:Proposed Changes
decryptedMessage
,verifiedMessage
,signature
,unverifiedMessage
,signedMessage
. (EDIT: Elsewhere, it was suggested that a more precise (though still informal) rule of thumb might be "use nouns when there is no rejection/failure case" or when the function "looks like" a Church encoding)get
andmk
. We do not saygetLength
, why saygetMessage
?mkSignature
, the name is just a bit wrong since it's actually building aSignedMessage
.IO
actions since they "do" things.libsodium
vocabulary is desirable, verbs make more sense. In that case, consider identifiers likeseal
andopenSeal
, though this is more complicated inCipher
since it usescrypto_box_easy
andcrypto_box_open_easy
, which are not exactly nice to rephrase.SignatureVerification
internally so that (if added to the public API) a user could chain computations dependent on the signature being valid for the message and keep track of that information directly rather than the arguably lossyMaybe
.Maybe
to allow simpler chaining across modules. That means I can chainCipher.decrypt
andSeal.open
andSignature.openMessage
without intentionally flattening their failure modes toMaybe
as might be required with a more descriptive type.Either e
with the right choice ofe
.Beta Was this translation helpful? Give feedback.
All reactions