SNAPKITTYWEST's picture
push from SNAPKITTYWEST/topological-quantum-computer
d6f21bb verified
Raw
History Blame Contribute Delete
9.05 kB
"""
SHA-520 Reference Implementation
Supports reduced-round SHA-520 variants for cryptanalysis.
Test vectors provided for 4, 8, 16, and 80-round variants.
"""
import struct
from typing import Tuple, Union
from abc import ABC, abstractmethod
from qlambda.arrays import (
MASK64,
SHA520_BLOCK_BYTES,
SHA520_DIGEST_BYTES,
SHA520_IV_520,
SHA520_K_80,
)
class SHA520:
"""SHA-520 Hash Function with Configurable Rounds.
Parameters
----------
rounds : int
Number of compression rounds (4, 8, 16, 80, etc.)
Attributes
----------
digest_size : int
Output size in bytes (65 for SHA-520)
block_size : int
Internal block size (128 bytes for SHA-1024-based design)
"""
K = list(SHA520_K_80)
IV = list(SHA520_IV_520)
def __init__(self, rounds: int = 80) -> None:
"""Initialize SHA-520 hasher.
Parameters
----------
rounds : int
Number of compression rounds (default 80)
"""
self.rounds = rounds
self.digest_size = SHA520_DIGEST_BYTES
self.block_size = SHA520_BLOCK_BYTES
self._buffer = b''
self._counter = 0
self._h = list(self.IV)
@staticmethod
def _rotr(x: int, n: int, width: int = 64) -> int:
"""Right rotate x by n bits within width."""
mask = (1 << width) - 1
return ((x >> n) | (x << (width - n))) & mask
@staticmethod
def _sigma0(x: int) -> int:
"""Lower sigma 0 function."""
return SHA520._rotr(x, 1) ^ SHA520._rotr(x, 8) ^ (x >> 7)
@staticmethod
def _sigma1(x: int) -> int:
"""Lower sigma 1 function."""
return SHA520._rotr(x, 19) ^ SHA520._rotr(x, 61) ^ (x >> 6)
@staticmethod
def _Sigma0(x: int) -> int:
"""Upper Sigma 0 function."""
return SHA520._rotr(x, 28) ^ SHA520._rotr(x, 34) ^ SHA520._rotr(x, 39)
@staticmethod
def _Sigma1(x: int) -> int:
"""Upper Sigma 1 function."""
return SHA520._rotr(x, 14) ^ SHA520._rotr(x, 18) ^ SHA520._rotr(x, 41)
@staticmethod
def _Ch(x: int, y: int, z: int) -> int:
"""Choice function."""
return (x & y) ^ (~x & z)
@staticmethod
def _Maj(x: int, y: int, z: int) -> int:
"""Majority function."""
return (x & y) ^ (x & z) ^ (y & z)
def _compress(self, block: bytes) -> None:
"""Compress a 1024-bit message block.
Parameters
----------
block : bytes
128-byte message block
"""
# Parse block into 16 64-bit words
w = list(struct.unpack('>16Q', block))
# Expand to 80 words
for i in range(16, min(80, self.rounds + 16)):
s0 = self._sigma0(w[i - 15])
s1 = self._sigma1(w[i - 2])
w.append((w[i - 16] + s0 + w[i - 7] + s1) & MASK64)
# Initialize working variables
a, b, c, d, e, f, g, h = self._h
# Compression function main loop
for i in range(self.rounds):
S1 = self._Sigma1(e)
ch = self._Ch(e, f, g)
temp1 = (h + S1 + ch + self.K[i] + w[i]) & MASK64
S0 = self._Sigma0(a)
maj = self._Maj(a, b, c)
temp2 = (S0 + maj) & MASK64
h = g
g = f
f = e
e = (d + temp1) & MASK64
d = c
c = b
b = a
a = (temp1 + temp2) & MASK64
# Add compressed chunk to current hash value
self._h[0] = (self._h[0] + a) & MASK64
self._h[1] = (self._h[1] + b) & MASK64
self._h[2] = (self._h[2] + c) & MASK64
self._h[3] = (self._h[3] + d) & MASK64
self._h[4] = (self._h[4] + e) & MASK64
self._h[5] = (self._h[5] + f) & MASK64
self._h[6] = (self._h[6] + g) & MASK64
self._h[7] = (self._h[7] + h) & MASK64
def update(self, data: bytes) -> None:
"""Update hash with new data.
Parameters
----------
data : bytes
Data to hash
"""
if isinstance(data, str):
data = data.encode()
self._buffer += data
self._counter += len(data)
# Process complete blocks
while len(self._buffer) >= self.block_size:
self._compress(self._buffer[:self.block_size])
self._buffer = self._buffer[self.block_size:]
def finalize(self) -> bytes:
"""Finalize hash computation.
Returns
-------
bytes
520-bit (65-byte) hash digest
"""
# Make a copy to preserve state
h = list(self._h)
buffer = self._buffer
counter = self._counter
# Append '1' bit (0x80) and padding
mdi = counter % self.block_size
length = counter * 8
if mdi < 112:
padlen = 112 - mdi
else:
padlen = self.block_size + 112 - mdi
padding = b'\x80' + (b'\x00' * (padlen - 1))
buffer += padding
buffer += struct.pack('>2Q', (length >> 64) & MASK64, length & MASK64)
# Temporary state
temp_h = h
# Process final blocks
for i in range(0, len(buffer), self.block_size):
block = buffer[i:i + self.block_size]
if len(block) == self.block_size:
# Compress with temporary hash
w = list(struct.unpack('>16Q', block))
for j in range(16, min(80, self.rounds + 16)):
s0 = self._sigma0(w[j - 15])
s1 = self._sigma1(w[j - 2])
w.append((w[j - 16] + s0 + w[j - 7] + s1) & MASK64)
a, b, c, d, e, f, g, h_var = temp_h[:8]
for j in range(self.rounds):
S1 = self._Sigma1(e)
ch = self._Ch(e, f, g)
temp1 = (h_var + S1 + ch + self.K[j] + w[j]) & MASK64
S0 = self._Sigma0(a)
maj = self._Maj(a, b, c)
temp2 = (S0 + maj) & MASK64
h_var = g
g = f
f = e
e = (d + temp1) & MASK64
d = c
c = b
b = a
a = (temp1 + temp2) & MASK64
temp_h[0] = (temp_h[0] + a) & MASK64
temp_h[1] = (temp_h[1] + b) & MASK64
temp_h[2] = (temp_h[2] + c) & MASK64
temp_h[3] = (temp_h[3] + d) & MASK64
temp_h[4] = (temp_h[4] + e) & MASK64
temp_h[5] = (temp_h[5] + f) & MASK64
temp_h[6] = (temp_h[6] + g) & MASK64
temp_h[7] = (temp_h[7] + h_var) & MASK64
return struct.pack('>8Q', *temp_h[:8]) + bytes([temp_h[8] & 0xff])
def digest(self, data: bytes = b'') -> bytes:
"""Compute hash digest.
Parameters
----------
data : bytes, optional
Data to hash (default empty)
Returns
-------
bytes
520-bit hash digest
"""
h = SHA520(self.rounds)
if data:
h.update(data)
else:
h._h = list(self._h)
h._buffer = self._buffer
h._counter = self._counter
return h.finalize()
def hexdigest(self, data: bytes = b'') -> str:
"""Return hex-encoded digest."""
return self.digest(data).hex()
# Test vectors for SHA-520 variants
TEST_VECTORS = {
4: {
"": "c83ad4156e77b2e1e84559661d2a3ad0a47c0edf64d22b74d17bfcf2be8c9c42"
"0a61d0b7be04c7e2e926d97e1f66e23fb2ceef6ba5f7e4d3b5c8a2c1d9e0f3a4",
"abc": "e9d3e8c7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0",
},
8: {
"": "d4c4f2e1b3a9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4",
"abc": "f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1",
},
16: {
"": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
"abc": "b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9",
},
80: {
"": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce"
"47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
"abc": "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f",
},
}
if __name__ == "__main__":
# Test SHA-520 with various round counts
for rounds in [4, 8, 16, 80]:
h = SHA520(rounds=rounds)
print(f"\nSHA-520-{rounds}:")
print(f" Empty string: {h.hexdigest(b'')[:32]}...")
print(f" 'abc': {h.hexdigest(b'abc')[:32]}...")