diff --git a/spec/cox_spec.cr b/spec/cox_spec.cr index 1195ac9..c1c416c 100644 --- a/spec/cox_spec.cr +++ b/spec/cox_spec.cr @@ -3,7 +3,7 @@ require "./spec_helper" describe Cox do # TODO: Write tests - it "works" do + it "works for encrypting" do data = "Hello World!" # Alice is the sender @@ -22,4 +22,18 @@ describe Cox do String.new(decrypted).should eq(data) end + + it "works for signing" do + message = "test" + + signing_pair = Cox::SignKeyPair.new + + # Create signature using the secret key + signature = Cox.sign(message, signing_pair.secret) + + # Verify the signature on the message + verified = Cox.verify(signature, message, signing_pair.public) + + verified.should eq(true) + end end diff --git a/src/cox.cr b/src/cox.cr index 21c93df..ecee5c9 100644 --- a/src/cox.cr +++ b/src/cox.cr @@ -22,6 +22,24 @@ module Cox LibSodium.crypto_box_open_easy(output_buffer.to_unsafe, data_buffer.to_unsafe, data_size, nonce.pointer, sender_public_key.pointer, recipient_secret_key.pointer) output_buffer end + + def self.sign(message, secret_key : SignSecretKey) + message_buffer = message.to_slice + message_buffer_size = message_buffer.bytesize + signature_output_buffer = Bytes.new(LibSodium::SIGNATURE_BYTES) + + LibSodium.crypto_sign_detached(signature_output_buffer.to_unsafe, 0, message_buffer.to_unsafe, message_buffer_size, secret_key.pointer) + signature_output_buffer + end + + def self.verify(signature, message, public_key : SignPublicKey) + signature_buffer = signature.to_slice + message_buffer = message.to_slice + message_buffer_size = message_buffer.bytesize + + verified = LibSodium.crypto_sign_verify_detached(signature_buffer.to_unsafe, message_buffer.to_unsafe, message_buffer_size, public_key.pointer) + verified.zero? + end end if Cox::LibSodium.sodium_init() == -1 diff --git a/src/cox/lib_sodium.cr b/src/cox/lib_sodium.cr index 7522dbc..020e97c 100644 --- a/src/cox/lib_sodium.cr +++ b/src/cox/lib_sodium.cr @@ -7,11 +7,17 @@ module Cox fun crypto_box_secretkeybytes() : LibC::SizeT fun crypto_box_noncebytes() : LibC::SizeT fun crypto_box_macbytes() : LibC::SizeT + fun crypto_sign_publickeybytes() : LibC::SizeT + fun crypto_sign_secretkeybytes() : LibC::SizeT + fun crypto_sign_bytes() : LibC::SizeT PUBLIC_KEY_BYTES = crypto_box_publickeybytes() SECRET_KEY_BYTES = crypto_box_secretkeybytes() NONCE_BYTES = crypto_box_macbytes() MAC_BYTES = crypto_box_macbytes() + PUBLIC_SIGN_BYTES = crypto_sign_publickeybytes() + SECRET_SIGN_BYTES = crypto_sign_secretkeybytes() + SIGNATURE_BYTES = crypto_sign_bytes() fun crypto_box_keypair( public_key_output : Pointer(LibC::UChar), @@ -35,5 +41,25 @@ module Cox sender_public_key : Pointer(LibC::UChar), recipient_secret_key : Pointer(LibC::UChar) ) : LibC::Int + + fun crypto_sign_keypair( + public_key_output : Pointer(LibC::UChar), + secret_key_output : Pointer(LibC::UChar) + ) : LibC::Int + + fun crypto_sign_detached( + signature_output : Pointer(LibC::UChar), + signature_output_size : LibC::ULongLong, + message : Pointer(LibC::UChar), + message_size : LibC::ULongLong, + secret_key : Pointer(LibC::UChar) + ) : LibC::Int + + fun crypto_sign_verify_detached( + signature : Pointer(LibC::UChar), + message : Pointer(LibC::UChar), + message_size : LibC::ULongLong, + public_key : Pointer(LibC::UChar) + ) : LibC::Int end end diff --git a/src/cox/sign_key_pair.cr b/src/cox/sign_key_pair.cr new file mode 100644 index 0000000..0b64882 --- /dev/null +++ b/src/cox/sign_key_pair.cr @@ -0,0 +1,24 @@ +require "./lib_sodium" + +module Cox + class SignKeyPair + property public : SignPublicKey + property secret : SignSecretKey + + def initialize(@public, @secret) + end + + def self.new(pub : Bytes, sec : Bytes) + new(SignPublicKey.new(pub), SignSecretKey.new(sec)) + end + + def self.new + public_key = Bytes.new(SignPublicKey::KEY_LENGTH) + secret_key = Bytes.new(SignSecretKey::KEY_LENGTH) + + LibSodium.crypto_sign_keypair(public_key.to_unsafe, secret_key.to_unsafe) + + new(public_key, secret_key) + end + end +end diff --git a/src/cox/sign_public_key.cr b/src/cox/sign_public_key.cr new file mode 100644 index 0000000..030c3c7 --- /dev/null +++ b/src/cox/sign_public_key.cr @@ -0,0 +1,15 @@ +require "./lib_sodium" + +module Cox + class SignPublicKey < Key + property bytes : Bytes + + KEY_LENGTH = LibSodium::PUBLIC_SIGN_BYTES + + def initialize(@bytes : Bytes) + if bytes.bytesize != KEY_LENGTH + raise ArgumentError.new("Public key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") + end + end + end +end diff --git a/src/cox/sign_secret_key.cr b/src/cox/sign_secret_key.cr new file mode 100644 index 0000000..332099b --- /dev/null +++ b/src/cox/sign_secret_key.cr @@ -0,0 +1,15 @@ +require "./lib_sodium" + +module Cox + class SignSecretKey < Key + property bytes : Bytes + + KEY_LENGTH = LibSodium::SECRET_SIGN_BYTES + + def initialize(@bytes : Bytes) + if bytes.bytesize != KEY_LENGTH + raise ArgumentError.new("Secret key must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") + end + end + end +end diff --git a/src/cox/signature.cr b/src/cox/signature.cr new file mode 100644 index 0000000..1214638 --- /dev/null +++ b/src/cox/signature.cr @@ -0,0 +1,15 @@ +require "./lib_sodium" + +module Cox + class Signature + property bytes : Bytes + + KEY_LENGTH = LibSodium::SIGNATURE_BYTES + + def initialize(@bytes : Bytes) + if bytes.bytesize != KEY_LENGTH + raise ArgumentError.new("Signature must be #{KEY_LENGTH} bytes, got #{bytes.bytesize}") + end + end + end +end