Skip to content

Commit

Permalink
Add documentation and examples. Change hash to use hash_init, hash_up…
Browse files Browse the repository at this point in the history
…date and digest
  • Loading branch information
NorbertSzydlik authored and lpil committed Sep 30, 2024
1 parent 18f157e commit ea7f16a
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 46 deletions.
90 changes: 78 additions & 12 deletions src/gleam/crypto.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,93 @@ pub type HashAlgorithm {
Sha1
}

/// Computes a digest of the input bit string.
@external(erlang, "gleam_crypto_ffi", "hash")
@external(javascript, "../gleam_crypto_ffi.mjs", "hash")
pub fn hash(a: HashAlgorithm, b: BitArray) -> BitArray
pub type Hasher

pub type HashState
/// Computes a digest of the input bit string.
///
/// Example:
/// ```gleam
/// let digest = <<hash(Sha256, <<"a":utf8>>):int>>
/// ```
///
pub fn hash(a: HashAlgorithm, b: BitArray) -> BitArray {
hash_init(a)
|> hash_update(b)
|> digest
}

/// Starts hash algorithm for streaming
/// Initializes the state for a streaming hash digest calculation.
/// Then you can add data into the digest algorithm using `hash_update` function.
/// Finally you use `hash_final` to retrieve the digest.
///
/// It is useful for hashing streams of data or
/// large amount of it without the need to load it all to the memory.
///
/// Simple example:
/// ```gleam
/// let hash =
/// hash_init(Sha512)
/// |> hash_update(with: <<"data to hash":utf8>>)
/// |> digest
/// ```
///
/// Example actor that hashes incoming stream of messages.
/// ```gleam
/// import gleam/erlang/process.{type Subject}
/// import gleam/otp/actor
/// import gleam/crypto
///
/// pub type Command {
/// Hash(data: BitArray)
/// Finalize(reply_with: Subject(BitArray))
/// Shutdown
/// }
///
/// // Receive chunks of message to digest in `Hash` messages.
/// // Responds with digest after receiving Finalize message.
/// fn handle_message(
/// algorithm: crypto.HashAlgorithm,
/// ) -> fn(Command, crypto.Hasher) -> actor.Next(Command, crypto.Hasher) {
/// fn(message: Command, hasher: crypto.Hasher) {
/// case message {
/// Shutdown -> actor.Stop(process.Normal)
/// Hash(hash_chunk) -> actor.continue(crypto.hash_update(hasher, hash_chunk))
/// Finalize(client) -> {
/// process.send(client, crypto.digest(hasher))
/// actor.continue(crypto.hash_init(algorithm))
/// }
/// }
/// }
/// }
///
/// fn start_actor(algorithm: crypto.HashAlgorithm) {
/// actor.start(crypto.hash_init(algorithm), handle_message(algorithm))
/// }
///
/// let assert Ok(my_actor) = start_actor(crypto.Sha256)
/// process.send(my_actor, Hash(<<"chunk1":utf8>>))
/// // many more message chunks
/// process.send(my_actor, Hash(<<"chunkN":utf8>>))
/// let digest = process.call(my_actor, Finalize, 1000)
/// ```
///
@external(erlang, "gleam_crypto_ffi", "hash_init")
@external(javascript, "../gleam_crypto_ffi.mjs", "hashInit")
pub fn hash_init(a: HashAlgorithm) -> HashState
pub fn hash_init(algorithm algorithm: HashAlgorithm) -> Hasher

/// Starts hash algorithm for streaming
/// Adds data to a streaming digest calculation.
/// See hash_init for more information and examples.
///
@external(erlang, "crypto", "hash_update")
@external(javascript, "../gleam_crypto_ffi.mjs", "hashUpdate")
pub fn hash_update(state: HashState, data: BitArray) -> HashState
pub fn hash_update(hasher hasher: Hasher, with hash_chunk: BitArray) -> Hasher

/// Starts hash algorithm for streaming
/// Finalizes a streaming hash calculation.
/// See hash_init for more information and examples.
///
@external(erlang, "crypto", "hash_final")
@external(javascript, "../gleam_crypto_ffi.mjs", "hashFinal")
pub fn hash_final(state: HashState) -> BitArray
@external(javascript, "../gleam_crypto_ffi.mjs", "digest")
pub fn digest(hasher hasher: Hasher) -> BitArray

/// Calculates the HMAC (hash-based message authentication code) for a bit
/// string.
Expand Down
5 changes: 1 addition & 4 deletions src/gleam_crypto_ffi.erl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-module(gleam_crypto_ffi).
-export([hmac/3, hash/2, hash_init/1]).
-export([hmac/3, hash_init/1]).

convert_algorithm(Algorithm) ->
case Algorithm of
Expand All @@ -10,8 +10,5 @@ convert_algorithm(Algorithm) ->
hmac(Data, Algorithm, Key) ->
crypto:mac(hmac, convert_algorithm(Algorithm), Key, Data).

hash(Algorithm, Data) ->
crypto:hash(convert_algorithm(Algorithm), Data).

hash_init(Algorithm) ->
crypto:hash_init(convert_algorithm(Algorithm)).
17 changes: 5 additions & 12 deletions src/gleam_crypto_ffi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,16 @@ export function hmac(data, algorithm, key) {
return new BitArray(array);
}

export function hash(algorithm, data) {
const hasher = crypto.createHash(algorithmName(algorithm));
hasher.update(data.buffer);
const array = new Uint8Array(hasher.digest());
return new BitArray(array);
}

export function hashInit(algorithm) {
return crypto.createHash(algorithmName(algorithm));
}

export function hashUpdate(state, data) {
state.update(data.buffer);
return state;
export function hashUpdate(hasher, hashChunk) {
hasher.update(hashChunk.buffer);
return hasher;
}

export function hashFinal(state) {
const array = new Uint8Array(state.digest());
export function digest(hasher) {
const array = new Uint8Array(hasher.digest());
return new BitArray(array);
}
36 changes: 18 additions & 18 deletions test/gleam/crypto_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ pub fn hash_sha1_test() {

pub fn hash_sha1_stream_test() {
crypto.hash_init(crypto.Sha1)
|> crypto.hash_update(<<"hello ":utf8>>)
|> crypto.hash_update(<<"stream":utf8>>)
|> crypto.hash_final
|> crypto.hash_update(with: <<"hello ":utf8>>)
|> crypto.hash_update(with: <<"stream":utf8>>)
|> crypto.digest
|> should.equal(crypto.hash(crypto.Sha1, <<"hello stream":utf8>>))
}

Expand All @@ -36,9 +36,9 @@ pub fn hash_sha256_test() {

pub fn hash_sha256_stream_test() {
crypto.hash_init(crypto.Sha256)
|> crypto.hash_update(<<"hello ":utf8>>)
|> crypto.hash_update(<<"stream":utf8>>)
|> crypto.hash_final
|> crypto.hash_update(with: <<"hello ":utf8>>)
|> crypto.hash_update(with: <<"stream":utf8>>)
|> crypto.digest
|> should.equal(crypto.hash(crypto.Sha256, <<"hello stream":utf8>>))
}

Expand All @@ -52,9 +52,9 @@ pub fn hash_sha224_test() {

pub fn hash_sha224_stream_test() {
crypto.hash_init(crypto.Sha224)
|> crypto.hash_update(<<"hello ":utf8>>)
|> crypto.hash_update(<<"stream":utf8>>)
|> crypto.hash_final
|> crypto.hash_update(with: <<"hello ":utf8>>)
|> crypto.hash_update(with: <<"stream":utf8>>)
|> crypto.digest
|> should.equal(crypto.hash(crypto.Sha224, <<"hello stream":utf8>>))
}

Expand All @@ -69,9 +69,9 @@ pub fn hash_sha384_test() {

pub fn hash_sha384_stream_test() {
crypto.hash_init(crypto.Sha384)
|> crypto.hash_update(<<"hello ":utf8>>)
|> crypto.hash_update(<<"stream":utf8>>)
|> crypto.hash_final
|> crypto.hash_update(with: <<"hello ":utf8>>)
|> crypto.hash_update(with: <<"stream":utf8>>)
|> crypto.digest
|> should.equal(crypto.hash(crypto.Sha384, <<"hello stream":utf8>>))
}

Expand All @@ -87,9 +87,9 @@ pub fn hash_sha512_test() {

pub fn hash_sha512_stream_test() {
crypto.hash_init(crypto.Sha512)
|> crypto.hash_update(<<"hello ":utf8>>)
|> crypto.hash_update(<<"stream":utf8>>)
|> crypto.hash_final
|> crypto.hash_update(with: <<"hello ":utf8>>)
|> crypto.hash_update(with: <<"stream":utf8>>)
|> crypto.digest
|> should.equal(crypto.hash(crypto.Sha512, <<"hello stream":utf8>>))
}

Expand All @@ -102,9 +102,9 @@ pub fn hash_md5_test() {

pub fn hash_md5_stream_test() {
crypto.hash_init(crypto.Md5)
|> crypto.hash_update(<<"hello ":utf8>>)
|> crypto.hash_update(<<"stream":utf8>>)
|> crypto.hash_final
|> crypto.hash_update(with: <<"hello ":utf8>>)
|> crypto.hash_update(with: <<"stream":utf8>>)
|> crypto.digest
|> should.equal(crypto.hash(crypto.Md5, <<"hello stream":utf8>>))
}

Expand Down

0 comments on commit ea7f16a

Please sign in to comment.