Public release:

  - Salt assertions by default in GeneratorOptions.
  - Add RandomSaltGenerator::Default().
  - Add RandomSaltGenerator and return absl::StatusOr from Sa...
  - Plumb salt_generator through GeneratorOptions.
  - Plumb SaltGenerator through CreateManifestEncoder.
  - Support salting assertions in ManifestBoxEncoder.
  - Rename AssertionEncoder to AssertionCborEncoder and move ...

GitOrigin-RevId: f992ff2f29d57da04aa41ee75e83311f3edcd521
Change-Id: Iee438a9b0b480a92455fdfb86bfadca1fe9acd69
diff --git a/assertion/BUILD b/assertion/BUILD
index a7250bb..4ad9372 100644
--- a/assertion/BUILD
+++ b/assertion/BUILD
@@ -90,13 +90,11 @@
 )
 
 cc_library(
-    name = "assertion_encoder",
-    srcs = ["assertion_encoder.cc"],
-    hdrs = ["assertion_encoder.h"],
+    name = "assertion_cbor_encoder",
+    srcs = ["assertion_cbor_encoder.cc"],
+    hdrs = ["assertion_cbor_encoder.h"],
     deps = [
         "//cbor",
-        "//jumbf:box_builder",
-        "//jumbf:constants",
         "//proto:actions_assertion_cbor_proto",
         "//proto:actions_assertion_cc_proto",
         "//proto:assertion_cc_proto",
@@ -116,20 +114,16 @@
         "@abseil-cpp//absl/status:status_macros",
         "@abseil-cpp//absl/status:statusor",
         "@abseil-cpp//absl/strings:cord",
-        "@abseil-cpp//absl/strings:string_view",
     ],
 )
 
 cc_test(
-    name = "assertion_encoder_test",
-    srcs = ["assertion_encoder_test.cc"],
+    name = "assertion_cbor_encoder_test",
+    srcs = ["assertion_cbor_encoder_test.cc"],
     deps = [
-        ":assertion_encoder",
-        ":assertion_parser",
+        ":assertion_cbor_encoder",
         "//cbor",
         "//cbor:parse",
-        "//jumbf:box",
-        "//jumbf:parse",
         "//proto:actions_assertion_cbor_proto",
         "//proto:actions_assertion_cc_proto",
         "//proto:assertion_cc_proto",
@@ -142,13 +136,8 @@
         "//proto:ingredient_assertion_cbor_proto",
         "//proto:ingredient_assertion_cc_proto",
         "//testing:proto_test_utils",
-        "//testing:test_validation_tracker",
-        "@abseil-cpp//absl/log:check",
         "@abseil-cpp//absl/status",
-        "@abseil-cpp//absl/status:status_macros",
         "@abseil-cpp//absl/status:status_matchers",
-        "@abseil-cpp//absl/strings",
-        "@abseil-cpp//absl/strings:string_view",
         "@googletest//:gtest_main",
     ],
 )
diff --git a/assertion/assertion_encoder.cc b/assertion/assertion_cbor_encoder.cc
similarity index 76%
rename from assertion/assertion_encoder.cc
rename to assertion/assertion_cbor_encoder.cc
index 2599280..d36b710 100644
--- a/assertion/assertion_encoder.cc
+++ b/assertion/assertion_cbor_encoder.cc
@@ -13,19 +13,14 @@
 // limitations under the License.
 //
 
-#include "assertion/assertion_encoder.h"
-
-#include <utility>
+#include "assertion/assertion_cbor_encoder.h"
 
 #include "absl/base/no_destructor.h"
 #include "absl/status/status.h"
 #include "absl/status/status_macros.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/cord.h"
-#include "absl/strings/string_view.h"
 #include "cbor/options.h"
-#include "jumbf/box_builder.h"
-#include "jumbf/constants.h"
 #include "proto/actions_assertion.cbor.h"
 #include "proto/actions_assertion.pb.h"
 #include "proto/assertion.pb.h"
@@ -54,7 +49,7 @@
 
 }  // namespace
 
-absl::StatusOr<absl::Cord> StandardAssertionEncoder::ToCbor(
+absl::StatusOr<absl::Cord> StandardAssertionCborEncoder::ToCbor(
     const Assertion& assertion) const {
   if (assertion.has_actions()) {
     return EncodeProtoToCbor(assertion.actions(), options_);
@@ -80,18 +75,8 @@
   return absl::InvalidArgumentError("Assertion not supported.");
 }
 
-absl::StatusOr<jumbf::BuiltSuperBox> AssertionEncoder::ToJumbf(
-    const Assertion& assertion) const {
-  ABSL_ASSIGN_OR_RETURN(absl::Cord cbor_data, ToCbor(assertion));
-  jumbf::SuperBoxBuilder assertion_builder(
-      jumbf::kCborBoxTypeUuid, {.label = absl::Cord(assertion.label())});
-  ABSL_RETURN_IF_ERROR(
-      assertion_builder.AddContent(jumbf::kCborBoxType, std::move(cbor_data)));
-  return std::move(assertion_builder).Finalize();  // NOLINT: rvalue method call
-}
-
-const AssertionEncoder& GetAssertionEncoder() {
-  static const absl::NoDestructor<StandardAssertionEncoder> encoder;
+const AssertionCborEncoder& GetAssertionCborEncoder() {
+  static const absl::NoDestructor<StandardAssertionCborEncoder> encoder;
   return *encoder;
 }
 
diff --git a/assertion/assertion_cbor_encoder.h b/assertion/assertion_cbor_encoder.h
new file mode 100644
index 0000000..82d44fb
--- /dev/null
+++ b/assertion/assertion_cbor_encoder.h
@@ -0,0 +1,53 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef THIRD_PARTY_CREDENTIO_ASSERTION_ASSERTION_CBOR_ENCODER_H_
+#define THIRD_PARTY_CREDENTIO_ASSERTION_ASSERTION_CBOR_ENCODER_H_
+
+#include "absl/status/statusor.h"
+#include "absl/strings/cord.h"
+#include "cbor/options.h"
+#include "proto/assertion.pb.h"
+
+namespace credentio {
+
+// An encoder to serialize an Assertion's metadata to the CBOR format.
+class AssertionCborEncoder {
+ public:
+  virtual ~AssertionCborEncoder() = default;
+
+  virtual absl::StatusOr<absl::Cord> ToCbor(
+      const Assertion& assertion) const = 0;
+};
+
+class StandardAssertionCborEncoder : public AssertionCborEncoder {
+ public:
+  explicit StandardAssertionCborEncoder(
+      bool skip_validity_checks_for_test = false)
+      : options_({.skip_validation = skip_validity_checks_for_test}) {}
+  ~StandardAssertionCborEncoder() override = default;
+
+  absl::StatusOr<absl::Cord> ToCbor(const Assertion& assertion) const override;
+
+ private:
+  cbor::FromProtoOptions options_;
+};
+
+// Returns a singleton StandardAssertionCborEncoder.
+const AssertionCborEncoder& GetAssertionCborEncoder();
+
+}  // namespace credentio
+
+#endif  // THIRD_PARTY_CREDENTIO_ASSERTION_ASSERTION_CBOR_ENCODER_H_
diff --git a/assertion/assertion_encoder_test.cc b/assertion/assertion_cbor_encoder_test.cc
similarity index 79%
rename from assertion/assertion_encoder_test.cc
rename to assertion/assertion_cbor_encoder_test.cc
index 1faa065..e16d47e 100644
--- a/assertion/assertion_encoder_test.cc
+++ b/assertion/assertion_cbor_encoder_test.cc
@@ -13,27 +13,16 @@
 // limitations under the License.
 //
 
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 
-#include <memory>
-#include <optional>
 #include <string>
-#include <utility>
 
-#include "absl/log/check.h"
 #include "absl/status/status.h"
-#include "absl/status/status_macros.h"
 #include "absl/status/status_matchers.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
-#include "absl/strings/string_view.h"
-#include "assertion/assertion_parser.h"
 #include "cbor/cbor.h"
 #include "cbor/parse.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "jumbf/box.h"
-#include "jumbf/parse.h"
 #include "proto/actions_assertion.cbor.h"
 #include "proto/actions_assertion.pb.h"
 #include "proto/assertion.pb.h"
@@ -46,7 +35,6 @@
 #include "proto/ingredient_assertion.cbor.h"
 #include "proto/ingredient_assertion.pb.h"
 #include "testing/proto_test_utils.h"
-#include "testing/test_validation_tracker.h"
 
 namespace credentio {
 
@@ -59,18 +47,18 @@
 using ::credentio_testing::ParseTextProtoOrDie;
 using ::testing::HasSubstr;
 
-TEST(AssertionEncoderTest, UnsupportedAssertionToEncode) {
+TEST(AssertionCborEncoderTest, UnsupportedAssertionToEncode) {
   auto assertion = ParseTextProtoOrDie<Assertion>(R"pb(
     label: "unsupported_assertion"
   )pb");
 
-  StandardAssertionEncoder encoder;
+  StandardAssertionCborEncoder encoder;
   EXPECT_THAT(encoder.ToCbor(assertion),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        HasSubstr("Assertion not supported.")));
 }
 
-TEST(AssertionEncoderTest, EncodeAction) {
+TEST(AssertionCborEncoderTest, EncodeAction) {
   auto assertion = ParseTextProtoOrDie<Assertion>(R"pb(
     label: "c2pa.actions.v2"
     actions {
@@ -126,7 +114,7 @@
     }
   )pb");
 
-  StandardAssertionEncoder encoder;
+  StandardAssertionCborEncoder encoder;
   auto cbor = encoder.ToCbor(assertion);
   ABSL_ASSERT_OK(cbor);
   std::string cbor_string(*cbor);
@@ -140,7 +128,7 @@
   EXPECT_THAT(result, EqualsProto(assertion.actions()));
 }
 
-TEST(AssertionEncoderTest, EncodeBmffBasedHash) {
+TEST(AssertionCborEncoderTest, EncodeBmffBasedHash) {
   auto assertion = ParseTextProtoOrDie<Assertion>(R"pb(
     label: "c2pa.hash.bmff.v3"
     bmff_based_hash {
@@ -163,7 +151,7 @@
     }
   )pb");
 
-  StandardAssertionEncoder encoder;
+  StandardAssertionCborEncoder encoder;
   auto cbor = encoder.ToCbor(assertion);
   ABSL_ASSERT_OK(cbor);
   std::string cbor_string(*cbor);
@@ -177,7 +165,7 @@
   EXPECT_THAT(result, EqualsProto(assertion.bmff_based_hash()));
 }
 
-TEST(AssertionEncoderTest, EncodeDataHash) {
+TEST(AssertionCborEncoderTest, EncodeDataHash) {
   auto assertion = ParseTextProtoOrDie<Assertion>(R"pb(
     label: "c2pa.hash.data"
     data_hash {
@@ -191,7 +179,7 @@
     }
   )pb");
 
-  StandardAssertionEncoder encoder;
+  StandardAssertionCborEncoder encoder;
   auto cbor = encoder.ToCbor(assertion);
   ABSL_ASSERT_OK(cbor);
   std::string cbor_string(*cbor);
@@ -211,7 +199,7 @@
   EXPECT_THAT(actual, EqualsProto(expected));
 }
 
-TEST(AssertionEncoderTest, EncodeIngredientAssertion) {
+TEST(AssertionCborEncoderTest, EncodeIngredientAssertion) {
   auto assertion = ParseTextProtoOrDie<Assertion>(R"pb(
     label: "c2pa.ingredient.v3"
     ingredient_v3 {
@@ -289,7 +277,7 @@
     }
   )pb");
 
-  StandardAssertionEncoder encoder;
+  StandardAssertionCborEncoder encoder;
   auto cbor = encoder.ToCbor(assertion);
   ABSL_ASSERT_OK(cbor);
   std::string cbor_string(*cbor);
@@ -303,7 +291,7 @@
   EXPECT_THAT(result, EqualsProto(assertion.ingredient_v3()));
 }
 
-TEST(AssertionEncoderTest, EncodeBoxesHash) {
+TEST(AssertionCborEncoderTest, EncodeBoxesHash) {
   auto assertion = ParseTextProtoOrDie<Assertion>(R"pb(
     label: "c2pa.hash.boxes"
     boxes_hash {
@@ -314,7 +302,7 @@
     }
   )pb");
 
-  StandardAssertionEncoder encoder;
+  StandardAssertionCborEncoder encoder;
   auto cbor = encoder.ToCbor(assertion);
   ABSL_ASSERT_OK(cbor);
   std::string cbor_string(*cbor);
@@ -328,50 +316,8 @@
   EXPECT_THAT(result, EqualsProto(assertion.boxes_hash()));
 }
 
-TEST(GetAssertionEncoderTest, ReturnsSameInstance) {
-  EXPECT_EQ(&GetAssertionEncoder(), &GetAssertionEncoder());
-}
-
-absl::StatusOr<Assertion> ParseAssertionBox(absl::string_view assertion_data) {
-  ABSL_ASSIGN_OR_RETURN(auto assertion_box,
-                        jumbf::ConsumeSuperBox(&assertion_data, 9));
-  if (assertion_box.contents.size() != 1) {
-    return absl::InvalidArgumentError(absl::StrCat(
-        "the content size is not 1, but ", assertion_box.contents.size()));
-  }
-  AssertionParser parser;
-  TestValidationTracker validation_tracker;
-  std::optional<Assertion> assertion =
-      parser.ParseCbor(assertion_box.description.label.value_or(""),
-                       assertion_box.contents[0].Get<jumbf::CborBox>().payload,
-                       validation_tracker.tracker());
-  if (!assertion.has_value()) {
-    return absl::InvalidArgumentError(
-        absl::StrJoin(validation_tracker.GetFailures(), ";"));
-  }
-  return assertion.value();
-}
-
-TEST(AssertionEncoderTest, ToJumbf) {
-  StandardAssertionEncoder encoder;
-  auto assertion = ParseTextProtoOrDie<Assertion>(R"pb(
-    label: "c2pa.actions.v2"
-    actions {
-      actions {
-        action: "c2pa.created"
-        software_agent { name: "test generator" }
-        description: "created for testing"
-        digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture"
-      }
-    }
-  )pb");
-  auto assertion_box = encoder.ToJumbf(assertion);
-  ABSL_ASSERT_OK(assertion_box);
-  auto serialized = std::move(*assertion_box).AsCord();
-
-  auto parsed_assertion = ParseAssertionBox(serialized.Flatten());
-  ABSL_ASSERT_OK(parsed_assertion);
-  EXPECT_THAT(*parsed_assertion, EqualsProto(assertion));
+TEST(GetAssertionCborEncoderTest, ReturnsSameInstance) {
+  EXPECT_EQ(&GetAssertionCborEncoder(), &GetAssertionCborEncoder());
 }
 
 }  // namespace
diff --git a/assertion/assertion_encoder.h b/assertion/assertion_encoder.h
deleted file mode 100644
index 822bb04..0000000
--- a/assertion/assertion_encoder.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2026 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//    https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#ifndef THIRD_PARTY_CREDENTIO_ASSERTION_ASSERTION_ENCODER_H_
-#define THIRD_PARTY_CREDENTIO_ASSERTION_ASSERTION_ENCODER_H_
-
-#include "absl/status/statusor.h"
-#include "absl/strings/cord.h"
-#include "cbor/options.h"
-#include "jumbf/box_builder.h"
-#include "proto/assertion.pb.h"
-
-namespace credentio {
-
-// An encoder to serialize an Assertion's metadata to the CBOR or JUMBF format.
-class AssertionEncoder {
- public:
-  virtual ~AssertionEncoder() = default;
-
-  virtual absl::StatusOr<absl::Cord> ToCbor(
-      const Assertion& assertion) const = 0;
-
-  virtual absl::StatusOr<jumbf::BuiltSuperBox> ToJumbf(
-      const Assertion& assertion) const;
-};
-
-class StandardAssertionEncoder : public AssertionEncoder {
- public:
-  explicit StandardAssertionEncoder(bool skip_validity_checks_for_test = false)
-      : options_({.skip_validation = skip_validity_checks_for_test}) {}
-  ~StandardAssertionEncoder() override = default;
-
-  absl::StatusOr<absl::Cord> ToCbor(const Assertion& assertion) const override;
-
- private:
-  cbor::FromProtoOptions options_;
-};
-
-// Returns a singleton StandardAssertionEncoder.
-const AssertionEncoder& GetAssertionEncoder();
-
-}  // namespace credentio
-
-#endif  // THIRD_PARTY_CREDENTIO_ASSERTION_ASSERTION_ENCODER_H_
diff --git a/claim/BUILD b/claim/BUILD
index a945a02..33a14d9 100644
--- a/claim/BUILD
+++ b/claim/BUILD
@@ -219,7 +219,7 @@
     srcs = ["manifest_store_builder_test.cc"],
     deps = [
         ":manifest_store_builder",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//constants:labels",
         "//crypto:algorithms",
         "//crypto:hash",
@@ -262,19 +262,22 @@
         ":claim_cbor_encoder",
         ":manifest_box",
         ":signer",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//assertion:assertion_referencer",
         "//async:async_context",
         "//constants:labels",
         "//crypto:algorithms",
         "//jumbf:box_builder",
         "//jumbf:constants",
+        "//jumbf:encode_params",
+        "//jumbf:utils",
         "//proto:actions_assertion_cc_proto",
         "//proto:assertion_cc_proto",
         "//proto:generator_info_cc_proto",
         "//proto:hashed_uri_cc_proto",
         "//proto:manifest_cc_proto",
         "//proto:multi_asset_hash_assertion_cc_proto",
+        "//salt:salt_generator",
         "//uuid",
         "@abseil-cpp//absl/base:nullability",
         "@abseil-cpp//absl/container:flat_hash_map",
@@ -300,7 +303,7 @@
         ":manifest_box_encoder",
         ":manifest_store_builder",
         ":signer",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//assertion:assertion_referencer",
         "//async:async_context",
         "//constants:labels",
@@ -321,6 +324,7 @@
         "//proto:signature_info_cc_proto",
         "//proto:validation_result_cc_proto",
         "//proto:validation_status_cc_proto",
+        "//salt:salt_generator",
         "//testing:create_test_crypto_write_handler",
         "//testing:local_key_crypto_write_handler",
         "//testing:manifest_store_test_utils",
@@ -464,7 +468,7 @@
         ":manifest_box_assembler",
         ":manifest_box_encoder",
         ":signer",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//assertion:assertion_referencer",
         "//async:async_context",
         "//constants:spec_version",
@@ -478,6 +482,7 @@
         "//proto:generator_info_cc_proto",
         "//proto:hashed_uri_cc_proto",
         "//proto:manifest_cc_proto",
+        "//salt:salt_generator",
         "//uuid",
         "@abseil-cpp//absl/base:nullability",
         "@abseil-cpp//absl/functional:any_invocable",
@@ -499,7 +504,7 @@
         ":manifest_box_encoder",
         ":manifest_store_builder",
         ":signer",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//async:async_context",
         "//crypto:crypto_read_handler",
         "//crypto:crypto_write_handler",
@@ -512,6 +517,8 @@
         "//proto:manifest_cc_proto",
         "//proto:signature_info_cc_proto",
         "//proto:validation_result_cc_proto",
+        "//proto:validation_status_cc_proto",
+        "//salt:salt_generator",
         "//testing:create_test_crypto_write_handler",
         "//testing:manifest_store_test_utils",
         "//testing:proto_test_utils",
diff --git a/claim/generator.cc b/claim/generator.cc
index 1417c1a..05faf35 100644
--- a/claim/generator.cc
+++ b/claim/generator.cc
@@ -23,7 +23,7 @@
 #include "absl/log/die_if_null.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "assertion/assertion_referencer.h"
 #include "async/async_context.h"
 #include "claim/claim_cbor_encoder.h"
@@ -41,6 +41,7 @@
 #include "proto/generator_info.pb.h"
 #include "proto/hashed_uri.pb.h"
 #include "proto/manifest.pb.h"
+#include "salt/salt_generator.h"
 
 namespace credentio {
 namespace {
@@ -71,13 +72,15 @@
       GeneratorInfo generator_info,
       const HasherFactory* absl_nonnull hash_factory,
       ClaimSigner* absl_nonnull claim_signer,
-      const AssertionEncoder* absl_nonnull assertion_cbor_encoder,
-      const ClaimCborEncoder* absl_nonnull claim_cbor_encoder)
+      const AssertionCborEncoder* absl_nonnull assertion_cbor_encoder,
+      const ClaimCborEncoder* absl_nonnull claim_cbor_encoder,
+      const SaltGenerator* absl_nonnull salt_generator)
       : generator_info_(std::move(generator_info)),
         hash_factory_(*ABSL_DIE_IF_NULL(hash_factory)),
         claim_signer_(*ABSL_DIE_IF_NULL(claim_signer)),
         assertion_cbor_encoder_(*ABSL_DIE_IF_NULL(assertion_cbor_encoder)),
-        claim_cbor_encoder_(*ABSL_DIE_IF_NULL(claim_cbor_encoder)) {}
+        claim_cbor_encoder_(*ABSL_DIE_IF_NULL(claim_cbor_encoder)),
+        salt_generator_(*ABSL_DIE_IF_NULL(salt_generator)) {}
 
   void Encode(ManifestBox* absl_nonnull manifest,
               const GenerateManifestStoreOptions& options, AsyncContext context,
@@ -88,7 +91,7 @@
     auto manifest_box_encoder = std::make_shared<ManifestBoxEncoder>(
         assertion_reference.get(), &generator_info_, &claim_signer_,
         &assertion_cbor_encoder_, &claim_cbor_encoder_, manifest,
-        hash_factory_.algorithm());
+        hash_factory_.algorithm(), &salt_generator_);
     manifest_box_encoder->Encode(
         ToClaimSignerOptions(options, std::move(context)),
         [assertion_reference, manifest_box_encoder, cb = std::move(cb)](
@@ -101,8 +104,9 @@
   GeneratorInfo generator_info_;
   const HasherFactory& hash_factory_;
   ClaimSigner& claim_signer_;
-  const AssertionEncoder& assertion_cbor_encoder_;
+  const AssertionCborEncoder& assertion_cbor_encoder_;
   const ClaimCborEncoder& claim_cbor_encoder_;
+  const SaltGenerator& salt_generator_;
 };
 }  // namespace
 
@@ -116,6 +120,7 @@
     const CreateManifestEncoderOptions& options) {
   return std::make_unique<ManifestEncoderImpl>(
       options.generator_info, options.hash_factory, options.claim_signer,
-      options.assertion_cbor_encoder, options.claim_cbor_encoder);
+      options.assertion_cbor_encoder, options.claim_cbor_encoder,
+      options.salt_generator);
 }
 }  // namespace credentio
diff --git a/claim/generator.h b/claim/generator.h
index a2f59d5..c179612 100644
--- a/claim/generator.h
+++ b/claim/generator.h
@@ -21,7 +21,7 @@
 #include "absl/base/nullability.h"
 #include "absl/functional/any_invocable.h"
 #include "absl/status/statusor.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "async/async_context.h"
 #include "claim/claim_cbor_encoder.h"
 #include "claim/manifest_box.h"
@@ -33,6 +33,7 @@
 #include "proto/generator.pb.h"
 #include "proto/generator_info.pb.h"
 #include "proto/manifest.pb.h"
+#include "salt/salt_generator.h"
 #include "uuid/uuid.h"
 
 namespace credentio {
@@ -74,11 +75,14 @@
   // Required.
   ClaimSigner* absl_nonnull claim_signer;
   // Required.
-  const AssertionEncoder* absl_nonnull assertion_cbor_encoder;
+  const AssertionCborEncoder* absl_nonnull assertion_cbor_encoder;
   // Required.
   const ClaimCborEncoder* absl_nonnull claim_cbor_encoder;
   // Required.
   const HasherFactory* absl_nonnull hash_factory;
+  // Required. Salts every assertion; use `SaltGenerator::NoOp()` to disable
+  // salting. Must outlive the created encoder.
+  const SaltGenerator* absl_nonnull salt_generator;
 };
 
 absl::StatusOr<std::unique_ptr<ManifestEncoder>> CreateManifestEncoder(
diff --git a/claim/generator_test.cc b/claim/generator_test.cc
index 8484500..5118f64 100644
--- a/claim/generator_test.cc
+++ b/claim/generator_test.cc
@@ -15,6 +15,7 @@
 
 #include "claim/generator.h"
 
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <utility>
@@ -26,7 +27,7 @@
 #include "absl/strings/cord.h"
 #include "absl/strings/string_view.h"
 #include "absl/synchronization/notification.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "async/async_context.h"
 #include "claim/claim_cbor_encoder.h"
 #include "claim/local_signer.h"
@@ -49,6 +50,8 @@
 #include "proto/manifest.pb.h"
 #include "proto/signature_info.pb.h"
 #include "proto/validation_result.pb.h"
+#include "proto/validation_status.pb.h"
+#include "salt/salt_generator.h"
 #include "testing/create_test_crypto_write_handler.h"
 #include "testing/manifest_store_test_utils.h"
 #include "testing/proto_test_utils.h"
@@ -63,6 +66,7 @@
 using ::credentio_testing::ParseTextProtoOrDie;
 using ::credentio_testing::PartiallyEqualsProto;
 using ::testing::HasSubstr;
+using ::testing::IsEmpty;
 using ::testing::Pointee;
 using ::testing::Pointwise;
 
@@ -89,6 +93,16 @@
   Uuid Generate() const override { return kUuid; }
 };
 
+class FakeSaltGenerator : public SaltGenerator {
+ public:
+  explicit FakeSaltGenerator(std::string salt) : salt_(std::move(salt)) {}
+
+  absl::StatusOr<std::string> Generate() const override { return salt_; }
+
+ private:
+  std::string salt_;
+};
+
 class ManifestAssemblerTest : public testing::Test {
  protected:
   ManifestAssemblerTest() {
@@ -573,9 +587,10 @@
         CreateManifestEncoder({
             .generator_info = generator_info_,
             .claim_signer = claim_signer_.get(),
-            .assertion_cbor_encoder = &GetAssertionEncoder(),
+            .assertion_cbor_encoder = &GetAssertionCborEncoder(),
             .claim_cbor_encoder = &GetClaimCborEncoder(),
             .hash_factory = &hash_factory_,
+            .salt_generator = &SaltGenerator::NoOp(),
         });
     ABSL_CHECK_OK(manifest_encoder);
     manifest_encoder_ = std::move(*manifest_encoder);
@@ -860,5 +875,58 @@
                   expected_validated_result))));
 }
 
+TEST_F(ManifestEncoderTest, EncodeManifestWithSaltGenerator) {
+  constexpr absl::string_view kSalt = "generator_salt16";
+  FakeSaltGenerator salt_generator{std::string(kSalt)};
+  absl::StatusOr<std::unique_ptr<ManifestEncoder>> manifest_encoder =
+      CreateManifestEncoder({
+          .generator_info = generator_info_,
+          .claim_signer = claim_signer_.get(),
+          .assertion_cbor_encoder = &GetAssertionCborEncoder(),
+          .claim_cbor_encoder = &GetClaimCborEncoder(),
+          .hash_factory = &hash_factory_,
+          .salt_generator = &salt_generator,
+      });
+  ABSL_ASSERT_OK(manifest_encoder);
+  ManifestBox manifest_box = {
+      .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a",
+      .instance_id = "12345678-1234-1234-1234-123456789012",
+      .hard_binding_assertion = ParseTextProtoOrDie(R"pb(
+        label: "c2pa.hash.bmff.v3"
+        bmff_based_hash { exclusions { xpath: "" } }
+      )pb"),
+      .actions_assertion = ParseTextProtoOrDie(R"pb(
+        label: "c2pa.actions.v2"
+        actions {
+          actions {
+            action: "c2pa.created"
+            digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture"
+          }
+        }
+      )pb"),
+      .ingredient_assertions = {},
+      .manifest_store_builder = ManifestStoreBuilder(&hash_factory_),
+  };
+
+  absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
+      Encode(**manifest_encoder, manifest_box);
+  ABSL_ASSERT_OK(built_manifest_store_box);
+  std::string manifest_store_bytes(
+      std::move(*built_manifest_store_box).AsCord());
+
+  // The hard binding and actions assertions are each salted once.
+  int salt_count = 0;
+  for (size_t pos = manifest_store_bytes.find(kSalt); pos != std::string::npos;
+       pos = manifest_store_bytes.find(kSalt, pos + kSalt.size())) {
+    ++salt_count;
+  }
+  EXPECT_EQ(salt_count, 2);
+  absl::StatusOr<std::unique_ptr<PartialValidationResultProto>> validated =
+      ValidateManifestStore(manifest_store_bytes);
+  ABSL_ASSERT_OK(validated);
+  EXPECT_THAT((*validated)->active_manifest().validation().failures(),
+              IsEmpty());
+}
+
 }  // namespace
 }  // namespace credentio
diff --git a/claim/manifest_box_encoder.cc b/claim/manifest_box_encoder.cc
index d93c169..12969d9 100644
--- a/claim/manifest_box_encoder.cc
+++ b/claim/manifest_box_encoder.cc
@@ -27,18 +27,22 @@
 #include "absl/strings/cord.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "assertion/assertion_referencer.h"
 #include "async/async_context.h"
 #include "claim/signer.h"
 #include "constants/labels.h"
 #include "jumbf/box_builder.h"
 #include "jumbf/constants.h"
+#include "jumbf/encode_params.h"
+#include "jumbf/utils.h"
 #include "proto/actions_assertion.pb.h"
 #include "proto/assertion.pb.h"
 #include "proto/generator_info.pb.h"
 #include "proto/hashed_uri.pb.h"
 #include "proto/manifest.pb.h"
 #include "proto/multi_asset_hash_assertion.pb.h"
+#include "salt/salt_generator.h"
 #include "uuid/uuid.h"
 
 namespace credentio {
@@ -63,6 +67,39 @@
   return std::move(builder).Finalize();
 }
 
+// Returns description box options for an assertion superbox labeled `label`.
+// If `salt_generator` produces a non-empty salt, the salt is embedded as a C2PA
+// salt box ('c2sh') in the description box private content, per C2PA
+// specification v2.2 sections 8.4.2.3 and 11.1.4.1.2.
+absl::StatusOr<jumbf::DescriptionBoxOptions> MakeSaltedDescriptionBoxOptions(
+    absl::string_view label, const SaltGenerator& salt_generator) {
+  jumbf::DescriptionBoxOptions options = {.label = absl::Cord(label)};
+  ABSL_ASSIGN_OR_RETURN(std::string salt, salt_generator.Generate());
+  if (salt.empty()) {
+    return options;
+  }
+  ABSL_ASSIGN_OR_RETURN(options.private_content,
+                        jumbf::CreateC2paSaltBox(salt));
+  return options;
+}
+
+// Encodes `assertion` to CBOR with `encoder` and wraps it in a CBOR content
+// type JUMBF superbox labeled with the assertion label and salted with
+// `salt_generator`.
+absl::StatusOr<jumbf::BuiltSuperBox> SerializeAssertionSuperBox(
+    const AssertionCborEncoder& encoder, const Assertion& assertion,
+    const SaltGenerator& salt_generator) {
+  ABSL_ASSIGN_OR_RETURN(absl::Cord cbor, encoder.ToCbor(assertion));
+  ABSL_ASSIGN_OR_RETURN(
+      jumbf::DescriptionBoxOptions description_options,
+      MakeSaltedDescriptionBoxOptions(assertion.label(), salt_generator));
+  jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid,
+                                 std::move(description_options));
+  ABSL_RETURN_IF_ERROR(
+      builder.AddContent(jumbf::kCborBoxType, std::move(cbor)));
+  return std::move(builder).Finalize();
+}
+
 struct SignatureBoxResult {
   jumbf::BuiltSuperBox box;
 };
@@ -118,7 +155,8 @@
   }
 
   ABSL_ASSIGN_OR_RETURN(jumbf::BuiltSuperBox assertion_box,
-                        assertion_encoder_.ToJumbf(assertion));
+                        SerializeAssertionSuperBox(assertion_cbor_encoder_,
+                                                   assertion, salt_generator_));
   ABSL_ASSIGN_OR_RETURN(HashedUri assertion_hashed_uri,
                         assertion_reference_.Generate(assertion_box));
   assertion_url_to_index_[assertion_hashed_uri.url()] =
@@ -160,9 +198,11 @@
     return absl::InvalidArgumentError(
         "Gathered assertion content type must be JSON.");
   }
-  jumbf::SuperBoxBuilder builder(
-      jumbf::kJsonBoxTypeUuid,
-      {.label = absl::Cord(gathered_assertion.label())});
+  ABSL_ASSIGN_OR_RETURN(jumbf::DescriptionBoxOptions description_options,
+                        MakeSaltedDescriptionBoxOptions(
+                            gathered_assertion.label(), salt_generator_));
+  jumbf::SuperBoxBuilder builder(jumbf::kJsonBoxTypeUuid,
+                                 std::move(description_options));
   ABSL_RETURN_IF_ERROR(builder.AddContent(
       jumbf::kJsonBoxType, absl::Cord(gathered_assertion.data())));
   ABSL_ASSIGN_OR_RETURN(jumbf::BuiltSuperBox assertion_box,
diff --git a/claim/manifest_box_encoder.h b/claim/manifest_box_encoder.h
index b78550d..77fc5b5 100644
--- a/claim/manifest_box_encoder.h
+++ b/claim/manifest_box_encoder.h
@@ -27,7 +27,7 @@
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/cord.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "assertion/assertion_referencer.h"
 #include "claim/claim_cbor_encoder.h"
 #include "claim/manifest_box.h"
@@ -40,6 +40,7 @@
 #include "proto/generator_info.pb.h"
 #include "proto/hashed_uri.pb.h"
 #include "proto/multi_asset_hash_assertion.pb.h"
+#include "salt/salt_generator.h"
 
 namespace credentio {
 
@@ -54,9 +55,10 @@
       const AssertionReferencer* absl_nonnull assertion_reference,
       const GeneratorInfo* absl_nonnull generator_info,
       const ClaimSigner* absl_nonnull claim_signer,
-      const AssertionEncoder* absl_nonnull assertion_cbor_encoder,
+      const AssertionCborEncoder* absl_nonnull assertion_cbor_encoder,
       const ClaimCborEncoder* absl_nonnull claim_cbor_encoder,
-      ManifestBox* absl_nonnull manifest, HashAlgorithm hash_algorithm)
+      ManifestBox* absl_nonnull manifest, HashAlgorithm hash_algorithm,
+      const SaltGenerator* absl_nonnull salt_generator)
       : assertion_reference_(*ABSL_DIE_IF_NULL(assertion_reference)),
         generator_info_(*ABSL_DIE_IF_NULL(generator_info)),
         claim_signer_(*ABSL_DIE_IF_NULL(claim_signer)),
@@ -64,8 +66,9 @@
                           {.label = absl::Cord(manifest->label)}),
         manifest_(*ABSL_DIE_IF_NULL(manifest)),
         hash_algorithm_(hash_algorithm),
-        assertion_encoder_(*ABSL_DIE_IF_NULL(assertion_cbor_encoder)),
-        claim_cbor_encoder_(*ABSL_DIE_IF_NULL(claim_cbor_encoder)) {}
+        assertion_cbor_encoder_(*ABSL_DIE_IF_NULL(assertion_cbor_encoder)),
+        claim_cbor_encoder_(*ABSL_DIE_IF_NULL(claim_cbor_encoder)),
+        salt_generator_(*ABSL_DIE_IF_NULL(salt_generator)) {}
 
   // Encodes the manifest to a JUMBF-type box.
   // Consumes the provided ManifestBox.
@@ -79,7 +82,8 @@
 
  private:
   // Encodes the given assertion and adds it to assertion_boxes_,
-  // assertion_hashed_uris_, and assertion_url_to_index_.
+  // assertion_hashed_uris_, and assertion_url_to_index_. The assertion box is
+  // salted with salt_generator_.
   absl::Status AddAssertion(Assertion& assertion);
 
   // Populates hashes for hashed URIs in the given actions assertion, then adds
@@ -126,8 +130,9 @@
   // Needed for resolving references between assertions.
   absl::flat_hash_map<std::string, size_t> assertion_url_to_index_;
   HashAlgorithm hash_algorithm_;
-  const AssertionEncoder& assertion_encoder_;
+  const AssertionCborEncoder& assertion_cbor_encoder_;
   const ClaimCborEncoder& claim_cbor_encoder_;
+  const SaltGenerator& salt_generator_;
 };
 }  // namespace credentio
 
diff --git a/claim/manifest_box_encoder_test.cc b/claim/manifest_box_encoder_test.cc
index d03ed35..9c95a15 100644
--- a/claim/manifest_box_encoder_test.cc
+++ b/claim/manifest_box_encoder_test.cc
@@ -15,6 +15,7 @@
 
 #include "claim/manifest_box_encoder.h"
 
+#include <cstddef>
 #include <memory>
 #include <string>
 #include <utility>
@@ -28,7 +29,7 @@
 #include "absl/strings/str_join.h"
 #include "absl/strings/string_view.h"
 #include "absl/synchronization/notification.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "assertion/assertion_referencer.h"
 #include "async/async_context.h"
 #include "claim/claim_cbor_encoder.h"
@@ -57,6 +58,7 @@
 #include "proto/signature_info.pb.h"
 #include "proto/validation_result.pb.h"
 #include "proto/validation_status.pb.h"
+#include "salt/salt_generator.h"
 #include "testing/create_test_crypto_write_handler.h"
 #include "testing/local_key_crypto_write_handler.h"
 #include "testing/manifest_store_test_utils.h"
@@ -70,6 +72,7 @@
 using ::absl_testing::StatusIs;
 using ::credentio_testing::ParseTextProtoOrDie;
 using ::testing::HasSubstr;
+using ::testing::IsEmpty;
 using ::testing::Pointee;
 
 constexpr auto kUuid =
@@ -140,6 +143,17 @@
   Uuid Generate() const override { return kUuid; }
 };
 
+class FakeSaltGenerator : public SaltGenerator {
+ public:
+  explicit FakeSaltGenerator(absl::StatusOr<std::string> salt)
+      : salt_(std::move(salt)) {}
+
+  absl::StatusOr<std::string> Generate() const override { return salt_; }
+
+ private:
+  absl::StatusOr<std::string> salt_;
+};
+
 class FakeHasherFactory : public HasherFactory {
  public:
   absl::StatusOr<std::unique_ptr<Hasher>> Create() const override {
@@ -161,7 +175,7 @@
   std::unique_ptr<CryptoWriteHandler> crypto_writer_;
   GeneratorInfo generator_info_;
   FakeHasherFactory hasher_factory_;
-  StandardAssertionEncoder assertion_cbor_encoder_;
+  StandardAssertionCborEncoder assertion_cbor_encoder_;
   const ClaimCborEncoder& claim_cbor_encoder_;
 
   ManifestBox CreateMinimalManifestBox() {
@@ -274,7 +288,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
   ABSL_ASSERT_OK(built_manifest_store_box);
@@ -298,7 +312,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   GenerateManifestStoreOptions options;
   options.set_time_stamping_mode(
       GenerateManifestStoreOptions::TIME_STAMPING_MODE_UNSPECIFIED);
@@ -316,7 +330,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   GenerateManifestStoreOptions options;
   options.set_time_stamping_mode(
       GenerateManifestStoreOptions::TIME_STAMPING_OPTIONAL);
@@ -334,7 +348,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   GenerateManifestStoreOptions options;
   options.set_time_stamping_mode(
       GenerateManifestStoreOptions::TIME_STAMPING_REQUIRED);
@@ -351,7 +365,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   GenerateManifestStoreOptions options;
   options.set_time_stamping_mode(
       GenerateManifestStoreOptions::TIME_STAMPING_REQUIRED);
@@ -372,7 +386,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   GenerateManifestStoreOptions options;
   options.set_time_stamping_mode(
       GenerateManifestStoreOptions::TIME_STAMPING_OMITTED_WITH_PADDING);
@@ -406,7 +420,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   EXPECT_THAT(Encode(manifest_box_encoder),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        HasSubstr("Opened action must have exactly one "
@@ -472,7 +486,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   EXPECT_THAT(Encode(manifest_box_encoder),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        HasSubstr("Opened action must have exactly one "
@@ -660,7 +674,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
   ABSL_ASSERT_OK(built_manifest_store_box);
@@ -915,7 +929,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
   ABSL_ASSERT_OK(built_manifest_store_box);
@@ -1080,7 +1094,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
   ABSL_ASSERT_OK(built_manifest_store_box);
@@ -1349,7 +1363,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
   ABSL_ASSERT_OK(built_manifest_store_box);
@@ -1643,7 +1657,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
   ABSL_ASSERT_OK(built_manifest_store_box);
@@ -1850,7 +1864,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
   ABSL_ASSERT_OK(built_manifest_store_box);
@@ -1900,7 +1914,7 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
 
   absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
       Encode(manifest_box_encoder);
@@ -1969,12 +1983,89 @@
   ManifestBoxEncoder manifest_box_encoder(
       &assertion_reference, &generator_info_, &claim_signer,
       &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
-      hasher_factory_.algorithm());
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
 
   EXPECT_THAT(Encode(manifest_box_encoder),
               StatusIs(absl::StatusCode::kInvalidArgument,
                        HasSubstr("content type must be JSON")));
 }
 
+// Returns the number of non-overlapping occurrences of `needle` in `haystack`.
+int CountOccurrences(absl::string_view haystack, absl::string_view needle) {
+  int count = 0;
+  for (size_t pos = haystack.find(needle); pos != absl::string_view::npos;
+       pos = haystack.find(needle, pos + needle.size())) {
+    ++count;
+  }
+  return count;
+}
+
+TEST_F(ManifestBoxEncoderTest, SaltsAllAssertionsWithSaltGenerator) {
+  constexpr absl::string_view kSalt = "0123456789abcdef";
+  FakeSaltGenerator salt_generator{std::string(kSalt)};
+  GatheredAssertion gathered;
+  gathered.set_label("com.example.gathered");
+  gathered.set_data("{}");
+  gathered.set_content_type(GatheredAssertion::CONTENT_TYPE_JSON);
+  ManifestBox manifest_box = CreateMinimalManifestBox();
+  manifest_box.gathered_assertions = {gathered};
+  AssertionReferencer assertion_reference(&hasher_factory_);
+  LocalClaimSigner claim_signer(crypto_writer_.get(), &hasher_factory_);
+  ManifestBoxEncoder manifest_box_encoder(
+      &assertion_reference, &generator_info_, &claim_signer,
+      &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
+      hasher_factory_.algorithm(), &salt_generator);
+
+  absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
+      Encode(manifest_box_encoder);
+  ABSL_ASSERT_OK(built_manifest_store_box);
+  std::string manifest_store_bytes(
+      std::move(*built_manifest_store_box).AsCord());
+
+  // Hard binding, actions and gathered assertions each get one C2PA salt box
+  // ('c2sh') containing the salt.
+  EXPECT_EQ(CountOccurrences(manifest_store_bytes, "c2sh"), 3);
+  EXPECT_EQ(CountOccurrences(manifest_store_bytes, kSalt), 3);
+  // Salted assertions must still validate.
+  absl::StatusOr<std::unique_ptr<PartialValidationResultProto>> validated =
+      ValidateManifestStore(manifest_store_bytes);
+  ABSL_ASSERT_OK(validated);
+  EXPECT_THAT((*validated)->active_manifest().validation().failures(),
+              IsEmpty());
+}
+
+TEST_F(ManifestBoxEncoderTest, DoesNotSaltAssertionsWithNoOpSaltGenerator) {
+  ManifestBox manifest_box = CreateMinimalManifestBox();
+  AssertionReferencer assertion_reference(&hasher_factory_);
+  LocalClaimSigner claim_signer(crypto_writer_.get(), &hasher_factory_);
+  ManifestBoxEncoder manifest_box_encoder(
+      &assertion_reference, &generator_info_, &claim_signer,
+      &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
+      hasher_factory_.algorithm(), &SaltGenerator::NoOp());
+
+  absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box =
+      Encode(manifest_box_encoder);
+  ABSL_ASSERT_OK(built_manifest_store_box);
+  std::string manifest_store_bytes(
+      std::move(*built_manifest_store_box).AsCord());
+
+  // No C2PA salt box ('c2sh') is written.
+  EXPECT_EQ(CountOccurrences(manifest_store_bytes, "c2sh"), 0);
+}
+
+TEST_F(ManifestBoxEncoderTest, ReturnsErrorWhenSaltGenerationFails) {
+  FakeSaltGenerator salt_generator{absl::InternalError("salt failure")};
+  ManifestBox manifest_box = CreateMinimalManifestBox();
+  AssertionReferencer assertion_reference(&hasher_factory_);
+  LocalClaimSigner claim_signer(crypto_writer_.get(), &hasher_factory_);
+  ManifestBoxEncoder manifest_box_encoder(
+      &assertion_reference, &generator_info_, &claim_signer,
+      &assertion_cbor_encoder_, &claim_cbor_encoder_, &manifest_box,
+      hasher_factory_.algorithm(), &salt_generator);
+
+  EXPECT_THAT(Encode(manifest_box_encoder),
+              StatusIs(absl::StatusCode::kInternal, HasSubstr("salt failure")));
+}
+
 }  // namespace
 }  // namespace credentio
diff --git a/claim/manifest_store_builder_test.cc b/claim/manifest_store_builder_test.cc
index bced268..f1642c7 100644
--- a/claim/manifest_store_builder_test.cc
+++ b/claim/manifest_store_builder_test.cc
@@ -28,7 +28,7 @@
 #include "absl/status/statusor.h"
 #include "absl/strings/cord.h"
 #include "absl/strings/string_view.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "constants/labels.h"
 #include "crypto/algorithms.h"
 #include "crypto/default/hasher.h"
@@ -103,7 +103,7 @@
   ManifestStoreBuilderTest() : hasher_factory_(), builder_(&hasher_factory_) {};
   FakeHasherFactory hasher_factory_;
   ManifestStoreBuilder builder_;
-  StandardAssertionEncoder cbor_encoder_;
+  StandardAssertionCborEncoder cbor_encoder_;
 };
 
 TEST_F(ManifestStoreBuilderTest, AddActiveManifest) {
diff --git a/generator/BUILD b/generator/BUILD
index 79b6e83..44517e6 100644
--- a/generator/BUILD
+++ b/generator/BUILD
@@ -83,7 +83,7 @@
         ":generator",
         ":generator_impl",
         ":manifest_store_transformer",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//async:async_context",
         "//claim:claim_cbor_encoder",
         "//claim:generator",
@@ -109,6 +109,7 @@
         "//proto:manifest_cc_proto",
         "//proto:signature_info_cc_proto",
         "//proto:validation_result_cc_proto",
+        "//salt:salt_generator",
         "//testing:create_test_crypto_write_handler",
         "//testing:manifest_store_test_utils",
         "//testing:proto_test_utils",
@@ -130,7 +131,7 @@
     hdrs = ["generator_options.h"],
     deps = [
         ":manifest_store_transformer",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//claim:claim_cbor_encoder",
         "//claim:generator",
         "//claim:signer",
@@ -138,6 +139,8 @@
         "//crypto:hash",
         "//crypto/default:hasher",
         "//proto:generator_info_cc_proto",
+        "//salt:random_salt_generator",
+        "//salt:salt_generator",
         "//uuid",
         "@abseil-cpp//absl/base:nullability",
     ],
@@ -151,13 +154,14 @@
         ":generator",
         ":generator_impl",
         ":generator_options",
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//async:async_context",
         "//claim:claim_cbor_encoder",
         "//claim:generator",
         "//claim:manifest_box_assembler",
         "//claim:signer",
         "//crypto:hash",
+        "//salt:salt_generator",
         "@abseil-cpp//absl/base:nullability",
         "@abseil-cpp//absl/functional:any_invocable",
         "@abseil-cpp//absl/status",
@@ -181,9 +185,12 @@
         "//crypto:hash",
         "//proto:generator_cc_proto",
         "//proto:generator_info_cc_proto",
+        "//salt:salt_generator",
+        "//testing:proto_test_utils",
         "@abseil-cpp//absl/functional:any_invocable",
         "@abseil-cpp//absl/status",
         "@abseil-cpp//absl/status:status_matchers",
+        "@abseil-cpp//absl/status:statusor",
         "@abseil-cpp//absl/strings:string_view",
         "@abseil-cpp//absl/synchronization",
         "@googletest//:gtest_main",
diff --git a/generator/generator_factory.cc b/generator/generator_factory.cc
index b30e6d6..0d2b1ef 100644
--- a/generator/generator_factory.cc
+++ b/generator/generator_factory.cc
@@ -23,7 +23,7 @@
 #include "absl/status/status.h"
 #include "absl/status/status_macros.h"
 #include "absl/status/statusor.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "async/async_context.h"
 #include "claim/claim_cbor_encoder.h"
 #include "claim/generator.h"
@@ -33,6 +33,7 @@
 #include "generator/generator.h"
 #include "generator/generator_impl.h"
 #include "generator/generator_options.h"
+#include "salt/salt_generator.h"
 
 namespace credentio {
 namespace {
@@ -112,11 +113,12 @@
           .claim_signer = claim_signer,
           .assertion_cbor_encoder = options.assertion_cbor_encoder
                                         ? options.assertion_cbor_encoder
-                                        : &GetAssertionEncoder(),
+                                        : &GetAssertionCborEncoder(),
           .claim_cbor_encoder = options.claim_cbor_encoder
                                     ? options.claim_cbor_encoder
                                     : &GetClaimCborEncoder(),
           .hash_factory = hash_factory,
+          .salt_generator = options.salt_generator,
       }));
   return std::make_unique<ManifestStoreGeneratorImpl>(
       std::move(manifest_assembler), std::move(manifest_encoder),
@@ -134,6 +136,9 @@
   if (options.uuid_generator == nullptr) {
     return absl::InvalidArgumentError("uuid_generator is required");
   }
+  if (options.salt_generator == nullptr) {
+    return absl::InvalidArgumentError("salt_generator is required");
+  }
   if (options.hash_factory == nullptr) {
     return absl::InvalidArgumentError("hash_factory is required");
   }
diff --git a/generator/generator_factory_test.cc b/generator/generator_factory_test.cc
index 4de7043..196b1ef 100644
--- a/generator/generator_factory_test.cc
+++ b/generator/generator_factory_test.cc
@@ -22,6 +22,7 @@
 #include "absl/functional/any_invocable.h"
 #include "absl/status/status.h"
 #include "absl/status/status_matchers.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
 #include "absl/synchronization/notification.h"
 #include "async/async_context.h"
@@ -36,11 +37,14 @@
 #include "gtest/gtest.h"
 #include "proto/generator.pb.h"
 #include "proto/generator_info.pb.h"
+#include "salt/salt_generator.h"
+#include "testing/proto_test_utils.h"
 
 namespace credentio {
 namespace {
 
 using ::absl_testing::StatusIs;
+using ::credentio_testing::ParseTextProtoOrDie;
 using ::testing::HasSubstr;
 using ::testing::NotNull;
 
@@ -69,6 +73,19 @@
   int* call_count_;
 };
 
+class CountingSaltGenerator : public SaltGenerator {
+ public:
+  absl::StatusOr<std::string> Generate() const override {
+    ++call_count_;
+    return "0123456789abcdef";
+  }
+
+  int call_count() const { return call_count_; }
+
+ private:
+  mutable int call_count_ = 0;
+};
+
 TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullSigningClaimSigner) {
   FakeClaimSigner template_signer;
   GeneratorOptions options;
@@ -119,6 +136,20 @@
                        HasSubstr("hash_factory is required")));
 }
 
+TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullSaltGenerator)
+__attribute__((no_sanitize("nullability"))) {
+  FakeClaimSigner signing_signer;
+  FakeClaimSigner template_signer;
+  GeneratorOptions options;
+  options.signing_claim_signer = &signing_signer;
+  options.template_claim_signer = &template_signer;
+  options.salt_generator = nullptr;
+
+  EXPECT_THAT(CreateGenerator(std::move(options)),
+              StatusIs(absl::StatusCode::kInvalidArgument,
+                       HasSubstr("salt_generator is required")));
+}
+
 TEST(GeneratorFactoryTest, ValidateOptionsRejectsNullManifestAssemblerFactory) {
   FakeClaimSigner signing_signer;
   FakeClaimSigner template_signer;
@@ -219,5 +250,54 @@
                                    HasSubstr("generator_context")));
 }
 
+TEST(GeneratorFactoryTest, SaltGeneratorUsedBySigningAndTemplateGenerators) {
+  FakeClaimSigner signing_signer;
+  FakeClaimSigner template_signer;
+  CountingSaltGenerator salt_generator;
+  GeneratorOptions options;
+  options.generator_info.set_name("TestGenerator");
+  options.generator_info.set_spec_version("2.2.0");
+  options.signing_claim_signer = &signing_signer;
+  options.template_claim_signer = &template_signer;
+  options.salt_generator = &salt_generator;
+  absl::StatusOr<std::unique_ptr<ManifestStoreGenerator>> generator =
+      CreateGenerator(std::move(options));
+  ABSL_ASSERT_OK(generator);
+  GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb(
+    hard_binding {
+      box_hash {
+        alg: "sha256"
+        boxes { names: "some name" hash: "some hash" }
+      }
+    }
+    actions {
+      created {
+        digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture"
+      }
+    }
+  )pb");
+
+  absl::Notification gen_done;
+  (*generator)
+      ->GenerateManifestStore(
+          request, AsyncContext(),
+          [&](absl::StatusOr<GenerateManifestStoreResponse> res) {
+            gen_done.Notify();
+          });
+  gen_done.WaitForNotification();
+  // One salt each for the hard binding and actions assertions.
+  EXPECT_EQ(salt_generator.call_count(), 2);
+
+  absl::Notification template_done;
+  (*generator)
+      ->GenerateManifestStoreTemplate(
+          request, AsyncContext(),
+          [&](absl::StatusOr<ManifestStoreTemplate> res) {
+            template_done.Notify();
+          });
+  template_done.WaitForNotification();
+  EXPECT_EQ(salt_generator.call_count(), 4);
+}
+
 }  // namespace
 }  // namespace credentio
diff --git a/generator/generator_impl_test.cc b/generator/generator_impl_test.cc
index 689d69c..32b2ce4 100644
--- a/generator/generator_impl_test.cc
+++ b/generator/generator_impl_test.cc
@@ -26,7 +26,7 @@
 #include "absl/strings/cord.h"
 #include "absl/strings/string_view.h"
 #include "absl/synchronization/notification.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "async/async_context.h"
 #include "claim/claim_cbor_encoder.h"
 #include "claim/generator.h"
@@ -57,6 +57,7 @@
 #include "proto/manifest.pb.h"
 #include "proto/signature_info.pb.h"
 #include "proto/validation_result.pb.h"
+#include "salt/salt_generator.h"
 #include "testing/create_test_crypto_write_handler.h"
 #include "testing/manifest_store_test_utils.h"
 #include "testing/proto_test_utils.h"
@@ -210,9 +211,10 @@
         CreateManifestEncoder({
             .generator_info = generator_info_,
             .claim_signer = claim_signer_.get(),
-            .assertion_cbor_encoder = &GetAssertionEncoder(),
+            .assertion_cbor_encoder = &GetAssertionCborEncoder(),
             .claim_cbor_encoder = &GetClaimCborEncoder(),
             .hash_factory = &hash_factory_,
+            .salt_generator = &SaltGenerator::NoOp(),
         });
     ABSL_CHECK_OK(manifest_encoder);
 
diff --git a/generator/generator_options.h b/generator/generator_options.h
index db3c507..e765356 100644
--- a/generator/generator_options.h
+++ b/generator/generator_options.h
@@ -17,7 +17,7 @@
 #define THIRD_PARTY_CREDENTIO_GENERATOR_GENERATOR_OPTIONS_H_
 
 #include "absl/base/nullability.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "claim/claim_cbor_encoder.h"
 #include "claim/generator.h"
 #include "claim/signer.h"
@@ -26,6 +26,8 @@
 #include "crypto/hash.h"
 #include "generator/manifest_store_transformer.h"
 #include "proto/generator_info.pb.h"
+#include "salt/random_salt_generator.h"
+#include "salt/salt_generator.h"
 #include "uuid/uuid.h"
 
 namespace credentio {
@@ -43,7 +45,7 @@
   // Optional. If provided, must outlive the created generator.
   const HasherFactory* absl_nonnull hash_factory = &DefaultHasherFactory();
   // Optional. If provided, must outlive the created generator.
-  const AssertionEncoder* assertion_cbor_encoder = nullptr;
+  const AssertionCborEncoder* assertion_cbor_encoder = nullptr;
   // Optional. If provided, must outlive the created generator.
   const ClaimCborEncoder* claim_cbor_encoder = nullptr;
   // Optional. If provided, must outlive the created generator.
@@ -52,6 +54,10 @@
   ManifestAssemblerFactory manifest_assembler_factory = CreateManifestAssembler;
   // The C2PA specification version the generated manifests conform to.
   SpecVersion spec_version = SpecVersion::kC2pa_2_2;
+  // Optional. Salts every assertion; use `SaltGenerator::NoOp()` to disable
+  // salting. If provided, must outlive the created generator.
+  const SaltGenerator* absl_nonnull salt_generator =
+      &RandomSaltGenerator::Default();
 };
 
 }  // namespace credentio
diff --git a/salt/BUILD b/salt/BUILD
index 5531c49..d04a345 100644
--- a/salt/BUILD
+++ b/salt/BUILD
@@ -13,11 +13,42 @@
 # limitations under the License.
 #
 
-load("@rules_cc//cc:defs.bzl", "cc_library")
+load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
 
 package(default_visibility = ["//visibility:public"])
 
 cc_library(
     name = "salt_generator",
+    srcs = ["salt_generator.cc"],
     hdrs = ["salt_generator.h"],
+    deps = [
+        "@abseil-cpp//absl/base:no_destructor",
+        "@abseil-cpp//absl/status:statusor",
+    ],
+)
+
+cc_library(
+    name = "random_salt_generator",
+    srcs = ["random_salt_generator.cc"],
+    hdrs = ["random_salt_generator.h"],
+    deps = [
+        ":salt_generator",
+        "@abseil-cpp//absl/base:no_destructor",
+        "@abseil-cpp//absl/status",
+        "@abseil-cpp//absl/status:statusor",
+        "@boringssl//:crypto",
+    ],
+)
+
+cc_test(
+    name = "random_salt_generator_test",
+    srcs = ["random_salt_generator_test.cc"],
+    deps = [
+        ":random_salt_generator",
+        ":salt_generator",
+        "@abseil-cpp//absl/container:flat_hash_set",
+        "@abseil-cpp//absl/status:status_matchers",
+        "@abseil-cpp//absl/status:statusor",
+        "@googletest//:gtest_main",
+    ],
 )
diff --git a/salt/random_salt_generator.cc b/salt/random_salt_generator.cc
new file mode 100644
index 0000000..977eb15
--- /dev/null
+++ b/salt/random_salt_generator.cc
@@ -0,0 +1,42 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "salt/random_salt_generator.h"
+
+#include <cstdint>
+#include <string>
+
+#include "absl/base/no_destructor.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "openssl/rand.h"
+#include "salt/salt_generator.h"
+
+namespace credentio {
+
+const RandomSaltGenerator& RandomSaltGenerator::Default() {
+  static const absl::NoDestructor<RandomSaltGenerator> kDefaultSaltGenerator;
+  return *kDefaultSaltGenerator;
+}
+
+absl::StatusOr<std::string> RandomSaltGenerator::Generate() const {
+  std::string salt(kDefaultSaltLength, '\0');
+  if (RAND_bytes(reinterpret_cast<uint8_t*>(salt.data()), salt.size()) != 1) {
+    return absl::InternalError("Failed to generate random salt.");
+  }
+  return salt;
+}
+
+}  // namespace credentio
diff --git a/salt/random_salt_generator.h b/salt/random_salt_generator.h
new file mode 100644
index 0000000..aaaa67c
--- /dev/null
+++ b/salt/random_salt_generator.h
@@ -0,0 +1,39 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef THIRD_PARTY_CREDENTIO_SALT_RANDOM_SALT_GENERATOR_H_
+#define THIRD_PARTY_CREDENTIO_SALT_RANDOM_SALT_GENERATOR_H_
+
+#include <string>
+
+#include "absl/status/statusor.h"
+#include "salt/salt_generator.h"
+
+namespace credentio {
+
+// Generates salts of `kDefaultSaltLength` bytes from a cryptographically secure
+// pseudorandom number generator, for use as C2PA salts (C2PA specification v2.2
+// Section 8.4.2.3). Thread-safe.
+class RandomSaltGenerator final : public SaltGenerator {
+ public:
+  // Returns a process-wide instance.
+  static const RandomSaltGenerator& Default();
+
+  absl::StatusOr<std::string> Generate() const override;
+};
+
+}  // namespace credentio
+
+#endif  // THIRD_PARTY_CREDENTIO_SALT_RANDOM_SALT_GENERATOR_H_
diff --git a/salt/random_salt_generator_test.cc b/salt/random_salt_generator_test.cc
new file mode 100644
index 0000000..a341015
--- /dev/null
+++ b/salt/random_salt_generator_test.cc
@@ -0,0 +1,62 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "salt/random_salt_generator.h"
+
+#include <string>
+#include <utility>
+
+#include "absl/container/flat_hash_set.h"
+#include "absl/status/status_matchers.h"
+#include "absl/status/statusor.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "salt/salt_generator.h"
+
+namespace credentio {
+namespace {
+
+using ::absl_testing::IsOkAndHolds;
+using ::testing::SizeIs;
+
+TEST(RandomSaltGeneratorTest, GeneratesSaltOfDefaultLength) {
+  RandomSaltGenerator salt_generator;
+
+  EXPECT_THAT(salt_generator.Generate(),
+              IsOkAndHolds(SizeIs(kDefaultSaltLength)));
+}
+
+TEST(RandomSaltGeneratorTest, DefaultReturnsSameInstance) {
+  EXPECT_EQ(&RandomSaltGenerator::Default(), &RandomSaltGenerator::Default());
+  EXPECT_THAT(RandomSaltGenerator::Default().Generate(),
+              IsOkAndHolds(SizeIs(kDefaultSaltLength)));
+}
+
+TEST(RandomSaltGeneratorTest, GeneratesUniqueSalts) {
+  constexpr int kNumSalts = 1000;
+  RandomSaltGenerator salt_generator;
+
+  absl::flat_hash_set<std::string> salts;
+  for (int i = 0; i < kNumSalts; ++i) {
+    absl::StatusOr<std::string> salt = salt_generator.Generate();
+    ABSL_ASSERT_OK(salt);
+    salts.insert(*std::move(salt));
+  }
+
+  EXPECT_THAT(salts, SizeIs(kNumSalts));
+}
+
+}  // namespace
+}  // namespace credentio
diff --git a/salt/salt_generator.cc b/salt/salt_generator.cc
new file mode 100644
index 0000000..9db77cb
--- /dev/null
+++ b/salt/salt_generator.cc
@@ -0,0 +1,40 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "salt/salt_generator.h"
+
+#include <string>
+
+#include "absl/base/no_destructor.h"
+#include "absl/status/statusor.h"
+
+namespace credentio {
+namespace {
+
+class NoOpSaltGenerator final : public SaltGenerator {
+ public:
+  absl::StatusOr<std::string> Generate() const override {
+    return std::string();
+  }
+};
+
+}  // namespace
+
+const SaltGenerator& SaltGenerator::NoOp() {
+  static const absl::NoDestructor<NoOpSaltGenerator> kNoOpSaltGenerator;
+  return *kNoOpSaltGenerator;
+}
+
+}  // namespace credentio
diff --git a/salt/salt_generator.h b/salt/salt_generator.h
index b274c75..c5827e9 100644
--- a/salt/salt_generator.h
+++ b/salt/salt_generator.h
@@ -19,10 +19,12 @@
 #include <cstddef>
 #include <string>
 
+#include "absl/status/statusor.h"
+
 namespace credentio {
 
-// Default salt length in bytes (128 bits of entropy) per C2PA specification
-// Section 8.3.1.3 and Section 8.4.2.3.
+// Default salt length in bytes (128 bits of entropy). C2PA specification v2.2
+// Section 8.4.2.3 allows salts of 16 or 32 bytes.
 inline constexpr size_t kDefaultSaltLength = 16;
 
 // Abstract interface for generating assertion salt bytes.
@@ -34,8 +36,13 @@
  public:
   virtual ~SaltGenerator() = default;
 
-  // Generates random salt bytes of length `kDefaultSaltLength`.
-  virtual std::string Generate() const = 0;
+  // Returns a generator that produces empty salts, i.e. assertions are not
+  // salted.
+  static const SaltGenerator& NoOp();
+
+  // Generates random salt bytes of length `kDefaultSaltLength`. An empty result
+  // means no salt. Returns an error if a salt cannot be generated.
+  virtual absl::StatusOr<std::string> Generate() const = 0;
 };
 
 }  // namespace credentio
diff --git a/testing/BUILD b/testing/BUILD
index 74e0186..f97d799 100644
--- a/testing/BUILD
+++ b/testing/BUILD
@@ -242,7 +242,7 @@
     srcs = ["jumbf_utils.cc"],
     hdrs = ["jumbf_utils.h"],
     deps = [
-        "//assertion:assertion_encoder",
+        "//assertion:assertion_cbor_encoder",
         "//constants:labels",
         "//crypto:hash",
         "//crypto/default:hasher",
diff --git a/testing/jumbf_utils.cc b/testing/jumbf_utils.cc
index d275786..cd1a2c6 100644
--- a/testing/jumbf_utils.cc
+++ b/testing/jumbf_utils.cc
@@ -28,7 +28,7 @@
 #include "absl/status/status_matchers.h"  // IWYU pragma: keep
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "constants/labels.h"
 #include "crypto/default/hasher.h"
 #include "crypto/hash.h"
@@ -126,7 +126,7 @@
 }
 
 std::string EncodeAssertionBoxOrDie(const Assertion& assertion,
-                                    const AssertionEncoder& encoder) {
+                                    const AssertionCborEncoder& encoder) {
   auto assertion_cbor = encoder.ToCbor(assertion);
   ABSL_CHECK_OK(assertion_cbor);
   std::string assertion_cbor_box =
diff --git a/testing/jumbf_utils.h b/testing/jumbf_utils.h
index e1912ff..c0ac50a 100644
--- a/testing/jumbf_utils.h
+++ b/testing/jumbf_utils.h
@@ -22,7 +22,7 @@
 
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
-#include "assertion/assertion_encoder.h"
+#include "assertion/assertion_cbor_encoder.h"
 #include "jumbf/box.h"
 #include "proto/assertion.pb.h"
 
@@ -61,7 +61,7 @@
 
 // Returns a serialized assertion JUMBF box.
 std::string EncodeAssertionBoxOrDie(const Assertion& assertion,
-                                    const AssertionEncoder& encoder);
+                                    const AssertionCborEncoder& encoder);
 
 // Returns the hash of the given box per
 // https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_hashed_uris.