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 }