Skip to content
Merged
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# Generated files
.docusaurus
.cache-loader

# External files loaded by fetch-readmes script
/docs/js-sdk/api
/docs/js-sdk/examples/quickstart/*.ts
/docs/js-sdk/examples/view-manifest/*.ts
Expand All @@ -23,6 +25,7 @@
/docs/rust-sdk/*.md
/docs/rust-sdk/docs/*.md
/docs/**/readme.md
/static/sb-alg-list.json

# Misc
.DS_Store
Expand Down
23 changes: 23 additions & 0 deletions docs/soft-bindings.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
id: sb-algs
title: Watermarking and fingerprinting algorithms
hide_table_of_contents: true
---

[_Durable Content Credentials_](https://contentauthenticity.org/blog/durable-content-credentials) is a concept that helps content provenance to persist across content platforms by also:

- **Invisible watermarks**, actively inserted into the content
- **Content fingerprinting**, passively computed from the content.

Platforms might remove C2PA manifest data, if, for example, they use software that does not yet support the standard. By storing a copy of the manifest data in an online database, you can use a watermark or a fingerprint to find it again.
Combining both watermark and fingerprints further improves the robustness of the provenance information.

In the C2PA specification, watermarks and content fingerprinting are known as [soft bindings](https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_soft_bindings), which can be used to find digital content, even if the underlying bits differ. The C2PA specification requires that soft bindings be generated using one of the [algorithms](https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_soft_binding_algorithm_list) approved by the C2PA Technical Working Group. The following table lists the algorithms from the [C2PA Soft Binding Algorithm List](https://github.com/c2pa-org/softbinding-algorithm-list).

:::note
The table below is provided **for information only** and is created and automatically updated based on data from the C2PA [softbinding-algorithm-list](https://github.com/c2pa-org/softbinding-algorithm-list/blob/main/softbinding-algorithm-list.json) repository, which is the single authoritative source of the information.
:::

import JSONToTable from '@site/src/components/JSONToTable';

<JSONToTable />
5 changes: 5 additions & 0 deletions scripts/fetch-readme.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ const readmes = [
repo: 'contentauth/c2pa-min',
path: 'README.md',
},
{
dest: resolve(__dirname, '../static/sb-alg-list.json'),
repo: 'c2pa-org/softbinding-algorithm-list',
path: 'softbinding-algorithm-list.json',
},
];

function resolveMarkdownLinks(linkBase, content) {
Expand Down
13 changes: 11 additions & 2 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,17 @@ const sidebars = {
id: 'faqs',
},
{
type: 'doc',
id: 'community-resources',
type: 'category',
label: 'Community resources',
link: { type: 'doc', id: 'community-resources' },
collapsed: true,
items: [
{
type: 'doc',
label: 'Watermarking and fingerprinting',
id: 'sb-algs',
},
],
},
{
type: 'doc',
Expand Down
130 changes: 130 additions & 0 deletions src/components/JSONToTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { useState, useEffect } from 'react';

// Define the fields to display and their custom headers
const DISPLAY_FIELDS = {
alg: 'Algorithm Name',
decodedMediaTypes: 'Media Types',
'entryMetadata.informationalUrl': 'URL',
'entryMetadata.contact': 'Contact',
};

// Helper function to safely extract nested values and flatten arrays
const getNestedValue = (obj, path) => {
const value = path
.split('.')
.reduce(
(acc, key) => (acc && acc[key] !== undefined ? acc[key] : 'N/A'),
obj,
);
return Array.isArray(value) ? value.join(', ') : value; // Flatten arrays into a comma-separated string
};

// Function to extract and filter only the required fields
const extractAndFilterObject = (obj) => {
let result = {};
for (const key in DISPLAY_FIELDS) {
result[key] = getNestedValue(obj, key);
}
return result;
};

// Helper function to capitalize each word in a string
const capitalizeWords = (str) => {
return str
.split(' ')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};

const JSONToTable = () => {
const [jsonData, setJsonData] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
fetch('/sb-alg-list.json')
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch the JSON file');
}
return response.json();
})
.then((data) => {
setJsonData(data);
setError(null);
})
.catch((err) => {
setError(err.message);
setJsonData(null);
});
}, []);

// Function to group data by the 'type' field
const groupDataByType = (data) => {
return data.reduce((acc, item) => {
const type = getNestedValue(item, 'type');
if (!acc[type]) acc[type] = [];
acc[type].push(item);
return acc;
}, {});
};

const generateTableHeaders = () => {
return Object.keys(DISPLAY_FIELDS).map((key) => (
<th key={key}>{DISPLAY_FIELDS[key]}</th>
));
};

const generateTableRows = (data) => {
return data.map((item, rowIndex) => {
const filteredData = extractAndFilterObject(item);
const url = filteredData['entryMetadata.informationalUrl'];
const description = getNestedValue(item, 'entryMetadata.description');

return (
<tr key={rowIndex}>
{Object.keys(DISPLAY_FIELDS).map((key, index) => (
<td key={index}>
{key === 'entryMetadata.informationalUrl' ? (
url !== 'N/A' && description !== 'N/A' ? (
<a href={url} target="_blank" rel="noopener noreferrer">
{description}
</a>
) : (
'N/A'
)
) : (
filteredData[key]
)}
</td>
))}
</tr>
);
});
};

if (error) return <p style={{ color: 'red' }}>{error}</p>;

if (!jsonData || !Array.isArray(jsonData)) {
return <p>No data found in the JSON. Expected an array of objects.</p>;
}

const groupedData = groupDataByType(jsonData);

return (
<div>
{Object.entries(groupedData).map(([type, data]) => (
<div key={type} style={{ marginBottom: '30px' }}>
<h2>{capitalizeWords(type)} algorithms</h2>
<table style={{ borderCollapse: 'collapse' }}>
<thead>
<tr>{generateTableHeaders()}</tr>
</thead>
<tbody>{generateTableRows(data)}</tbody>
</table>
</div>
))}
</div>
);
};

export default JSONToTable;
Loading