libdecaf
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
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:
identity/0,base/0,point_is_valid/1, andpoint_eq/2.point_add/2,point_sub/2,point_negate/1, andpoint_double/1.point_scalarmul(Point, Scalar),base_scalarmul(Scalar), andpoint_double_scalarmul(Point1, Scalar1, Point2, Scalar2).precompute(Point)andprecomputed_scalarmul(Table, Scalar). Tables are immutable resources and can be shared between processes.scalar_random/0,scalar_is_canonical/1,scalar_reduce/1,scalar_add/2,scalar_sub/2,scalar_mul/2,scalar_negate/1,scalar_halve/1, andscalar_invert/1.
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.