Skip to content

Commit 4b08cdc

Browse files
authored
feat: add signAndGetManifestBytes() (#36)
* feat: add signAndGetManifestBytes method * fix: wasm-opt installation path * chore: changeset * docs: update doc blocks
1 parent ff7ad1a commit 4b08cdc

File tree

8 files changed

+152
-16
lines changed

8 files changed

+152
-16
lines changed

.changeset/petite-weeks-own.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@contentauth/c2pa-wasm': patch
3+
'@contentauth/c2pa-web': patch
4+
---
5+
6+
Add signAndGetManifestBytes() method

Cargo.lock

Lines changed: 18 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/c2pa-wasm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ async-trait = "0.1.88"
2727
thiserror = "2.0.12"
2828
serde-wasm-bindgen = "0.6.5"
2929
serde = "1.0.219"
30+
serde_bytes = "0.11.19"
3031

3132
[dependencies.web-sys]
3233
version = "0.3.77"

packages/c2pa-wasm/src/wasm_builder.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::io::Cursor;
99

1010
use c2pa::Builder;
1111
use js_sys::{Error as JsError, JsString};
12-
use serde::Serialize;
12+
use serde::{Deserialize, Serialize};
1313
use serde_wasm_bindgen::Serializer;
1414
use wasm_bindgen::prelude::*;
1515
use web_sys::Blob;
@@ -27,6 +27,15 @@ pub struct WasmBuilder {
2727
serializer: Serializer,
2828
}
2929

30+
/// Holds the bytes of an asset and manifest.
31+
#[derive(Deserialize, Serialize)]
32+
struct AssetAndManifestBytes {
33+
#[serde(with = "serde_bytes")]
34+
pub asset: Vec<u8>,
35+
#[serde(with = "serde_bytes")]
36+
pub manifest: Vec<u8>,
37+
}
38+
3039
#[wasm_bindgen]
3140
impl WasmBuilder {
3241
/// Attempts to create a new `WasmBuilder` from a JSON ManifestDefinition string.
@@ -119,18 +128,55 @@ impl WasmBuilder {
119128
signer_definition: &SignerDefinition,
120129
format: &str,
121130
source: &Blob,
131+
) -> Result<Vec<u8>, JsError> {
132+
let mut asset: Vec<u8> = Vec::new();
133+
134+
self.sign_internal(signer_definition, format, source, &mut asset)
135+
.await?;
136+
137+
Ok(asset)
138+
}
139+
140+
/// Sign an asset using the provided SignerDefinition, format, and source Blob.
141+
/// Use this method to get both the manifest bytes and the bytes of the signed asset.
142+
#[wasm_bindgen(js_name = signAndGetManifestBytes)]
143+
pub async fn sign_and_get_manifest_bytes(
144+
&mut self,
145+
signer_definition: &SignerDefinition,
146+
format: &str,
147+
source: &Blob,
148+
) -> Result<JsValue, JsError> {
149+
let mut asset: Vec<u8> = Vec::new();
150+
151+
let manifest = self
152+
.sign_internal(signer_definition, format, source, &mut asset)
153+
.await?;
154+
155+
let result = AssetAndManifestBytes { manifest, asset }
156+
.serialize(&self.serializer)
157+
.map_err(WasmError::from)?;
158+
159+
Ok(result)
160+
}
161+
162+
async fn sign_internal(
163+
&mut self,
164+
signer_definition: &SignerDefinition,
165+
format: &str,
166+
source: &Blob,
167+
dest: &mut Vec<u8>,
122168
) -> Result<Vec<u8>, JsError> {
123169
let signer = WasmSigner::from_definition(&signer_definition)?;
124170
let mut stream = BlobStream::new(source);
125171

126-
let mut bytes: Vec<u8> = Vec::new();
127-
let mut cursor = Cursor::new(&mut bytes);
172+
let mut cursor = Cursor::new(dest);
128173

129-
self.builder
174+
let manifest = self
175+
.builder
130176
.sign_async(&signer, format, &mut stream, &mut cursor)
131177
.await
132-
.unwrap();
178+
.map_err(WasmError::from)?;
133179

134-
Ok(bytes)
180+
Ok(manifest)
135181
}
136182
}

packages/c2pa-web/src/lib/builder.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export interface Builder {
3939
* Sets the state of the no_embed flag. To skip embedding a manifest (e.g. for the remote-only case) set this to `true`.
4040
*
4141
* @param noEmbed Value to set the no_embed flag.
42-
* @returns
4342
*/
4443
setNoEmbed: (noEmbed: boolean) => Promise<void>;
4544

@@ -86,12 +85,28 @@ export interface Builder {
8685
*/
8786
sign: (signer: Signer, format: string, blob: Blob) => Promise<Uint8Array>;
8887

88+
/**
89+
* Sign an asset and get both the signed asset bytes and the manifest bytes.
90+
*
91+
* @todo Docs coming soon
92+
*/
93+
signAndGetManifestBytes: (
94+
signer: Signer,
95+
format: string,
96+
blob: Blob
97+
) => Promise<ManifestAndAssetBytes>;
98+
8999
/**
90100
* Dispose of this Builder, freeing the memory it occupied and preventing further use. Call this whenever the Builder is no longer needed.
91101
*/
92102
free: () => Promise<void>;
93103
}
94104

105+
export interface ManifestAndAssetBytes {
106+
manifest: Uint8Array;
107+
asset: Uint8Array;
108+
}
109+
95110
/**
96111
* @param worker - Worker (via WorkerManager) to be associated with this reader factory.
97112
* @returns A {@link BuilderFactory} object containing builder creation methods.
@@ -176,6 +191,21 @@ function createBuilder(
176191
return result;
177192
},
178193

194+
async signAndGetManifestBytes(signer: Signer, format: string, blob: Blob) {
195+
const payload = await getSerializablePayload(signer);
196+
const requestId = worker.registerSignReceiver(signer.sign);
197+
198+
const result = await tx.builder_signAndGetManifestBytes(
199+
id,
200+
requestId,
201+
payload,
202+
format,
203+
blob
204+
);
205+
206+
return result;
207+
},
208+
179209
async free() {
180210
onFree();
181211
await tx.builder_free(id);

packages/c2pa-web/src/lib/worker.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,39 @@ rx({
116116
);
117117
return transfer(signedBytes, signedBytes.buffer);
118118
},
119+
async builder_signAndGetManifestBytes(
120+
builderId,
121+
requestId,
122+
payload,
123+
format,
124+
blob
125+
) {
126+
const builder = builderMap.get(builderId);
127+
const { manifest, asset } = await builder.signAndGetManifestBytes(
128+
{
129+
reserveSize: payload.reserveSize,
130+
alg: payload.alg,
131+
sign: async (bytes) => {
132+
const result = await tx.sign(
133+
requestId,
134+
transfer(bytes, bytes.buffer),
135+
payload.reserveSize
136+
);
137+
return result;
138+
},
139+
},
140+
format,
141+
blob
142+
);
143+
144+
return transfer(
145+
{
146+
manifest,
147+
asset,
148+
},
149+
[manifest.buffer, asset.buffer]
150+
);
151+
},
119152
builder_free(builderId) {
120153
const builder = builderMap.get(builderId);
121154
builder.free();

packages/c2pa-web/src/lib/worker/rpc.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* it.
88
*/
99

10+
import { ManifestAndAssetBytes } from '../builder.js';
1011
import type { SerializableSigningPayload } from '../signer.js';
1112

1213
import { channel } from 'highgain';
@@ -61,6 +62,13 @@ const { createTx, rx } = channel<{
6162
format: string,
6263
blob: Blob
6364
) => Promise<Uint8Array>;
65+
builder_signAndGetManifestBytes: (
66+
builderId: number,
67+
requestId: number,
68+
payload: SerializableSigningPayload,
69+
format: string,
70+
blob: Blob
71+
) => Promise<ManifestAndAssetBytes>;
6472
builder_free: (builderId: number) => void;
6573
}>();
6674

tools/nx-wasm-bindgen/src/executors/build/executor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const runExecutor: PromiseExecutor<BuildExecutorSchema> = async (
5656
// Run wasm-opt on wasm-bindgen output, optimizing for size
5757
const wasmOptInput = path.join(outDir, `${outputWasmName}_bg.wasm`);
5858

59-
const wasmOpt = getWasmOptPath(context.cwd);
59+
const wasmOpt = getWasmOptPath(context.root);
6060
await $$`node ${wasmOpt} ${wasmOptInput} -o ${wasmOptInput} -Oz`;
6161

6262
// Compute SRI integrity and append it to wasm-bindgen's JS output and .d.ts as an exported const
@@ -90,8 +90,8 @@ const runExecutor: PromiseExecutor<BuildExecutorSchema> = async (
9090
export default runExecutor;
9191

9292
// Install wasm-opt's node wrapper if necessary and return a path to it.
93-
async function getWasmOptPath(cwd: string) {
94-
const basePath = path.join(cwd, 'tools/nx-wasm-bindgen');
93+
async function getWasmOptPath(rootDir: string) {
94+
const basePath = path.join(rootDir, 'tools/nx-wasm-bindgen');
9595
const downloadUrl = `https://github.com/WebAssembly/binaryen/releases/download/version_${WASM_OPT_VERSION}/binaryen-version_${WASM_OPT_VERSION}-node.tar.gz`;
9696
const downloadDir = path.join(basePath, 'download/');
9797
const downloadFilePath = path.join(downloadDir, 'wasm_opt.tar.gz');

0 commit comments

Comments
 (0)