about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2023-01-26T13·18+0100
committerclbot <clbot@tvl.fyi>2023-01-27T14·06+0000
commita94a1434cc2a57b330a2ad6f310573fb70e15e8a (patch)
treebb9b4c32c03252ae32e409fe9bd893de19568f21
parentbda5fc58d01d7513180da4456eb279a33f76bc1c (diff)
fix(tvix/cli): handle SRI hashes in outputHash r/5772
Instead of being called with `md5`, `sha1`, `sha256` or `sha512`,
`fetchurl.nix` (from corepkgs / `<nix`) can also be called with a `hash`
attribute, being an SRI hash.

In that case, `builtin.derivation` is called with `outputHashAlgo` being
an empty string, and `outputHash` being an SRI hash string.

In other cases, an SRI hash is passed as outputHash, but outputHashAlgo
is set too.

Nix does modify these values in (single, fixed) output specification it
serializes to ATerm, but keeps it unharmed in `env`.

Move this into a construct_output_hash helper function, that can be
tested better in isolation.

Change-Id: Id9d716a119664c44ea7747540399966752e20187
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7933
Reviewed-by: tazjin <tazjin@tvl.su>
Autosubmit: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
-rw-r--r--tvix/Cargo.lock129
-rw-r--r--tvix/Cargo.nix318
-rw-r--r--tvix/cli/Cargo.toml6
-rw-r--r--tvix/cli/src/derivation.rs126
-rw-r--r--tvix/cli/src/errors.rs32
5 files changed, 573 insertions, 38 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock
index c0b82eff86..1eb25afbe8 100644
--- a/tvix/Cargo.lock
+++ b/tvix/Cargo.lock
@@ -173,6 +173,15 @@ dependencies = [
 
 [[package]]
 name = "base64"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "base64"
 version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
@@ -215,17 +224,38 @@ dependencies = [
  "cc",
  "cfg-if",
  "constant_time_eq",
- "digest",
+ "digest 0.10.6",
  "rayon",
 ]
 
 [[package]]
 name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "block-buffer"
 version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.6",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
 ]
 
 [[package]]
@@ -235,6 +265,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
 
 [[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
 name = "byteorder"
 version = "1.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -495,7 +531,7 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.6",
  "typenum",
 ]
 
@@ -523,11 +559,20 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
 
 [[package]]
 name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
+[[package]]
+name = "digest"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
 dependencies = [
- "block-buffer",
+ "block-buffer 0.10.3",
  "crypto-common",
  "subtle",
 ]
@@ -617,6 +662,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
 name = "fastcdc"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -770,6 +821,15 @@ dependencies = [
 
 [[package]]
 name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
 version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
@@ -857,6 +917,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "hex"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
+
+[[package]]
 name = "http"
 version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1295,6 +1361,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
 [[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
 name = "os_str_bytes"
 version = "6.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1912,6 +1984,30 @@ dependencies = [
 ]
 
 [[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sha2"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug",
+]
+
+[[package]]
 name = "sha2"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1919,7 +2015,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest",
+ "digest 0.10.6",
 ]
 
 [[package]]
@@ -1983,6 +2079,21 @@ dependencies = [
 ]
 
 [[package]]
+name = "ssri"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9cec0d388f39fbe79d7aa600e8d38053bf97b1bc8d350da7c0ba800d0f423f2"
+dependencies = [
+ "base64 0.10.1",
+ "digest 0.8.1",
+ "hex",
+ "serde",
+ "sha-1",
+ "sha2 0.8.2",
+ "thiserror",
+]
+
+[[package]]
 name = "static_assertions"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2302,7 +2413,7 @@ dependencies = [
  "async-stream",
  "async-trait",
  "axum",
- "base64",
+ "base64 0.13.1",
  "bytes",
  "futures-core",
  "futures-util",
@@ -2498,11 +2609,15 @@ version = "0.1.0"
 dependencies = [
  "aho-corasick",
  "clap 4.0.32",
+ "data-encoding",
  "dirs",
  "rustyline",
  "smol_str",
+ "ssri",
+ "test-case",
  "tvix-derivation",
  "tvix-eval",
+ "tvix-store-bin",
 ]
 
 [[package]]
@@ -2514,7 +2629,7 @@ dependencies = [
  "glob",
  "serde",
  "serde_json",
- "sha2",
+ "sha2 0.10.6",
  "test-case",
  "test-generator",
  "thiserror",
diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix
index 27cc40d2bb..3160d02132 100644
--- a/tvix/Cargo.nix
+++ b/tvix/Cargo.nix
@@ -635,7 +635,24 @@ rec {
         ];
 
       };
-      "base64" = rec {
+      "base64 0.10.1" = rec {
+        crateName = "base64";
+        version = "0.10.1";
+        edition = "2015";
+        sha256 = "13k6bvd3n6dm7jqn9x918w65dd9xhx454bqphbnv0bkd6n9dj98b";
+        authors = [
+          "Alice Maz <alice@alicemaz.com>"
+          "Marshall Pierce <marshall@mpierce.org>"
+        ];
+        dependencies = [
+          {
+            name = "byteorder";
+            packageId = "byteorder";
+          }
+        ];
+
+      };
+      "base64 0.13.1" = rec {
         crateName = "base64";
         version = "0.13.1";
         edition = "2018";
@@ -743,7 +760,7 @@ rec {
           }
           {
             name = "digest";
-            packageId = "digest";
+            packageId = "digest 0.10.6";
             optional = true;
             features = [ "mac" ];
           }
@@ -768,7 +785,7 @@ rec {
         };
         resolvedDefaultFeatures = [ "default" "digest" "rayon" "std" ];
       };
-      "block-buffer" = rec {
+      "block-buffer 0.10.3" = rec {
         crateName = "block-buffer";
         version = "0.10.3";
         edition = "2018";
@@ -779,7 +796,52 @@ rec {
         dependencies = [
           {
             name = "generic-array";
-            packageId = "generic-array";
+            packageId = "generic-array 0.14.6";
+          }
+        ];
+
+      };
+      "block-buffer 0.7.3" = rec {
+        crateName = "block-buffer";
+        version = "0.7.3";
+        edition = "2015";
+        sha256 = "12v8wizynqin0hqf140kmp9s38q223mp1b0hkqk8j5pk8720v560";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-padding";
+            packageId = "block-padding";
+          }
+          {
+            name = "byte-tools";
+            packageId = "byte-tools";
+          }
+          {
+            name = "byteorder";
+            packageId = "byteorder";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "generic-array";
+            packageId = "generic-array 0.12.4";
+          }
+        ];
+
+      };
+      "block-padding" = rec {
+        crateName = "block-padding";
+        version = "0.1.5";
+        edition = "2015";
+        sha256 = "1xbkmysiz23vimd17rnsjpw9bgjxipwfslwyygqlkx4in3dxwygs";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "byte-tools";
+            packageId = "byte-tools";
           }
         ];
 
@@ -795,6 +857,16 @@ rec {
         features = { };
         resolvedDefaultFeatures = [ "default" ];
       };
+      "byte-tools" = rec {
+        crateName = "byte-tools";
+        version = "0.3.1";
+        edition = "2015";
+        sha256 = "1mqi29wsm8njpl51pfwr31wmpzs5ahlcb40wsjyd92l90ixcmdg3";
+        authors = [
+          "RustCrypto Developers"
+        ];
+
+      };
       "byteorder" = rec {
         crateName = "byteorder";
         version = "1.4.3";
@@ -1503,7 +1575,7 @@ rec {
         dependencies = [
           {
             name = "generic-array";
-            packageId = "generic-array";
+            packageId = "generic-array 0.14.6";
             features = [ "more_lengths" ];
           }
           {
@@ -1564,7 +1636,7 @@ rec {
         ];
 
       };
-      "digest" = rec {
+      "digest 0.10.6" = rec {
         crateName = "digest";
         version = "0.10.6";
         edition = "2018";
@@ -1575,7 +1647,7 @@ rec {
         dependencies = [
           {
             name = "block-buffer";
-            packageId = "block-buffer";
+            packageId = "block-buffer 0.10.3";
             optional = true;
           }
           {
@@ -1604,6 +1676,26 @@ rec {
         };
         resolvedDefaultFeatures = [ "alloc" "block-buffer" "core-api" "default" "mac" "std" "subtle" ];
       };
+      "digest 0.8.1" = rec {
+        crateName = "digest";
+        version = "0.8.1";
+        edition = "2015";
+        sha256 = "1madjl27f3kj5ql7kwgvb9c8b7yb7bv7yfgx7rqzj4i3fp4cil7k";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "generic-array";
+            packageId = "generic-array 0.12.4";
+          }
+        ];
+        features = {
+          "blobby" = [ "dep:blobby" ];
+          "dev" = [ "blobby" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
       "dirs" = rec {
         crateName = "dirs";
         version = "4.0.0";
@@ -1806,6 +1898,16 @@ rec {
         ];
         features = { };
       };
+      "fake-simd" = rec {
+        crateName = "fake-simd";
+        version = "0.1.2";
+        edition = "2015";
+        sha256 = "1vfylvk4va2ivqx85603lyqqp0zk52cgbs4n5nfbbbqx577qm2p8";
+        authors = [
+          "The Rust-Crypto Project Developers"
+        ];
+
+      };
       "fastcdc" = rec {
         crateName = "fastcdc";
         version = "2.0.0";
@@ -2214,7 +2316,27 @@ rec {
         ];
 
       };
-      "generic-array" = rec {
+      "generic-array 0.12.4" = rec {
+        crateName = "generic-array";
+        version = "0.12.4";
+        edition = "2015";
+        sha256 = "1gfpay78vijl9vrwl1k9v7fbvbhkhcmnrk4kfg9l6x24y4s9zpzz";
+        libName = "generic_array";
+        authors = [
+          "Bartłomiej Kamiński <fizyk20@gmail.com>"
+          "Aaron Trent <novacrazy@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "typenum";
+            packageId = "typenum";
+          }
+        ];
+        features = {
+          "serde" = [ "dep:serde" ];
+        };
+      };
+      "generic-array 0.14.6" = rec {
         crateName = "generic-array";
         version = "0.14.6";
         edition = "2015";
@@ -2478,6 +2600,16 @@ rec {
         };
         resolvedDefaultFeatures = [ "default" ];
       };
+      "hex" = rec {
+        crateName = "hex";
+        version = "0.3.2";
+        edition = "2015";
+        sha256 = "0xsdcjiik5j750j67zk42qdnmm4ahirk3gmkmcqgq7qls2jjcl40";
+        authors = [
+          "KokaKiwi <kokakiwi@kokakiwi.net>"
+        ];
+        features = { };
+      };
       "http" = rec {
         crateName = "http";
         version = "0.2.8";
@@ -3749,6 +3881,16 @@ rec {
         ];
 
       };
+      "opaque-debug" = rec {
+        crateName = "opaque-debug";
+        version = "0.2.3";
+        edition = "2015";
+        sha256 = "172j6bs8ndclqxa2m64qc0y1772rr73g4l9fg2svscgicnbfff98";
+        authors = [
+          "RustCrypto Developers"
+        ];
+
+      };
       "os_str_bytes" = rec {
         crateName = "os_str_bytes";
         version = "6.4.1";
@@ -5514,7 +5656,51 @@ rec {
         };
         resolvedDefaultFeatures = [ "serde" ];
       };
-      "sha2" = rec {
+      "sha-1" = rec {
+        crateName = "sha-1";
+        version = "0.8.2";
+        edition = "2015";
+        sha256 = "1pv387q0r7llk2cqzyq0nivzvkgqgzsiygqzlv7b68z9xl5lvngp";
+        libName = "sha1";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer 0.7.3";
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.8.1";
+          }
+          {
+            name = "fake-simd";
+            packageId = "fake-simd";
+          }
+          {
+            name = "opaque-debug";
+            packageId = "opaque-debug";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest 0.8.1";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha1-asm" ];
+          "asm-aarch64" = [ "asm" "libc" ];
+          "default" = [ "std" ];
+          "libc" = [ "dep:libc" ];
+          "sha1-asm" = [ "dep:sha1-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+      "sha2 0.10.6" = rec {
         crateName = "sha2";
         version = "0.10.6";
         edition = "2018";
@@ -5534,13 +5720,13 @@ rec {
           }
           {
             name = "digest";
-            packageId = "digest";
+            packageId = "digest 0.10.6";
           }
         ];
         devDependencies = [
           {
             name = "digest";
-            packageId = "digest";
+            packageId = "digest 0.10.6";
             features = [ "dev" ];
           }
         ];
@@ -5554,6 +5740,49 @@ rec {
         };
         resolvedDefaultFeatures = [ "default" "std" ];
       };
+      "sha2 0.8.2" = rec {
+        crateName = "sha2";
+        version = "0.8.2";
+        edition = "2015";
+        sha256 = "0s9yddvyg6anaikdl86wmwfim25c0d4m0xq0y2ghs34alxpg8mm2";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "block-buffer";
+            packageId = "block-buffer 0.7.3";
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.8.1";
+          }
+          {
+            name = "fake-simd";
+            packageId = "fake-simd";
+          }
+          {
+            name = "opaque-debug";
+            packageId = "opaque-debug";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest 0.8.1";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" "libc" ];
+          "default" = [ "std" ];
+          "libc" = [ "dep:libc" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
       "sharded-slab" = rec {
         crateName = "sharded-slab";
         version = "0.1.4";
@@ -5730,6 +5959,51 @@ rec {
         features = { };
         resolvedDefaultFeatures = [ "all" ];
       };
+      "ssri" = rec {
+        crateName = "ssri";
+        version = "7.0.0";
+        edition = "2018";
+        sha256 = "1wi3yk801a0bgkd51ly83dxzjfq5726hwq5asxwvx7zki39w1km9";
+        authors = [
+          "Kat Marchán <kzm@zkat.tech>"
+        ];
+        dependencies = [
+          {
+            name = "base64";
+            packageId = "base64 0.10.1";
+          }
+          {
+            name = "digest";
+            packageId = "digest 0.8.1";
+          }
+          {
+            name = "hex";
+            packageId = "hex";
+          }
+          {
+            name = "serde";
+            packageId = "serde";
+            optional = true;
+          }
+          {
+            name = "sha-1";
+            packageId = "sha-1";
+          }
+          {
+            name = "sha2";
+            packageId = "sha2 0.8.2";
+          }
+          {
+            name = "thiserror";
+            packageId = "thiserror";
+          }
+        ];
+        features = {
+          "default" = [ "serde" ];
+          "serde" = [ "dep:serde" ];
+        };
+        resolvedDefaultFeatures = [ "default" "serde" ];
+      };
       "static_assertions" = rec {
         crateName = "static_assertions";
         version = "1.1.0";
@@ -6635,7 +6909,7 @@ rec {
           }
           {
             name = "base64";
-            packageId = "base64";
+            packageId = "base64 0.13.1";
           }
           {
             name = "bytes";
@@ -7440,6 +7714,10 @@ rec {
             features = [ "derive" "env" ];
           }
           {
+            name = "data-encoding";
+            packageId = "data-encoding";
+          }
+          {
             name = "dirs";
             packageId = "dirs";
           }
@@ -7452,6 +7730,10 @@ rec {
             packageId = "smol_str";
           }
           {
+            name = "ssri";
+            packageId = "ssri";
+          }
+          {
             name = "tvix-derivation";
             packageId = "tvix-derivation";
           }
@@ -7459,6 +7741,16 @@ rec {
             name = "tvix-eval";
             packageId = "tvix-eval";
           }
+          {
+            name = "tvix-store-bin";
+            packageId = "tvix-store-bin";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "test-case";
+            packageId = "test-case";
+          }
         ];
 
       };
@@ -7492,7 +7784,7 @@ rec {
           }
           {
             name = "sha2";
-            packageId = "sha2";
+            packageId = "sha2 0.10.6";
           }
           {
             name = "thiserror";
diff --git a/tvix/cli/Cargo.toml b/tvix/cli/Cargo.toml
index 45ed05e089..83796855b6 100644
--- a/tvix/cli/Cargo.toml
+++ b/tvix/cli/Cargo.toml
@@ -12,6 +12,12 @@ tvix-eval = { path = "../eval" }
 tvix-derivation = { path = "../derivation" }
 rustyline = "10.0.0"
 clap = { version = "4.0", features = ["derive", "env"] }
+tvix-store-bin = { path = "../store" }
 dirs = "4.0.0"
 smol_str = "0.1"
 aho-corasick = "0.7"
+ssri = "7.0.0"
+data-encoding = "2.3.3"
+
+[dev-dependencies]
+test-case = "2.2.2"
diff --git a/tvix/cli/src/derivation.rs b/tvix/cli/src/derivation.rs
index d57503a696..122330b963 100644
--- a/tvix/cli/src/derivation.rs
+++ b/tvix/cli/src/derivation.rs
@@ -1,5 +1,6 @@
 //! Implements `builtins.derivation`, the core of what makes Nix build packages.
 
+use data_encoding::BASE64;
 use std::cell::RefCell;
 use std::collections::{btree_map, BTreeSet};
 use std::rc::Rc;
@@ -79,6 +80,82 @@ fn populate_inputs<I: IntoIterator<Item = String>>(
     }
 }
 
+/// Due to the support for SRI hashes, and how these are passed along to
+/// builtins.derivation, outputHash and outputHashAlgo can have values which
+/// need to be further modified before constructing the Derivation struct.
+///
+/// If outputHashAlgo is an SRI hash, outputHashAlgo must either be an empty
+/// string, or the hash algorithm as specified in the (single) SRI (entry).
+/// SRI strings with multiple hash algorithms are not supported.
+///
+/// In case an SRI string was used, the (single) fixed output is populated
+/// with the hash algo name, and the hash digest is populated with the
+/// (lowercase) hex encoding of the digest.
+///
+/// These values are only rewritten for the outputs, not what's passed to env.
+fn construct_output_hash(digest: &str, algo: &str, hash_mode: Option<&str>) -> Result<Hash, Error> {
+    let sri_parsed = digest.parse::<ssri::Integrity>();
+    // SRI strings can embed multiple hashes with different algos, but that's probably not supported
+
+    let (digest, algo): (String, String) = match sri_parsed {
+        Err(e) => {
+            // unable to parse as SRI, but algo not set
+            if algo.is_empty() {
+                // InvalidSRIString doesn't implement PartialEq, but our error does
+                return Err(Error::InvalidSRIString(e.to_string()));
+            }
+
+            // algo is set. Assume the digest is set to some nixbase32.
+            // TODO: more validation here
+
+            (digest.to_string(), algo.to_string())
+        }
+        Ok(sri_parsed) => {
+            // We don't support more than one SRI hash
+            if sri_parsed.hashes.len() != 1 {
+                return Err(Error::UnsupportedSRIMultiple(sri_parsed.hashes.len()).into());
+            }
+
+            // grab the first (and only hash)
+            let sri_parsed_hash = &sri_parsed.hashes[0];
+
+            // ensure the algorithm in the SRI is supported
+            if !(sri_parsed_hash.algorithm == ssri::Algorithm::Sha1
+                || sri_parsed_hash.algorithm == ssri::Algorithm::Sha256
+                || sri_parsed_hash.algorithm == ssri::Algorithm::Sha512)
+            {
+                Error::UnsupportedSRIAlgo(sri_parsed_hash.algorithm.to_string());
+            }
+
+            // if algo is set, it needs to match what the SRI says
+            if !algo.is_empty() && algo != sri_parsed_hash.algorithm.to_string() {
+                return Err(Error::ConflictingSRIHashAlgo(
+                    algo.to_string(),
+                    sri_parsed_hash.algorithm.to_string(),
+                ));
+            }
+
+            // the digest comes base64-encoded. We need to decode, and re-encode as hexlower.
+            match BASE64.decode(sri_parsed_hash.digest.as_bytes()) {
+                Err(e) => return Err(Error::InvalidSRIDigest(e).into()),
+                Ok(sri_digest) => (
+                    data_encoding::HEXLOWER.encode(&sri_digest),
+                    sri_parsed_hash.algorithm.to_string(),
+                ),
+            }
+        }
+    };
+
+    // mutate the algo string a bit more, depending on hashMode
+    let algo = match hash_mode {
+        None | Some("flat") => algo,
+        Some("recursive") => format!("r:{}", algo),
+        Some(other) => return Err(Error::InvalidOutputHashMode(other.to_string()).into()),
+    };
+
+    Ok(Hash { algo, digest })
+}
+
 /// Populate the output configuration of a derivation based on the
 /// parameters passed to the call, flipping the required
 /// parameters for a fixed-output derivation if necessary.
@@ -102,6 +179,12 @@ fn populate_output_configuration(
                     .as_str()
                     .to_string();
 
+                let digest_str = hash
+                    .force(vm)?
+                    .coerce_to_string(CoercionKind::Strong, vm)?
+                    .as_str()
+                    .to_string();
+
                 let hash_mode = match hash_mode {
                     None => None,
                     Some(mode) => Some(
@@ -112,23 +195,12 @@ fn populate_output_configuration(
                     ),
                 };
 
-                let algo = match hash_mode.as_deref() {
-                    None | Some("flat") => algo,
-                    Some("recursive") => format!("r:{}", algo),
-                    Some(other) => {
-                        return Err(Error::InvalidOutputHashMode(other.to_string()).into())
-                    }
-                };
-
-                out.hash = Some(Hash {
-                    algo,
-
-                    digest: hash
-                        .force(vm)?
-                        .coerce_to_string(CoercionKind::Strong, vm)?
-                        .as_str()
-                        .to_string(),
-                });
+                // construct out.hash
+                out.hash = Some(construct_output_hash(
+                    &digest_str,
+                    &algo,
+                    hash_mode.as_deref(),
+                )?);
             }
         },
 
@@ -371,6 +443,7 @@ pub use derivation_builtins::builtins as derivation_builtins;
 #[cfg(test)]
 mod tests {
     use super::*;
+    use test_case::test_case;
     use tvix_eval::observer::NoOpObserver;
 
     static mut OBSERVER: NoOpObserver = NoOpObserver {};
@@ -576,4 +649,23 @@ mod tests {
             vec!["--foo".to_string(), "42".to_string(), "--bar".to_string()]
         );
     }
+
+    #[test_case(
+        "sha256-swapHA/ZO8QoDPwumMt6s5gf91oYe+oyk4EfRSyJqMg=", "sha256", Some("flat"),
+        Ok(Hash { algo: "sha256".to_string(), digest: "b306a91c0fd93bc4280cfc2e98cb7ab3981ff75a187bea3293811f452c89a8c8".to_string() });
+        "sha256 and SRI"
+    )]
+    #[test_case(
+        "sha256-s6JN6XqP28g1uYMxaVAQMLiXcDG8tUs7OsE3QPhGqzA=", "", Some("flat"),
+        Ok(Hash { algo: "sha256".to_string(), digest: "b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30".to_string() });
+        "SRI only"
+    )]
+    fn test_construct_output_hash(
+        digest: &str,
+        algo: &str,
+        hash_mode: Option<&str>,
+        result: Result<Hash, Error>,
+    ) {
+        assert_eq!(construct_output_hash(digest, algo, hash_mode), result);
+    }
 }
diff --git a/tvix/cli/src/errors.rs b/tvix/cli/src/errors.rs
index cbf8ed9457..5791c5332b 100644
--- a/tvix/cli/src/errors.rs
+++ b/tvix/cli/src/errors.rs
@@ -1,7 +1,7 @@
 use std::{error, fmt::Display, rc::Rc};
 use tvix_derivation::DerivationError;
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq)]
 pub enum Error {
     // Errors related to derivation construction
     DuplicateOutput(String),
@@ -10,6 +10,11 @@ pub enum Error {
     ShadowedOutput(String),
     InvalidDerivation(DerivationError),
     InvalidOutputHashMode(String),
+    UnsupportedSRIAlgo(String),
+    UnsupportedSRIMultiple(usize),
+    InvalidSRIDigest(data_encoding::DecodeError),
+    InvalidSRIString(String),
+    ConflictingSRIHashAlgo(String, String),
 }
 
 impl Display for Error {
@@ -38,6 +43,31 @@ impl Display for Error {
                 f,
                 "invalid output hash mode: '{mode}', only 'recursive' and 'flat` are supported"
             ),
+            Error::UnsupportedSRIAlgo(algo) => {
+                write!(
+                    f,
+                    "unsupported sri algorithm: {algo}, only sha1, sha256 or sha512 is supported"
+                )
+            }
+            Error::UnsupportedSRIMultiple(n) => {
+                write!(
+                    f,
+                    "invalid number of sri hashes in string ({n}), only one hash is supported"
+                )
+            }
+            Error::InvalidSRIDigest(err) => {
+                write!(f, "invalid sri digest: {}", err)
+            }
+            Error::InvalidSRIString(err) => {
+                write!(f, "failed to parse SRI string: {}", err)
+            }
+            Error::ConflictingSRIHashAlgo(algo, sri_algo) => {
+                write!(
+                    f,
+                    "outputHashAlgo is set to {}, but outputHash contains SRI with algo {}",
+                    algo, sri_algo
+                )
+            }
         }
     }
 }