1 module chloride.sign; 2 3 import chloride.core; 4 import chloride.random : randomArray; 5 6 import std.array : uninitializedArray; 7 8 import deimos.sodium.crypto_sign; 9 10 /// 11 unittest { 12 import std..string : representation; 13 immutable ubyte[] message = representation("hello"); 14 auto keys = makeSigningKeys(); 15 auto signed = signMessage(message, keys.privateKey); 16 assert(openSignedMessage(signed, keys.publicKey) == message); 17 } 18 19 /// 20 unittest { 21 import std..string : representation; 22 immutable ubyte[] message = representation("hello"); 23 auto keys = makeSigningKeys(); 24 auto sig = messageSignature(message, keys.privateKey); 25 assert(verifySignature(message, sig, keys.publicKey)); 26 } 27 28 alias SignPublicKey = ubyte[crypto_sign_PUBLICKEYBYTES]; 29 alias SignPrivateKey = ubyte[crypto_sign_SECRETKEYBYTES]; 30 alias SigningSeed = ubyte[crypto_sign_SEEDBYTES]; 31 alias SignatureLength = crypto_sign_BYTES; 32 33 struct SigningKeys { 34 SignPublicKey publicKey; 35 SignPrivateKey privateKey; 36 } 37 38 /** 39 * Generate a new random key pair suitable for public key signatures. 40 */ 41 SigningKeys makeSigningKeys() { 42 SigningKeys pair; 43 auto result = crypto_sign_keypair(pair.publicKey.ptr, pair.privateKey.ptr); 44 enforceSodium(result == 0); 45 return pair; 46 } 47 48 /** 49 * Generate a key pair from a seed. 50 */ 51 SigningKeys makeSigningKeys(in SigningSeed seed) { 52 SigningKeys pair; 53 auto result = crypto_sign_seed_keypair(pair.publicKey.ptr, pair.privateKey.ptr, seed.ptr); 54 enforceSodium(result == 0); 55 return pair; 56 } 57 58 /** 59 * Generate a seed that can be used to generate key pairs with `makeSigningKeys`. 60 */ 61 alias makeSigningSeed = randomArray!SigningSeed; 62 63 /** 64 * Sign a message using a private key by prepending a signature to the message. 65 */ 66 ubyte[] signMessage(in ubyte[] message, in SignPrivateKey key) { 67 ubyte[] sm = uninitializedArray!(ubyte[])(message.length + SignatureLength); 68 auto result = crypto_sign(sm.ptr, null, message.ptr, message.length, key.ptr); 69 enforceSodium(result == 0); 70 return sm; 71 } 72 73 /** 74 * Compute a signature for a message using a private key. Unlike `signMessage` 75 * this does not include the original message. 76 */ 77 ubyte[SignatureLength] messageSignature(in ubyte[] message, in SignPrivateKey key) { 78 ubyte[SignatureLength] signature = void; 79 auto result = crypto_sign_detached(signature.ptr, null, message.ptr, message.length, key.ptr); 80 enforceSodium(result == 0); 81 return signature; 82 } 83 84 /** 85 * Verify a signed message using the public key corresponding to the private 86 * key used to sign it. If verification succeeds return the message (without the signature), 87 * otherwise return null. 88 */ 89 ubyte[] openSignedMessage(in ubyte[] signed, in SignPublicKey key) in { 90 assert(signed.length > SignatureLength); 91 } body { 92 ubyte[] message = uninitializedArray!(ubyte[])(signed.length - SignatureLength); 93 if (crypto_sign_open(message.ptr, null, signed.ptr, signed.length, key.ptr) == 0) { 94 return message; 95 } else { 96 return null; 97 } 98 } 99 100 /** 101 * Verify a signed message using the public key corresponding to the private key 102 * used to sign it. Returns true on success and false on failure. This method is passed 103 * the message and signature separately. 104 */ 105 bool verifySignature(in ubyte[] message, in ubyte[SignatureLength] signature, in SignPublicKey key) { 106 return crypto_sign_verify_detached(signature.ptr, message.ptr, message.length, key.ptr) == 0; 107 }