Initial commit

This commit is contained in:
Hanzo Dev
2025-07-25 20:12:57 -05:00
parent c3d2563353
commit 9cbec1931a
19 changed files with 550 additions and 16 deletions
+103
View File
@@ -0,0 +1,103 @@
name: CI
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.24']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Cache Go modules
uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install dependencies
run: |
go mod download
make install-tools
- name: Format check
run: |
gofmt -s -l .
test -z "$(gofmt -s -l .)"
- name: Lint
run: make lint
- name: Run tests
run: make test-coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.out
fail_ci_if_error: true
- name: Run benchmarks
run: make bench
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: ./...
- name: Run Nancy vulnerability scanner
run: |
go install github.com/sonatype-nexus-community/nancy@latest
go list -json -m all | nancy sleuth
build:
name: Build
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: ['1.21', '1.22']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Build
run: make build
- name: Verify module
run: make verify
+83
View File
@@ -0,0 +1,83 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
- 'crypto/v*.*.*'
permissions:
contents: write
jobs:
test:
name: Test Before Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Install dependencies
run: |
go mod download
make install-tools
- name: Run tests
run: make test
- name: Run linter
run: make lint
release:
name: Create Release
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
- name: Build binaries
run: |
# Build for multiple platforms
GOOS=linux GOARCH=amd64 go build -o dist/crypto-linux-amd64 ./...
GOOS=linux GOARCH=arm64 go build -o dist/crypto-linux-arm64 ./...
GOOS=darwin GOARCH=amd64 go build -o dist/crypto-darwin-amd64 ./...
GOOS=darwin GOARCH=arm64 go build -o dist/crypto-darwin-arm64 ./...
GOOS=windows GOARCH=amd64 go build -o dist/crypto-windows-amd64.exe ./...
- name: Generate changelog
id: changelog
uses: mikepenz/release-changelog-builder-action@v4
with:
configuration: ".github/changelog-config.json"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
files: |
dist/*
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update Go Module Index
run: |
curl -X POST https://proxy.golang.org/github.com/luxfi/crypto/@v/${{ github.ref_name }}.info
+72
View File
@@ -0,0 +1,72 @@
# Makefile for luxfi/crypto module
.PHONY: all build test clean fmt lint install-tools test-coverage
# Variables
GOBIN := $(shell go env GOPATH)/bin
GOLANGCI_LINT_VERSION := v1.54.2
COVERAGE_FILE := coverage.out
COVERAGE_HTML := coverage.html
# Default target
all: clean fmt lint test build
# Build the module
build:
@echo "Building crypto module..."
@go build -v ./...
# Run tests
test:
@echo "Running tests..."
@go test -v -race -timeout=10m ./...
# Run tests with coverage
test-coverage:
@echo "Running tests with coverage..."
@go test -v -race -timeout=10m -coverprofile=$(COVERAGE_FILE) -covermode=atomic ./...
@go tool cover -html=$(COVERAGE_FILE) -o $(COVERAGE_HTML)
@echo "Coverage report generated: $(COVERAGE_HTML)"
# Format code
fmt:
@echo "Formatting code..."
@go fmt ./...
@go mod tidy
# Run linter
lint: install-tools
@echo "Running linter..."
@$(GOBIN)/golangci-lint run ./...
# Install development tools
install-tools:
@echo "Installing development tools..."
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
# Clean build artifacts
clean:
@echo "Cleaning..."
@go clean -cache -testcache
@rm -f $(COVERAGE_FILE) $(COVERAGE_HTML)
# Run benchmarks
bench:
@echo "Running benchmarks..."
@go test -bench=. -benchmem ./...
# Check for security vulnerabilities
security:
@echo "Checking for vulnerabilities..."
@go list -json -m all | nancy sleuth
# Update dependencies
deps:
@echo "Updating dependencies..."
@go get -u -t ./...
@go mod tidy
# Verify module
verify:
@echo "Verifying module..."
@go mod verify
+3 -3
View File
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/luxfi/node/utils" "github.com/luxfi/crypto"
"github.com/luxfi/crypto/bls/blstest" "github.com/luxfi/crypto/bls/blstest"
) )
@@ -19,7 +19,7 @@ func BenchmarkVerify(b *testing.B) {
for _, messageSize := range blstest.BenchmarkSizes { for _, messageSize := range blstest.BenchmarkSizes {
b.Run(strconv.Itoa(messageSize), func(b *testing.B) { b.Run(strconv.Itoa(messageSize), func(b *testing.B) {
message := utils.RandomBytes(messageSize) message := crypto.RandomBytes(messageSize)
signature := sign(privateKey, message) signature := sign(privateKey, message)
b.ResetTimer() b.ResetTimer()
@@ -99,7 +99,7 @@ func BenchmarkPublicKeyFromValidUncompressedBytes(b *testing.B) {
func BenchmarkSignatureFromBytes(b *testing.B) { func BenchmarkSignatureFromBytes(b *testing.B) {
sk := newKey(require.New(b)) sk := newKey(require.New(b))
message := utils.RandomBytes(32) message := crypto.RandomBytes(32)
signature := sign(sk, message) signature := sign(sk, message)
signatureBytes := SignatureToBytes(signature) signatureBytes := SignatureToBytes(signature)
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/luxfi/node/utils" "github.com/luxfi/crypto"
blst "github.com/supranational/blst/bindings/go" blst "github.com/supranational/blst/bindings/go"
) )
@@ -48,7 +48,7 @@ func TestAggregationThreshold(t *testing.T) {
} }
// The transaction's unsigned bytes are publicly known. // The transaction's unsigned bytes are publicly known.
msg := utils.RandomBytes(1234) msg := crypto.RandomBytes(1234)
// People may attempt time sign the transaction. // People may attempt time sign the transaction.
sigs := []*Signature{ sigs := []*Signature{
+2 -2
View File
@@ -9,13 +9,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/luxfi/node/utils" "github.com/luxfi/crypto"
) )
func TestPublicKeyFromCompressedBytesWrongSize(t *testing.T) { func TestPublicKeyFromCompressedBytesWrongSize(t *testing.T) {
require := require.New(t) require := require.New(t)
pkBytes := utils.RandomBytes(PublicKeyLen + 1) pkBytes := crypto.RandomBytes(PublicKeyLen + 1)
_, err := PublicKeyFromCompressedBytes(pkBytes) _, err := PublicKeyFromCompressedBytes(pkBytes)
require.ErrorIs(err, ErrFailedPublicKeyDecompress) require.ErrorIs(err, ErrFailedPublicKeyDecompress)
} }
+3 -3
View File
@@ -8,13 +8,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/luxfi/node/utils" "github.com/luxfi/crypto"
) )
func TestSignatureBytes(t *testing.T) { func TestSignatureBytes(t *testing.T) {
require := require.New(t) require := require.New(t)
msg := utils.RandomBytes(1234) msg := crypto.RandomBytes(1234)
sk := newKey(require) sk := newKey(require)
sig := sign(sk, msg) sig := sign(sk, msg)
@@ -31,7 +31,7 @@ func TestSignatureBytes(t *testing.T) {
func TestAggregateSignaturesNoop(t *testing.T) { func TestAggregateSignaturesNoop(t *testing.T) {
require := require.New(t) require := require.New(t)
msg := utils.RandomBytes(1234) msg := crypto.RandomBytes(1234)
sk := newKey(require) sk := newKey(require)
sig := sign(sk, msg) sig := sign(sk, msg)
+2 -2
View File
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/luxfi/node/utils" "github.com/luxfi/crypto"
"github.com/luxfi/crypto/bls/blstest" "github.com/luxfi/crypto/bls/blstest"
) )
@@ -17,7 +17,7 @@ func BenchmarkSign(b *testing.B) {
signer := NewSigner(require.New(b)) signer := NewSigner(require.New(b))
for _, messageSize := range blstest.BenchmarkSizes { for _, messageSize := range blstest.BenchmarkSizes {
b.Run(strconv.Itoa(messageSize), func(b *testing.B) { b.Run(strconv.Itoa(messageSize), func(b *testing.B) {
message := utils.RandomBytes(messageSize) message := crypto.RandomBytes(messageSize)
b.ResetTimer() b.ResetTimer()
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/luxfi/node/utils" "github.com/luxfi/crypto"
blst "github.com/supranational/blst/bindings/go" blst "github.com/supranational/blst/bindings/go"
) )
@@ -27,7 +27,7 @@ func TestSecretKeyFromBytesZero(t *testing.T) {
func TestSecretKeyFromBytesWrongSize(t *testing.T) { func TestSecretKeyFromBytesWrongSize(t *testing.T) {
require := require.New(t) require := require.New(t)
skBytes := utils.RandomBytes(SecretKeyLen + 1) skBytes := crypto.RandomBytes(SecretKeyLen + 1)
_, err := FromBytes(skBytes) _, err := FromBytes(skBytes)
require.ErrorIs(err, ErrFailedSecretKeyDeserialize) require.ErrorIs(err, ErrFailedSecretKeyDeserialize)
} }
@@ -35,7 +35,7 @@ func TestSecretKeyFromBytesWrongSize(t *testing.T) {
func TestSecretKeyBytes(t *testing.T) { func TestSecretKeyBytes(t *testing.T) {
require := require.New(t) require := require.New(t)
msg := utils.RandomBytes(1234) msg := crypto.RandomBytes(1234)
sk, err := New() sk, err := New()
require.NoError(err) require.NoError(err)
@@ -11,9 +11,9 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/luxfi/node/proto/pb/signer"
"github.com/luxfi/crypto/bls" "github.com/luxfi/crypto/bls"
"github.com/luxfi/crypto/bls/signer/localsigner" "github.com/luxfi/crypto/bls/signer/localsigner"
"github.com/luxfi/node/proto/pb/signer"
) )
var ( var (
+56
View File
@@ -0,0 +1,56 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cb58
import (
"bytes"
"errors"
"fmt"
"math"
"github.com/mr-tron/base58/base58"
"github.com/luxfi/crypto/hashing"
)
const checksumLen = 4
var (
ErrBase58Decoding = errors.New("base58 decoding error")
ErrMissingChecksum = errors.New("input string is smaller than the checksum size")
ErrBadChecksum = errors.New("invalid input checksum")
errEncodingOverFlow = errors.New("encoding overflow")
)
// Encode [bytes] to a string using cb58 format.
// [bytes] may be nil, in which case it will be treated the same as an empty
// slice.
func Encode(bytes []byte) (string, error) {
bytesLen := len(bytes)
if bytesLen > math.MaxInt32-checksumLen {
return "", errEncodingOverFlow
}
checked := make([]byte, bytesLen+checksumLen)
copy(checked, bytes)
copy(checked[len(bytes):], hashing.Checksum(bytes, checksumLen))
return base58.Encode(checked), nil
}
// Decode [str] to bytes from cb58.
func Decode(str string) ([]byte, error) {
decodedBytes, err := base58.Decode(str)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrBase58Decoding, err)
}
if len(decodedBytes) < checksumLen {
return nil, ErrMissingChecksum
}
// Verify the checksum
rawBytes := decodedBytes[:len(decodedBytes)-checksumLen]
checksum := decodedBytes[len(decodedBytes)-checksumLen:]
if !bytes.Equal(checksum, hashing.Checksum(rawBytes, checksumLen)) {
return nil, ErrBadChecksum
}
return rawBytes, nil
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cb58
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
// Test encoding bytes to a string and decoding back to bytes
func TestEncodeDecode(t *testing.T) {
require := require.New(t)
type test struct {
bytes []byte
str string
}
id := [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
tests := []test{
{
nil,
"45PJLL",
},
{
[]byte{},
"45PJLL",
},
{
[]byte{0},
"1c7hwa",
},
{
[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255},
"1NVSVezva3bAtJesnUj",
},
{
id[:],
"SkB92YpWm4Q2ijQHH34cqbKkCZWszsiQgHVjtNeFF2HdvDQU",
},
}
for _, test := range tests {
// Encode the bytes
strResult, err := Encode(test.bytes)
require.NoError(err)
// Make sure the string repr. is what we expected
require.Equal(test.str, strResult)
// Decode the string
bytesResult, err := Decode(strResult)
require.NoError(err)
// Make sure we got the same bytes back
require.True(bytes.Equal(test.bytes, bytesResult))
}
}
func FuzzEncodeDecode(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
require := require.New(t)
// Encode bytes to string
dataStr, err := Encode(data)
require.NoError(err)
// Decode string to bytes
gotData, err := Decode(dataStr)
require.NoError(err)
require.Equal(data, gotData)
})
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package crypto
import gocrypto "crypto"
// Certificate represents a TLS certificate
// This is a minimal representation needed for NodeID generation
type Certificate struct {
// Raw contains the complete ASN.1 DER content of the certificate
Raw []byte
// PublicKey contains the public key from the certificate
PublicKey gocrypto.PublicKey
}
+1
View File
@@ -11,6 +11,7 @@ require (
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
github.com/luxfi/geth v1.16.2 github.com/luxfi/geth v1.16.2
github.com/luxfi/node v1.15.0 github.com/luxfi/node v1.15.0
github.com/mr-tron/base58 v1.2.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/supranational/blst v0.3.15 github.com/supranational/blst v0.3.15
golang.org/x/crypto v0.40.0 golang.org/x/crypto v0.40.0
+2
View File
@@ -38,6 +38,8 @@ github.com/luxfi/geth v1.16.2 h1:rTzLxDe2Zbt7vv3j1rRBt6jnC1Alf1Hglq25cdj+C0Q=
github.com/luxfi/geth v1.16.2/go.mod h1:XqDpjw2AlF38QOc91CM/MaUe5RNun7Cqe+WS4qxMKcQ= github.com/luxfi/geth v1.16.2/go.mod h1:XqDpjw2AlF38QOc91CM/MaUe5RNun7Cqe+WS4qxMKcQ=
github.com/luxfi/node v1.15.0 h1:sSN6vGnoRb4IMFxQjhT9rFw/S9Vprsdq9zYxg9DMsRI= github.com/luxfi/node v1.15.0 h1:sSN6vGnoRb4IMFxQjhT9rFw/S9Vprsdq9zYxg9DMsRI=
github.com/luxfi/node v1.15.0/go.mod h1:wTa2R9wr4DyWVn2c4XlkT1uzpggjFTnyXW9mtRr+pYI= github.com/luxfi/node v1.15.0/go.mod h1:wTa2R9wr4DyWVn2c4XlkT1uzpggjFTnyXW9mtRr+pYI=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+11
View File
@@ -0,0 +1,11 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package hashing
// Hasher is an interface to compute a hash value.
type Hasher interface {
// Hash takes a string and computes its hash value.
// Values must be computed deterministically.
Hash([]byte) uint64
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package hashing
import (
"crypto/sha256"
"errors"
"fmt"
"io"
// This file generates addresses from public keys with ripemd160. Though ripemd160 is not
// generally recommended for use, the small size of the public key input is considered harder to
// attack than larger payloads.
//
// Bitcoin similarly uses ripemd160 to generate addresses from public keys.
//
// Reference: https://online.tugraz.at/tug_online/voe_main2.getvolltext?pCurrPk=17675
"golang.org/x/crypto/ripemd160" //nolint:gosec
)
const (
HashLen = sha256.Size
AddrLen = ripemd160.Size
)
var ErrInvalidHashLen = errors.New("invalid hash length")
// Hash256 A 256 bit long hash value.
type Hash256 = [HashLen]byte
// Hash160 A 160 bit long hash value.
type Hash160 = [ripemd160.Size]byte
// ComputeHash256Array computes a cryptographically strong 256 bit hash of the
// input byte slice.
func ComputeHash256Array(buf []byte) Hash256 {
return sha256.Sum256(buf)
}
// ComputeHash256 computes a cryptographically strong 256 bit hash of the input
// byte slice.
func ComputeHash256(buf []byte) []byte {
arr := ComputeHash256Array(buf)
return arr[:]
}
// ComputeHash160Array computes a cryptographically strong 160 bit hash of the
// input byte slice.
func ComputeHash160Array(buf []byte) Hash160 {
h, err := ToHash160(ComputeHash160(buf))
if err != nil {
panic(err)
}
return h
}
// ComputeHash160 computes a cryptographically strong 160 bit hash of the input
// byte slice.
func ComputeHash160(buf []byte) []byte {
// See the comment on the ripemd160 import as to why the risk of use is
// considered acceptable.
ripe := ripemd160.New() //nolint:gosec
_, err := io.Writer(ripe).Write(buf)
if err != nil {
panic(err)
}
return ripe.Sum(nil)
}
// Checksum creates a checksum of [length] bytes from the 256 bit hash of the
// byte slice.
//
// Returns: the lower [length] bytes of the hash
// Panics if length > 32.
func Checksum(bytes []byte, length int) []byte {
hash := ComputeHash256Array(bytes)
return hash[len(hash)-length:]
}
func ToHash256(bytes []byte) (Hash256, error) {
hash := Hash256{}
if bytesLen := len(bytes); bytesLen != HashLen {
return hash, fmt.Errorf("%w: expected 32 bytes but got %d", ErrInvalidHashLen, bytesLen)
}
copy(hash[:], bytes)
return hash, nil
}
func ToHash160(bytes []byte) (Hash160, error) {
hash := Hash160{}
if bytesLen := len(bytes); bytesLen != ripemd160.Size {
return hash, fmt.Errorf("%w: expected 20 bytes but got %d", ErrInvalidHashLen, bytesLen)
}
copy(hash[:], bytes)
return hash, nil
}
func PubkeyBytesToAddress(key []byte) []byte {
return ComputeHash160(ComputeHash256(key))
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package crypto
import (
"crypto/rand"
)
// RandomBytes returns a slice of n random bytes
func RandomBytes(n int) []byte {
bytes := make([]byte, n)
// #nosec G404 - we don't need cryptographic randomness for test data
_, _ = rand.Read(bytes)
return bytes
}