diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d92ce4f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "spec/multicodec"] + path = spec/multicodec + url = https://github.com/multiformats/multicodec.git +[submodule "spec/multihash"] + path = spec/multihash + url = https://github.com/multiformats/multihash.git diff --git a/multihash.go b/multihash.go index ffb0944..52c2f56 100644 --- a/multihash.go +++ b/multihash.go @@ -64,7 +64,9 @@ const ( DBL_SHA2_256 = 0x56 - MURMUR3 = 0x22 + MURMUR3_128 = 0x22 + // Deprecated: use MURMUR3_128 + MURMUR3 = MURMUR3_128 X11 = 0x1100 ) @@ -101,7 +103,7 @@ var Names = map[string]uint64{ "sha3-384": SHA3_384, "sha3-512": SHA3_512, "dbl-sha2-256": DBL_SHA2_256, - "murmur3": MURMUR3, + "murmur3-128": MURMUR3_128, "keccak-224": KECCAK_224, "keccak-256": KECCAK_256, "keccak-384": KECCAK_384, @@ -123,7 +125,7 @@ var Codes = map[uint64]string{ SHA3_384: "sha3-384", SHA3_512: "sha3-512", DBL_SHA2_256: "dbl-sha2-256", - MURMUR3: "murmur3", + MURMUR3_128: "murmur3-128", KECCAK_224: "keccak-224", KECCAK_256: "keccak-256", KECCAK_384: "keccak-384", @@ -147,7 +149,7 @@ var DefaultLengths = map[uint64]int{ DBL_SHA2_256: 32, KECCAK_224: 28, KECCAK_256: 32, - MURMUR3: 4, + MURMUR3_128: 4, KECCAK_384: 48, KECCAK_512: 64, SHAKE_128: 32, diff --git a/multihash_test.go b/multihash_test.go index 9a7c722..3d579f9 100644 --- a/multihash_test.go +++ b/multihash_test.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "strings" "testing" ) @@ -20,7 +21,7 @@ var tCodes = map[uint64]string{ 0x16: "sha3-256", 0x17: "sha3-224", 0x56: "dbl-sha2-256", - 0x22: "murmur3", + 0x22: "murmur3-128", 0x1A: "keccak-224", 0x1B: "keccak-256", 0x1C: "keccak-384", @@ -45,7 +46,7 @@ var testCases = []TestCase{ {"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", 0x12, "sha2-256"}, {"2c26b46b", 0x12, "sha2-256"}, {"2c26b46b68ffc68ff99b453c1d30413413", 0xb240, "blake2b-512"}, - {"243ddb9e", 0x22, "murmur3"}, + {"243ddb9e", 0x22, "murmur3-128"}, {"f00ba4", 0x1b, "keccak-256"}, {"f84e95cb5fbd2038863ab27d3cdeac295ad2d4ab96ad1f4b070c0bf36078ef08", 0x18, "shake-128"}, {"1af97f7818a28edfdfce5ec66dbdc7e871813816d7d585fe1f12475ded5b6502b7723b74e2ee36f2651a10a8eaca72aa9148c3c761aaceac8f6d6cc64381ed39", 0x19, "shake-256"}, @@ -175,6 +176,34 @@ func TestTable(t *testing.T) { t.Error("Table mismatch: ", Names[v], k) } } + + for name, code := range Names { + if tCodes[code] != name { + if strings.HasPrefix(name, "blake") { + // skip these + continue + } + if name == "sha3" { + // tested as "sha3-512" + continue + } + t.Error("missing test case for: ", name) + } + } + + for code, name := range Codes { + if tCodes[code] != name { + if strings.HasPrefix(name, "blake") { + // skip these + continue + } + if name == "sha3" { + // tested as "sha3-512" + continue + } + t.Error("missing test case for: ", name) + } + } } func ExampleDecode() { diff --git a/spec/multicodec b/spec/multicodec new file mode 160000 index 0000000..ba90699 --- /dev/null +++ b/spec/multicodec @@ -0,0 +1 @@ +Subproject commit ba906993c21b6de66740d966926367bd741ee0be diff --git a/spec/multihash b/spec/multihash new file mode 160000 index 0000000..cde1aef --- /dev/null +++ b/spec/multihash @@ -0,0 +1 @@ +Subproject commit cde1aef8158d193d73012b7d730013f05c2f7063 diff --git a/spec_test.go b/spec_test.go new file mode 100644 index 0000000..08d550e --- /dev/null +++ b/spec_test.go @@ -0,0 +1,133 @@ +package multihash + +import ( + "encoding/csv" + "fmt" + "os" + "strconv" + "strings" + "testing" +) + +func TestSpec(t *testing.T) { + file, err := os.Open("spec/multicodec/table.csv") + if err != nil { + t.Fatal(err) + } + defer file.Close() + + reader := csv.NewReader(file) + reader.LazyQuotes = false + reader.FieldsPerRecord = 4 + reader.TrimLeadingSpace = true + + values, err := reader.ReadAll() + if err != nil { + t.Error(err) + } + expectedFunctions := make(map[uint64]string, len(values)-1) + if values[0][0] != "name" || values[0][1] != "tag" || values[0][2] != "code" { + t.Fatal("table format has changed") + } + + for _, v := range values[1:] { + name := v[0] + tag := v[1] + codeStr := v[2] + + if tag != "multihash" { + // not a multihash function + continue + } + + var code uint64 + if !strings.HasPrefix(codeStr, "0x") { + t.Errorf("invalid multicodec code %q (%s)", codeStr, name) + continue + } + + i, err := strconv.ParseUint(codeStr[2:], 16, 64) + if err != nil { + t.Errorf("invalid multibase code %q (%s)", codeStr, name) + continue + } + code = uint64(i) + expectedFunctions[code] = name + } + + for code, name := range Codes { + expectedName, ok := expectedFunctions[code] + if !ok { + t.Errorf("multihash %q (%x) not defined in the spec", name, code) + continue + } + if expectedName != name { + t.Errorf("encoding %q (%x) has unexpected name %q", expectedName, code, name) + } + } +} + +func TestSpecVectors(t *testing.T) { + file, err := os.Open("spec/multihash/tests/values/test_cases.csv") + if err != nil { + t.Error(err) + return + } + defer file.Close() + + reader := csv.NewReader(file) + reader.LazyQuotes = false + reader.FieldsPerRecord = 4 + reader.TrimLeadingSpace = true + + values, err := reader.ReadAll() + if err != nil { + t.Error(err) + } + if len(values) == 0 { + t.Error("no test values") + return + } + + // check the header. + if values[0][0] != "algorithm" || + values[0][1] != "bits" || + values[0][2] != "input" || + values[0][3] != "multihash" { + t.Fatal("table format has changed") + } + + for i, testCase := range values[1:] { + function := testCase[0] + lengthStr := testCase[1] + input := testCase[2] + expectedStr := testCase[3] + + t.Run(fmt.Sprintf("%d/%s/%s", i, function, lengthStr), func(t *testing.T) { + code, ok := Names[function] + if !ok { + t.Skipf("skipping %s: not supported", function) + return + } + + length, err := strconv.ParseInt(lengthStr, 10, 64) + if err != nil { + t.Fatalf("failed to decode length: %s", err) + } + + if length%8 != 0 { + t.Fatal("expected the length to be a multiple of 8") + } + + actual, err := Sum([]byte(input), code, int(length/8)) + if err != nil { + t.Fatalf("failed to hash: %s", err) + } + actualStr := actual.HexString() + if actualStr != expectedStr { + t.Fatalf("got the wrong hash: expected %s, got %s", expectedStr, actualStr) + } + }) + + } +} diff --git a/sum.go b/sum.go index c68e160..bfc2043 100644 --- a/sum.go +++ b/sum.go @@ -185,7 +185,7 @@ func registerNonStdlibHashFuncs() { RegisterHashFunc(SHA3_384, sumSHA3_384) RegisterHashFunc(SHA3_512, sumSHA3_512) - RegisterHashFunc(MURMUR3, sumMURMUR3) + RegisterHashFunc(MURMUR3_128, sumMURMUR3) RegisterHashFunc(SHAKE_128, sumSHAKE128) RegisterHashFunc(SHAKE_256, sumSHAKE256) diff --git a/sum_test.go b/sum_test.go index d5123fa..296be3a 100644 --- a/sum_test.go +++ b/sum_test.go @@ -47,7 +47,7 @@ var sumTestCases = []SumTestCase{ {BLAKE2B_MIN, -1, "foo", "81e4020152"}, {BLAKE2B_MIN, 1, "foo", "81e4020152"}, {BLAKE2S_MAX, 32, "foo", "e0e4022008d6cad88075de8f192db097573d0e829411cd91eb6ec65e8fc16c017edfdb74"}, - {MURMUR3, 4, "beep boop", "2204243ddb9e"}, + {MURMUR3_128, 4, "beep boop", "2204243ddb9e"}, {KECCAK_256, 32, "foo", "1b2041b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d"}, {KECCAK_512, -1, "beep boop", "1d40e161c54798f78eba3404ac5e7e12d27555b7b810e7fd0db3f25ffa0c785c438331b0fbb6156215f69edf403c642e5280f4521da9bd767296ec81f05100852e78"}, {SHAKE_128, 32, "foo", "1820f84e95cb5fbd2038863ab27d3cdeac295ad2d4ab96ad1f4b070c0bf36078ef08"},