Switch labeled hash to explicit formatting
This commit is contained in:
parent
af4dbad99f
commit
1ac81dde54
2 changed files with 118 additions and 22 deletions
|
@ -3,9 +3,12 @@ package lhash
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mr-tron/base58"
|
||||||
|
|
||||||
"github.com/safing/portbase/container"
|
"github.com/safing/portbase/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,11 +63,33 @@ func Load(labeledHash []byte) (*LabeledHash, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFromString loads a labeled hash from the given string.
|
// FromHex loads a labeled hash from the given hexadecimal string.
|
||||||
func LoadFromString(labeledHash string) (*LabeledHash, error) {
|
func FromHex(hexEncoded string) (*LabeledHash, error) {
|
||||||
raw, err := base64.RawURLEncoding.DecodeString(labeledHash)
|
raw, err := hex.DecodeString(hexEncoded)
|
||||||
if err != nil {
|
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)
|
return Load(raw)
|
||||||
|
@ -78,13 +103,37 @@ func (lh *LabeledHash) Bytes() []byte {
|
||||||
return c.CompileData()
|
return c.CompileData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the string representation of the labeled hash (base64 raw url encoding).
|
// Hex returns the hexadecimal string representation of the labeled hash.
|
||||||
func (lh *LabeledHash) String() string {
|
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())
|
return base64.RawURLEncoding.EncodeToString(lh.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches returns true if the digest of the given data matches the hash.
|
// Base58 returns the Base58 string representation of the labeled hash using
|
||||||
func (lh *LabeledHash) Matches(data []byte) bool {
|
// 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 := lh.alg.new()
|
||||||
_, _ = hasher.Write(data) // never returns an error
|
_, _ = hasher.Write(data) // never returns an error
|
||||||
defer hasher.Reset() // internal state may leak data if kept in memory
|
defer hasher.Reset() // internal state may leak data if kept in memory
|
||||||
|
|
|
@ -7,8 +7,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testEmpty = []byte("")
|
testEmpty = []byte("")
|
||||||
testFox = []byte("The quick brown fox jumps over the lazy dog.")
|
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) {
|
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
|
// test fox
|
||||||
lh = Digest(alg, testFox)
|
lh = Digest(alg, testFoxData)
|
||||||
if !bytes.Equal(lh.Bytes()[2:], foxBytes) {
|
if !bytes.Equal(lh.Bytes()[2:], foxBytes) {
|
||||||
t.Errorf("alg %d: test fox: digest mismatch, expected %+v, got %+v", alg, foxBytes, lh.Bytes()[2:])
|
t.Errorf("alg %d: test fox: digest mismatch, expected %+v, got %+v", alg, foxBytes, lh.Bytes()[2:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// test matching
|
// test matching with serialized/loaded labeled hash
|
||||||
if !lh.Matches(testFox) {
|
if !lh.MatchesData(testFoxData) {
|
||||||
t.Errorf("alg %d: failed to match reference", alg)
|
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)
|
t.Errorf("alg %d: failed to non-match garbage", alg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serialize
|
// Test representations
|
||||||
lhs := Digest(alg, testFox).String()
|
|
||||||
// load
|
// Hex
|
||||||
loaded, err := LoadFromString(lhs)
|
lhs := Digest(alg, testFoxData)
|
||||||
|
loaded, err := FromHex(lhs.Hex())
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
testFormat(t, alg, lhs, loaded)
|
||||||
|
|
||||||
// test matching with serialized/loaded labeled hash
|
// Base64
|
||||||
if !loaded.Matches(testFox) {
|
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)
|
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)
|
t.Errorf("alg %d: failed to non-match garbage", alg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue