Switch labeled hash to explicit formatting

This commit is contained in:
Daniel 2021-05-04 15:28:23 +02:00
parent af4dbad99f
commit 1ac81dde54
2 changed files with 118 additions and 22 deletions

View file

@ -3,9 +3,12 @@ package lhash
import (
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/safing/portbase/container"
)
@ -60,11 +63,33 @@ func Load(labeledHash []byte) (*LabeledHash, error) {
}, nil
}
// LoadFromString loads a labeled hash from the given string.
func LoadFromString(labeledHash string) (*LabeledHash, error) {
raw, err := base64.RawURLEncoding.DecodeString(labeledHash)
// FromHex loads a labeled hash from the given hexadecimal string.
func FromHex(hexEncoded string) (*LabeledHash, error) {
raw, err := hex.DecodeString(hexEncoded)
if err != nil {
return nil, fmt.Errorf("failed to decode: %s", err)
return nil, fmt.Errorf("failed to decode hex: %s", err)
}
return Load(raw)
}
// FromBase64 loads a labeled hash from the given Base64 string using raw url
// encoding.
func FromBase64(base64Encoded string) (*LabeledHash, error) {
raw, err := base64.RawURLEncoding.DecodeString(base64Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base64: %s", err)
}
return Load(raw)
}
// FromBase58 loads a labeled hash from the given Base58 string using the BTC
// alphabet.
func FromBase58(base58Encoded string) (*LabeledHash, error) {
raw, err := base58.Decode(base58Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base58: %s", err)
}
return Load(raw)
@ -78,13 +103,37 @@ func (lh *LabeledHash) Bytes() []byte {
return c.CompileData()
}
// String returns the string representation of the labeled hash (base64 raw url encoding).
func (lh *LabeledHash) String() string {
// Hex returns the hexadecimal string representation of the labeled hash.
func (lh *LabeledHash) Hex() string {
return hex.EncodeToString(lh.Bytes())
}
// Base64 returns the Base64 string representation of the labeled hash using
// raw url encoding.
func (lh *LabeledHash) Base64() string {
return base64.RawURLEncoding.EncodeToString(lh.Bytes())
}
// Matches returns true if the digest of the given data matches the hash.
func (lh *LabeledHash) Matches(data []byte) bool {
// Base58 returns the Base58 string representation of the labeled hash using
// the BTC alphabet.
func (lh *LabeledHash) Base58() string {
return base58.Encode(lh.Bytes())
}
// Equal returns true if the given labeled hash is equal.
// Equality is checked by comparing both the algorithm and the digest value.
func (lh *LabeledHash) Equal(other *LabeledHash) bool {
return lh.alg == other.alg &&
subtle.ConstantTimeCompare(lh.digest, other.digest) == 1
}
// MatchesString returns true if the digest of the given string matches the hash.
func (lh *LabeledHash) MatchesString(s string) bool {
return lh.MatchesData([]byte(s))
}
// MatchesData returns true if the digest of the given data matches the hash.
func (lh *LabeledHash) MatchesData(data []byte) bool {
hasher := lh.alg.new()
_, _ = hasher.Write(data) // never returns an error
defer hasher.Reset() // internal state may leak data if kept in memory

View file

@ -7,8 +7,11 @@ import (
)
var (
testEmpty = []byte("")
testFox = []byte("The quick brown fox jumps over the lazy dog.")
testEmpty = []byte("")
testFox = "The quick brown fox jumps over the lazy dog."
testFoxData = []byte(testFox)
noMatch = "no match"
noMatchData = []byte(noMatch)
)
func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
@ -32,33 +35,77 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
}
// test fox
lh = Digest(alg, testFox)
lh = Digest(alg, testFoxData)
if !bytes.Equal(lh.Bytes()[2:], foxBytes) {
t.Errorf("alg %d: test fox: digest mismatch, expected %+v, got %+v", alg, foxBytes, lh.Bytes()[2:])
}
// test matching
if !lh.Matches(testFox) {
// test matching with serialized/loaded labeled hash
if !lh.MatchesData(testFoxData) {
t.Errorf("alg %d: failed to match reference", alg)
}
if lh.Matches([]byte("nope")) {
if !lh.MatchesString(testFox) {
t.Errorf("alg %d: failed to match reference", alg)
}
if lh.MatchesData(noMatchData) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}
if lh.MatchesString(noMatch) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}
// serialize
lhs := Digest(alg, testFox).String()
// load
loaded, err := LoadFromString(lhs)
// Test representations
// Hex
lhs := Digest(alg, testFoxData)
loaded, err := FromHex(lhs.Hex())
if err != nil {
t.Errorf("alg %d: failed to load from string: %s", alg, err)
t.Errorf("alg %d: failed to load from hex string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)
// test matching with serialized/loaded labeled hash
if !loaded.Matches(testFox) {
// Base64
lhs = Digest(alg, testFoxData)
loaded, err = FromBase64(lhs.Base64())
if err != nil {
t.Errorf("alg %d: failed to load from base64 string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)
// Base58
lhs = Digest(alg, testFoxData)
loaded, err = FromBase58(lhs.Base58())
if err != nil {
t.Errorf("alg %d: failed to load from base58 string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)
}
func testFormat(t *testing.T, alg Algorithm, lhs, loaded *LabeledHash) {
noMatchLH := Digest(alg, noMatchData)
// Test equality.
if !lhs.Equal(loaded) {
t.Errorf("alg %d: equality test failed", alg)
}
if lhs.Equal(noMatchLH) {
t.Errorf("alg %d: non-equality test failed", alg)
}
// Test matching.
if !loaded.MatchesData(testFoxData) {
t.Errorf("alg %d: failed to match reference", alg)
}
if loaded.Matches([]byte("nope")) {
if !loaded.MatchesString(testFox) {
t.Errorf("alg %d: failed to match reference", alg)
}
if loaded.MatchesData(noMatchData) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}
if loaded.MatchesString(noMatch) {
t.Errorf("alg %d: failed to non-match garbage", alg)
}
}