Skip to content

Packaging WebAssembly (WASM) modules in a Hermes Application

Overview of a WASM Component Module

WASM Component Modules consist of:

  • Metadata which describes the module.
  • The compiled WASM Code itself, which MUST target the Hermes WASM Component Model API.
  • An Optional configuration JSON schema.
  • An Optional default configuration file.
  • An Optional settings JSON schema.
  • And a required author's signature.

WASM Component ModuleWASM Components are individually packaged and signed before inclusion into an application./shareOPTIONAL: Data files specific to this Module.metadata.jsonDefines the module. + Module Name + Version + Description ? Source Repo + License/smodule.wasmCompiled WASM Component Module.config.schema.jsonOPTIONAL: Schema for the config.json file.config.jsonConditionally Optional: Modules runtime config file. MUST Exist if `config.schema.json` exists.settings.schema.jsonOPTIONAL: Schema for the user option settings.json file.author.coseAuthors signature over the wasm module.WASM Components are individually packaged and signed before inclusion into an application. OPTIONAL: Data files specific to this Module. Defines the module. + Module Name + Version + Description ? Source Repo + License/s Compiled WASM Component Module. OPTIONAL: Schema for the config.json file. Conditionally Optional: Modules runtime config file. MUST Exist if `config.schema.json` exists. OPTIONAL: Schema for the user option settings.json file. Authors signature over the wasm module.

WASM Component Module Metadata

Metadata for a module must conform to the Hermes Module metadata Schema. It holds information so that the Wasm module can be identified, including its source and license. The metadata is purely descriptive and does not contain any information related to the configuration of the module itself.

WASM Component Module Metadata - Schema

Schema: hermes_module_metadata.schema.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_metadata.schema.json",
    "title": "WASM Module Metadata Schema",
    "description": "Metadata which defines a Hermes WASM Component Module and is carried inside the Hermes Module itself.",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "$schema": {
            "type": "string",
            "title": "Hermes WASM Component Module Metadata Schema Reference",
            "default": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_metadata.schema.json",
            "pattern": "^(https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/)|(.*/)hermes_module_metadata.schema.json$",
            "description": "Reference to the Module Metadata Schema.\nShould be to https:// this will be validated.\nLocal references are only to simplify development."
        },
        "name": {
            "type": "string",
            "title": "WASM module default name",
            "description": "Default Name of the WASM Component Module."
        },
        "version": {
            "type": "string",
            "title": "WASM module version",
            "description": "Version of the WASM module",
            "pattern": "^V[0-9]+\\.[0-9]+\\.[0-9]+( .*$)?"
        },
        "description": {
            "type": "string",
            "title": "WASM module description",
            "description": "Short description of the WASM module"
        },
        "src": {
            "type": "array",
            "title": "Links to the source of this WASM module.",
            "description": "Can be links to code repositories or the Authors website.\nSpecifying any `src` is Optional, however if defined, then these URLs must be reachable when the WASM module is packaged.",
            "items": {
                "type": "string",
                "format": "uri",
                "pattern": "^https://[^\\s/?#]+.[^\\s/?#]+(/[^\\s?#]*)?$"
            },
            "uniqueItems": true,
            "minItems": 1
        },
        "copyright": {
            "type": "array",
            "title": "WASM Module Copyright",
            "description": "List of Copyright Notices/Claims over the WASM module.\nIf no copyright is claimed then there should be an explicit statement about the WASM module being in the public domain.",
            "items": {
                "type": "string"
            },
            "uniqueItems": true,
            "minItems": 1
        },
        "license": {
            "type": "array",
            "title": "WASM Module License",
            "description": "List of Licenses/Claims over the WASM module.\nMust declare at least `spdx` or `file`, `note` is Optional.\nIf `file` is specified it must exist within the Package for the WASM module to be valid.",
            "uniqueItems": true,
            "minItems": 1,
            "items": {
                "type": "object",
                "additionalProperties": false,
                "minProperties": 1,
                "$comment": "Validation Should be one or both of `spdx` and `file` and `note` is optional.",
                "properties": {
                    "spdx": {
                        "type": "string",
                        "title": "License SPDX ID",
                        "description": "SPDX ID of the License.\nIn the case of proprietary or non SPDX licenses, do not specify a SPDX ID and just include a license file."
                    },
                    "file": {
                        "type": "string",
                        "title": "License File",
                        "description": "Path to the License File inside the Package.\nMust be a `.txt` file and it must exist at Package creation time.\nFile MUST be text, displayable using monospaced fonts and no line exceeds 80 characters.",
                        "pattern": "^/.*\\.txt$"
                    },
                    "note": {
                        "type": "string",
                        "title": "License Note",
                        "description": "Note about the License.\nFor Example: Could limit the license to a particular aspect of the WASM module or part of the code."
                    }
                }
            }
        },
        "build_date": {
            "type": "integer",
            "title": "WASM module Build Date",
            "description": "Unix Epoch Timestamp of when the WASM module was packaged or built.\nThis field will be overwritten if present, by the hermes packaging system.\nThe field is required, but this will be checked when the package is validated.",
            "default": 0
        },
        "developer": {
            "type": "object",
            "title": "WASM Module Developer",
            "additionalProperties": true,
            "properties": {
                "name": {
                    "type": "string",
                    "title": "WASM Module Developer Name",
                    "description": "Name of the Developer"
                }
            },
            "required": [
                "name"
            ]
        }
    },
    "required": [
        "$schema",
        "name",
        "version",
        "description",
        "copyright",
        "license"
    ]
}

WASM Component Module Metadata - Example

Example: hermes_module_metadata.json
{
    "$schema": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_metadata.schema.json",
    "name": "counter",
    "version": "V2.7.3",
    "description": "An example Counter module",
    "src": [
        "https://github.com/input-output-hk/hermes",
        "https://github.com/input-output-hk/catalyst-voices"
    ],
    "copyright": [
        "Copyright â’¸ 2024, IOG Singapore."
    ],
    "license": [
        {
            "spdx": "Apache-2.0",
            "file": "/srv/data/apache2.txt",
            "note": "Both SPDX and File can be specified together.\nThis allows the text of the License to be shown in any admin UI."
        },
        {
            "spdx": "MIT",
            "file": "/srv/data/mit.txt"
        },
        {
            "spdx": "Apache-2.0",
            "note": "Just defines the license by the SPDX ID."
        },
        {
            "file": "/srv/data/apache2.txt",
            "note": "Just defines the license by the file path."
        }
    ],
    "developer": {
        "name": "IOG Singapore.",
        "contact": "Contact details.",
        "payment": "wallet address"
    }
}

WASM Component Module Configuration

Each WASM Component Module can be configured. The purpose of this is to allow the same WASM code to have different parameterized functionality.

If one thinks of the Wasm Component Module as a kind of Class,
the configuration allows a specific Instance of the class to be created.

Copies of the same Module can have different config, which allows the creation of multiple Instances of the same class.

The packaging process efficiently removes redundancy in the package and will link between modules that are identical.

This configuration is defined by the WASM Component Module Author, and can be modified by the Application author. The user has no ability to alter the configuration.

Configuration is controlled by two files.

  • config.schema.json - This is a JSON Schema document which defines the structure expected of the configuration for the Module.
  • config.json - The default configuration of the module.

Both must be present if configuration is defined. It is valid for a WASM Component Module to not have any configuration, and so there would be no configuration files present in this case.

Wasm Component Module Settings

Settings are module configuration that are defined by the user of the application. This is how a user of the application can configure how the application should run. If there is user controllable configuration, then the WASM Component Module will contain a settings.schema.json file. This file defines the configuration options available to the user. The user MUST make a configuration for the application for each WASM module that requires it before the application can run. This is simplified because the schema can contain defaults which will be used if the user has made no selection. Therefore, if a WASM Module declares defaults for all options, the user need not make any changes to it.

This file is optional, and is only included in the WASM Component Module if there is actual configuration that can be changed. Otherwise, it is not present.

WASM Module read-only shareable data

WASM Modules may need data sets to execute their functionality efficiently. Data which is strongly associated with a module is packaged with a module in its share directory.

While this data is strongly associated with a module, it may be used by any module within the application. It is also possible for an Application to modify the shared data a WASM Module can see, without altering the signed Module itself.

There is no restriction on the kinds or amount of shared data within a module. Nor is it required by a Module.

WASM Component Module signatures

Individual Modules have an Author. This allows us to compose applications by using pre-written WASM Component Modules as building blocks. But to do so, the Author of the Module must sign it.

This allows us to validate that the Module is coming from a trusted source.

Accordingly, similar to Applications themselves, individual WASM Component Modules needs to be signed by their author.

The signature file is called author.cose and it is a signature across all the other files within the module. A module is invalid if the signature does not match, OR there are files present which are either unknown or not included in the signature.

Packaging a WASM Component Module

Similar to an Application, Hermes WASM Component Modules are packaged and signed by the Hermes application.

Packaging a Module is controlled by a manifest file, which must conform to the Hermes WASM Component Module Manifest JSON schema.

The WASM Component Module Packaging Process

  1. Create an unsigned WASM Component Module.
  2. Sign it as one or more authors.

Creating the unsigned Application Package

./hermes module package <manifest.json> [<optional output path>] [--name <module name override>]
  • manifest.json - Defines the location of all the src artifacts needed to build the package. This file must conform to the manifests JSON schema. An example manifest of this JSON schema is here.
  • [<optional output path>] - By default the module will be created in the same directory where manifest placed. This option allows the path of the generated module to be set, it can be absolute or relative to the manifest directory.
  • --name module name override - The name to give the module file, instead of taking it from the manifest file.

Note: the extension .hmod will automatically be added to the module name to signify this is a Hermes WASM Component Module.

Signing the WASM module package

More detailed explanation about signing procedure could be found here.

As the author of the WASM module package:

./hermes module sign <X.509 Private Cert> <module_name.hmod>

This takes the X.509 Private Certificate presented, and signs or counter-signs the Application package.

Note: A Hermes WASM Component Module is INVALID if it does not contain at least 1 Author Signature.

Author signature payload

WASM module package author signature payload according to the signing spec should follow this schema:

Schema: hermes_module_cose_author_payload.schema.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_cose_author_payload.schema.json",
    "title": "Hermes WASM Module Author COSE Payload Schema",
    "description": "Defines the COSE signing payload. A WASM module package specific information which should be cryptographically protected.",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "metadata": {
            "type": "string",
            "title": "Blake2b hash hex of metadata.json package file",
            "description": "A hex representation of the Blake2b hash of the metadata.json file inside the package.",
            "pattern": "^[0-9a-f]{64}$"
        },
        "component": {
            "type": "string",
            "title": "Blake2b hash hex of module.wasm package file",
            "description": "A hex representation of the Blake2b hash of the module.wasm file inside the package.",
            "pattern": "^[0-9a-f]{64}$"
        },
        "config": {
            "type": "object",
            "title": "WASM Module Config Object.",
            "description": "Object representing the WASM Module Config File.",
            "properties": {
                "file": {
                    "type": "string",
                    "title": "Blake2b hash hex of config.json package file",
                    "description": "A hex representation of the Blake2b hash of the config.json file inside the package.",
                    "pattern": "^[0-9a-f]{64}$"
                },
                "schema": {
                    "type": "string",
                    "title": "Blake2b hash hex of config.schema.json package file",
                    "description": "A hex representation of the Blake2b hash of the config.schema.json file inside the package.",
                    "pattern": "^[0-9a-f]{64}$"
                }
            },
            "required": [
                "schema"
            ]
        },
        "settings": {
            "type": "object",
            "title": "WASM Module Settings Object.",
            "description": "Object representing the WASM Module Settings.",
            "properties": {
                "schema": {
                    "type": "string",
                    "title": "Blake2b hash hex of settings.schema.json package file",
                    "description": "A hex representation of the Blake2b hash of the settings.schema.json file inside the package.",
                    "pattern": "^[0-9a-f]{64}$"
                }
            },
            "required": [
                "schema"
            ]
        },
        "share": {
            "type": "string",
            "title": "Blake2b hash hex of the whole share package directory",
            "description": "A hex representation of the Blake2b hash of the whole share directory inside the package.",
            "pattern": "^[0-9a-f]{64}$"
        }
    },
    "required": [
        "metadata",
        "component"
    ]
}

WASM module package author signature payload example:

Example: hermes_module_cose_author_payload.json
{
    "metadata": "e6d4ecf4e0df7688f8fb5d564fc6bcafffdfc46bb793631a9ed3bb6d888561df",
    "component": "0cfdb8d790b0a30fb8eb12d98c1791f35a279c0a5f8bf313cd582fc6b8cd69d5",
    "config": {
        "file": "69e66dc097dbd3d3a333c699d05b2c7173d6d9cc7d6a7e35745339e282d70904",
        "schema": "0da53b44f7de08af0743779dfb9448fa32fe3584e9b940ae740a1bbc74741bba"
    },
    "settings": {
        "schema": "9cd493f0cbdb907814b142e22e76fa8cf0e95bcd3117d4c3e7c4cbe4b885d932"
    },
    "share": "ed6ac923cfa79ab05cf2a41dc43493e0f74d8bd7596185be5bce74ffda85d9a5"
}

Inspecting a Hermes Application

./hermes package inspect <app_package_name>

This command will dump the logical contents of the WASM Component Module and if it is considered valid or not. It does not extract files from the module.
If files need to be extracted or individually accessed outside of Hermes, any [HDF5 Viewer] can be used. As the module is compressed, part of the information that is displayed should be the total module size on-disk, and the true size of the uncompressed data it contains. The compressed/uncompressed statistic should be per file, and also for the total module.

WASM Component Module Manifest - Schema

Schema: hermes_module_manifest.schema.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_manifest.schema.json",
    "title": "Hermes WASM Module Manifest Schema",
    "description": "Defines the src packages which are used to build a Hermes WASM Component Module.",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "$schema": {
            "type": "string",
            "title": "WASM Module Package Manifest Schema Reference",
            "default": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_manifest.schema.json",
            "pattern": "^(https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/)|(.*/)hermes_module_manifest.schema.json$",
            "description": "Reference to the WASM Manifest Schema.\nShould be to https:// this will be validated.\nLocal references are only to simplify development."
        },
        "name": {
            "type": "string",
            "title": "Hermes WASM Module Package name.",
            "description": "Name of the Hermes WASM module package with which it will be created.",
            "default": "module"
        },
        "metadata": {
            "type": "string",
            "title": "Hermes WASM Module Metadata File",
            "description": "Link to the Hermes WASM module metadata JSON file.\nWill be renamed to `metadata.json` inside the module.\nIt Could be a valid URI or regular local path on your system.",
            "pattern": "^([a-z0-9-_\\.+]+://)?(/?([a-zA-Z0-9-_\\.]+))+$",
            "default": "metadata.json"
        },
        "component": {
            "type": "string",
            "title": "WASM Component File",
            "description": "Link to the Hermes Module WASM Component file.\nThis is a fully compiled and linked WASM Object file.\nWill be renamed to `module.wasm` inside the module.\nIt Could be a valid URI or regular local path on your system.",
            "pattern": "^([a-z0-9-_\\.+]+://)?(/?([a-zA-Z0-9-_\\.]+))+$",
            "default": "module.wasm"
        },
        "config": {
            "type": "object",
            "title": "WASM Module Config Object.",
            "description": "Object representing the WASM Module Config File.",
            "properties": {
                "file": {
                    "type": "string",
                    "title": "WASM Module Config File Location",
                    "description": "Path to the WASM Component Library Module Config File.\nThis will be renamed to `config.json` inside the module.\nIt Could be a valid URI or regular local path on your system.",
                    "pattern": "^([a-z0-9-_\\.+]+://)?(/?([a-zA-Z0-9-_\\.]+))+$"
                },
                "schema": {
                    "type": "string",
                    "title": "WASM Module Config Schema Location",
                    "description": "Path to the WASM Component Module Config Schema File.\nThis will be renamed to `config.schema.json` inside the module.\nIt Could be a valid URI or regular local path on your system.",
                    "pattern": "^([a-z0-9-_\\.+]+://)?(/?([a-zA-Z0-9-_\\.]+))+$"
                }
            },
            "required": [
                "schema"
            ]
        },
        "settings": {
            "type": "object",
            "title": "WASM Module Settings Object.",
            "description": "Object representing the WASM Module Settings.",
            "properties": {
                "schema": {
                    "type": "string",
                    "title": "WASM Module Settings Schema Location",
                    "description": "Path to the WASM Component Module Settings Schema File.\nThis will be renamed to `settings.schema.json` inside the module.\nIt Could be a valid URI or regular local path on your system.",
                    "pattern": "^([a-z0-9-_\\.+]+://)?(/?([a-zA-Z0-9-_\\.]+))+$"
                }
            },
            "required": [
                "schema"
            ]
        },
        "share": {
            "type": "string",
            "title": "WASM Module Share Dataset.",
            "description": "Path to the WASM Component Library Module Shareable Data.\nWill set the default data defined for the module itself.\nIt Could be a valid URI or regular local path on your system.",
            "pattern": "^([a-z0-9-_\\.+]+://)?(/?([a-zA-Z0-9-_\\.]+))+$"
        }
    },
    "required": [
        "$schema"
    ]
}

WASM Component Module Manifest - Example

Example: hermes_module_manifest.json
{
    "$schema": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_manifest.schema.json",
    "name": "module",
    "metadata": "file://modules/counter/counter_metadata.json",
    "component": "file://counter.wasm",
    "config": {
        "file": "file://modules/counter/config.json",
        "schema": "file://modules/counter/config.schema.json"
    },
    "settings": {
        "schema": "file://modules/counter/settings.schema.json"
    },
    "share": "file://modules/counter/share"
}

WASM Component Module Manifest - Minimal Example

Example: MINIMAL hermes_module_minimal_manifest.json
{
    "$schema": "https://raw.githubusercontent.com/input-output-hk/hermes/main/hermes/schemas/hermes_module_manifest.schema.json"
}