Skip to content

Commit

Permalink
refactored hash.go. Now exposes only two hashing methods. (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyStiles authored Oct 29, 2020
1 parent cbd1e15 commit 7304bf6
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 63 deletions.
45 changes: 13 additions & 32 deletions hash.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package poly

import (
"crypto"
_ "crypto/md5"
_ "crypto/sha1"
_ "crypto/sha256"
_ "crypto/sha512"
"encoding/hex"
"errors"
"hash"
"io"
"strings"

_ "golang.org/x/crypto/blake2b"
_ "golang.org/x/crypto/blake2s"
_ "golang.org/x/crypto/ripemd160"
_ "golang.org/x/crypto/sha3"
"lukechampine.com/blake3"
)

// Where each hash function comes from.
Expand Down Expand Up @@ -89,48 +87,31 @@ func BoothLeastRotation(sequence string) int {
return leastRotationIndex
}

//RotateSequence rotates circular sequences to deterministic point.
// RotateSequence rotates circular sequences to deterministic point.
func RotateSequence(sequence string) string {
rotationIndex := BoothLeastRotation(sequence)
concatenatedSequence := sequence + sequence
sequence = concatenatedSequence[rotationIndex : rotationIndex+len(sequence)]
return sequence
}

// GenericSequenceHash takes an AnnotatedSequence and a hash function and hashes it.
// from https://stackoverflow.com/questions/32620290/how-to-dynamically-switch-between-hash-algorithms-in-golang <- this had a bug I had to fix! - Tim
func GenericSequenceHash(annotatedSequence AnnotatedSequence, hash crypto.Hash) (string, error) {
if !hash.Available() {
return "", errors.New("hash unavailable")
}
// Hash is a method wrapper for hashing AnnotatedSequence structs.
func (annotatedSequence AnnotatedSequence) Hash(hash hash.Hash) string {
if annotatedSequence.Meta.Locus.Circular {
annotatedSequence.Sequence.Sequence = RotateSequence(annotatedSequence.Sequence.Sequence)
}
h := hash.New()
io.WriteString(h, strings.ToUpper(annotatedSequence.Sequence.Sequence))
return hex.EncodeToString(h.Sum(nil)), nil
}

// Hash is a method wrapper for hashing annotatedSequence structs.
func (annotatedSequence AnnotatedSequence) Hash(hash crypto.Hash) string {
seqHash, _ := GenericSequenceHash(annotatedSequence, hash)
seqHash, _ := hashSequence(annotatedSequence.Sequence.Sequence, hash)
return seqHash
}

// Blake3SequenceHash Blake3 function doesn't use standard golang hash interface
// so we couldn't use it with the generic sequence hash.
func Blake3SequenceHash(annotatedSequence AnnotatedSequence) string {

if annotatedSequence.Meta.Locus.Circular {
annotatedSequence.Sequence.Sequence = RotateSequence(annotatedSequence.Sequence.Sequence)
}

b := blake3.Sum256([]byte(strings.ToUpper(annotatedSequence.Sequence.Sequence)))
return hex.EncodeToString(b[:])
// Hash is a method wrapper for hashing sequences contained in Feature structs.
func (feature Feature) Hash(hash hash.Hash) string {
seqHash, _ := hashSequence(feature.GetSequence(), hash)
return seqHash
}

// Blake3Hash is a method wrapper for hashing annotatedSequence structs with Blake3.
func (annotatedSequence AnnotatedSequence) Blake3Hash() string {
seqHash := Blake3SequenceHash(annotatedSequence)
return seqHash
// hashSequence takes a string and a hashing function and returns a hashed string.
func hashSequence(sequence string, hash hash.Hash) (string, error) {
io.WriteString(hash, strings.ToUpper(sequence))
return hex.EncodeToString(hash.Sum(nil)), nil
}
18 changes: 15 additions & 3 deletions hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,25 @@ import (
"testing"

"github.com/sergi/go-diff/diffmatchpatch"
"lukechampine.com/blake3"
)

func TestBlake3HashRegression(t *testing.T) {
func TestHashRegression(t *testing.T) {
puc19GbkBlake3Hash := "4b0616d1b3fc632e42d78521deb38b44fba95cca9fde159e01cd567fa996ceb9"
puc19 := ReadGbk("data/puc19.gbk")
if got := puc19.Blake3Hash(); got != puc19GbkBlake3Hash {
t.Errorf("TestBlake3HashRegression has failed. Blake3 has returned %q, want %q", got, puc19GbkBlake3Hash)
if got := puc19.Hash(blake3.New(32, nil)); got != puc19GbkBlake3Hash {
t.Errorf("TestHashRegression has failed. Blake3 sequence hash has returned %q, want %q", got, puc19GbkBlake3Hash)
}

// testing feature hash method
ampRHash := "e2bc8192c23919ce4e2b2b03f7af644ab8b714865a429408c57d30fa57557a85"
for _, feature := range puc19.Features {
if feature.Attributes["label"] == "AmpR" {
hash := feature.Hash(blake3.New(32, nil))
if hash != ampRHash {
t.Errorf("TestHashRegression has failed. Blake3 feature sequence hash has returned %q, want %q", hash, ampRHash)
}
}
}
}

Expand Down
10 changes: 6 additions & 4 deletions io.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,17 @@ type Feature struct {
GbkLocationString string `json:"gbk_location_string"`
Sequence string `json:"sequence"`
SequenceLocation Location `json:"sequence_location"`
SequenceHash string `json:"sequence_hash"`
SequenceHashFunction string `json:"hash_function"`
ParentAnnotatedSequence *AnnotatedSequence `json:"-"`
}

// Sequence holds raw sequence information in an AnnotatedSequence struct.
type Sequence struct {
Description string `json:"description"`
Hash string `json:"hash"`
HashFunction string `json:"hash_function"`
Sequence string `json:"sequence"`
Description string `json:"description"`
SequenceHash string `json:"sequence_hash"`
SequenceHashFunction string `json:"hash_function"`
Sequence string `json:"sequence"`
// ParentAnnotatedSequence *AnnotatedSequence
}

Expand Down
48 changes: 25 additions & 23 deletions poly/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/TimothyStiles/poly"
"github.com/urfave/cli/v2"
"lukechampine.com/blake3"
)

/******************************************************************************
Expand Down Expand Up @@ -188,8 +189,8 @@ func hashCommand(c *cli.Context) error {

// handler for outputting JSON to stdout
if c.String("o") == "json" {
annotatedSequence.Sequence.Hash = sequenceHash // adding hash to JSON
annotatedSequence.Sequence.HashFunction = strings.ToUpper(c.String("f")) // adding hash type to JSON
annotatedSequence.Sequence.SequenceHash = sequenceHash // adding hash to JSON
annotatedSequence.Sequence.SequenceHashFunction = strings.ToUpper(c.String("f")) // adding hash type to JSON
output, _ := json.MarshalIndent(annotatedSequence, "", " ")
fmt.Fprint(c.App.Writer, string(output))
}
Expand Down Expand Up @@ -240,8 +241,8 @@ func hashCommand(c *cli.Context) error {

// handler for outputting JSON.
if strings.ToLower(c.String("o")) == "json" {
annotatedSequence.Sequence.Hash = sequenceHash
annotatedSequence.Sequence.HashFunction = strings.ToUpper(c.String("f"))
annotatedSequence.Sequence.SequenceHash = sequenceHash
annotatedSequence.Sequence.SequenceHashFunction = strings.ToUpper(c.String("f"))

if c.Bool("--log") == true {
output, _ := json.MarshalIndent(annotatedSequence, "", " ")
Expand Down Expand Up @@ -436,45 +437,46 @@ func flagSwitchHash(c *cli.Context, annotatedSequence poly.AnnotatedSequence) st
var hashString string
switch strings.ToUpper(c.String("f")) {
case "MD5":
hashString = annotatedSequence.Hash(crypto.MD5)
hashString = annotatedSequence.Hash(crypto.MD5.New())
case "SHA1":
hashString = annotatedSequence.Hash(crypto.SHA1)
hashString = annotatedSequence.Hash(crypto.SHA1.New())
case "SHA244":
hashString = annotatedSequence.Hash(crypto.SHA224)
hashString = annotatedSequence.Hash(crypto.SHA224.New())
case "SHA256":
hashString = annotatedSequence.Hash(crypto.SHA256)
hashString = annotatedSequence.Hash(crypto.SHA256.New())
case "SHA384":
hashString = annotatedSequence.Hash(crypto.SHA384)
hashString = annotatedSequence.Hash(crypto.SHA384.New())
case "SHA512":
hashString = annotatedSequence.Hash(crypto.SHA512)
hashString = annotatedSequence.Hash(crypto.SHA512.New())
case "RIPEMD160":
hashString = annotatedSequence.Hash(crypto.RIPEMD160)
hashString = annotatedSequence.Hash(crypto.RIPEMD160.New())
case "SHA3_224":
hashString = annotatedSequence.Hash(crypto.SHA3_224)
hashString = annotatedSequence.Hash(crypto.SHA3_224.New())
case "SHA3_256":
hashString = annotatedSequence.Hash(crypto.SHA3_256)
hashString = annotatedSequence.Hash(crypto.SHA3_256.New())
case "SHA3_384":
hashString = annotatedSequence.Hash(crypto.SHA3_384)
hashString = annotatedSequence.Hash(crypto.SHA3_384.New())
case "SHA3_512":
hashString = annotatedSequence.Hash(crypto.SHA3_512)
hashString = annotatedSequence.Hash(crypto.SHA3_512.New())
case "SHA512_224":
hashString = annotatedSequence.Hash(crypto.SHA512_224)
hashString = annotatedSequence.Hash(crypto.SHA512_224.New())
case "SHA512_256":
hashString = annotatedSequence.Hash(crypto.SHA512_256)
hashString = annotatedSequence.Hash(crypto.SHA512_256.New())
case "BLAKE2s_256":
hashString = annotatedSequence.Hash(crypto.BLAKE2s_256)
hashString = annotatedSequence.Hash(crypto.BLAKE2s_256.New())
case "BLAKE2b_256":
hashString = annotatedSequence.Hash(crypto.BLAKE2b_256)
hashString = annotatedSequence.Hash(crypto.BLAKE2b_256.New())
case "BLAKE2b_384":
hashString = annotatedSequence.Hash(crypto.BLAKE2b_384)
hashString = annotatedSequence.Hash(crypto.BLAKE2b_384.New())
case "BLAKE2b_512":
hashString = annotatedSequence.Hash(crypto.BLAKE2b_512)
hashString = annotatedSequence.Hash(crypto.BLAKE2b_512.New())
case "BLAKE3":
hashString = annotatedSequence.Blake3Hash()
hashString = annotatedSequence.Hash(blake3.New(32, nil))
// hashString = annotatedSequence.Blake3Hash()
case "NO":
hashString = poly.RotateSequence(annotatedSequence.Sequence.Sequence)
default:
hashString = annotatedSequence.Blake3Hash()
hashString = annotatedSequence.Hash(blake3.New(32, nil))
break
}
return hashString
Expand Down
2 changes: 1 addition & 1 deletion poly/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func TestHashJSON(t *testing.T) {
t.Fatalf("Run error: %s", err)
}

hashOutputString := poly.ReadJSON("../data/puc19.json").Sequence.Hash
hashOutputString := poly.ReadJSON("../data/puc19.json").Sequence.SequenceHash
os.Remove("../data/puc19.json")

if hashOutputString != puc19GbkBlake3Hash {
Expand Down

0 comments on commit 7304bf6

Please sign in to comment.