Skip to content

Commit c770e48

Browse files
committed
Fix: Supporting unaligned SHA-256 states
1 parent 652735d commit c770e48

File tree

3 files changed

+367
-271
lines changed

3 files changed

+367
-271
lines changed

golang/lib.go

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,18 @@ package sz
3636
// #cgo nocallback sz_hash_state_update
3737
// #cgo noescape sz_hash_state_digest
3838
// #cgo nocallback sz_hash_state_digest
39+
// #cgo noescape sz_sha256_state_init
40+
// #cgo nocallback sz_sha256_state_init
41+
// #cgo noescape sz_sha256_state_update
42+
// #cgo nocallback sz_sha256_state_update
43+
// #cgo noescape sz_sha256_state_digest
44+
// #cgo nocallback sz_sha256_state_digest
3945
// #define SZ_DYNAMIC_DISPATCH 1
4046
// #include <stringzilla/stringzilla.h>
4147
import "C"
4248
import (
4349
"fmt"
50+
"io"
4451
"unsafe"
4552
)
4653

@@ -185,8 +192,8 @@ func (h *Hasher) Digest() uint64 {
185192
return uint64(C.sz_hash_state_digest(&h.state))
186193
}
187194

188-
// SHA256 computes the SHA-256 cryptographic hash of the input data.
189-
func Sha256(data []byte) [32]byte {
195+
// HashSha256 computes the SHA-256 cryptographic hash of the input data.
196+
func HashSha256(data []byte) [32]byte {
190197
var state C.sz_sha256_state_t
191198
C.sz_sha256_state_init(&state)
192199
if len(data) > 0 {
@@ -197,45 +204,66 @@ func Sha256(data []byte) [32]byte {
197204
return digest
198205
}
199206

200-
// Sha256Hasher is a streaming SHA-256 hasher.
201-
type Sha256Hasher struct {
207+
// Sha256 is a streaming SHA-256 hasher that implements hash.Hash and io.Writer.
208+
type Sha256 struct {
202209
state C.sz_sha256_state_t
203210
}
204211

212+
// Compile-time interface checks
213+
var _ io.Writer = (*Sha256)(nil)
214+
205215
// NewSha256 creates a new streaming SHA-256 hasher.
206-
func NewSha256() *Sha256Hasher {
207-
h := &Sha256Hasher{}
216+
func NewSha256() *Sha256 {
217+
h := &Sha256{}
208218
C.sz_sha256_state_init(&h.state)
209219
return h
210220
}
211221

212-
// Write adds data to the streaming SHA-256 hasher.
213-
func (h *Sha256Hasher) Write(p []byte) *Sha256Hasher {
214-
if len(p) == 0 {
215-
return h
222+
// Write adds data to the streaming SHA-256 hasher. Implements io.Writer.
223+
func (h *Sha256) Write(p []byte) (n int, err error) {
224+
if len(p) > 0 {
225+
C.sz_sha256_state_update(&h.state, (*C.char)(unsafe.Pointer(&p[0])), C.ulong(len(p)))
216226
}
217-
C.sz_sha256_state_update(&h.state, (*C.char)(unsafe.Pointer(&p[0])), C.ulong(len(p)))
218-
return h
227+
return len(p), nil
219228
}
220229

221-
// Digest returns the current SHA-256 hash without consuming the state.
222-
func (h *Sha256Hasher) Digest() [32]byte {
230+
// Sum appends the current hash to b and returns the resulting slice.
231+
// It does not change the underlying hash state. Implements hash.Hash.
232+
func (h *Sha256) Sum(b []byte) []byte {
233+
digest := h.Digest()
234+
return append(b, digest[:]...)
235+
}
236+
237+
// Reset resets the hasher to its initial state. Implements hash.Hash.
238+
func (h *Sha256) Reset() {
239+
C.sz_sha256_state_init(&h.state)
240+
}
241+
242+
// Size returns the number of bytes Sum will return. Implements hash.Hash.
243+
func (h *Sha256) Size() int {
244+
return 32
245+
}
246+
247+
// BlockSize returns the hash's underlying block size. Implements hash.Hash.
248+
func (h *Sha256) BlockSize() int {
249+
return 64
250+
}
251+
252+
// Digest returns the current SHA-256 hash as a 32-byte array without consuming the state.
253+
// This is a convenience method in addition to the standard hash.Hash interface.
254+
func (h *Sha256) Digest() [32]byte {
223255
var digest [32]byte
224256
C.sz_sha256_state_digest(&h.state, (*C.uchar)(unsafe.Pointer(&digest[0])))
225257
return digest
226258
}
227259

228260
// Hexdigest returns the current SHA-256 hash as a lowercase hexadecimal string.
229-
func (h *Sha256Hasher) Hexdigest() string {
261+
// This is a convenience method matching Python's hashlib interface.
262+
func (h *Sha256) Hexdigest() string {
230263
digest := h.Digest()
231264
return fmt.Sprintf("%x", digest)
232265
}
233266

234-
// Reset resets the hasher to its initial state.
235-
func (h *Sha256Hasher) Reset() {
236-
C.sz_sha256_state_init(&h.state)
237-
}
238-
239267
// Count returns the number of overlapping or non-overlapping instances of `substr` in `str`.
240268
// If `substr` is an empty string, returns 1 + the length of the `str`.
241269
// https://pkg.go.dev/strings#Count

golang/lib_test.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,12 @@ func TestHashing(t *testing.T) {
201201
// TestSha256 verifies SHA-256 hashing with NIST test vectors.
202202
func TestSha256(t *testing.T) {
203203
// NIST test vectors
204-
empty := sz.Sha256([]byte(""))
204+
empty := sz.HashSha256([]byte(""))
205205
if fmt.Sprintf("%x", empty) != "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
206206
t.Fatalf("SHA-256 empty string mismatch")
207207
}
208208

209-
abc := sz.Sha256([]byte("abc"))
209+
abc := sz.HashSha256([]byte("abc"))
210210
if fmt.Sprintf("%x", abc) != "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" {
211211
t.Fatalf("SHA-256 'abc' mismatch")
212212
}
@@ -215,14 +215,29 @@ func TestSha256(t *testing.T) {
215215
// TestSha256Streaming verifies streaming SHA-256 hasher.
216216
func TestSha256Streaming(t *testing.T) {
217217
hasher := sz.NewSha256()
218-
hasher.Write([]byte("Hello, ")).Write([]byte("world!"))
218+
hasher.Write([]byte("Hello, "))
219+
hasher.Write([]byte("world!"))
219220
progressive := hasher.Digest()
220221

221-
oneshot := sz.Sha256([]byte("Hello, world!"))
222+
oneshot := sz.HashSha256([]byte("Hello, world!"))
222223
if progressive != oneshot {
223224
t.Fatalf("Streaming SHA-256 mismatch: %x != %x", progressive, oneshot)
224225
}
225226

227+
// Test hash.Hash interface compliance
228+
if hasher.Size() != 32 {
229+
t.Fatalf("Size() should return 32")
230+
}
231+
if hasher.BlockSize() != 64 {
232+
t.Fatalf("BlockSize() should return 64")
233+
}
234+
235+
// Test Sum() method
236+
sum := hasher.Sum(nil)
237+
if len(sum) != 32 {
238+
t.Fatalf("Sum(nil) should return 32 bytes")
239+
}
240+
226241
// Hexdigest and reset
227242
if len(hasher.Hexdigest()) != 64 {
228243
t.Fatalf("Hexdigest wrong length")

0 commit comments

Comments
 (0)