Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions lib/std/hash/siphash.c3
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) 2025 Zack Puhl <[email protected]>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
//
// SipHash is a secure pseudorandom function (PRF) which digests a 128-bit key
// and a variable-length message to produce a 64- or 128-bit hash value.
//
// SipHash can be employed in numerous useful ways and structures, e.g.:
// - Hash Tables
// - Message Authentication Codes
// - Denial of Service (hash flooding) resistance
// - Bloom filters
// - Keyed runtime identifier derivation
//
// Read more: https://en.wikipedia.org/wiki/SipHash
//
//

// COMMON HASH VARIANTS.
// These two forms of SipHash (24 and 48) are the most widely
// used by many implementations.

// These provide typical 64-bit hash results.
// -- Best for performance-critical applications.
module std::hash::siphash24;
import std::hash::siphash;
alias SipHash24 = SipHash { ulong, 2, 4 };
alias hash = siphash::hash { ulong, 2, 4 };

// -- Best for conservative security applications.
module std::hash::siphash48;
import std::hash::siphash;
alias SipHash48 = SipHash { ulong, 4, 8 };
alias hash = siphash::hash { ulong, 4, 8 };


// Exact same as above, but for 128-bit outputs. Algorithm internally changes slightly.
module std::hash::siphash24_128;
import std::hash::siphash;
alias SipHash24_128 = SipHash { uint128, 2, 4 };
alias hash = siphash::hash { uint128, 2, 4 };

module std::hash::siphash48_128;
import std::hash::siphash;
alias SipHash48_128 = SipHash { uint128, 4, 8 };
alias hash = siphash::hash { uint128, 4, 8 };

<*
@require OutType.typeid == uint128.typeid || OutType.typeid == ulong.typeid : "Module OutType must be either uint128 or ulong."
*>
module std::hash::siphash { OutType, BLOCK_ROUNDS, FINALIZE_ROUNDS };


struct SipHash
{
ulong[4] v;
long m;
int m_idx;
usz len;
}

fn OutType hash(char[] data, uint128 key)
{
SipHash s;
s.init(key);
s.update(data);
return s.final();
}

fn void SipHash.init(&self, uint128 key)
{
ulong[2] key_64 = bitcast(key, ulong[2]);

self.v = {
0x736f_6d65_7073_6575 ^ key_64[0],
0x646f_7261_6e64_6f6d ^ key_64[1],
0x6c79_6765_6e65_7261 ^ key_64[0],
0x7465_6462_7974_6573 ^ key_64[1],
};

$if OutType.typeid == uint128.typeid :
self.v[1] ^= 0xEE;
$endif
}

<*
@param [in] data : "Bytes to hash"
*>
fn void SipHash.update(&self, char[] data)
{
self.len += data.len;

foreach (byte : data)
{
self.m |= (long)byte << (self.m_idx++ << 3);

if (self.m_idx < 8) continue;

// This runs every time the m_idx is 8, then it resets to 0.
self.v[3] ^= self.m;

$for var $i = 0; $i < BLOCK_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor

self.v[0] ^= self.m;

self.m_idx = 0;
self.m = 0;
}
}

fn OutType SipHash.final(&self)
{
char[8] last = { [7] = (char)self.len };

self.update(last[(self.m_idx < 7 ? self.m_idx : 7)..]);

$if OutType.typeid == uint128.typeid :
self.v[2] ^= 0xEE;
$else
self.v[2] ^= 0xFF;
$endif

$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor

$if OutType.typeid == ulong.typeid :
return self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];
$else
ulong lo = self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3];

self.v[1] ^= 0xDD;

$for var $i = 0; $i < FINALIZE_ROUNDS; ++$i : // unrolled loop
self.round();
$endfor

return lo | ((uint128)(self.v[0] ^ self.v[1] ^ self.v[2] ^ self.v[3]) << 64);
$endif
}


fn void SipHash.round(&self) @local
{
self.v[0] += self.v[1];
self.v[1] = self.v[1].rotl(13);
self.v[1] ^= self.v[0];
self.v[0] = self.v[0].rotl(32);
self.v[2] += self.v[3];
self.v[3] = self.v[3].rotl(16);
self.v[3] ^= self.v[2];
self.v[0] += self.v[3];
self.v[3] = self.v[3].rotl(21);
self.v[3] ^= self.v[0];
self.v[2] += self.v[1];
self.v[1] = self.v[1].rotl(17);
self.v[1] ^= self.v[2];
self.v[2] = self.v[2].rotl(32);
}



1 change: 1 addition & 0 deletions releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Formatting option "%h" now supports pointers.
- Improve error on unsigned implicit conversion to signed.
- Update error message for struct initialization #2286
- Add SipHash family of keyed PRFs. #2287
- `$is_const` is deprecated in favour of `@is_const` based on `$defined`.
- Multiline contract comments #2113
- Removed the use of temp allocator in backtrace printing.
Expand Down
215 changes: 215 additions & 0 deletions test/unit/stdlib/hash/siphash.c3
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// Copyright (c) 2025 Zack Puhl <[email protected]>. All rights reserved.
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::hash::siphash_tests;
import std::math;

char[64] plaintext = math::iota(char[64]);


module std::hash::siphash_tests @test;

import std::hash;

const uint128 DEFAULT_TEST_KEY = 0x0f0e_0d0c_0b0a_0908_0706_0504_0302_0100;

fn void sanity_check_default_test_key()
=> test::@check(((char*)&DEFAULT_TEST_KEY)[:16] == { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, "Bad key value as char[].");


fn void streamed_plaintext_64()
{
ulong expected = 0x958a324ceb064572;

SipHash24 s;

s.init(DEFAULT_TEST_KEY);

s.update(plaintext[:5]);
s.update(plaintext[5:25]);
s.update(plaintext[30..^2]); // because the end of the for-loop doesn't get to the full 64 chars, it's 63

ulong hashval = s.final();

test::@check(hashval == expected, "Expected streamed hash mismatch [%x expected // %x actual].", expected, hashval);
}


fn void streamed_plaintext_128()
{
uint128 expected = 0x7cbd3f979a063e504a83502f77d15051;

SipHash24_128 s;

s.init(DEFAULT_TEST_KEY);

s.update(plaintext[:1]);
s.update(plaintext[1:2]);
s.update(plaintext[3:12]);
s.update(plaintext[15..^2]); // because the end of the for-loop doesn't get to the full 64 chars, it's 63

uint128 hashval = s.final();

test::@check(hashval == expected, "Expected streamed hash mismatch [%x expected // %x actual].", expected, hashval);
}


// These siphash-2,4 vectors are from the great and wise sages themselves:
// https://github.com/veorq/SipHash/blob/master/vectors.h
const ulong[64] VECTORS_24 = {
0x726fdb47dd0e0e31, 0x74f839c593dc67fd, 0x0d6c8009d9a94f5a, 0x85676696d7fb7e2d,
0xcf2794e0277187b7, 0x18765564cd99a68d, 0xcbc9466e58fee3ce, 0xab0200f58b01d137,
0x93f5f5799a932462, 0x9e0082df0ba9e4b0, 0x7a5dbbc594ddb9f3, 0xf4b32f46226bada7,
0x751e8fbc860ee5fb, 0x14ea5627c0843d90, 0xf723ca908e7af2ee, 0xa129ca6149be45e5,
0x3f2acc7f57c29bdb, 0x699ae9f52cbe4794, 0x4bc1b3f0968dd39c, 0xbb6dc91da77961bd,
0xbed65cf21aa2ee98, 0xd0f2cbb02e3b67c7, 0x93536795e3a33e88, 0xa80c038ccd5ccec8,
0xb8ad50c6f649af94, 0xbce192de8a85b8ea, 0x17d835b85bbb15f3, 0x2f2e6163076bcfad,
0xde4daaaca71dc9a5, 0xa6a2506687956571, 0xad87a3535c49ef28, 0x32d892fad841c342,
0x7127512f72f27cce, 0xa7f32346f95978e3, 0x12e0b01abb051238, 0x15e034d40fa197ae,
0x314dffbe0815a3b4, 0x027990f029623981, 0xcadcd4e59ef40c4d, 0x9abfd8766a33735c,
0x0e3ea96b5304a7d0, 0xad0c42d6fc585992, 0x187306c89bc215a9, 0xd4a60abcf3792b95,
0xf935451de4f21df2, 0xa9538f0419755787, 0xdb9acddff56ca510, 0xd06c98cd5c0975eb,
0xe612a3cb9ecba951, 0xc766e62cfcadaf96, 0xee64435a9752fe72, 0xa192d576b245165a,
0x0a8787bf8ecb74b2, 0x81b3e73d20b49b6f, 0x7fa8220ba3b2ecea, 0x245731c13ca42499,
0xb78dbfaf3a8d83bd, 0xea1ad565322a1a0b, 0x60e61c23a3795013, 0x6606d7e446282b93,
0x6ca4ecb15c5f91e1, 0x9f626da15c9625f3, 0xe51b38608ef25f57, 0x958a324ceb064572,
};

fn void siphash24_vectors()
{
for (usz i = 0; i < VECTORS_24.len; ++i)
{
ulong hashval = siphash24::hash(plaintext[:i], DEFAULT_TEST_KEY);

test::@check(hashval == VECTORS_24[i], "Expected hash mismatch [%x expected // %x actual].", VECTORS_24[i], hashval);
}
}


const ulong[64] VECTORS_48 = {
0xc879052b9938da41, 0xc85914f95295b851, 0x33c3ddbef0163792, 0x05c147657dd4466a,
0x48fac14a2b5938c2, 0xe14752cfd9d7c2f6, 0x8e5535c834bcb66b, 0x4efdbe5a713fd747,
0x50db2f079c8bb520, 0x5312e15ef39a3136, 0x8f848d0adbd0a948, 0x810a0436603969cc,
0x6197a77a53686d4b, 0x6950c9f2e9963729, 0x689a62a7ea1b4388, 0x83d389d57da9a6e0,
0x70acb28053f59c55, 0x4e79e37a11c5b7d5, 0x2b10ad3446453c5a, 0xbc3d5aa3af80a4c0,
0xc84b28e50927c278, 0x9dd6eb0d467026ef, 0xd884d0a986ef76d9, 0xe8d0ea191881d9e3,
0x16ecea3eb53c3389, 0xc64973645f6c1531, 0xa432763535ce4ca5, 0xfed2a7c025895d06,
0x8b3a1a2282aabb2b, 0x707b0964cefb0b87, 0x8bee9564f9e0d840, 0x12dffa0bf4a7fc79,
0xd29e762ff2fb0b00, 0xfa22e5f891556840, 0x0d9d14d874fee62b, 0xed60750b0e2f7eba,
0x97e1a7ed84e3e902, 0xb6632795620ae8c4, 0xd36d5c5dc6ed2783, 0xc02fa464d164fc79,
0x4e61fccb11754a15, 0x6fe6a0ec7c8d148b, 0xfa03c454b669eedf, 0xc9b77b69a6368fc5,
0x2131c6059cbec5a6, 0x3189cdfb59878ab5, 0x25c4cc04673a68d7, 0x8d44a2e5e1e66acb,
0x73513a3a5b69266e, 0x4aac339fcf077178, 0x84747bd9da907516, 0x06f36bf01e686b00,
0xa6cfef6602309b1c, 0x4bb3b0d1882f8d28, 0xfe6bf5acbd0611e0, 0x28036e5b0e1f10c0,
0x0a1c1b5b4591a7c3, 0x0f3a0b9ee1af0757, 0x4f5953fe29725ae6, 0x4caf1aabb99d2f00,
0x0606c14450cb2859, 0x2173857b960138d5, 0xcc99091a4f36db05, 0x23de0355bc8477e6,
};

fn void siphash48_vectors()
{
for (usz i = 0; i < VECTORS_48.len; ++i)
{
ulong hashval = siphash48::hash(plaintext[:i], DEFAULT_TEST_KEY);

test::@check(hashval == VECTORS_48[i], "Expected hash mismatch [%x expected // %x actual].", VECTORS_48[i], hashval);
}
}


// Yet again, these test vectors are prized heirloom values handed down through the generations:
// https://github.com/veorq/SipHash/blob/master/vectors.h
const uint128[64] VECTORS_24_128 = {
0x930255c71472f66de6a825ba047f81a3, 0x45fc229b1159763444af996bd8c187da, 0xe4ff0af6de8ba3fcc75da4a48d227781, 0x51ed8529b0b6335f4ea967520cb6709c,
0x7955cd7b7c6e0f7daf8f9c2dc16481f8, 0x27960e69077a5254886f778059876813, 0x5ea1d78f30a05e481386208b33caee14, 0x3982f01fa64ab8c053c1dbd8beebf1a1,
0xb49714f364e2830f61f55862baa9623b, 0xed716dbb028b7fc4abbad90a06994426, 0xbafbd0f3d34754c956691478c30d1100, 0x18dce5816fdcb4a277666b3868c55101,
0x25c13285f64d638258f35e9066b226d6, 0xf752b9c44f9329d0108bc0e947e26998, 0x024949e45f48c77e9cded766aceffc31, 0xd9c3cf970fec087e11a8b03399e99354,
0x77052385bf1533fdbb54b067caa4e26e, 0x4077e47ac466c05498b88d73e8063d47, 0x23f7aefe81a44d298548bf23e4e526a4, 0xb12e51528920d574b0fa65cf31770178,
0xeb3938e8a544933e7390223f83fc259e, 0x121d073ecd14228a215a52be5a498e56, 0xae0aff8e52109c469a6bd15245b5294a, 0x1c69bf9a9ae28ccfe0f5a9d5dd84d1c9,
0xad32618a178a2a88d850bd78ae79b42d, 0x6f8f8dcbeab951507b445e2d045fce8e, 0x661f147886e0ae7ee807c3b3b4530b9c, 0x94eb9e122febd3bfe4eaa669af48f2ab,
0xf4ae587302f335b9884b576816da6406, 0xb76a7c463cfdd40ce97d33bfc49d4baa, 0x87226d68d4d71a2bde6baf1f477f5cea, 0x353dc4524fde2317fcfa233218b03929,
0x68eb4665559d3e363efcea5eca56397c, 0xcfffa94e5f9db6b6321cf0467107c677, 0xde549b30f1f02509df7e84b86c98a637, 0xc88c3c922e1a2407f9a8a99de6f005a7,
0x11674f90ed769e1e4648c4291f7dc43d, 0x2b69d3c551473c0d1a0efce601bf620d, 0xb5e7be4b085efde49e667cca8b46038c, 0xd92bd2d0e5cc73449c2caf3bb95b8a52,
0xd83b91c6c80cae97ad5dc9951e306adf, 0xdbb6705e289135e7397f852c90891180, 0x5b0ccacc34ae5036bb31c2c96a3417e6, 0x89df5aecdc211840aa21b7ef3734d927,
0x4273cc66b1c9b1d8785e9ced9d7d2389, 0x4cb150a294fa8911657d5ebf91806d4a, 0x022949cf3d0efc3f89aee75560f9330e, 0x1b1563dc4bd8c88ed1190b722b431ce6,
0x169b2608a6559037cf82f749f5aee5f7, 0x03641a20adf237a84fa5b7d00f038d43, 0x3f4286f2270d7e24e304bf4feed390a5, 0x38f5f9ae7cd35cb1c493fe72a1c1e25f,
0x7c013a8bd03d13b26eb306bd5c32972c, 0x9ed32a009f65f09f94ca6b7a2214c892, 0x871d91d64108d5fb8c32d80b1150e8dc, 0xda832592b52be3481279dac78449f167,
0x362a1da96f16947ee94ed572cff23819, 0x8e6904163024620ffe49ed46961e4874, 0x1d8a3d58d0386400d8d6a998dea5fc57, 0x595357d9743676d4be1cdcef1cdeec9f,
0x40e772d8cb73ca6653f128eb000c04e3, 0x7a0f6793591ca9ccfe1d836a9a009776, 0xbd5947f0a447d505a067f52123545358, 0x7cbd3f979a063e504a83502f77d15051,
};

fn void siphash24_128_vectors()
{
for (usz i = 0; i < VECTORS_24_128.len; ++i)
{
uint128 hashval = siphash24_128::hash(plaintext[:i], DEFAULT_TEST_KEY);

test::@check(hashval == VECTORS_24_128[i], "Expected hash mismatch [%x expected // %x actual].", VECTORS_24_128[i], hashval);
}
}


const uint128[64] VECTORS_48_128 = {
0x6c0aa78354e8eccfe904a96d58ce641f, 0x80a2c791a77cf26a47794cefa85d3447, 0xa42182185f817322c62dca96a35f49e1, 0x027605817fb69c5a834ec54a8473a2c7,
0xef6d651fe0c8952a4ece3ef4bb521f54, 0x5dbe12bf0c994f241548f30dd43b9717, 0xfff5e108c9567db1cd8032560d360b6b, 0xe0eed2ff548b6b72c2f14b183be100ed,
0x63e0caaf235a4a36f5edf98f1346d9a7, 0xe80bfbe449559a8ba3ec5c54b714739e, 0x7512b99a885be6ae68d18984c6626c58, 0x997a2e2f1e84abc447d1a34ca65271e6,
0xaf16a8ee83089e3e2ee58d90ea7a1c7f, 0x344aefb50d33353f9233b792bf7a82de, 0xf97d4c35e28e6737c59a370f64637559, 0xcb6290dcfaf7783d593a453a30034d28,
0x9cad4e8dabe5fdc0b7637f59a2c74a91, 0xcbc387ce953b453aee55a44ba415510d, 0x1479ce845a88378394d8f10b9d939b54, 0x6cb4728c5713fbd2eb8a34cd6997176c,
0x8b40862fee002a68e057c938c136d0aa, 0x263ad74eff4490eebf70b62fc4eeb121, 0xeb143f4617c9534637ed9729d6a19305, 0x4ee25786c6fff1a01ef9197762313d11,
0xd6c452c2f014730edd6ae02df74c39b3, 0xd28963cde46b45e241c3359dda982a92, 0x4d8db2cb50eadca24ab357f730626b59, 0x48ee596575df8406807e5bd597e44ec2,
0x778101dce49d2b5ed41e6836a1b69c5e, 0xb278819dfeed337904d35686ca39fabf, 0xa3ae34813a807a355a79d0a118942218, 0x6eb0771a57697b732e4e4753ff963e4a,
0x5094f1632f572f2e847237d0f9f0d5fe, 0x6f44bcc629660cc46342f9c186583339, 0x9fbf8b1997462c8bb01087b33bf9a5ee, 0x3ad46661061243370e724f70b6c76e80,
0xa2b24760aeeeaf9db439c9f09ded696e, 0x38d72f7f1730a294c7f9b698f27bc793, 0xe60ce64c2a3affda75a82a8cd99cadff, 0xe310f55776ef64cdcd934af9fd2f994d,
0x9bce1bbe96e086a11ea1e0244e627032, 0x77313409c4c7ff1f51ff4ecbe0bbe831, 0x5bab1670a0128a6407d99a87057de1cb, 0xfc0a6a36468bd2d5e28be97043d44888,
0x0c5e095465cfb50ca9761042b2d1ffb7, 0x0c05d11ffe84d8bbf6a823d56c666b6a, 0x24621330bd8cf105c8f5fb50838afea8, 0xb5627ed594963aebf23682ee7a11e7cc,
0x049ff6feba90306b0cb728fce4f0253a, 0x4add3144e4f806818bc49f7426e6053f, 0x5b3477dcfad5ff0a5c167276f9796876, 0x0e2fdda19484c2c98b596cb65aa07143,
0x6602e0d6efb6221fbaf1a5a2d35bf865, 0x1166af8a229f6acaef224be5da61cf76, 0xda84a49dab9053889fa2db9fe3c2dc6c, 0x5aaa2f7b61e2e4d8b2673bcceaaceee1,
0x6d5aabd52eb0d678170fe14c6f9fd20b, 0x07a6655b834587e07c26526a159f18ad, 0x8f211ce3996bec3dd466257271996b0f, 0x741a9e3d6f9cf3d366f43d4ffac8a4a1,
0x1df99e2da7d8a6c11fc2f08cb83d1a3b, 0xe52e698a5a63c3562800c0ef026848d1, 0x3c1c2cc2314956fd9919ae7c8f5fa1ee, 0xa5d46b90ee61792093dbc42863aef563,
};

fn void siphash48_128_vectors()
{
for (usz i = 0; i < VECTORS_48_128.len; ++i)
{
uint128 hashval = siphash48_128::hash(plaintext[:i], DEFAULT_TEST_KEY);

test::@check(hashval == VECTORS_48_128[i], "Expected hash mismatch [%x expected // %x actual].", VECTORS_48_128[i], hashval);
}
}


alias SecHash = SipHash { uint128, 256, 512 };
alias sec_hash = siphash::hash { uint128, 256, 512 };

fn void test_custom_sip()
{
uint128 key = 0x8a52f8467c2fafa9ab4d7396d6fe01a3;
char[] data =
"As you can see, the hash rounds are flexible and so is the return type, between a 64-bit and 128-bit key."
" Output values are simple, quick, and deterministic."
.tcopy();

uint128 expected = 0x47de68b601e964b343c29358f864cc40;
uint128 actual = sec_hash(data, key);

test::@check(actual == expected, "Expected hash mismatch [%x expected // %x actual].", expected, actual);

// ---------- Slight data change.
data[0] = 'a';
expected = 0x15fa9037e63089732d86ab5bab3938e1;
actual = sec_hash(data, key);

test::@check(actual == expected, "Expected hash mismatch [%x expected // %x actual].", expected, actual);

// ---------- Slight key change; streamed.
key -= 1;
expected = 0xceeaca813e87ad83dde110a402eac1ef;
SecHash s;
s.init(key);
s.update(data[:2]);
s.update(data[2..^2]);
s.update(data[^1..]);
actual = s.final();

test::@check(actual == expected, "Expected hash mismatch [%x expected // %x actual].", expected, actual);
}
Loading