Source code for nix_daemon_protocol.nar_hash

"""NARHash — base16-encoded NAR SHA256 hash as a WireString."""

from __future__ import annotations

from .wire_scalar import WireScalar

# What `Hash::to_string(HashFormat::Base16, true)` of Nix puts in front of the
# digest. The database of a local store holds that form, at
# `local-store.cc:677`, and the wire does not.
ALGORITHMS = ("blake3", "md5", "sha1", "sha256", "sha512")


[docs] class NARHash(WireScalar): """Base16-encoded NAR SHA256 hash — no algorithm prefix on wire. `WorkerProto::Serialise<UnkeyedValidPathInfo>::write` at `worker-protocol.cc:356` of Nix writes `narHash.to_string(HashFormat::Base16, false)`, and `false` is what leaves the name of the algorithm out. **A source of this value may still carry the name, and this takes it off.** `LocalStore` of Nix writes `sha256:<digest>` into the `narHash` column of its database, so a fast path that reads that column and answers a client sends a string that `nix-daemon` never sends. The client accepts it, because `Hash::parseAny` at `worker-protocol.cc:339` reads both forms, so nothing failed and the two answers differed. `tests/parity` of pynixd is what saw it. """ def __new__(cls, value: str = "", *, hash: str | None = None) -> NARHash: # noqa: A002 if hash is not None: if value and value != hash: raise ValueError("NARHash value and hash disagree") value = hash head, separator, digest = value.partition(":") if separator and head in ALGORITHMS: value = digest return super().__new__(cls, value) @property def hash(self) -> str: """Compatibility spelling for the canonical string value.""" return self