diff --git a/js/docs/astro.config.mjs b/js/docs/astro.config.mjs index 11de09ad9..4bd9328c0 100644 --- a/js/docs/astro.config.mjs +++ b/js/docs/astro.config.mjs @@ -70,6 +70,10 @@ export default defineConfig({ // label: "Reference", // autogenerate: { directory: "reference" }, // }, + { + label: "Video Metadata", + autogenerate: { directory: "video-metadata" }, + }, { label: "Components", autogenerate: { directory: "components" }, diff --git a/js/docs/src/content/docs/video-metadata/c2pa-integration.md b/js/docs/src/content/docs/video-metadata/c2pa-integration.md new file mode 100644 index 000000000..a59a3b176 --- /dev/null +++ b/js/docs/src/content/docs/video-metadata/c2pa-integration.md @@ -0,0 +1,193 @@ +--- +title: "C2PA Integration" +sidebar: + order: 40 +--- + +The actual metadata insertion and signing done to each video segment is through +[C2PA](https://c2pa.org/) tooling. A +[fork](https://github.com/streamplace/c2pa-rs) of their Rust SDK that adds +support for ES256K is used, and called in Go through FFI. The code for this can +be seen in `rust/iroh-streamplace/src/c2pa.rs`. + +The metadata stored in the video with C2PA must be in a valid C2PA "manifest" +format, documented in the specification. Here is an example Streamplace +manifest, extracted from the MP4 segment using +[c2patool](https://github.com/contentauth/c2pa-rs/tree/main/cli). + +```json +{ + "active_manifest": "urn:c2pa:b23b55a7-bd34-4138-99d8-ce565fab3934", + "manifests": { + "urn:c2pa:b23b55a7-bd34-4138-99d8-ce565fab3934": { + "claim_generator_info": [ + { + "name": "c2pa-rs", + "version": "0.58.0", + "org.contentauth.c2pa_rs": "0.58.0" + } + ], + "title": "Livestream Segment at 2025-10-21T19:24:24.156Z", + "instance_id": "xmp:iid:17f3177c-7cfe-4de2-9a23-019dcdb00559", + "ingredients": [], + "assertions": [ + { + "label": "c2pa.actions.v2", + "data": { + "actions": [ + { + "action": "c2pa.created" + }, + { + "action": "c2pa.published" + } + ], + "allActionsIncluded": false + } + }, + { + "label": "c2pa.hash.bmff.v3", + "data": { + "exclusions": [ + { + "xpath": "/uuid", + "length": null, + "data": [ + { + "offset": 8, + "value": "2P7D1hsOSDySl1goh37EgQ==" + } + ], + "subset": null, + "version": null, + "flags": null, + "exact": null + }, + { + "xpath": "/ftyp", + "length": null, + "data": null, + "subset": null, + "version": null, + "flags": null, + "exact": null + }, + { + "xpath": "/mfra", + "length": null, + "data": null, + "subset": null, + "version": null, + "flags": null, + "exact": null + } + ], + "alg": "sha256", + "hash": "HrLwGm+HdaZh9TkBiWhJH1Mo7QcvLgmhMThcG8f3qZc=", + "name": "jumbf manifest" + } + }, + { + "label": "place.stream.metadata", + "data": { + "@context": { + "photoshop": "http://ns.adobe.com/photoshop/1.0/", + "dc": "http://purl.org/dc/elements/1.1/", + "xmpRights": "http://ns.adobe.com/xap/1.0/rights/", + "Iptc4xmpExt": "http://iptc.org/std/Iptc4xmpExt/2008-02-29/" + }, + "dc:creator": "did:plc:y3lae7hmqiwyq7w2v3bcb2c2", + "dc:title": ["🦎🦎"], + "dc:date": ["2025-10-21T19:24:24.156Z"], + "distributionPolicy": { + "deleteAfter": "2025-10-21T19:29:24.000Z" + }, + "Iptc4xmpExt:LinkedEncRightsExpr": "http://creativecommons.org/publicdomain/zero/1.0/" + }, + "kind": "Json" + }, + { + "label": "place.stream.metadata.configuration", + "data": { + "$type": "place.stream.metadata.configuration", + "contentRights": { + "license": "place.stream.metadata.contentRights#cc0_1__0" + }, + "distributionPolicy": { + "deleteAfter": 300 + } + } + }, + { + "label": "place.stream.livestream", + "data": { + "url": "https://picnic-labs-nicholas-not.trycloudflare.com", + "post": { + "cid": "bafyreicucf722xnyf74psia5ghd5usdnona4e7bkcgbqhdma2a6dokqh5m", + "uri": "at://did:plc:y3lae7hmqiwyq7w2v3bcb2c2/app.bsky.feed.post/3lxyfybn55m2o" + }, + "$type": "place.stream.livestream", + "thumb": { + "ref": { + "$link": "bafkreiauoc74hcintbaua7tvp233qbfl4iymiyocc5aclhyohkz3bdinty" + }, + "size": 9231, + "$type": "blob", + "mimeType": "image/jpeg" + }, + "title": "🦎🦎", + "createdAt": "2025-10-06T16:25:06.950Z" + } + } + ], + "signature_info": { + "issuer": "Streamplace", + "common_name": "did:key:zQ3shfmFgwDstMiGaAkS4HhMJ7p3pTVhyLTHz9ABbhd4v4KJn", + "cert_serial_number": "54472225560857906834076190516168844896" + }, + "label": "urn:c2pa:b23b55a7-bd34-4138-99d8-ce565fab3934" + } + } +} +``` + +The official version of c2patool can extract this manifest, but will not +consider it valid due to the use of ES256K. If you build c2patool from the +[fork](https://github.com/streamplace/c2pa-rs) used by Streamplace, it will +validate. + +Note the variety of information stored in the manifest: user DID, signing key, +timestamp, content warnings, copyright, etc. More can be added in the future, +for example whether you consent to remixing. + +You can see several assertions with the name `place.stream.*`. This is where +Streamplace-specific metadata is stored, and is related to the lexicon. It's the +easiest place to parse out this metadata. + +In addition to the primary `place.stream` assertions, we make a best-effort +attempt to translate the Streamplace assertions into spec-compliant C2PA +metadata assertions, which are also included in the signed manifest. This allows +other C2PA-compliant software to parse out information about Streamplace +segments, such as content warnings. However, not everything Streamplace does +fits neatly into C2PA-compliant metadata, so the primary source of truth for +metadata on a Streamplace segment remains the `place.stream` assertions. + +## Code paths + +The C2PA manifest is built from the existing livestream metadata in +`pkg/media/manifest_builder.go`. This `ManifestBuilder` does the work of mapping +metadata settings from the user stored in the database into C2PA manifest +information. This is used by the media signer (`pkg/media/media_signer.go`) +which calls the Go-Rust wrapper to actually perform the C2PA injection into the +MP4, located at `pkg/iroh/generated/iroh_streamplace/iroh_streamplace.go` and +`rust/iroh-streamplace/src/c2pa.rs`. + +The stream key is what is used for C2PA signing. + +## Transcoding + +C2PA supports linking media in a hierarchy, where one piece of media can derive +from one or more parents. Although this is not supported by Streamplace yet, in +the future it will be possible to add this information into video metadata when +a segment gets transcoded. The output video segment will contain a +`c2pa.transcoded` action that links back to the original video segment. diff --git a/js/docs/src/content/docs/video-metadata/intro.md b/js/docs/src/content/docs/video-metadata/intro.md new file mode 100644 index 000000000..0185a7773 --- /dev/null +++ b/js/docs/src/content/docs/video-metadata/intro.md @@ -0,0 +1,32 @@ +--- +title: "Introduction" +sidebar: + order: 10 +--- + +Every stream on Streamplace is cryptographically bound with metadata that +captures details about how the stream should be viewed, used, distributed, and +monetized. This metadata also identifies the provenance of the stream, including +who the creator is and any transformations it has undergone. Any Streamplace +node operator can inspect the stream and verify that this metadata is intact and +trustworthy. + +This means that even when Streamplace video is downloaded or redistributed, it +can still be provably linked back to the original streamer, as long as metadata +wasn't stripped. This is a powerful property that allows for sourcing, +fact-checking, remixing, and more, all with attribution built-in. + +The technical standard Streamplace has adopted for this is the +[Coalition for Content Provenance and Authenticity](https://c2pa.org/) (C2PA). +The benefit of adhering to a standard is that it's a well-vetted specification +developed by many organizations in the digital media space, which means +Streamplace doesn't need to reinvent the wheel. It also provides +interoperability as more companies, devices, and software ecosystems adopt the +same standard. + +However, the current Streamplace implementation isn't fully compliant with the +C2PA standard. Streamplace uses the ES256K algorithm (ECDSA with SHA-256 over +`secp256k1`) for signing, which aligns with the cryptographic standards of the +AT Protocol and other decentralized systems but is not yet officially supported +by C2PA. We hope the standard will recognize this algorithm in the future to +simplify integration with decentralized protocols. diff --git a/js/docs/src/content/docs/video-metadata/metadata-record.md b/js/docs/src/content/docs/video-metadata/metadata-record.md new file mode 100644 index 000000000..54df241ba --- /dev/null +++ b/js/docs/src/content/docs/video-metadata/metadata-record.md @@ -0,0 +1,46 @@ +--- +title: "Metadata Record" +sidebar: + order: 30 +--- + +The `place.stream.metadata.configuration` record is the core structure that +defines how a stream should be presented, used, and distributed. The current +lexicon is a starting point for future iterations. + +This record is created by users through the Streamplace frontend and contains +three main components: + +1. **Content Warnings** (`place.stream.metadata.contentWarnings`): Users can + select content warnings to indicate to node operators and viewers what types + of warnings have been disclosed. The system supports ten predefined warning + categories including _violence_, _nudity_, _flashing lights_, _language_, + _drug use_, _death_, _sexuality_, _suffering_, _fantasy violence_, and + _personally identifiable information (PII)_. These categories are based on + the + [IPTC controlled vocabulary for content warnings](https://cv.iptc.org/newscodes/contentwarning/). + Each warning provides descriptions to help creators properly categorize their + content. Streamplace node operators may also configure their nodes to exclude + certain types of content. +2. **Content Rights** (`place.stream.metadata.contentRights`): This section + captures copyright and attribution information, including the creator’s name, + copyright notice, publication year, license type, and credit line. The system + supports various pre-defined licensing options from several Creative Commons + licenses (CC0, CC-BY, CC-BY-SA, CC-BY-NC, CC-BY-NC-SA, CC-BY-ND, and + CC-BY-NC-ND) to “All Rights Reserved”, as well as the option to input custom + licensing terms. +3. **Distribution Policy** (`place.stream.metadata.distributionPolicy`): This + section currently allows creators to specify a `deleteAfter` property, which + is meant to indicate the time after which the user no longer wants the stream + to be made available for playback. It also allows you to optionally restrict + syndication of your livestream to a certain set of broadcasters. + +When a user creates or updates their metadata configuration through the +frontend, the record is published to their Personal Data Server (PDS) with the +AT URI pattern `at://[did]/place.stream.metadata.configuration/self`. When a +user goes live, the node extracts this metadata configuration from the local +database and uses it to build C2PA manifests for each stream segment. This +ensures that every piece of the stream carries the creator’s intentions and +requirements for how it should be handled in a tamper-resistant manner. For +detailed schema information, +[see the lexicon reference](/docs/lex-reference/metadata/place-stream-metadata-configuration/). diff --git a/js/docs/src/content/docs/video-metadata/signing.md b/js/docs/src/content/docs/video-metadata/signing.md new file mode 100644 index 000000000..a23826244 --- /dev/null +++ b/js/docs/src/content/docs/video-metadata/signing.md @@ -0,0 +1,37 @@ +--- +title: "How Signing Works" +sidebar: + order: 20 +--- + +The signing process integrates the user's identity and preferences with the C2PA +standard to produce a verifiable stream. At a high level, this is how it works: + +1. **Key Generation**: The user clicks "Generate Stream Key" on the Streamplace + frontend to create a `secp256k1` keypair. +2. **Key Distribution**: The user is given a stream key that includes the + private key combined with their DID, encoded in a multibase format. The + corresponding public key is stored in the user's PDS as a `place.stream.key` + AT Protocol record for public verification. +3. **Node Synchronization (Key)**: When the `place.stream.key` record is + created, the AT Protocol firehose picks it up. Streamplace nodes then sync + this record to a local SQLite database. +4. **Metadata Configuration**: In a similar process, the user creates a + `place.stream.metadata.configuration` record via the frontend. This record + contains the user's preferences for **content warnings**, **content rights**, + and **distribution policy**. This record is also synced by nodes to their + local database. +5. **Stream Authentication**: When a user starts a stream, they include their + stream key as a param in the WHIP or RTMPS request to the node. The node + decodes the key, extracts the private key and DID, and verifies that the + public key exists and is valid. +6. **Signer Creation**: Once authenticated, the node creates a signer instance + using the user's private key. +7. **Segmentation**: The incoming live stream is segmented into one-second MP4 + chunks. +8. **Manifest Creation and Signing**: For each segment, the node creates a C2PA + manifest using the user's metadata configuration. It then uses the streamer's + private key to sign the manifest, and embeds the signed manifest directly + into the MP4 segment. +9. **Signed Segments**: The output is a continuous stream of MP4 segments, each + cryptographically signed and containing its own C2PA manifest.