c2pa 0.80.3

Rust SDK for C2PA (Coalition for Content Provenance and Authenticity) implementors
Documentation
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

//! Example App showing how to use the new v2 API
use std::io::{Cursor, Seek};

use anyhow::Result;
use c2pa::{
    crypto::raw_signature::SigningAlg, settings::Settings, validation_results::ValidationState,
    Builder, CallbackSigner, Context, Reader,
};
use serde_json::json;

const TEST_IMAGE: &[u8] = include_bytes!("../tests/fixtures/CA.jpg");
const CERTS: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pub");
const PRIVATE_KEY: &[u8] = include_bytes!("../tests/fixtures/certs/ed25519.pem");

fn manifest_def(title: &str, format: &str) -> String {
    json!({
        "title": title,
        "format": format,
        "claim_generator_info": [
            {
                "name": "c2pa test",
                "version": env!("CARGO_PKG_VERSION")
            }
        ],
        "thumbnail": {
            "format": format,
            "identifier": "manifest_thumbnail.jpg"
        },
        "ingredients": [
            {
                "title": "Test",
                "format": "image/jpeg",
                "instance_id": "12345",
                "relationship": "inputTo"
            }
        ],
        "assertions": [
            {
                "label": "c2pa.actions",
                "data": {
                    "actions": [
                        {
                            "action": "c2pa.edited",
                            "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia",
                            "softwareAgent": {
                                "name": "My AI Tool",
                                "version": "0.1.0"
                            }
                        }
                    ]
                }
            }
        ]
    }).to_string()
}

/// This example demonstrates how to use the API to create a manifest store
/// It uses only streaming apis, showing how to avoid file i/o
/// This example uses the `ed25519` signing algorithm
fn main() -> Result<()> {
    let title = "edited.jpg";
    let format = "image/jpeg";
    let parent_name = "CA.jpg";
    let mut source = Cursor::new(TEST_IMAGE);

    let modified_core = toml::toml! {
        [core]
        debug = true
        hash_alg = "sha512"
        max_memory_usage = 123456
    }
    .to_string();

    let mut settings =
        Settings::new().with_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
    settings.update_from_str(&modified_core, "toml")?;

    let ed_signer =
        |_context: *const (), data: &[u8]| CallbackSigner::ed25519_sign(data, PRIVATE_KEY);
    let signer = CallbackSigner::new(ed_signer, SigningAlg::Ed25519, CERTS);

    // The context here holds loaded settings for further use
    let context = Context::new()
        .with_settings(settings)?
        .with_signer(signer)
        .into_shared();

    // Create a builder using the context to propagate settings usage to the Builder
    let mut builder =
        Builder::from_shared_context(&context).with_definition(manifest_def(title, format))?;
    builder.add_ingredient_from_stream(
        json!({
            "title": parent_name,
            "relationship": "parentOf",
            "label": "parent_label",  // use a label to tie this ingredient to an action
        })
        .to_string(),
        format,
        &mut source,
    )?;

    builder.add_action(json!({
        "action": "c2pa.opened",
        "parameters": {
            "ingredientIds": ["parent_label"], // the ingredient title to reference the ingredient
        }
    }))?;

    let thumb_uri = builder
        .definition
        .thumbnail
        .as_ref()
        .map(|t| t.identifier.clone());

    // add a manifest thumbnail ( just reuse the image for now )
    if let Some(uri) = thumb_uri {
        if !uri.starts_with("self#jumbf") {
            source.rewind()?;
            builder.add_resource(&uri, &mut source)?;
        }
    }

    // write the manifest builder to an archived stream
    let mut archive = Cursor::new(Vec::new());
    builder.to_archive(&mut archive)?;

    // write the archived stream to a file for debugging
    //let debug_path = format!("{}/../target/test.c2pa", env!("CARGO_MANIFEST_DIR"));
    // std::fs::write(debug_path, archive.get_ref())?;

    // unzip the manifest builder from the archived stream
    archive.rewind()?;

    let mut builder = Builder::from_shared_context(&context).with_archive(&mut archive)?;
    // sign the ManifestStoreBuilder and write it to the output stream
    let mut dest = Cursor::new(Vec::new());
    builder.save_to_stream(format, &mut source, &mut dest)?;

    // read and validate the signed manifest store
    dest.rewind()?;

    let reader = Reader::from_shared_context(&context).with_stream(format, &mut dest)?;

    // extract a thumbnail image from the ManifestStore
    let mut thumbnail = Cursor::new(Vec::new());
    if let Some(manifest) = reader.active_manifest() {
        if let Some(thumbnail_ref) = manifest.thumbnail_ref() {
            reader.resource_to_stream(&thumbnail_ref.identifier, &mut thumbnail)?;
            println!(
                "wrote thumbnail {} of size {}",
                thumbnail_ref.format,
                thumbnail.get_ref().len()
            );
        }
    }

    println!("{}", reader.json());
    assert_eq!(reader.validation_state(), ValidationState::Trusted);
    assert_eq!(reader.active_manifest().unwrap().title().unwrap(), title);

    Ok(())
}

#[cfg(test)]
mod tests {
    use c2pa_macros::c2pa_test_async;
    #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
    use wasm_bindgen_test::*;

    use super::*;

    #[c2pa_test_async]
    async fn test_api() -> Result<()> {
        main()
    }
}