chore: modernize CI and update Go toolchain

- Bump Go from 1.19 to 1.26 and update all dependencies
- Rewrite CI workflow with matrix strategy (Linux, macOS, Windows)
- Update GitHub Actions to current versions (checkout@v4, setup-go@v5)
- Update CodeQL actions from v1 to v3
- Fix cross-platform bug in mock/path.go (path.Join -> filepath.Join)
- Clean up dependabot config (weekly schedule, remove stale ignore)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Allen Lane
2026-02-14 20:58:51 -05:00
parent cc85a4bdb1
commit 2a19755804
657 changed files with 49050 additions and 32001 deletions

View File

@@ -1,4 +1,4 @@
FROM golang:1.20@sha256:2edf6aab2d57644f3fe7407132a0d1770846867465a39c2083770cf62734b05d
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088
ENV GOOS=linux
ENV GOARCH=arm
@@ -10,7 +10,6 @@ ENV PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig
RUN dpkg --add-architecture armhf \
&& apt update \
&& apt install -y --no-install-recommends \
upx \
gcc-arm-linux-gnueabihf \
libc6-dev-armhf-cross \
pkg-config \

View File

@@ -1,4 +1,4 @@
FROM golang:1.20@sha256:2edf6aab2d57644f3fe7407132a0d1770846867465a39c2083770cf62734b05d
FROM golang:1.24@sha256:14fd8a55e59a560704e5fc44970b301d00d344e45d6b914dda228e09f359a088
ENV GOOS=linux
ENV GOARCH=arm64

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2023 pjbgf
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -4,7 +4,7 @@ export CGO_ENABLED := 1
.PHONY: test
test:
go test ./...
go test -race -timeout 15s ./...
.PHONY: bench
bench:
@@ -31,10 +31,6 @@ build-nocgo:
# Run cross-compilation to assure supported architectures.
cross-build: build-arm build-arm64 build-nocgo
generate:
go run sha1cdblock_amd64_asm.go -out sha1cdblock_amd64.s
sed -i 's;&\samd64;&\n// +build !noasm,gc,amd64;g' sha1cdblock_amd64.s
verify: generate
verify:
git diff --exit-code
go vet ./...

View File

@@ -6,8 +6,7 @@ collision attacks.
The `cgo/lib` code is a carbon copy of the [original code], based on
the award winning [white paper] by Marc Stevens.
The Go implementation is largely based off Go's generic sha1.
At present no SIMD optimisations have been implemented.
The Go and native implementations are largely based off upstream Go.
## Usage

View File

@@ -38,8 +38,7 @@ type digest struct {
len uint64
// col defines whether a collision has been found.
col bool
blockFunc func(dig *digest, p []byte)
col bool
}
func (d *digest) MarshalBinary() ([]byte, error) {
@@ -99,7 +98,7 @@ func (d *digest) UnmarshalBinary(b []byte) error {
func consumeUint64(b []byte) ([]byte, uint64) {
_ = b[7]
x := uint64(b[7]) | uint64(b[6])<<8 | uint64(b[shared.WordBuffers])<<16 | uint64(b[4])<<24 |
x := uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
return b[8:], x
}
@@ -126,21 +125,9 @@ func (d *digest) Reset() {
// implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler to
// marshal and unmarshal the internal state of the hash.
func New() hash.Hash {
d := new(digest)
d.blockFunc = block
var d digest
d.Reset()
return d
}
// NewGeneric is equivalent to New but uses the Go generic implementation,
// avoiding any processor-specific optimizations.
func NewGeneric() hash.Hash {
d := new(digest)
d.blockFunc = blockGeneric
d.Reset()
return d
return &d
}
func (d *digest) Size() int { return Size }
@@ -158,14 +145,14 @@ func (d *digest) Write(p []byte) (nn int, err error) {
n := copy(d.x[d.nx:], p)
d.nx += n
if d.nx == shared.Chunk {
d.blockFunc(d, d.x[:])
block(d, d.x[:])
d.nx = 0
}
p = p[n:]
}
if len(p) >= shared.Chunk {
n := len(p) &^ (shared.Chunk - 1)
d.blockFunc(d, p[:n])
block(d, p[:n])
p = p[n:]
}
if len(p) > 0 {
@@ -184,18 +171,20 @@ func (d *digest) Sum(in []byte) []byte {
func (d *digest) checkSum() [Size]byte {
len := d.len
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
var tmp [64]byte
var tmp [64 + 8]byte
tmp[0] = 0x80
var t uint64
if len%64 < 56 {
d.Write(tmp[0 : 56-len%64])
t = 56 - len%64
} else {
d.Write(tmp[0 : 64+56-len%64])
t = 64 + 56 - len%64
}
// Length in bits.
len <<= 3
binary.BigEndian.PutUint64(tmp[:], len)
d.Write(tmp[0:8])
padlen := tmp[:t+8]
binary.BigEndian.PutUint64(tmp[t:], len)
d.Write(padlen)
if d.nx != 0 {
panic("d.nx != 0")
@@ -214,7 +203,8 @@ func (d *digest) checkSum() [Size]byte {
// Sum returns the SHA-1 checksum of the data.
func Sum(data []byte) ([Size]byte, bool) {
d := New().(*digest)
var d digest
d.Reset()
d.Write(data)
return d.checkSum(), d.col
}

View File

@@ -1,48 +1,50 @@
//go:build !noasm && gc && amd64
// +build !noasm,gc,amd64
//go:build !noasm && gc && amd64 && !arm64
// +build !noasm,gc,amd64,!arm64
package sha1cd
import (
"math"
"unsafe"
"runtime"
"github.com/klauspost/cpuid/v2"
shared "github.com/pjbgf/sha1cd/internal"
)
type sliceHeader struct {
base uintptr
len int
cap int
}
var hasSHANI = (runtime.GOARCH == "amd64" &&
cpuid.CPU.Supports(cpuid.AVX) &&
cpuid.CPU.Supports(cpuid.SHA) &&
cpuid.CPU.Supports(cpuid.SSE3) &&
cpuid.CPU.Supports(cpuid.SSE4))
// blockAMD64 hashes the message p into the current state in dig.
// blockAMD64 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 blockAMD64(dig *digest, p sliceHeader, m1 []uint32, cs [][5]uint32)
func blockAMD64(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
func block(dig *digest, p []byte) {
if forceGeneric || !hasSHANI {
blockGeneric(dig, p)
return
}
m1 := [shared.Rounds]uint32{}
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.
ips := sliceHeader{
base: uintptr(unsafe.Pointer(&p[0])),
len: int(math.Min(float64(len(p)), float64(shared.Chunk))),
cap: shared.Chunk,
}
// The assembly code only supports processing a block at a time,
// so adjust the chunk accordingly.
chunk := p[:shared.Chunk]
blockAMD64(dig, ips, m1[:], cs[:])
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
rectifyCompressionState(m1, &cs)
col := checkCollision(m1, cs, dig.h)
if col {
dig.col = true
blockAMD64(dig, ips, m1[:], cs[:])
blockAMD64(dig, ips, m1[:], cs[:])
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
blockAMD64(dig.h[:], chunk, m1[:], cs[:])
}
p = p[shared.Chunk:]

File diff suppressed because it is too large Load Diff

48
vendor/github.com/pjbgf/sha1cd/sha1cdblock_arm64.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
//go:build !noasm && gc && arm64 && !amd64
// +build !noasm,gc,arm64,!amd64
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 || !hasSHA1 {
blockGeneric(dig, p)
return
}
m1 := [shared.Rounds]uint32{}
cs := [shared.PreStepState][shared.WordBuffers]uint32{}
for len(p) >= shared.Chunk {
// 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[:])
rectifyCompressionState(m1, &cs)
col := checkCollision(m1, cs, dig.h)
if col {
dig.col = true
blockARM64(dig.h[:], chunk, m1[:], cs[:])
blockARM64(dig.h[:], chunk, m1[:], cs[:])
}
p = p[shared.Chunk:]
}
}

249
vendor/github.com/pjbgf/sha1cd/sha1cdblock_arm64.s generated vendored Normal file
View File

@@ -0,0 +1,249 @@
//go:build !noasm && gc && arm64 && !amd64
#include "textflag.h"
// License information for the original SHA1 arm64 implemention:
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found at:
// - https://github.com/golang/go/blob/master/LICENSE
//
// Reference implementations:
// - https://github.com/noloader/SHA-Intrinsics/blob/master/sha1-arm.c
// - https://github.com/golang/go/blob/master/src/crypto/sha1/sha1block_arm64.s
#define HASHUPDATECHOOSE \
SHA1C V16.S4, V1, V2 \
SHA1H V3, V1 \
VMOV V2.B16, V3.B16
#define HASHUPDATEPARITY \
SHA1P V16.S4, V1, V2 \
SHA1H V3, V1 \
VMOV V2.B16, V3.B16
#define HASHUPDATEMAJ \
SHA1M V16.S4, V1, V2 \
SHA1H V3, V1 \
VMOV V2.B16, V3.B16
// func blockARM64(h []uint32, p []byte, m1 []uint32, cs [][5]uint32)
TEXT ·blockARM64(SB), NOSPLIT, $80-96
MOVD h_base+0(FP), R0
MOVD p_base+24(FP), R1
MOVD p_len+32(FP), R2
MOVD m1_base+48(FP), R3
MOVD cs_base+72(FP), R4
LSR $6, R2, R2
LSL $6, R2, R2
ADD R16, R2, R21
VLD1.P 16(R0), [V0.S4]
FMOVS (R0), F20
SUB $16, R0, R0
loop:
CMP R16, R21
BLS end
// Load block (p) into 16-bytes vectors.
VLD1.P 16(R1), [V4.B16]
VLD1.P 16(R1), [V5.B16]
VLD1.P 16(R1), [V6.B16]
VLD1.P 16(R1), [V7.B16]
// Load K constants to V19
MOVD $·sha1Ks(SB), R22
VLD1 (R22), [V19.S4]
VMOV V0.B16, V2.B16
VMOV V20.S[0], V1
VMOV V2.B16, V3.B16
VDUP V19.S[0], V17.S4
// Little Endian
VREV32 V4.B16, V4.B16
VREV32 V5.B16, V5.B16
VREV32 V6.B16, V6.B16
VREV32 V7.B16, V7.B16
// LOAD M1 rounds 0-15
VST1.P [V4.S4], (R3)
VST1.P [V5.S4], (R3)
VST1.P [V6.S4], (R3)
VST1.P [V7.S4], (R3)
// LOAD CS 0
VST1.P [V0.S4], (R4) // ABCD pre-round 0
VST1.P V1.S[0], 4(R4) // E pre-round 0
// Rounds 0-3
VDUP V19.S[1], V18.S4
VADD V17.S4, V4.S4, V16.S4
SHA1SU0 V6.S4, V5.S4, V4.S4
HASHUPDATECHOOSE
SHA1SU1 V7.S4, V4.S4
// Rounds 4-7
VADD V17.S4, V5.S4, V16.S4
SHA1SU0 V7.S4, V6.S4, V5.S4
HASHUPDATECHOOSE
SHA1SU1 V4.S4, V5.S4
// LOAD M1 rounds 16-19
VST1.P [V4.S4], (R3)
// Rounds 8-11
VADD V17.S4, V6.S4, V16.S4
SHA1SU0 V4.S4, V7.S4, V6.S4
HASHUPDATECHOOSE
SHA1SU1 V5.S4, V6.S4
// LOAD M1 rounds 20-23
VST1.P [V5.S4], (R3)
// Rounds 12-15
VADD V17.S4, V7.S4, V16.S4
SHA1SU0 V5.S4, V4.S4, V7.S4
HASHUPDATECHOOSE
SHA1SU1 V6.S4, V7.S4
// LOAD M1 rounds 24-27
VST1.P [V6.S4], (R3)
// Rounds 16-19
VADD V17.S4, V4.S4, V16.S4
SHA1SU0 V6.S4, V5.S4, V4.S4
HASHUPDATECHOOSE
SHA1SU1 V7.S4, V4.S4
// LOAD M1 rounds 28-31
VST1.P [V7.S4], (R3)
// Rounds 20-23
VDUP V19.S[2], V17.S4
VADD V18.S4, V5.S4, V16.S4
SHA1SU0 V7.S4, V6.S4, V5.S4
HASHUPDATEPARITY
SHA1SU1 V4.S4, V5.S4
// LOAD M1 rounds 32-35
VST1.P [V4.S4], (R3)
// Rounds 24-27
VADD V18.S4, V6.S4, V16.S4
SHA1SU0 V4.S4, V7.S4, V6.S4
HASHUPDATEPARITY
SHA1SU1 V5.S4, V6.S4
// LOAD M1 rounds 36-39
VST1.P [V5.S4], (R3)
// Rounds 28-31
VADD V18.S4, V7.S4, V16.S4
SHA1SU0 V5.S4, V4.S4, V7.S4
HASHUPDATEPARITY
SHA1SU1 V6.S4, V7.S4
// LOAD M1 rounds 40-43
VST1.P [V6.S4], (R3)
// Rounds 32-35
VADD V18.S4, V4.S4, V16.S4
SHA1SU0 V6.S4, V5.S4, V4.S4
HASHUPDATEPARITY
SHA1SU1 V7.S4, V4.S4
// LOAD M1 rounds 44-47
VST1.P [V7.S4], (R3)
// Rounds 36-39
VADD V18.S4, V5.S4, V16.S4
SHA1SU0 V7.S4, V6.S4, V5.S4
HASHUPDATEPARITY
SHA1SU1 V4.S4, V5.S4
// LOAD M1 rounds 48-51
VST1.P [V4.S4], (R3)
// Rounds 44-47
VDUP V19.S[3], V18.S4
VADD V17.S4, V6.S4, V16.S4
SHA1SU0 V4.S4, V7.S4, V6.S4
HASHUPDATEMAJ
SHA1SU1 V5.S4, V6.S4
// LOAD M1 rounds 52-55
VST1.P [V5.S4], (R3)
// Rounds 44-47
VADD V17.S4, V7.S4, V16.S4
SHA1SU0 V5.S4, V4.S4, V7.S4
HASHUPDATEMAJ
SHA1SU1 V6.S4, V7.S4
// LOAD M1 rounds 56-59
VST1.P [V6.S4], (R3)
// Rounds 48-51
VADD V17.S4, V4.S4, V16.S4
SHA1SU0 V6.S4, V5.S4, V4.S4
HASHUPDATEMAJ
SHA1SU1 V7.S4, V4.S4
// LOAD M1 rounds 60-63
VST1.P [V7.S4], (R3)
// Rounds 52-55
VADD V17.S4, V5.S4, V16.S4
SHA1SU0 V7.S4, V6.S4, V5.S4
HASHUPDATEMAJ
SHA1SU1 V4.S4, V5.S4
// LOAD CS 58
VST1.P [V3.S4], (R4) // ABCD pre-round 56
VST1.P V1.S[0], 4(R4) // E pre-round 56
// Rounds 56-59
VADD V17.S4, V6.S4, V16.S4
SHA1SU0 V4.S4, V7.S4, V6.S4
HASHUPDATEMAJ
SHA1SU1 V5.S4, V6.S4
// Rounds 60-63
VADD V18.S4, V7.S4, V16.S4
SHA1SU0 V5.S4, V4.S4, V7.S4
HASHUPDATEPARITY
SHA1SU1 V6.S4, V7.S4
// LOAD CS 65
VST1.P [V3.S4], (R4) // ABCD pre-round 64
VST1.P V1.S[0], 4(R4) // E pre-round 64
// Rounds 64-67
VADD V18.S4, V4.S4, V16.S4
HASHUPDATEPARITY
// LOAD M1 rounds 68-79
VST1.P [V4.S4], (R3)
VST1.P [V5.S4], (R3)
VST1.P [V6.S4], (R3)
VST1.P [V7.S4], (R3)
// Rounds 68-71
VADD V18.S4, V5.S4, V16.S4
HASHUPDATEPARITY
// Rounds 72-75
VADD V18.S4, V6.S4, V16.S4
HASHUPDATEPARITY
// Rounds 76-79
VADD V18.S4, V7.S4, V16.S4
HASHUPDATEPARITY
// Add working registers to hash state.
VADD V2.S4, V0.S4, V0.S4
VADD V1.S4, V20.S4, V20.S4
end:
// Update h with final hash values.
VST1.P [V0.S4], (R0)
FMOVS F20, (R0)
RET
DATA ·sha1Ks+0(SB)/4, $0x5A827999 // K0
DATA ·sha1Ks+4(SB)/4, $0x6ED9EBA1 // K1
DATA ·sha1Ks+8(SB)/4, $0x8F1BBCDC // K2
DATA ·sha1Ks+12(SB)/4, $0xCA62C1D6 // K3
GLOBL ·sha1Ks(SB), RODATA, $16

View File

@@ -15,6 +15,8 @@ import (
"github.com/pjbgf/sha1cd/ubc"
)
var forceGeneric bool
// blockGeneric is a portable, pure Go version of the SHA-1 block step.
// It's used by sha1block_generic.go and tests.
func blockGeneric(dig *digest, p []byte) {
@@ -139,11 +141,12 @@ func blockGeneric(dig *digest, p []byte) {
dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4] = h0, h1, h2, h3, h4
}
//go:noinline
func checkCollision(
m1 [shared.Rounds]uint32,
cs [shared.PreStepState][shared.WordBuffers]uint32,
state [shared.WordBuffers]uint32) bool {
h [shared.WordBuffers]uint32,
) bool {
if mask := ubc.CalculateDvMask(m1); mask != 0 {
dvs := ubc.SHA1_dvs()
@@ -167,7 +170,7 @@ func checkCollision(
// ubc's DM prior to the SHA recompression step.
m1, dvs[i].Dm,
csState,
state)
h)
if col {
return true
@@ -178,6 +181,7 @@ func checkCollision(
return false
}
//go:nosplit
func hasCollided(step uint32, m1, dm [shared.Rounds]uint32,
state [shared.WordBuffers]uint32, h [shared.WordBuffers]uint32) bool {
// Intermediary Hash Value.
@@ -266,3 +270,42 @@ func hasCollided(step uint32, m1, dm [shared.Rounds]uint32,
return false
}
// rectifyCompressionState fixes the compression state when using the
// SIMD implementations.
//
// Due to the way that hardware acceleration works, the rounds
// are executed 4 at a time. Therefore, the state for cs58 and cs65
// are not available directly through the assembly logic. The states
// returned are for cs56 and cs64. This function updates indexes 1 and 2
// of cs to contain the respective CS values for rounds 58 and 65.
//
//go:nosplit
func rectifyCompressionState(
m1 [shared.Rounds]uint32,
cs *[shared.PreStepState][shared.WordBuffers]uint32,
) {
if cs == nil {
return
}
func3 := func(state [shared.WordBuffers]uint32, i int) [shared.WordBuffers]uint32 {
a, b, c, d, e := state[0], state[1], state[2], state[3], state[4]
f := ((b | c) & d) | (b & c)
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K2
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
return [shared.WordBuffers]uint32{a, b, c, d, e}
}
func4 := func(state [shared.WordBuffers]uint32, i int) [shared.WordBuffers]uint32 {
a, b, c, d, e := state[0], state[1], state[2], state[3], state[4]
f := b ^ c ^ d
t := bits.RotateLeft32(a, 5) + f + e + m1[i] + shared.K3
a, b, c, d, e = t, a, bits.RotateLeft32(b, 30), c, d
return [shared.WordBuffers]uint32{a, b, c, d, e}
}
cs57 := func3(cs[1], 56)
cs[1] = func3(cs57, 57)
cs[2] = func4(cs[2], 64)
}

View File

@@ -1,5 +1,4 @@
//go:build !amd64 || noasm || !gc
// +build !amd64 noasm !gc
//go:build (!amd64 && !arm64) || noasm
package sha1cd

View File

@@ -1,3 +0,0 @@
// ubc package provides ways for SHA1 blocks to be checked for
// Unavoidable Bit Conditions that arise from crypto analysis attacks.
package ubc

View File

@@ -1,8 +1,10 @@
// ubc package provides ways for SHA1 blocks to be checked for
// Unavoidable Bit Conditions that arise from crypto analysis attacks.
package ubc
// Based on the C implementation from Marc Stevens and Dan Shumow.
// https://github.com/cr-marcstevens/sha1collisiondetection
package ubc
type DvInfo struct {
// DvType, DvK and DvB define the DV: I(K,B) or II(K,B) (see the paper).
// https://marc-stevens.nl/research/papers/C13-S.pdf
@@ -21,10 +23,12 @@ type DvInfo struct {
Dm [80]uint32
}
// CalculateDvMask takes as input an expanded message block and verifies the unavoidable bitconditions
// for all listed DVs. It returns a dvmask where each bit belonging to a DV is set if all
// unavoidable bitconditions for that DV have been met.
// Thus, one needs to do the recompression check for each DV that has its bit set.
// CalculateDvMask takes as input an expanded message block and
// verifies the unavoidable bitconditions for all listed DVs. It returns
// a dvmask where each bit belonging to a DV is set if all unavoidable
// bitconditions for that DV have been met.
//
//go:nosplit
func CalculateDvMask(W [80]uint32) uint32 {
mask := uint32(0xFFFFFFFF)
mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) | ^(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit | DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit))
@@ -363,6 +367,7 @@ func not(x uint32) uint32 {
return 0
}
//go:nosplit
func SHA1_dvs() []DvInfo {
return sha1_dvs
}