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. Liccium supports two approaches for signing: X.509 Certificate-based signing and Keypair-based signing with embedded credentials.
Overview
Metadata signatures in Liccium 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
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 Liccium requirements:
Code
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
Step 3: Create the JWT Signature
Generate a JWT signature using your certificate and private key:
Code
Step 4: Complete Certificate-Based Example
Full example with validation:
Code
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
Code
Step 2: Convert Public Key to JWK
Code
Step 3: Create JWT Signature with JWK in Header
Code
Step 5: Complete Keypair-Based Example
Code
Signature Verification
Decode the JWT (without verification) to inspect structure:
Code
Certificate-based (x5c): header contains x5c with base64-encoded certificate.
Keypair-based (jwk): header contains jwk with EC P-256 public key.
Security Best Practices
Security Considerations:
- Never log or expose private keys
- Use secure key storage in production (HSM, key vaults)
- Implement proper error handling for signing failures
- Validate all input data before signing
- Store credentials securely and validate before inclusion
Key Management
- Secure Storage – Store private keys in HSM or key vaults
- Access Control – Limit access to private keys to authorized systems
- Key Rotation – Regularly rotate keys/certificates and update DID documents
- Monitoring – Monitor key usage and detect unauthorized signing
Common Issues and Solutions
| Issue | Cause | Solution |
|---|---|---|
| Invalid signature format | Incorrect algorithm or header | Use ES256 and proper x5c or jwk header |
| Certificate/JWK not found | Missing x5c or jwk in JWT header | Include base64 certificate or JWK in header |
| Validation fails | Metadata missing required fields | Validate structure before signing ($schema, @context, iscc, name, timestamp, declarerId, credentials) |
| Key mismatch | Private key doesn't match certificate/public key | Verify key pair correspondence |
| Credentials validation fails | Missing or invalid credentials array | Ensure credentials is a non-empty array with valid VC structure |
Integration with Liccium
Include your signature in the declaration payload:
Code
For complete declaration examples, see the Declaration API.
Signing Registry Objects
The following is an example for the TDMAI registry for AI preferences.
If your declaration includes a tdmaiRegistry record, the same signing process must be applied to it independently, using the same private key and approach chosen above.
declarationMetadata.tdmaiRegistry is the TDM-AI registry record: an object with the required fields iscc, declarerId and timestamp (milliseconds, within 60 seconds of server time), plus one sub-object per active opt-out plugin — tdmrep ({"tdm-reservation": 0|1, "tdm-policy": "<url>"}), faia ({"faiaFlag", "activityCode", "systemAttribution", "systemVersion"}), and further plugins (ietf, c2pa, ...) keyed by plugin name. It is transmitted as this JSON object — a JSON-serialized string is not accepted. The JWT payload carries the same object (key order does not matter — the registry compares the records canonically).
The record requires two additional top-level fields in the declaration payload:
tdmaiRegistrySignature– an ES256 JWT (public JWK embedded in the header) whose payload is{ "tdmaiRegistry": <the same record object> }tdmaiRegistryTsaSignature– an RFC 3161 TSA timestamp (tsq/tsr, base64) over thetdmaiRegistrySignatureJWT, produced identically to the maintsaSignature
Example
Code
The tdmaiRegistrySignature and tdmaiRegistryTsaSignature fields are only required when a tdmaiRegistry value is present in the declaration. If no registry payload is provided, these fields can be omitted entirely.
Next Steps
- TSA Signature Creation – Add timestamp signatures for temporal verification
- Test your signatures with the Declaration API
- Implement error handling for production use
- Monitor certificate/key expiration and plan for renewal