CommonsDB Developer PortalCommonsDB Developer PortalCommonsDB Developer Portal
  • Documentation
  • Declaration API
  • Metadata API
  • Search API
  • Status API
Getting Started
Authentication
    Verifiable CredentialsX.509 Certification.well-known/did.json SetupMetadata SignatureTSA Signature
Useful Links
    Free TSA service
powered by Zudoku
Authentication

Metadata Signature Creation

To ensure the declaration is verifiable, all metadata must be signed using a private key, which generates a cryptographic signature proving its authenticity. CommonsDB supports two approaches for signing: X.509 Certificate-based signing and Keypair-based signing with embedded credentials.

Overview

Metadata signatures in CommonsDB serve multiple purposes:

  • Authentication: Proves the identity of the declarer
  • Integrity: Ensures the metadata hasn't been tampered with
  • Non-repudiation: Provides proof that the declaration was made by the certificate holder

Two Signing Approaches

CommonsDB supports two methods for creating signatures:

Approach 1: X.509 Certificate

  • ✓ Uses X.509 certificate chain
  • ✓ Certificate embedded in JWT header (x5c)
  • ✓ Requires certificate authority validation
  • ✓ Best for enterprise/organizational use

Approach 2: Generated Keypair

  • ✓ Uses self-generated EC keypair
  • ✓ Public key embedded as JWK in JWT header
  • ✓ Credentials included in publicMetadata
  • ✓ Best for flexible, self-managed identities

Prerequisites

Before creating signatures, ensure you have:

Required Components

  • ✓ EC (P-256) private key
  • ✓ Node.js environment
  • ✓ Structured public metadata
  • ✓ Verifiable credentials (for keypair approach)

Setup Requirements

  • ✓ JWT signing library (jsonwebtoken)
  • ✓ Crypto module for key operations
  • ✓ Valid declarerId (DID format)
  • ✓ Complete metadata structure

Approach 1: X.509 Certificate-Based Signing

This approach uses X.509 certificates to establish identity. The certificate chain is embedded in the JWT header using the x5c field.

Step 1: Prepare Your Public Metadata

Structure your public metadata according to CommonsDB requirements:

Code(javascript)
const publicMetadata = { // Required schema/context references "$schema": "https://w3id.org/commonsdb/schema/0.2.0.json", "@context": "https://w3id.org/commonsdb/context/0.2.0.json", // Core ISCC fields iscc: "ISCC:KACYPXW445FNGZZ2", name: "Example Content Title", description: "Detailed description of the content", mediatype: "image/jpeg", timestamp: Date.now(), declarerId: "did:web:yourdomain.com", // Required: Credentials array credentials: [ { "@context": ["https://www.w3.org/ns/credentials/v2"], type: ["VerifiableCredential", "VerifiableAttestation"], proof: { type: "JwtProof2020", jwt: "eyJhbGciOiJSUzI1NiJ9..." // Your credential JWT } } ], // Optional supplier metadata supplierMetadata: { location: "https://example.com/content", name: "Example Content Title", description: "Detailed description", rightsStatement: "https://creativecommons.org/publicdomain/mark/1.0/" } };

Metadata Requirements: Ensure your metadata includes all required fields: $schema, @context, iscc, name, timestamp, declarerId, and credentials. Missing required fields will cause signature validation to fail.

Step 2: Load Your Certificate

Load your X.509 certificate and private key:

Code(javascript)
const fs = require('fs'); const jwt = require('jsonwebtoken'); // Load the certificate and private key const certificatePem = fs.readFileSync('path/to/your/cert.pem', 'utf8'); const privateKeyPem = fs.readFileSync('path/to/your/private-key.pem', 'utf8'); // Convert certificate to base64 for JWT header (x5c) // Remove PEM headers/footers and newlines const certBase64 = certificatePem .replace(/-----BEGIN CERTIFICATE-----/g, '') .replace(/-----END CERTIFICATE-----/g, '') .replace(/\n/g, '') .trim();

Step 3: Create the JWT Signature

Generate a JWT signature using your certificate and private key:

Code(javascript)
const signature = jwt.sign(publicMetadata, privateKeyPem, { algorithm: 'ES256', // Use ES256 for P-256 elliptic curve header: { typ: 'JWT', alg: 'ES256', x5c: [certBase64], // Include the base64-encoded certificate } });

Step 4: Complete Certificate-Based Example

Here's a complete example:

Code(javascript)
const fs = require('fs'); const jwt = require('jsonwebtoken'); class CertificateSignatureCreator { constructor(certPath, privateKeyPath) { const certPem = fs.readFileSync(certPath, 'utf8'); this.privateKey = fs.readFileSync(privateKeyPath, 'utf8'); // Extract base64 certificate (remove PEM formatting) this.certBase64 = certPem .replace(/-----BEGIN CERTIFICATE-----/g, '') .replace(/-----END CERTIFICATE-----/g, '') .replace(/\n/g, '') .trim(); } signMetadata(publicMetadata) { // Validate required fields this.validateMetadata(publicMetadata); // Create JWT signature with x5c header const signature = jwt.sign(publicMetadata, this.privateKey, { algorithm: 'ES256', header: { typ: 'JWT', alg: 'ES256', x5c: [this.certBase64], } }); return signature; } validateMetadata(metadata) { const requiredFields = [ '$schema', '@context', 'iscc', 'name', 'timestamp', 'declarerId', 'credentials' ]; for (const field of requiredFields) { if (!metadata[field]) { throw new Error(`Missing required field: ${field}`); } } if (!Array.isArray(metadata.credentials) || metadata.credentials.length === 0) { throw new Error('credentials must be a non-empty array'); } } } // Usage example const signer = new CertificateSignatureCreator( 'path/to/cert.pem', 'path/to/private-key.pem' ); const signature = signer.signMetadata(publicMetadata); console.log('Generated signature:', signature);

Approach 2: Keypair-Based Signing with Embedded JWK

This approach uses a self-generated EC keypair. The public key is embedded in the JWT header as a JSON Web Key (JWK), and verifiable credentials are included in the publicMetadata.

Step 1: Generate an EC Keypair

First, generate an EC (P-256) keypair:

Code(bash)
# Generate private key openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem # Extract public key openssl ec -in private_key.pem -pubout -out public_key.pem

Step 2: Prepare Your Public Metadata with Credentials

Include your verifiable credentials in the publicMetadata:

Code(javascript)
const publicMetadata = { // Required schema/context references "$schema": "https://w3id.org/commonsdb/schema/0.2.0.json", "@context": "https://w3id.org/commonsdb/context/0.2.0.json", // Core ISCC fields iscc: "ISCC:KACYPXW445FNGZZ2", name: "Example Content Title", description: "Detailed description of the content", mediatype: "image/jpeg", timestamp: Date.now(), declarerId: "did:key:zXwpS3znFpTwEPepQh9pDBEXJe15DjPeCQyhJ1gv5ukdtCoHsMkSahUM1Br5Zufb7EqkzbYaGayuNqa9Yn1cRV2ECvsS", // Required: Credentials array with verifiable credentials credentials: [ { "@context": ["https://www.w3.org/ns/credentials/v2"], id: "urn:uuid:ec41919f-fb54-4f53-8234-d7c0f7b76828", type: ["VerifiableCredential", "VerifiableAttestation", "VerifiableSupplier"], issuer: "did:web:openfuture.eu", validFrom: "2025-07-09T10:56:50.328Z", validUntil: "2028-07-09T10:56:50.328Z", credentialSubject: { id: "did:key:zXwpS3znFpTwEPepQh9pDBEXJe15DjPeCQyhJ1gv5ukdtCoHsMkSahUM1Br5Zufb7EqkzbYaGayuNqa9Yn1cRV2ECvsS", sameAs: "Your Organization Name", dataSupplierFor: "registry.commonsdb.org" }, proof: { type: "JwtProof2020", jwt: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlHa1RDQ0JQbWdBd0lCQWdJREZTeWpNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1Gd3hDekFKQmdOVkJBWVRBbE5KTVJRd0VnWURWUVFLRXd0SVlXeGpiMjBnWkM1a0xqRVhNQlVHQTFVRVlSTU9Wa0ZVVTBrdE5ETXpOVE14TWpZeEhqQWNCZ05WQkFNVEZVaGhiR052YlNCRFFTQlFUeUJsTFhObFlXd2dNakFlRncweU5UQTFNRFV4TURFd05ERmFGdzB5T0RBMU1EVXhNREV3TkRGYU1JR0lNUXN3Q1FZRFZRUUdFd0pPVERFZU1Cd0dBMVVFQ2hNVlUxUkpRMGhVU1U1SElFOVFSVTRnUmxWVVZWSkZNUnN3R1FZRFZRUmhFeEpXUVZST1RDMDROakUzTnpnMU16UkNNREV4SERBYUJnTlZCQXNURTJVdGMyVmhiQ0JqWlhKMGFXWnBZMkYwWlhNeEhqQWNCZ05WQkFNVEZXUnBaRHAzWldJNmIzQmxibVoxZEhWeVpTNWxkVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNb2h1ZnkvV3lqVzRHL0gzRDlvVFo3d3R5MTdTQ1NOQWYyOXhBM1oxdWtldkNHb3hweHJTdXl3MDc3UnRoc2NHNUZueklVMkVoOXBIMENXSDkxQmpwVjFHdU9kUGZIdzZ1aWMrdmlreVoxNGFXVDhMMnZNcGFUZmVmOXgybWtrUUNkU0FTOFFDZjYrRHpuL3FEVzBLRGtiYkltVmxWRUhYVzdLdk9ZVllqVXdHT3Q4WGc0Y2JDWG12RnNkTWpycmVwd01ubWtjQ1REdDFuQ3ltbDBtZGJJWTFoR29aOHVBUENrcUNQeEkxZmVHZFRTcWxJSzlnYjZOT29yMFMzTXFFeFdhSFJMRENCNGY0VSsxdnJZamRNakZjQ09ERk1PcjF4RWNuSVc1ZUxubGR6WnBuR2JwTHQvN0UyYzJvbzllenNRSmF1c1VyWFBWRWxaOW95MGlXaTBDQXdFQUFhT0NBcTB3Z2dLcE1DY0dBMVVkSlFRZ01CNEdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd1F3RXdZRFZSMGpCQXd3Q29BSVJ6WEl2R0hpWFo0d2VBWUlLd1lCQlFVSEFRTUViREJxTUJVR0NDc0dBUVVGQndzQ01Ba0dCd1FBaSt4SkFRSXdDQVlHQkFDT1JnRUJNRElHQmdRQWprWUJCVEFvTUNZV0lHaDBkSEJ6T2k4dmQzZDNMbWhoYkdOdmJTNXphUzl5WlhCdmMybDBiM0o1RXdKRlRqQVRCZ1lFQUk1R0FRWXdDUVlIQkFDT1JnRUdBakNCZ0FZSUt3WUJCUVVIQVFFRWREQnlNRTBHQ0NzR0FRVUZCekFDaGtGb2RIUndPaTh2ZDNkM0xtaGhiR052YlM1emFTOTFjR3h2WVdSekwzSmxjRzl6YVhSdmNua3ZTR0ZzWTI5dFgwTkJYMUJQWDJVdGMyVmhiRjh5TG1ObGNqQWhCZ2dyQmdFRkJRY3dBWVlWYUhSMGNEb3ZMMjlqYzNBdWFHRnNZMjl0TG5OcE1HWUdBMVVkSUFSZk1GMHdVQVlLS3dZQkJBR3VNd1VFQXpCQ01FQUdDQ3NHQVFVRkJ3SUJGalJvZEhSd09pOHZkM2QzTG1oaGJHTnZiUzV6YVM5MWNHeHZZV1J6TDJacGJHVnpMME5RVTE5b1lXeGpiMjFmWTJFdWNHUm1NQWtHQndRQWkreEFBUUV3Z2JNR0ExVWRId1NCcXpDQnFEQ0JwYUNCb3FDQm40WmxiR1JoY0RvdkwyeGtZWEF1YUdGc1kyOXRMbk5wTDJOdVBVaGhiR052YlNVeU1FTkJKVEl3VUU4bE1qQmxMWE5sWVd3bE1qQXlMRzg5U0dGc1kyOXRMR005VTBrL1kyVnlkR2xtYVdOaGRHVnlaWFp2WTJGMGFXOXViR2x6ZER0aWFXNWhjbm1HTm1oMGRIQTZMeTlrYjIxcGJtRXVhR0ZzWTI5dExuTnBMMk55YkhNdmFHRnNZMjl0WDJOaFgzQnZYMlV0YzJWaGJGOHlMbU55YkRBUkJnTlZIUTRFQ2dRSVFLRE52WFZ1eG9Bd0RnWURWUjBQQVFIL0JBUURBZ1hnTUNBR0ExVWRFUVFaTUJlQ0ZXUnBaRHAzWldJNmIzQmxibVoxZEhWeVpTNWxkVEFKQmdOVkhSTUVBakFBTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCZ1FBSjd1dWh0RlBOVlk3QXFYcWNTbVBuV0FrMURFZVJsemY3RjBqMURra05yZ1RiOTdML2FCaVJFZi9IRDQxZUNjaDVmZmFOUmJ3VmhLaFhDTG82MS9EY3RJbGxZcXRERDJ5R1o5bWhseGRPNFBtaWJhQW9lVUp4RmEyZm5QWVpPSEd0cjhRNFFTeU0vYWZzaHBnZDdEOVJ0Q2l2SjBZWTQ3ZWpyZG9yT3lUZXJZdUttQUtZVUZYMWlJMFA2VjhTQmRkOC9wdG0zWmpjbnRtRXdDZTRYUmowSGUzTjRWRUp3WkdhR3JNdmV5SmR0Z0RNZUpzYmVld3NpRkY4RGJ6S0xUZGpRc2dGQURFQ0kzL3FxTFo2UG1nUEZ3YUF3cDMvSDZHc2hONVovMlNnWUhXbGorWVYvTkphUWxiNmM2MjhmbndRLytzTEJvSnJ2aThTbFhrekZxSG53V1cvMTg3WC9OSEYyclFHQVZPanVIMGR2WklpaWZjZnZLVEFySzJBaWtldENuZWlpeFY1NllSZ3BsU3FXNHZ0VDdCTzIzN2R3a3NlV3g0Wkx6emo0M3d6RThBSEJ1ZWpaY3FnUWtzTlRlMmtISCtLMXo1cDd0SVBuMjdHYUcwL2Ryay83UGFtQkZ2c0pUTXBUZkhycTRaRUZiSEF5Z2FrNzdxV0srTmNnVzg9Il19.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvbnMvY3JlZGVudGlhbHMvdjIiXSwiaWQiOiJ1cm46dXVpZDplYzQxOTE5Zi1mYjU0LTRmNTMtODIzNC1kN2MwZjdiNzY4MjgiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVmVyaWZpYWJsZUF0dGVzdGF0aW9uIiwiVmVyaWZpYWJsZVN1cHBsaWVyIl0sImlzc3VlciI6ImRpZDp3ZWI6b3BlbmZ1dHVyZS5ldSIsInZhbGlkRnJvbSI6IjIwMjUtMDctMDlUMTA6NTY6NTAuMzI4WiIsInZhbGlkVW50aWwiOiIyMDI4LTA3LTA5VDEwOjU2OjUwLjMyOFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDprZXk6elh3cFMzem5GcFR3RVBlcFFoOXBEQkVYSmUxNURqUGVDUXloSjFndjV1a2R0Q29Ic01rU2FoVU0xQnI1WnVmYjdFcWt6YllhR2F5dU5xYTlZbjFjUlYyRUN2c1MiLCJzYW1lQXMiOiJFdXJvcGVhbmEgRm91bmRhdGlvbiIsImRhdGFTdXBwbGllckZvciI6InJlZ2lzdHJ5LmNvbW1vbnNkYi5vcmcifSwiY3JlZGVudGlhbFNjaGVtYSI6W3siaWQiOiJodHRwczovL2dpdGh1Yi5jb20vQ3JlYXRvckNyZWRlbnRpYWxzL3NwZWNpZmljYXRpb25zL2Jsb2IvbWFpbi9qc29uLXNjaGVtYS92ZXJpZmljYXRpb24tY3JlZGVudGlhbHMvc3VwcGxpZXIvc2NoZW1hLmpzb24iLCJ0eXBlIjoiSnNvblNjaGVtYSJ9XSwiaWF0IjoxNzUyMDU0Nzk3fQ.JIKkpqH9mcoEoM4a20E462nUYCsm5ypkTcwgr2PNpOMomXRwZ5M_k6DlCRw4C85ejjxZPBm5P2uLiMsHJKpjayW9GsuJiL-_QlE_e70UiFEhW_gqNoXPEYdr1JL6DY0zcK3huhVpqpEBuPL4q0_ehAqmF-2sJI3_nVAXvcvDC0FJU3iAvZiKRpZfo9wAvlyi7URWZqzZqp0DRNPJVofCM_cmylQXwzU7VUofp-NLmpGtSpgio-GJi-hJbO3VwaxDQHIAcJp02i9jVVF8ii-8HNUqtZAoUQmbzA83eebl5rGEckH9dD7sBYYsKimse9t2A527zcnRbtabwPWfScP7lg" }, credentialSchema: [ { id: "https://github.com/CreatorCredentials/specifications/blob/main/json-schema/verification-credentials/supplier/schema.json", type: "JsonSchema" } ] } ], // Optional supplier metadata supplierMetadata: { location: "https://example.com/content", name: "Example Content Title", description: "Detailed description", rightsStatement: "https://creativecommons.org/publicdomain/mark/1.0/" } };

Step 3: Convert Public Key to JWK Format

Convert your public key to JSON Web Key (JWK) format:

Code(javascript)
const fs = require('fs'); const crypto = require('crypto'); // Load public key const publicKeyPem = fs.readFileSync('public_key.pem', 'utf8'); // Convert to JWK const keyObject = crypto.createPublicKey(publicKeyPem); const jwk = keyObject.export({ format: 'jwk' }); console.log('JWK:', JSON.stringify(jwk, null, 2));

Step 4: Create JWT Signature with Embedded JWK

Generate a JWT signature with the JWK embedded in the header:

Code(javascript)
const fs = require('fs'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); // Load keys const privateKeyPem = fs.readFileSync('private_key.pem', 'utf8'); const publicKeyPem = fs.readFileSync('public_key.pem', 'utf8'); // Convert public key to JWK const keyObject = crypto.createPublicKey(publicKeyPem); const jwk = keyObject.export({ format: 'jwk' }); // Create JWT signature with JWK in header const signature = jwt.sign(publicMetadata, privateKeyPem, { algorithm: 'ES256', header: { typ: 'JWT', alg: 'ES256', jwk: jwk, // Embed JWK in header } });

Step 5: Complete Keypair-Based Example

Here's a complete example:

Code(javascript)
const fs = require('fs'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); class KeypairSignatureCreator { constructor(privateKeyPath, publicKeyPath) { this.privateKey = fs.readFileSync(privateKeyPath, 'utf8'); const publicKeyPem = fs.readFileSync(publicKeyPath, 'utf8'); // Convert public key to JWK const keyObject = crypto.createPublicKey(publicKeyPem); this.jwk = keyObject.export({ format: 'jwk' }); } signMetadata(publicMetadata) { // Validate required fields this.validateMetadata(publicMetadata); // Create JWT signature with JWK in header const signature = jwt.sign(publicMetadata, this.privateKey, { algorithm: 'ES256', header: { typ: 'JWT', alg: 'ES256', jwk: this.jwk, // Embed JWK in header } }); return signature; } validateMetadata(metadata) { const requiredFields = [ '$schema', '@context', 'iscc', 'name', 'timestamp', 'declarerId', 'credentials' ]; for (const field of requiredFields) { if (!metadata[field]) { throw new Error(`Missing required field: ${field}`); } } if (!Array.isArray(metadata.credentials) || metadata.credentials.length === 0) { throw new Error('credentials must be a non-empty array'); } } } // Usage example const signer = new KeypairSignatureCreator( 'private_key.pem', 'public_key.pem' ); const signature = signer.signMetadata(publicMetadata); console.log('Generated signature:', signature);

Signature Verification

To verify that your signature is correctly formatted, you can decode the JWT:

Code(javascript)
const jwt = require('jsonwebtoken'); // Decode JWT without verification to inspect structure const decoded = jwt.decode(signature, { complete: true }); console.log('JWT Header:', JSON.stringify(decoded.header, null, 2)); console.log('JWT Payload:', JSON.stringify(decoded.payload, null, 2));

Expected Header Structures

Certificate-based (x5c):

Code(json)
{ "typ": "JWT", "alg": "ES256", "x5c": ["base64-encoded-certificate"] }

Keypair-based (jwk):

Code(json)
{ "typ": "JWT", "alg": "ES256", "jwk": { "kty": "EC", "crv": "P-256", "x": "base64url-encoded-x-coordinate", "y": "base64url-encoded-y-coordinate" } }

Security Best Practices

Security Considerations:

  • Never log or expose private keys
  • Use secure key storage solutions in production (HSM, key vaults, encrypted storage)
  • Implement proper error handling for signing failures
  • Validate all input data before signing
  • Store credentials securely and validate them before inclusion

Key Management

  1. Secure Storage

    Store private keys in secure locations (HSM, key vaults, encrypted storage)

  2. Access Control

    Limit access to private keys to authorized systems only

  3. Key Rotation

    Regularly rotate keys/certificates and update DID documents

  4. Monitoring

    Monitor key usage and detect unauthorized signing attempts


Common Issues and Solutions

IssueCauseSolution
Invalid signature formatIncorrect algorithm or headerEnsure ES256 algorithm and proper x5c or jwk header
Certificate/JWK not foundMissing x5c or jwk in JWT headerInclude base64-encoded certificate or JWK in header
Validation failsMetadata missing required fieldsValidate metadata structure before signing (check $schema, @context, iscc, name, timestamp, declarerId, credentials)
Key mismatchPrivate key doesn't match certificate/public keyVerify key pair correspondence
Credentials validation failsMissing or invalid credentials arrayEnsure credentials is a non-empty array with valid verifiable credential structure

Integration with CommonsDB

Once you have your signature, include it in your CommonsDB declaration:

Code(javascript)
const declarationPayload = { signature: signature, // Your generated JWT signature tsaSignature: { tsr: "base64-encoded-timestamp-response", tsq: "base64-encoded-timestamp-request" }, declarationMetadata: { publicMetadata: publicMetadata } };

For complete declaration examples, see the Declaration API documentation.


Next Steps

After creating your signature:

  1. TSA Signature Creation - Add timestamp signatures for temporal verification
  2. Test your signatures with the Declaration API
  3. Implement error handling for production use
  4. Monitor certificate/key expiration and plan for renewal

Your signature is now ready for use with CommonsDB declarations, providing cryptographic proof of authenticity and integrity for your metadata.

Last modified on January 14, 2026
.well-known/did.json SetupTSA Signature
On this page
  • Overview
  • Two Signing Approaches
  • Prerequisites
  • Approach 1: X.509 Certificate-Based Signing
    • Step 1: Prepare Your Public Metadata
    • Step 2: Load Your Certificate
    • Step 3: Create the JWT Signature
    • Step 4: Complete Certificate-Based Example
  • Approach 2: Keypair-Based Signing with Embedded JWK
    • Step 1: Generate an EC Keypair
    • Step 2: Prepare Your Public Metadata with Credentials
    • Step 3: Convert Public Key to JWK Format
    • Step 4: Create JWT Signature with Embedded JWK
    • Step 5: Complete Keypair-Based Example
  • Signature Verification
    • Expected Header Structures
  • Security Best Practices
    • Key Management
  • Common Issues and Solutions
  • Integration with CommonsDB
  • Next Steps