Repository: go
Issue: hash: add Clone
Labels: Proposal,Proposal-Accepted,Proposal-Crypto

There isn't a general way to clone the state of a hash.Hash, but #20573 introduced the concept of hash.Hash implementations also implementing [encoding.BinaryMarshaler](https://pkg.go.dev/encoding#BinaryMarshaler) and [encoding.BinaryUnmarshaler](https://pkg.go.dev/encoding#BinaryUnmarshaler), and the hash.Hash docs commit our implementations to doing that.

> Hash implementations in the standard library (e.g. [hash/crc32](https://pkg.go.dev/hash/crc32) and [crypto/sha256](https://pkg.go.dev/crypto/sha256)) implement the [encoding.BinaryMarshaler](https://pkg.go.dev/encoding#BinaryMarshaler) and [encoding.BinaryUnmarshaler](https://pkg.go.dev/encoding#BinaryUnmarshaler) interfaces.

That allows cloning the hash state without recomputing it, as done in HMAC.

https://github.com/golang/go/blob/db40d1a4c434e319e70af87ef1024a211a9e5a98/src/crypto/hmac/hmac.go#L96-L103

However, it's obscure and pretty clunky to use.

I propose we add a `hash.Clone` helper function.

```
package hash

// Clone returns a separate Hash instance with the same state as h.
//
// h must implement encoding.BinaryMarshaler and encoding.BinaryUnmarshaler,
// or be provided by the Go standard library. Otherwise, Clone returns an error.
func Clone(h Hash) (Hash, error)
```

In practice, we should only fallback to BinaryMarshaler + BinaryUnmarshaler for the general case, while for standard library implementations we can do an undocumented interface upgrade to `interface { Clone() Hash }`. In that sense, `hash.Clone` is a way to hide the interface upgrade as a more discoverable and easier to use function.

(Yet another example of why we should be returning concrete types everywhere rather than interfaces.)

### CloneXOF

If #69518 is accepted, I propose we also add hash.CloneXOF.

```
package hash

// CloneXOF returns a separate XOF instance with the same state as h.
//
// h must implement encoding.BinaryMarshaler and encoding.BinaryUnmarshaler,
// or be provided by the Go standard library or by the golang.org/x/crypto module
// (starting at version v0.x.y). Otherwise, Clone returns an error.
func CloneXOF(h XOF) (XOF, error)
```

None of our XOFs actually implement BinaryMarshaler + BinaryUnmarshaler, but they have their own interface methods `Clone() ShakeHash` and `Clone() XOF` that each return an interface. I can't really think of a way to use them from CloneXOF, so instead we can add hidden methods `CloneXOF() hash.XOF` and interface upgrade to them.

As we look at moving packages from x/crypto to the standard library (#65269) we should switch x/crypto/sha3 and x/crypto/blake2[bs] from returning interfaces to returning concrete types, at least for XOFs. Then they can have a `Clone()` method that returns a concrete type, and a `CloneXOF()` method that returns a hash.XOF interface and enables `hash.CloneXOF`.

(If anyone has better ideas for how to make this less redundant, I would welcome them. I considered and rejected using reflect to call the existing Clone methods because hash is a pretty core package. This sort of interface-method-that-needs-to-return-a-value-implementing-said-interface scenarios are always annoying.)

/cc @golang/security @cpu @qmuntal (who filed something similar in #69293, as I found while searching refs for this)