libdecaf

CI Hex.pm HexDocs

Cryptographic signatures, key agreement, hashes, extendable-output functions (XOFs), and random bytes for Erlang and Elixir. Built on ed448goldilocks, with XKCP and K12 acceleration selected at runtime on ARM64 and x86-64.

Use libdecaf for the high-level API. Specialized modules provide group arithmetic, TUAK, and explicit PRNG state management.

API reference · Hash and XOF guide · Migrating to 3.0 · Changelog

Algorithm Support

Algorithm Group Purpose Definition
Ed25519 EdDSA Signature RFC 8032
Ed25519ctx EdDSA Signature RFC 8032
Ed25519ph EdDSA Signature RFC 8032
Ed448 EdDSA Signature RFC 8032
Ed448ph EdDSA Signature RFC 8032
X25519 ECDH Key agreement RFC 7748
X448 ECDH Key agreement RFC 7748
Ristretto255 Curve25519 Prime-order group RFC 9496
Decaf448 Curve448 Prime-order group RFC 9496
SHA2-512 (SHA-512) SHA-2 Hash FIPS 180-4
SHA3-224 SHA-3 Hash FIPS 202
SHA3-256 SHA-3 Hash FIPS 202
SHA3-384 SHA-3 Hash FIPS 202
SHA3-512 SHA-3 Hash FIPS 202
SHAKE128 SHA-3 Hash / XOF FIPS 202
SHAKE256 SHA-3 Hash / XOF FIPS 202
cSHAKE128 SHA-3 Derived Functions Hash / XOF SP 800-185r1
cSHAKE256 SHA-3 Derived Functions Hash / XOF SP 800-185r1
KMAC128 SHA-3 Derived Functions MAC SP 800-185r1
KMAC256 SHA-3 Derived Functions MAC SP 800-185r1
KMACXOF128 SHA-3 Derived Functions Keyed XOF SP 800-185r1
KMACXOF256 SHA-3 Derived Functions Keyed XOF SP 800-185r1
ParallelHash128 SHA-3 Derived Functions Parallel hash SP 800-185r1
ParallelHash256 SHA-3 Derived Functions Parallel hash SP 800-185r1
ParallelHashXOF128 SHA-3 Derived Functions Parallel XOF SP 800-185r1
ParallelHashXOF256 SHA-3 Derived Functions Parallel XOF SP 800-185r1
TupleHash128 SHA-3 Derived Functions Tuple hash SP 800-185r1
TupleHash256 SHA-3 Derived Functions Tuple hash SP 800-185r1
TupleHashXOF128 SHA-3 Derived Functions Tuple XOF SP 800-185r1
TupleHashXOF256 SHA-3 Derived Functions Tuple XOF SP 800-185r1
Keccak-224 Keccak Hash Keccak submission v3
Keccak-256 Keccak Hash Keccak submission v3
Keccak-384 Keccak Hash Keccak submission v3
Keccak-512 Keccak Hash Keccak submission v3
Keccak-XOF-128 Keccak Hash / XOF Keccak submission v3
Keccak-XOF-256 Keccak Hash / XOF Keccak submission v3
SpongeRNG Keccak Pseudorandom bytes Upstream SpongeRNG API
TUAK Keccak Mobile authentication / key generation 3GPP TS 35.231
HopMAC128 KangarooTwelve Parallel MAC / keyed XOF RFC 9861
HopMAC256 KangarooTwelve Parallel MAC / keyed XOF RFC 9861
KT128 (KangarooTwelve) KangarooTwelve Parallel hash / XOF RFC 9861
KT256 KangarooTwelve Parallel hash / XOF RFC 9861
TurboSHAKE128 KangarooTwelve Hash / XOF RFC 9861
TurboSHAKE256 KangarooTwelve Hash / XOF RFC 9861

Installation

Erlang with Rebar3:

{deps, [{libdecaf, "3.0.0"}]}.

Elixir:

defp deps do
  [{:libdecaf, "~> 3.0"}]
end

For erlang.mk, add libdecaf to DEPS and set dep_libdecaf = hex 3.0.0.

Building requires Erlang/OTP 28 or later, GNU Make 4 or later, a C/C++ toolchain with C23/C++20 support, CMake 3.22 or later, and Python 3. CI covers the latest OTP 28 and OTP 29. The native dependency is included in the package.

Quick start

Erlang:

{ok, _} = libdecaf:start(),
Digest = libdecaf:hash(sha3_256, <<"abc">>),
<<"3A985DA74FE225B2045C172D6BD390BD855F086E3E9D525B46BFE24511431532">> = binary:encode_hex(Digest),
Token = libdecaf:strong_rand_bytes(32).

Elixir calls the same Erlang API:

{:ok, _} = :libdecaf.start()
digest = :libdecaf.hash(:sha3_256, "abc")
token = :libdecaf.strong_rand_bytes(32)

Signatures and key agreement

Generate an Ed25519 keypair, sign a message, and verify it:

Keypair = libdecaf:eddsa_keypair(ed25519, binary:copy(<<7>>, 32)),
PublicKey = libdecaf:eddsa_keypair_extract_public_key(Keypair),
% <<234,74,108,99,226,156,82,10,190,245,80,123,19,46,197,249,149,71,118,174,190,190,123,146,66,30,234,105,20,70,210,44>>
Signature = libdecaf:eddsa_sign({ed25519, Keypair}, <<"message">>),
% <<158,70,173,9,82,66,72,106,182,139,11,102,100,173,224,144,156,152,224,45,224,25,217,136,16,196,72,215,229,141,67,25,200,174,148,26,71,79,110,246,78,169,221,34,16,108,92,82,25,110,71,40,249,163,70,71,55,187,172,132,223,47,171,10>>
true = libdecaf:eddsa_verify({ed25519, PublicKey}, Signature, <<"message">>).

Ed25519ctx, Ed25519ph, Ed448, and Ed448ph accept an optional context in the parameter tuple. Ed25519ph and Ed448ph also support streaming:

Keypair = libdecaf:eddsa_keypair(ed448, binary:copy(<<7>>, 57)),
S0 = libdecaf:eddsa_sign_init({ed448ph, Keypair, <<"protocol/v1">>}),
S1 = libdecaf:eddsa_sign_update(S0, [<<"hello ">>, <<"world">>]),
Signature = libdecaf:eddsa_sign_final(S1),
% <<250,135,86,250,37,207,197,146,249,8,53,190,58,232,120,77,166,35,216,0,214,125,227,12,25,138,28,240,164,92,213,243,85,229,225,12,191,14,141,187,205,147,144,113,238,71,214,29,158,204,27,36,5,159,242,209,0,248,242,70,27,36,1,224,98,227,69,46,63,134,224,81,12,199,169,41,216,39,225,108,50,186,100,149,81,100,250,5,169,157,88,178,65,158,236,217,91,230,189,120,241,25,143,23,7,8,249,108,199,61,175,31,43,0>>
V0 = libdecaf:eddsa_verify_init({ed448ph, Keypair, <<"protocol/v1">>}, Signature),
V1 = libdecaf:eddsa_verify_update(V0, <<"hello world">>),
true = libdecaf:eddsa_verify_final(V1).

Pass original message bytes; the prehash is computed internally.

For X25519 or X448 key agreement:

Alice = libdecaf:ecdh_keypair(x25519, binary:copy(<<7>>, 32)),
Bob = libdecaf:ecdh_keypair(x25519, binary:copy(<<19>>, 32)),
Shared = libdecaf:ecdh_shared_secret(x25519, Alice, Bob),
% <<56,90,192,126,49,140,20,89,192,19,41,48,240,234,208,113,4,39,59,75,124,237,47,225,137,172,185,88,125,224,112,124>>
Shared = libdecaf:ecdh_shared_secret(x25519, Bob, Alice).
% <<56,90,192,126,49,140,20,89,192,19,41,48,240,234,208,113,4,39,59,75,124,237,47,225,137,172,185,88,125,224,112,124>>

Shared secrets are raw bytes for your protocol's key derivation. A rejected peer key returns error. For curve-specific operations and encoded-key APIs, see libdecaf_curve25519 and libdecaf_curve448.

Hashes, authentication tags, and XOFs

Choose the algorithm and options when initializing a stream. Keep each returned state; states are immutable and can be reused to branch a computation. Input accepts iodata, and all lengths are in bytes.

H0 = libdecaf:hash_init(sha3_256),
H1 = libdecaf:hash_update(H0, <<"hello ">>),
H2 = libdecaf:hash_update(H1, <<"world">>),
Digest = libdecaf:hash_final(H2),
Digest = libdecaf:hash(sha3_256, <<"hello world">>).

KMAC and HopMAC take a key and optional customization string:

Key = libdecaf:strong_rand_bytes(32),
Tag = libdecaf:hash({kmac128, 32, Key, <<"orders/v1">>}, <<"invoice-42">>).

XOFs let you read as much output as needed. Once squeezing starts, the returned state can keep squeezing but cannot absorb more input:

X0 = libdecaf:xof_init(shake128),
X1 = libdecaf:xof_absorb(X0, <<"abc">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Output = <<First/binary, Second/binary>>,
Output = libdecaf:xof(shake128, <<"abc">>, 32).

KT, HopMAC, and ParallelHash can use the parallel APIs for large inputs:

Input = binary:copy(<<"abc">>, 10000),
Digest = libdecaf:parallel_hash(kt128, Input),
Digest = libdecaf:hash(kt128, Input).

The parallel streaming APIs are parallel_hash_init/1, parallel_hash_update/2, parallel_hash_final/1, and the corresponding parallel_xof_init/1, parallel_xof_absorb/2, parallel_xof_squeeze/2 functions. They produce the same output as the ordinary APIs.

See the Hash and XOF guide for parameter forms, TupleHash element boundaries, customization, and parallel configuration.

Specialized modules

libdecaf_ristretto255 and libdecaf_decaf448 provide prime-order group arithmetic. libdecaf_tuak exposes TUAK authentication and key generation. Use libdecaf:strong_rand_bytes/1 for automatically seeded random bytes, or libdecaf_spongerng for explicit seeded streams.

Implementations are checked against published vectors from RFCs 7748, 8032, 9496, and 9861, NIST FIPS 180-4, FIPS 202, SP 800-185, and 3GPP TUAK conformance data.

Benchmark highlights

Bulk throughput on an Apple M3 Max with 14 schedulers, OTP 29, and the ARM SHA3 backend. The parallel APIs reach 41 GB/s for KT128 and 21 GB/s for ParallelHash128, about 17× and 18× their ordinary APIs:

Algorithm Ordinary API Parallel API Speedup
TurboSHAKE128 2.5 GB/s — —
HopMAC128 2.4 GB/s 41 GB/s 17×
KT128 2.4 GB/s 41 GB/s 17×
KT256 2.0 GB/s 31 GB/s 16×
TurboSHAKE256 2.0 GB/s — —
HopMAC256 1.9 GB/s 31 GB/s 16×
cSHAKE128 1.2 GB/s — —
Keccak-XOF-128 1.2 GB/s — —
KMAC128 1.2 GB/s — —
KMACXOF128 1.2 GB/s — —
ParallelHash128 1.2 GB/s 21 GB/s 18×
ParallelHashXOF128 1.2 GB/s 20 GB/s 17×
SHAKE128 1.2 GB/s — —
TupleHash128 1.2 GB/s — —
TupleHashXOF128 1.2 GB/s — —
cSHAKE256 1.0 GB/s — —
Keccak-224 1.0 GB/s — —
Keccak-256 1.0 GB/s — —
Keccak-XOF-256 1.0 GB/s — —
KMAC256 1.0 GB/s — —
KMACXOF256 1.0 GB/s — —
ParallelHashXOF256 1.0 GB/s 16 GB/s 17×
SHA3-224 1.0 GB/s — —
SHA3-256 1.0 GB/s — —
SHAKE256 1.0 GB/s — —
TupleHash256 1.0 GB/s — —
TupleHashXOF256 1.0 GB/s — —
ParallelHash256 0.9 GB/s 16 GB/s 17×
Keccak-384 0.8 GB/s — —
SHA3-384 0.8 GB/s — —
SHA2-512 0.7 GB/s — —
Keccak-512 0.5 GB/s — —
SHA3-512 0.5 GB/s — —

Five warmed streaming passes, including initialization and finalization, with reused 64 MiB updates. Ordinary messages are 1 GiB; parallel messages are 32 GiB to measure sustained worker-pool throughput. ParallelHash uses 8 KiB blocks; workers drain their queues until idle. Rates use decimal GB/s. Speedups use the unrounded measurements. KMACXOF128, KMACXOF256, TupleHashXOF128, TupleHashXOF256, ParallelHashXOF128, and ParallelHashXOF256 use the XOF API; other rows use the Hash API. Small messages favor the ordinary API.

Full measurements and methodology and parallel size sweeps and native baselines are available for comparison.

Building, testing, and documentation

gmake
gmake tests
gmake lint
gmake docs

ExDoc writes the API reference and guides to doc/index.html, including source links for release 3.0.0. rebar3 ex_doc builds the same documentation directly.

Use gmake format after editing C or Erlang sources. Generated files are maintained through just codegen. Development covers code generation, selected test suites, sanitizers, and package checks.

Usage

Each example is independent and can be pasted into an Erlang shell.

ECDH

X25519

Generate two keypairs and derive the same shared secret from either side:

Alice = libdecaf:ecdh_keypair(x25519, binary:copy(<<7>>, 32)),
Bob = libdecaf:ecdh_keypair(x25519, binary:copy(<<19>>, 32)),
{AlicePK, AliceSK} = libdecaf:ecdh_keypair_extract(Alice),
{BobPK, BobSK} = libdecaf:ecdh_keypair_extract(Bob),
Shared = libdecaf:ecdh_shared_secret(x25519, AliceSK, BobPK),
% <<56,90,192,126,49,140,20,89,192,19,41,48,240,234,208,113,4,39,59,75,124,237,47,225,137,172,185,88,125,224,112,124>>
Shared = libdecaf:ecdh_shared_secret(x25519, BobSK, AlicePK),
% <<56,90,192,126,49,140,20,89,192,19,41,48,240,234,208,113,4,39,59,75,124,237,47,225,137,172,185,88,125,224,112,124>>
Shared = libdecaf:ecdh_shared_secret(x25519, Alice, Bob).
% <<56,90,192,126,49,140,20,89,192,19,41,48,240,234,208,113,4,39,59,75,124,237,47,225,137,172,185,88,125,224,112,124>>

X448

Generate two keypairs and derive the same shared secret from either side:

Alice = libdecaf:ecdh_keypair(x448, binary:copy(<<7>>, 56)),
Bob = libdecaf:ecdh_keypair(x448, binary:copy(<<19>>, 56)),
{AlicePK, AliceSK} = libdecaf:ecdh_keypair_extract(Alice),
{BobPK, BobSK} = libdecaf:ecdh_keypair_extract(Bob),
Shared = libdecaf:ecdh_shared_secret(x448, AliceSK, BobPK),
% <<89,173,245,248,51,14,165,105,119,217,108,10,134,211,178,251,229,202,192,178,194,183,165,167,164,162,200,119,15,228,58,219,231,61,132,217,246,202,71,197,75,178,149,36,67,199,141,73,249,232,49,162,147,160,26,91>>
Shared = libdecaf:ecdh_shared_secret(x448, BobSK, AlicePK),
% <<89,173,245,248,51,14,165,105,119,217,108,10,134,211,178,251,229,202,192,178,194,183,165,167,164,162,200,119,15,228,58,219,231,61,132,217,246,202,71,197,75,178,149,36,67,199,141,73,249,232,49,162,147,160,26,91>>
Shared = libdecaf:ecdh_shared_secret(x448, Alice, Bob).
% <<89,173,245,248,51,14,165,105,119,217,108,10,134,211,178,251,229,202,192,178,194,183,165,167,164,162,200,119,15,228,58,219,231,61,132,217,246,202,71,197,75,178,149,36,67,199,141,73,249,232,49,162,147,160,26,91>>

EdDSA

Ed25519

Keypair = libdecaf:eddsa_keypair(ed25519, binary:copy(<<7>>, 32)),
PublicKey = libdecaf:eddsa_keypair_extract_public_key(Keypair),
% <<234,74,108,99,226,156,82,10,190,245,80,123,19,46,197,249,149,71,118,174,190,190,123,146,66,30,234,105,20,70,210,44>>
Signature = libdecaf:eddsa_sign({ed25519, Keypair}, <<"message">>),
% <<158,70,173,9,82,66,72,106,182,139,11,102,100,173,224,144,156,152,224,45,224,25,217,136,16,196,72,215,229,141,67,25,200,174,148,26,71,79,110,246,78,169,221,34,16,108,92,82,25,110,71,40,249,163,70,71,55,187,172,132,223,47,171,10>>
true = libdecaf:eddsa_verify({ed25519, PublicKey}, Signature, <<"message">>),
false = libdecaf:eddsa_verify({ed25519, PublicKey}, Signature, <<"changed">>).

Ed25519ctx

Keypair = libdecaf:eddsa_keypair(ed25519, binary:copy(<<7>>, 32)),
PublicKey = libdecaf:eddsa_keypair_extract_public_key(Keypair),
% <<234,74,108,99,226,156,82,10,190,245,80,123,19,46,197,249,149,71,118,174,190,190,123,146,66,30,234,105,20,70,210,44>>
Signature = libdecaf:eddsa_sign({ed25519ctx, Keypair, <<"protocol/v1">>}, <<"message">>),
% <<225,28,218,178,129,22,148,106,230,86,190,156,24,181,107,8,255,228,205,158,156,23,7,216,124,29,34,166,245,128,9,29,129,248,140,229,129,140,136,112,183,232,127,170,24,63,194,13,111,11,196,32,45,187,127,32,103,236,144,180,252,20,192,13>>
true = libdecaf:eddsa_verify({ed25519ctx, PublicKey, <<"protocol/v1">>}, Signature, <<"message">>),
false = libdecaf:eddsa_verify({ed25519ctx, PublicKey, <<"protocol/v1">>}, Signature, <<"changed">>).

Ed25519ph

Keypair = libdecaf:eddsa_keypair(ed25519, binary:copy(<<7>>, 32)),
PublicKey = libdecaf:eddsa_keypair_extract_public_key(Keypair),
% <<234,74,108,99,226,156,82,10,190,245,80,123,19,46,197,249,149,71,118,174,190,190,123,146,66,30,234,105,20,70,210,44>>
Signature = libdecaf:eddsa_sign({ed25519ph, Keypair, <<"protocol/v1">>}, <<"message">>),
% <<157,150,110,134,199,68,181,74,177,61,158,106,116,106,125,94,151,139,0,117,50,87,76,207,255,11,67,26,52,247,231,112,207,195,232,201,172,31,133,151,100,231,32,210,171,38,85,120,126,237,243,69,221,212,183,24,59,61,158,185,228,51,46,4>>
true = libdecaf:eddsa_verify({ed25519ph, PublicKey, <<"protocol/v1">>}, Signature, <<"message">>),
false = libdecaf:eddsa_verify({ed25519ph, PublicKey, <<"protocol/v1">>}, Signature, <<"changed">>).

The prehash variant also supports signing and verifying incremental input:

Keypair = libdecaf:eddsa_keypair(ed25519, binary:copy(<<7>>, 32)),
S0 = libdecaf:eddsa_sign_init({ed25519ph, Keypair, <<"protocol/v1">>}),
S1 = libdecaf:eddsa_sign_update(S0, <<"first chunk">>),
S2 = libdecaf:eddsa_sign_update(S1, [<<"second">>, <<" chunk">>]),
Signature = libdecaf:eddsa_sign_final(S2),
% <<115,150,11,153,74,88,35,246,186,51,173,182,139,255,182,152,148,126,242,101,155,163,143,113,255,56,156,63,189,189,29,164,209,136,178,113,245,38,85,149,116,133,200,85,71,21,57,39,241,226,17,156,177,107,176,159,18,240,48,82,60,78,33,13>>
V0 = libdecaf:eddsa_verify_init({ed25519ph, Keypair, <<"protocol/v1">>}, Signature),
V1 = libdecaf:eddsa_verify_update(V0, <<"first chunksecond chunk">>),
true = libdecaf:eddsa_verify_final(V1).

Ed448

Keypair = libdecaf:eddsa_keypair(ed448, binary:copy(<<7>>, 57)),
PublicKey = libdecaf:eddsa_keypair_extract_public_key(Keypair),
% <<173,6,146,34,59,218,219,38,130,119,188,230,125,31,232,101,85,172,241,83,82,117,240,113,38,245,211,72,93,171,77,106,240,183,172,151,108,109,141,96,160,211,134,118,186,25,150,154,171,214,167,147,169,43,157,116,0>>
Signature = libdecaf:eddsa_sign({ed448, Keypair, <<"protocol/v1">>}, <<"message">>),
% <<133,11,22,150,207,65,162,191,170,97,156,165,224,22,137,61,50,206,91,142,249,53,47,64,161,68,73,141,212,64,147,174,19,210,11,90,52,180,174,79,188,4,156,16,142,183,206,56,91,135,58,175,90,234,160,142,128,214,158,231,68,81,202,85,21,22,165,197,199,1,87,105,108,28,242,106,250,118,254,77,186,232,204,200,140,22,8,170,66,149,78,149,110,186,78,31,218,82,87,149,139,173,227,61,92,5,47,140,254,93,62,195,9,0>>
true = libdecaf:eddsa_verify({ed448, PublicKey, <<"protocol/v1">>}, Signature, <<"message">>),
false = libdecaf:eddsa_verify({ed448, PublicKey, <<"protocol/v1">>}, Signature, <<"changed">>).

Ed448ph

Keypair = libdecaf:eddsa_keypair(ed448, binary:copy(<<7>>, 57)),
PublicKey = libdecaf:eddsa_keypair_extract_public_key(Keypair),
% <<173,6,146,34,59,218,219,38,130,119,188,230,125,31,232,101,85,172,241,83,82,117,240,113,38,245,211,72,93,171,77,106,240,183,172,151,108,109,141,96,160,211,134,118,186,25,150,154,171,214,167,147,169,43,157,116,0>>
Signature = libdecaf:eddsa_sign({ed448ph, Keypair, <<"protocol/v1">>}, <<"message">>),
% <<189,92,163,178,112,125,176,192,96,108,171,158,29,58,174,161,180,90,140,95,3,234,240,0,245,25,91,233,208,63,82,96,35,6,196,245,172,119,251,202,39,103,103,243,207,29,137,158,187,122,203,110,11,82,100,140,128,225,157,73,131,134,88,155,168,18,37,110,25,61,70,151,177,10,125,108,92,3,131,193,94,66,166,189,99,57,43,31,149,178,151,163,93,52,199,89,69,218,219,108,7,245,40,54,3,140,4,184,215,33,217,167,44,0>>
true = libdecaf:eddsa_verify({ed448ph, PublicKey, <<"protocol/v1">>}, Signature, <<"message">>),
false = libdecaf:eddsa_verify({ed448ph, PublicKey, <<"protocol/v1">>}, Signature, <<"changed">>).

The prehash variant also supports signing and verifying incremental input:

Keypair = libdecaf:eddsa_keypair(ed448, binary:copy(<<7>>, 57)),
S0 = libdecaf:eddsa_sign_init({ed448ph, Keypair, <<"protocol/v1">>}),
S1 = libdecaf:eddsa_sign_update(S0, <<"first chunk">>),
S2 = libdecaf:eddsa_sign_update(S1, [<<"second">>, <<" chunk">>]),
Signature = libdecaf:eddsa_sign_final(S2),
% <<37,113,72,86,1,179,62,198,43,216,196,67,110,88,203,172,123,145,193,81,164,103,120,228,215,141,74,142,217,164,142,254,8,74,252,200,138,137,35,100,18,251,58,217,46,211,36,57,3,93,79,165,17,30,227,251,0,253,96,185,241,18,222,89,171,13,252,228,19,1,67,242,233,175,175,59,171,127,163,80,131,179,200,95,82,197,86,130,161,66,242,177,32,254,155,158,86,9,98,254,18,123,22,186,205,92,95,35,20,72,127,29,51,0>>
V0 = libdecaf:eddsa_verify_init({ed448ph, Keypair, <<"protocol/v1">>}, Signature),
V1 = libdecaf:eddsa_verify_update(V0, <<"first chunksecond chunk">>),
true = libdecaf:eddsa_verify_final(V1).

SHA-2

SHA2-512

Digest = libdecaf:hash(sha2_512, <<"test">>),
<<"EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(sha2_512),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

SHA-3

SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128, and SHAKE256 follow FIPS 202.

SHA3-224

Digest = libdecaf:hash(sha3_224, <<"test">>),
<<"3797BF0AFBBFCA4A7BBBA7602A2B552746876517A7F9B7CE2DB0AE7B">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(sha3_224),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

SHA3-256

Digest = libdecaf:hash(sha3_256, <<"test">>),
<<"36F028580BB02CC8272A9A020F4200E346E276AE664E45EE80745574E2F5AB80">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(sha3_256),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

SHA3-384

Digest = libdecaf:hash(sha3_384, <<"test">>),
<<"E516DABB23B6E30026863543282780A3AE0DCCF05551CF0295178D7FF0F1B41EECB9DB3FF219007C4E097260D58621BD">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(sha3_384),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

SHA3-512

Digest = libdecaf:hash(sha3_512, <<"test">>),
<<"9ECE086E9BAC491FAC5C1D1046CA11D737B92A2B2EBD93F005D7B710110C0A678288166E7FBE796883A4F2E9B3CA9F484F521D0CE464345CC1AEC96779149C14">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(sha3_512),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

SHAKE128

Digest = libdecaf:hash({shake128, 32}, <<"test">>),
<<"D3B0AA9CD8B7255622CEBC631E867D4093D6F6010191A53973C45FEC9B07C774">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({shake128, 32}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init(shake128),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof(shake128, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

SHAKE256

Digest = libdecaf:hash({shake256, 64}, <<"test">>),
<<"B54FF7255705A71EE2925E4A3E30E41AED489A579D5595E0DF13E32E1E4DD202A7C7F68B31D6418D9845EB4D757ADDA6AB189E1BB340DB818E5B3BC725D992FA">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({shake256, 64}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init(shake256),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof(shake256, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

SHA-3 Derived Functions

cSHAKE128, cSHAKE256, KMAC128, KMAC256, KMACXOF128, KMACXOF256, TupleHash128, TupleHash256, TupleHashXOF128, TupleHashXOF256, ParallelHash128, ParallelHash256, ParallelHashXOF128, and ParallelHashXOF256 follow SP 800-185r1. Streaming XOFs use xof_init/1, xof_absorb/2, and xof_squeeze/2. The implementation is checked against the published NIST sample vectors.

cSHAKE128

Digest = libdecaf:hash({cshake128, 32, <<>>, <<"orders/v1">>}, <<"test">>),
<<"7FD68754245F98054D1FCC9D15797F436B10593FBEE936EC258D93DA9B1D4819">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({cshake128, 32, <<>>, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init({cshake128, <<>>, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({cshake128, <<>>, <<"orders/v1">>}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

cSHAKE256

Digest = libdecaf:hash({cshake256, 64, <<>>, <<"orders/v1">>}, <<"test">>),
<<"05DDED5B90731349BB2FDE03017EDDD79C4DF2AC2916D4C8ABE20F8497F5526AE6DDC3E77A7386C0BAEEE74A6B77BF7690592DA605BE60697152BE05AFED4EEF">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({cshake256, 64, <<>>, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init({cshake256, <<>>, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({cshake256, <<>>, <<"orders/v1">>}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

KMAC128

Key = binary:copy(<<42>>, 32),
Digest = libdecaf:hash({kmac128, 32, Key, <<"orders/v1">>}, <<"test">>),
<<"BD253D5934FA049BCDEE23BE38C0F8140ACAB58871AA66743311474F0EE8F295">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({kmac128, 32, Key, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

KMAC256

Key = binary:copy(<<42>>, 32),
Digest = libdecaf:hash({kmac256, 64, Key, <<"orders/v1">>}, <<"test">>),
<<"36900A040B41078C245289A47DE36BFF4D0648B4E9CD07CF7A03D7FFDB2F14500C3C78904C7E38ED10614B728C6E26832A77E7F737C0A5EBA2F3C821261D6EF2">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({kmac256, 64, Key, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

KMACXOF128

Key = binary:copy(<<42>>, 32),
Output = libdecaf:xof({kmacxof128, Key, <<"orders/v1">>}, <<"test">>, 32),
<<"19758F994FC86E5AC532D9013E06A5612250443EC92C84A1892EE36B5957E0AD">> = binary:encode_hex(Output),
X0 = libdecaf:xof_init({kmacxof128, Key, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"te">>),
X2 = libdecaf:xof_absorb(X1, <<"st">>),
{X3, First} = libdecaf:xof_squeeze(X2, 16),
{_X4, Second} = libdecaf:xof_squeeze(X3, 16),
Output = <<First/binary, Second/binary>>.

KMACXOF256

Key = binary:copy(<<42>>, 32),
Output = libdecaf:xof({kmacxof256, Key, <<"orders/v1">>}, <<"test">>, 64),
<<"2AD69ED92D86F34398BC9855C288730DCC7A41B0B6F2C3A761505F6BA73B6432820160D1FC7FEDBE6B763BAEE08D47E70D147F28A686E032AFC15DE65C8A7B16">> = binary:encode_hex(Output),
X0 = libdecaf:xof_init({kmacxof256, Key, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"te">>),
X2 = libdecaf:xof_absorb(X1, <<"st">>),
{X3, First} = libdecaf:xof_squeeze(X2, 32),
{_X4, Second} = libdecaf:xof_squeeze(X3, 32),
Output = <<First/binary, Second/binary>>.

ParallelHash128

The block size is 8192 bytes in this example. It is part of the algorithm and affects the result.

Digest = libdecaf:hash({parallelhash128, 32, 8192, <<"orders/v1">>}, <<"test">>),
<<"DEA7EA1B668B74CF973B29B8A30623A9954D9A4C865C8D7CD8B5E428A06685E8">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({parallelhash128, 32, 8192, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

ParallelHash256

The block size is 8192 bytes in this example. It is part of the algorithm and affects the result.

Digest = libdecaf:hash({parallelhash256, 64, 8192, <<"orders/v1">>}, <<"test">>),
<<"1D46D1679966B7E657AB030C99BA01CCAE1AEC7F546DD99412A70291783AB57B7180022C21DF13F8E5E02DB1B6C58EA003648E667BA2F3282866843AE268DE20">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({parallelhash256, 64, 8192, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

ParallelHashXOF128

The block size is 8192 bytes in this example. It is part of the algorithm and affects the result.

Output = libdecaf:xof({parallelhashxof128, 8192, <<"orders/v1">>}, <<"test">>, 32),
<<"B422C86E30394BB84A36D35D89FEBDACFBCC82ECE70D33FA67F672D643136422">> = binary:encode_hex(Output),
X0 = libdecaf:xof_init({parallelhashxof128, 8192, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"te">>),
X2 = libdecaf:xof_absorb(X1, <<"st">>),
{X3, First} = libdecaf:xof_squeeze(X2, 16),
{_X4, Second} = libdecaf:xof_squeeze(X3, 16),
Output = <<First/binary, Second/binary>>.

ParallelHashXOF256

The block size is 8192 bytes in this example. It is part of the algorithm and affects the result.

Output = libdecaf:xof({parallelhashxof256, 8192, <<"orders/v1">>}, <<"test">>, 64),
<<"C2D467DE5A74F446A4BC2BC634D10DEC31477F5C65174D20CA1F0607C2AD75724D0C7B52D008886799C78860DB65438E41E05CBF9609DDD2A3752CF43D3A830B">> = binary:encode_hex(Output),
X0 = libdecaf:xof_init({parallelhashxof256, 8192, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"te">>),
X2 = libdecaf:xof_absorb(X1, <<"st">>),
{X3, First} = libdecaf:xof_squeeze(X2, 32),
{_X4, Second} = libdecaf:xof_squeeze(X3, 32),
Output = <<First/binary, Second/binary>>.

TupleHash128

Each update or absorb call adds one complete tuple element; element boundaries affect the result.

Digest = libdecaf:hash({tuplehash128, 32, <<"orders/v1">>}, [<<"alice">>, <<"invoice-42">>]),
<<"C8AD94695C71CBAF85D0851A3462823E6543C45331D1E00FB1624D5658EEA280">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({tuplehash128, 32, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"alice">>),
H2 = libdecaf:hash_update(H1, <<"invoice-42">>),
Digest = libdecaf:hash_final(H2).

TupleHash256

Each update or absorb call adds one complete tuple element; element boundaries affect the result.

Digest = libdecaf:hash({tuplehash256, 64, <<"orders/v1">>}, [<<"alice">>, <<"invoice-42">>]),
<<"763211356624B0944EE1363B45F3DEB4B144AC764D2D6491B894EEAA2050021502880F1A7E15BC4CF16C872DFA7012ABCD1C16EEA7EF836D544E9A18019CA512">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({tuplehash256, 64, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"alice">>),
H2 = libdecaf:hash_update(H1, <<"invoice-42">>),
Digest = libdecaf:hash_final(H2).

TupleHashXOF128

Each update or absorb call adds one complete tuple element; element boundaries affect the result.

Output = libdecaf:xof({tuplehashxof128, <<"orders/v1">>}, [<<"alice">>, <<"invoice-42">>], 32),
<<"B081749DA3F2AB1590D581662444200F81FAB67687524C231CB3BD8A9D267447">> = binary:encode_hex(Output),
X0 = libdecaf:xof_init({tuplehashxof128, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"alice">>),
X2 = libdecaf:xof_absorb(X1, <<"invoice-42">>),
{X3, First} = libdecaf:xof_squeeze(X2, 16),
{_X4, Second} = libdecaf:xof_squeeze(X3, 16),
Output = <<First/binary, Second/binary>>.

TupleHashXOF256

Each update or absorb call adds one complete tuple element; element boundaries affect the result.

Output = libdecaf:xof({tuplehashxof256, <<"orders/v1">>}, [<<"alice">>, <<"invoice-42">>], 64),
<<"93CA795525F3ECB05A3E2FB33B7DE3160650BAD31C9458F24281E01DFF80A5147D2F5AB2EC041FC9014973C8213FB84A417F0441E4DAD0E14332E42136D04E3D">> = binary:encode_hex(Output),
X0 = libdecaf:xof_init({tuplehashxof256, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"alice">>),
X2 = libdecaf:xof_absorb(X1, <<"invoice-42">>),
{X3, First} = libdecaf:xof_squeeze(X2, 32),
{_X4, Second} = libdecaf:xof_squeeze(X3, 32),
Output = <<First/binary, Second/binary>>.

Keccak

The original Keccak and Keccak-XOF functions use their original domain separation; their output differs from SHA-3 and SHAKE.

Keccak-224

Digest = libdecaf:hash(keccak_224, <<"test">>),
<<"3BE30A9FF64F34A5861116C5198987AD780165F8366E67AFF4760B5E">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(keccak_224),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

Keccak-256

Digest = libdecaf:hash(keccak_256, <<"test">>),
<<"9C22FF5F21F0B81B113E63F7DB6DA94FEDEF11B2119B4088B89664FB9A3CB658">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(keccak_256),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

Keccak-384

Digest = libdecaf:hash(keccak_384, <<"test">>),
<<"53D0BA137307D4C2F9B6674C83EDBD58B70C0F4340133ED0ADC6FBA1D2478A6A03B7788229E775D2DE8AE8C0759D0527">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(keccak_384),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

Keccak-512

Digest = libdecaf:hash(keccak_512, <<"test">>),
<<"1E2E9FC2002B002D75198B7503210C05A1BAAC4560916A3C6D93BCCE3A50D7F00FD395BF1647B9ABB8D1AFCC9C76C289B0C9383BA386A956DA4B38934417789E">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init(keccak_512),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

Keccak-XOF-128

Digest = libdecaf:hash({keccakxof_128, 32}, <<"test">>),
<<"EB016C78385007273FE08D8F8C4859EB4B2BE21A30BACB99A9E1C2A3E5694083">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({keccakxof_128, 32}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init(keccakxof_128),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof(keccakxof_128, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

Keccak-XOF-256

Digest = libdecaf:hash({keccakxof_256, 64}, <<"test">>),
<<"9C22FF5F21F0B81B113E63F7DB6DA94FEDEF11B2119B4088B89664FB9A3CB65836383B2AA62B92467212DC730ADD6ABE274F5EFE209970816EDE3759E8770E2C">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({keccakxof_256, 64}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init(keccakxof_256),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof(keccakxof_256, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

SpongeRNG

For automatically seeded random bytes:

Bytes = libdecaf:strong_rand_bytes(32),
32 = byte_size(Bytes).

For an explicit deterministic stream, initialize from a seed buffer. Reusing a state repeats its output; keep the returned state to advance:

S0 = libdecaf_spongerng:init_from_buffer(<<"example seed">>, true),
{S1, First} = libdecaf_spongerng:next(S0, 16),
{_, First} = libdecaf_spongerng:next(S0, 16),
{_, Second} = libdecaf_spongerng:next(S1, 16),
true = First =/= Second.

Initialize from a seed file with a byte limit:

Seed = <<"example seed file">>,
ok = file:write_file("libdecaf-example-seed.bin", Seed),
S0 = libdecaf_spongerng:init_from_file("libdecaf-example-seed.bin", byte_size(Seed), true),
{_S1, Bytes} = libdecaf_spongerng:next(S0, 16),
16 = byte_size(Bytes),
ok = file:delete("libdecaf-example-seed.bin").

Initialize a nondeterministic stream from the operating system's random device:

S0 = libdecaf_spongerng:init_from_dev_urandom(),
{_S1, Bytes} = libdecaf_spongerng:next(S0, 16),
16 = byte_size(Bytes).

Mix additional seed data into a new state with stir/2:

S0 = libdecaf_spongerng:init_from_buffer(<<"example seed">>, true),
S1 = libdecaf_spongerng:stir(S0, <<"additional seed">>),
{_, Before} = libdecaf_spongerng:next(S0, 16),
{_, After} = libdecaf_spongerng:next(S1, 16),
true = Before =/= After.

TUAK

libdecaf_tuak provides the 3GPP TUAK authentication and key-generation functions: topc/2,3, f1/5,6, f1_star/5,6, f2/3,4, f3/3,4, f4/3,4, f5/3,4, f5_star/3,4, and the combined f2345/3,4. Matching tuak_* functions are available directly from libdecaf_nif.

Inputs are fixed-size binaries in the published test-vector byte order. Subscriber keys contain 16 or 32 bytes, TOP/TOPc 32 bytes, RAND 16 bytes, SQN six bytes, and AMF two bytes. Derive TOPc once for a subscriber key and operator TOP, then reuse it with that key and the same iteration count.

Key = binary:copy(<<16#AB>>, 16),
TOP = binary:copy(<<16#55>>, 32),
RAND = binary:copy(<<16#42>>, 16),
SQN = binary:copy(<<16#11>>, 6),
TOPc = libdecaf_tuak:topc(Key, TOP),
MAC_A = libdecaf_tuak:f1(Key, TOPc, RAND, SQN, <<255, 255>>),
<<"F9A54E6AEAA8618D">> = binary:encode_hex(MAC_A),
{RES, CK, IK, AK} = libdecaf_tuak:f2345(Key, TOPc, RAND, #{res_length => 4}),
<<"657ACD64">> = binary:encode_hex(RES).

Optional maps select mac_length (8/16/32 bytes), res_length (4/8/16/32), ck_length (16/32), ik_length (16/32), and positive iterations. Each operation accepts its applicable options only. Defaults are MAC=8, RES=8, CK=16, IK=16, and one complete Keccak-f[1600] iteration.

The RES, CK, and IK lengths jointly select the f2–f5 computation. Use matching options for individual calls, or obtain all four results together with f2345. The implementation is tested against all TUAK sets in the Release 19 implementers' data and conformance data.

KangarooTwelve

TurboSHAKE128, TurboSHAKE256, KT128, KT256, HopMAC128, and HopMAC256 follow RFC 9861. KT128 is also known as KangarooTwelve. KT128, KT256, HopMAC128, and HopMAC256 support the parallel APIs for large messages.

HopMAC128

Key = binary:copy(<<42>>, 32),
Digest = libdecaf:hash({hopmac128, 32, Key, <<"orders/v1">>}, <<"test">>),
<<"5C592EBA4A7D60CEEA830C400E52B119F83F070E3E5B62066ADA016F966519F4">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({hopmac128, 32, Key, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

Key = binary:copy(<<42>>, 32),
X0 = libdecaf:xof_init({hopmac128, Key, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({hopmac128, Key, <<"orders/v1">>}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

HopMAC256

Key = binary:copy(<<42>>, 32),
Digest = libdecaf:hash({hopmac256, 64, Key, <<"orders/v1">>}, <<"test">>),
<<"54EF02A096DEA1AAB300FDD3D5A82D19A19B56F344AEAD9FE980782DB146D3218F6CCA1AF64537322D5982FBD664D053BDAE1E8CCF757A1B565CE79F7632E2EC">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({hopmac256, 64, Key, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

Key = binary:copy(<<42>>, 32),
X0 = libdecaf:xof_init({hopmac256, Key, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({hopmac256, Key, <<"orders/v1">>}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

KT128

Digest = libdecaf:hash({kt128, 32, <<"orders/v1">>}, <<"test">>),
<<"2ED388506219872F7A4DA11AB0446E381FE2C6144D0454417B49D7F202540991">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({kt128, 32, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init({kt128, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({kt128, <<"orders/v1">>}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

KT256

Digest = libdecaf:hash({kt256, 64, <<"orders/v1">>}, <<"test">>),
<<"34F86E13C61E23D0F6E2B6CDDD936930E2EFC3F9AACF306EA195F4987E101079CA69F76640B0D274DE7F13FD7FA2DA79A6C4D9E778B30AC792D070E33D2686DB">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({kt256, 64, <<"orders/v1">>}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init({kt256, <<"orders/v1">>}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({kt256, <<"orders/v1">>}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

TurboSHAKE128

Digest = libdecaf:hash({turboshake128, 32, 16#1F}, <<"test">>),
<<"7CA05832EFD738ED36053404AD75409D51403048626A3B6611D31D6E91F2DA96">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({turboshake128, 32, 16#1F}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init({turboshake128, 16#1F}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({turboshake128, 16#1F}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

TurboSHAKE256

Digest = libdecaf:hash({turboshake256, 64, 16#1F}, <<"test">>),
<<"738E222629C3C14B893B81EA371AE72711F66FD59A9CCC152A9DE7659A35862AC8A863633D4962B1711B62491E4B28910CE3BAE16252FCE8B1AC2482A12861A6">> = binary:encode_hex(Digest),
H0 = libdecaf:hash_init({turboshake256, 64, 16#1F}),
H1 = libdecaf:hash_update(H0, <<"te">>),
H2 = libdecaf:hash_update(H1, <<"st">>),
Digest = libdecaf:hash_final(H2).

For incremental XOF output:

X0 = libdecaf:xof_init({turboshake256, 16#1F}),
X1 = libdecaf:xof_absorb(X0, <<"test">>),
{X2, First} = libdecaf:xof_squeeze(X1, 16),
{_X3, Second} = libdecaf:xof_squeeze(X2, 16),
Whole = libdecaf:xof({turboshake256, 16#1F}, <<"test">>, 32),
Whole = <<First/binary, Second/binary>>.

Prime-order groups

Curve25519

Ristretto255

libdecaf_ristretto255 and libdecaf_decaf448 expose the prime-order groups defined in RFC 9496. Points and scalars use canonical little-endian binary encodings: 32 bytes for Ristretto255 and 56 bytes for Decaf448. Identity points and zero scalars are accepted.

Group = libdecaf_ristretto255,
Scalar = Group:scalar_random(),
Point = Group:base_scalarmul(Scalar),
true = Group:point_is_valid(Point),
Identity = Group:identity(),
Identity = Group:point_add(Point, Group:point_negate(Point)),
Table = Group:precompute(Point),
Twice = Group:point_double(Point),
Twice = Group:precomputed_scalarmul(Table, Group:scalar_reduce(<<2>>)).

Curve448

Decaf448
Group = libdecaf_decaf448,
Scalar = Group:scalar_random(),
Point = Group:base_scalarmul(Scalar),
true = Group:point_is_valid(Point),
Identity = Group:identity(),
Identity = Group:point_add(Point, Group:point_negate(Point)),
Table = Group:precompute(Point),
Twice = Group:point_double(Point),
Twice = Group:precomputed_scalarmul(Table, Group:scalar_reduce(<<2>>)).

Both modules provide the same operations:

scalar_reduce/1 reduces an arbitrary-length little-endian integer modulo the group order. Other scalar operations require canonical inputs; invalid points, scalars, and resource types raise badarg. The validation functions return false for invalid inputs. Inverting the zero scalar returns error.

point_from_hash/1 implements RFC 9496 element derivation from uniformly distributed bytes: 64 bytes for Ristretto255 or 112 bytes for Decaf448. Hash or expand messages to the required length before calling this function. point_from_hash_nonuniform/1 exposes the underlying single mapping with a 32-byte or 56-byte input; its distribution differs from the uniform mapping. Call the group modules directly, for example libdecaf_ristretto255:point_add/2 or libdecaf_decaf448:point_add/2.

License

MIT. Vendored dependencies retain their own license notices.