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
7 changes: 4 additions & 3 deletions cgo/sha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ func init() {
}

func New() hash.Hash {
d := new(digest)
var d digest
d.Reset()
return d
return &d
}

type digest struct {
Expand Down Expand Up @@ -60,7 +60,8 @@ func (d *digest) Size() int { return Size }
func (d *digest) BlockSize() int { return BlockSize }

func Sum(data []byte) ([]byte, bool) {
d := New().(*digest)
var d digest
d.Reset()
d.Write(data)

h, c := d.sum()
Expand Down
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module github.com/pjbgf/sha1cd

go 1.21
go 1.22

toolchain go1.24.6

// Temporary dependency to be removed once CPU feature checks
// are natively supported. https://github.com/golang/go/issues/73787
require github.com/klauspost/cpuid/v2 v2.3.0

require golang.org/x/sys v0.30.0 // indirect
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2 changes: 0 additions & 2 deletions sha1cd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
shared "github.com/pjbgf/sha1cd/internal"
)

//go:generate go run -C asm . -out ../sha1cdblock_amd64.s -pkg $GOPACKAGE

func init() {
crypto.RegisterHash(crypto.SHA1, New)
}
Expand Down
205 changes: 142 additions & 63 deletions sha1cd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,66 +58,76 @@ var golden = []sha1Test{
}

func TestGolden(t *testing.T) {
for i := 0; i < len(golden); i++ {
g := golden[i]
v, _ := Sum([]byte(g.in))
s := fmt.Sprintf("%x", v)
if s != g.out {
t.Fatalf("Sum function: sha1(%s) = %s want %s", g.in, s, g.out)
}
c := New()
for j := 0; j < 3; j++ {
var sum []byte
switch j {
case 0, 1:
io.WriteString(c, g.in)
sum = c.Sum(nil)
case 2:
io.WriteString(c, g.in[0:len(g.in)/2])
c.Sum(nil)
io.WriteString(c, g.in[len(g.in)/2:])
sum = c.Sum(nil)
}
s := fmt.Sprintf("%x", sum)
if s != g.out {
t.Fatalf("sha1[%d](%s) = %s want %s", j, g.in, s, g.out)
for _, generic := range []bool{false, true} {
forceGeneric = generic
t.Run(fmt.Sprintf("generic %v", generic), func(t *testing.T) {
for i := 0; i < len(golden); i++ {
g := golden[i]
v, _ := Sum([]byte(g.in))
s := fmt.Sprintf("%x", v)
if s != g.out {
t.Fatalf("Sum function: sha1(%s) = %s want %s", g.in, s, g.out)
}
c := New()
for j := 0; j < 3; j++ {
var sum []byte
switch j {
case 0, 1:
io.WriteString(c, g.in)
sum = c.Sum(nil)
case 2:
io.WriteString(c, g.in[0:len(g.in)/2])
c.Sum(nil)
io.WriteString(c, g.in[len(g.in)/2:])
sum = c.Sum(nil)
}
s := fmt.Sprintf("%x", sum)
if s != g.out {
t.Fatalf("sha1[%d](%s) = %s want %s", j, g.in, s, g.out)
}
c.Reset()
}
}
c.Reset()
}
})
}
}

func TestGoldenMarshal(t *testing.T) {
h := New().(*digest)
h2 := New().(*digest)
for _, g := range golden {
h.Reset()
h2.Reset()
for _, generic := range []bool{false, true} {
forceGeneric = generic
t.Run(fmt.Sprintf("generic %v", generic), func(t *testing.T) {
h := New().(*digest)
h2 := New().(*digest)
for _, g := range golden {
h.Reset()
h2.Reset()

io.WriteString(h, g.in[:len(g.in)/2])
io.WriteString(h, g.in[:len(g.in)/2])

state, err := h.MarshalBinary()
if err != nil {
t.Errorf("could not marshal: %v", err)
continue
}
state, err := h.MarshalBinary()
if err != nil {
t.Errorf("could not marshal: %v", err)
continue
}

if string(state) != g.halfState {
t.Errorf("sha1(%q) state = %+q, want %+q", g.in, state, g.halfState)
continue
}
if string(state) != g.halfState {
t.Errorf("sha1(%q) state = %+q, want %+q", g.in, state, g.halfState)
continue
}

if err := h2.UnmarshalBinary(state); err != nil {
t.Errorf("could not unmarshal: %v", err)
continue
}
if err := h2.UnmarshalBinary(state); err != nil {
t.Errorf("could not unmarshal: %v", err)
continue
}

io.WriteString(h, g.in[len(g.in)/2:])
io.WriteString(h2, g.in[len(g.in)/2:])
io.WriteString(h, g.in[len(g.in)/2:])
io.WriteString(h2, g.in[len(g.in)/2:])

if actual, actual2 := h.Sum(nil), h2.Sum(nil); !bytes.Equal(actual, actual2) {
t.Errorf("sha1(%q) = 0x%x != marshaled 0x%x", g.in, actual, actual2)
}
if actual, actual2 := h.Sum(nil), h2.Sum(nil); !bytes.Equal(actual, actual2) {
t.Errorf("sha1(%q) = 0x%x != marshaled 0x%x", g.in, actual, actual2)
}
}
})
}
}

Expand Down Expand Up @@ -172,23 +182,28 @@ func safeSum(h hash.Hash) (sum []byte, err error) {
}

func TestLargeHashes(t *testing.T) {
for i, test := range largeUnmarshalTests {
for _, generic := range []bool{false, true} {
forceGeneric = generic
t.Run(fmt.Sprintf("generic %v", generic), func(t *testing.T) {
for i, test := range largeUnmarshalTests {

h := New().(*digest)
if err := h.UnmarshalBinary([]byte(test.state)); err != nil {
t.Errorf("test %d could not unmarshal: %v", i, err)
continue
}
h := New().(*digest)
if err := h.UnmarshalBinary([]byte(test.state)); err != nil {
t.Errorf("test %d could not unmarshal: %v", i, err)
continue
}

sum, err := safeSum(h)
if err != nil {
t.Errorf("test %d could not sum: %v", i, err)
continue
}
sum, err := safeSum(h)
if err != nil {
t.Errorf("test %d could not sum: %v", i, err)
continue
}

if fmt.Sprintf("%x", sum) != test.sum {
t.Errorf("test %d sum mismatch: expect %s got %x", i, test.sum, sum)
}
if fmt.Sprintf("%x", sum) != test.sum {
t.Errorf("test %d sum mismatch: expect %s got %x", i, test.sum, sum)
}
}
})
}
}

Expand Down Expand Up @@ -217,3 +232,67 @@ func testAllocations(h hash.Hash, t *testing.T) {
t.Errorf("allocs = %d, want < 1", n)
}
}

func TestRectifyCompressionState(t *testing.T) {
t.Parallel()

tests := []struct {
m1 [80]uint32
cs *[3][5]uint32
want *[3][5]uint32
}{
{
m1: func() [80]uint32 {
var m1 [80]uint32
for i := range 80 {
m1[i] = uint32(i + 1)
}
return m1
}(),
cs: &[3][5]uint32{
{0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0},
{0x12345678, 0x9ABCDEF0, 0x11111111, 0x22222222, 0x33333333},
{0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD, 0xEEEEEEEE},
},
want: &[3][5]uint32{
{0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0},
{0x24AD25B3, 0x1B09D17A, 0x048D159E, 0x26AF37BC, 0x11111111},
{0xB951B104, 0xAAAAAAAA, 0xEEEEEEEE, 0xCCCCCCCC, 0xDDDDDDDD},
},
},
{
m1: [80]uint32{},
cs: &[3][5]uint32{
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
},
want: &[3][5]uint32{
{0, 0, 0, 0, 0},
{0x7293586D, 0x8F1BBCDC, 0, 0, 0},
{0xCA62C1D6, 0, 0, 0, 0},
},
},
{
m1: [80]uint32{},
cs: nil,
want: nil,
},
}

for i, tc := range tests {
t.Run(fmt.Sprintf("tc#%d", i), func(t *testing.T) {
t.Parallel()

rectifyCompressionState(tc.m1, tc.cs)

if tc.want == nil {
if tc.cs != nil {
t.Errorf("cs should be nil, %v", tc.cs)
}
} else if *tc.cs != *tc.want {
t.Errorf("rectifyCompressionState() = %v, want %v", tc.cs, tc.want)
}
})
}
}
6 changes: 3 additions & 3 deletions sha1cdblock_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ func block(dig *digest, p []byte) {
cs := [shared.PreStepState][shared.WordBuffers]uint32{}

for len(p) >= shared.Chunk {
// Only send a block to be processed, as the collission detection
// works on a block by block basis.
// The assembly code only supports processing a block at a time,
// so adjust the chunk accordingly.
chunk := p[:shared.Chunk]

blockAMD64(dig.h[:], chunk, m1[:], cs[:])

col := checkCollision(m1, cs, dig.h[:])
col := checkCollision(m1, cs, dig.h)
if col {
dig.col = true

Expand Down
14 changes: 10 additions & 4 deletions sha1cdblock_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
package sha1cd

import (
"runtime"

"github.com/klauspost/cpuid/v2"
shared "github.com/pjbgf/sha1cd/internal"
)

var hasSHA1 = (runtime.GOARCH == "arm64" && cpuid.CPU.Supports(cpuid.SHA1))

// blockARM64 hashes the message p into the current state in h.
// Both m1 and cs are used to store intermediate results which are used by the collision detection logic.
//
//go:noescape
func blockARM64(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)

func block(dig *digest, p []byte) {
if forceGeneric {
if forceGeneric || !hasSHA1 {
blockGeneric(dig, p)
return
}
Expand All @@ -23,13 +28,14 @@ func block(dig *digest, p []byte) {
cs := [shared.PreStepState][shared.WordBuffers]uint32{}

for len(p) >= shared.Chunk {
// Only send a block to be processed, as the collission detection
// works on a block by block basis.
// The assembly code only supports processing a block at a time,
// so adjust the chunk accordingly.
chunk := p[:shared.Chunk]

blockARM64(dig.h[:], chunk, m1[:], cs[:])

col := checkCollision(m1, cs, dig.h[:])
rectifyCompressionState(m1, &cs)
col := checkCollision(m1, cs, dig.h)
if col {
dig.col = true

Expand Down
Loading
Loading