

MD6
MD6 is a pure Go implementation of the MD6 cryptographic hash function, designed by Ronald L. Rivest and submitted to the NIST SHA-3 competition. This package produces bit-identical output to the original C reference implementation and follows a similar API as Go's standard crypto/ hash packages such as crypto/sha256.
This code is currently a POC and needs optimized for better performance.
Installation & Import
To install, run:
go get github.com/cyclone-github/md6
Then import the package in your project:
import "github.com/cyclone-github/md6"
Usage
One-Shot Hashing
package main
import (
"encoding/hex"
"fmt"
"github.com/cyclone-github/md6"
)
func main() {
// md6-256 (returns [32]byte)
digest := md6.Sum256([]byte("hello world"))
fmt.Println(hex.EncodeToString(digest[:]))
// md6-512 (returns [64]byte)
digest512 := md6.Sum512([]byte("hello world"))
fmt.Println(hex.EncodeToString(digest512[:]))
// any size from 1–512 bits (returns []byte)
digest128 := md6.Sum(128, []byte("hello world"))
fmt.Println(hex.EncodeToString(digest128))
}
Streaming (hash.Hash Interface)
package main
import (
"encoding/hex"
"fmt"
"io"
"os"
"github.com/cyclone-github/md6"
)
func main() {
// stream multiple writes
h := md6.New256()
h.Write([]byte("hello"))
h.Write([]byte(" "))
h.Write([]byte("world"))
fmt.Println(hex.EncodeToString(h.Sum(nil)))
// hash a file
f, _ := os.Open("document.pdf")
defer f.Close()
h2 := md6.New256()
io.Copy(h2, f)
fmt.Printf("MD6-256: %x\n", h2.Sum(nil))
}
Mid-Stream Sum
Sum(nil) does not modify internal state — you can call it mid-stream, continue writing, and call it again:
h := md6.New256()
h.Write([]byte("hello"))
partial := h.Sum(nil) // md6-256("hello")
h.Write([]byte(" world"))
full := h.Sum(nil) // md6-256("hello world")
Reset and Reuse
h := md6.New256()
h.Write([]byte("first message"))
hash1 := h.Sum(nil)
h.Reset()
h.Write([]byte("second message"))
hash2 := h.Sum(nil)
Keyed Hashing
MD6 natively supports a key (salt) of up to 64 bytes, incorporated directly into every compression call:
key := []byte("my-secret-key")
h := md6.NewKeyed(256, key)
h.Write([]byte("message"))
mac := h.Sum(nil)
Full Parameter Control
h := md6.NewFull(
256, // digest size in bits (1–512)
[]byte("key"), // key (nil for no key, up to 64 bytes)
64, // L: mode parameter (0 = sequential, 64 = fully hierarchical)
0, // r: rounds (0 = use default: 40 + d/4)
)
Overview
MD6 was designed by Ronald L. Rivest (the "R" in RSA) at MIT and submitted to the NIST SHA-3 competition in 2008. It advanced to the first round of evaluation before being withdrawn by Rivest due to concerns about provable security at reduced round counts needed for competitive performance. The MD6 specification, C reference implementation, and supporting paper are available at (Way Back Machine) groups.csail.mit.edu/cis/md6.
This package is a complete rewrite in Pure Go. It was ported by studying the C reference source (md6.h, md6_compress.c, md6_mode.c) alongside the NIST submission paper, then implemented and tested in Go.
Features
Available Functions
Constructors
All constructors return hash.Hash.
-
New() hash.Hash
Returns an MD6-256 digest (default).
-
New128() hash.Hash
Returns an MD6-128 digest.
-
New224() hash.Hash
Returns an MD6-224 digest.
-
New256() hash.Hash
Returns an MD6-256 digest.
-
New384() hash.Hash
Returns an MD6-384 digest.
-
New512() hash.Hash
Returns an MD6-512 digest.
-
NewSize(bits int) hash.Hash
Returns an MD6 digest of the given size in bits (1–512).
-
NewKeyed(bits int, key []byte) hash.Hash
Returns a new keyed (salted) hash with the given digest size and key (up to 64 bytes).
-
NewFull(bits int, key []byte, L, r int) hash.Hash
Returns a new hash with full control over all MD6 parameters. Set r to 0 for the default round count.
One-Shot Functions
-
Sum256(data []byte) [32]byte
Returns an MD6-256 hash of data.
-
Sum512(data []byte) [64]byte
Returns an MD6-512 hash of data.
-
Sum(bits int, data []byte) []byte
Returns an MD6 hash of data with the given digest size in bits (1–512).
hash.Hash Methods
-
Write(p []byte) (int, error)
Absorbs data into the hash state. Always returns len(p), nil.
-
Sum(b []byte) []byte
Appends the current hash to b without changing internal state.
-
Reset()
Resets the hash to its initial state with the same parameters.
-
Size() int
Returns the output size in bytes.
-
BlockSize() int
Returns the underlying block size (512 bytes).
Constants
- Size = 32 — MD6-256 default output size in bytes
- Size128 = 16 — MD6-128 output size in bytes
- Size224 = 28 — MD6-224 output size in bytes
- Size256 = 32 — MD6-256 output size in bytes
- Size384 = 48 — MD6-384 output size in bytes
- Size512 = 64 — MD6-512 output size in bytes
- BlockSize = 512 — compression block size in bytes
Comparison to Go's stdlib "crypto/sha256"
This MD6 package closely follows the design of Go's standard crypto/ hash packages:
-
API Parity:
Both use constructors that return hash.Hash (e.g. sha256.New() / md6.New256()), one-shot functions that return fixed-size arrays (e.g. sha256.Sum256() / md6.Sum256()), and the same Write / Sum / Reset interface, making the MD6 API immediately familiar to Go developers.
-
Streaming Support:
Just like crypto/sha256, data can be written incrementally via Write() and finalized with Sum(). The result is identical regardless of how the input is chunked.
-
Additional Capabilities:
MD6 supports features beyond standard hash packages: arbitrary digest sizes (1–512 bits) via NewSize() / Sum(), built-in keyed hashing via NewKeyed(), and full parameter control via NewFull() for tuning tree depth and round count.
Algorithm Details
Compression Function
Each compression call takes 89 64-bit words (712 bytes) as input and produces 16 64-bit words (128 bytes) of output:
Input block N (89 words):
N[ 0..14] Q 15 words Constant vector (fractional bits of sqrt(6))
N[15..22] K 8 words Key / salt (zero-padded)
N[23] U 1 word Node ID: (level << 56) | index
N[24] V 1 word Control word: (r, L, z, p, keylen, d)
N[25..88] B 64 words Data payload
The compression loop runs r rounds (default r = 40 + d/4). Each round applies 16 steps of NLFSR computation with quadratic feedback terms, bitwise shifts, and XOR operations. The round constant S evolves after each round: S = (S << 1) ^ (S >> 63) ^ (S & Smask).
Tree Mode
With the default L = 64, MD6 operates as a 4-ary tree:
Level 1: [block0] [block1] [block2] ... [blockN] ← 512-byte input blocks
↓ ↓ ↓ ↓
Level 2: [ C0 , C1 , C2 , C3 ] ← 4 × 128-byte chunks per block
↓
Level 3: [ ... ] ← continues upward
↓
Root: [final compression with z=1] ← last d bits = hash output
Each non-root compression uses z = 0. The single root compression uses z = 1, and the final hash value is the last d bits of its 1024-bit output.
Validation
All 113 tests pass. Run them with:
go test -v ./...
Run benchmarks with:
go test -bench=. -benchmem ./...
References
License
The original MD6 C reference implementation is released under the MIT License by Ronald L. Rivest. This Go port is released under the same license. See the LICENSE file for details.