Contract content and Contract ID generation

The contract IDs are indeed derived from the contract arguments, among others. So in theory, it is possible to check whether a contract ID and a contract instance fit together. In practice, you can leverage the explicit disclosure feature which was released as alpha with Daml 2.7: If you explicitly attach the contract of interest to a Ledger API command submission that fetches this contract, then the participant will check the consistency between the contract ID and the data from the Created node before committing the transaction. To that end, you must have stored the contract metadata field along with your contract.

The mechanisms behind this are as follows: By the documented contract ID scheme, a Daml contract ID consists of a version prefix, a discriminator, and a suffix. For version V1, the discriminator is defined as the SHA256-HMAC of some of the data you’re referring to. The HMAC is seeded with a “node seed”. The node seed is included for confidentiality reasons: If someone sees a contract ID and has some background knowledge about what this contract may refer to, then the node seed prevents them from simply recomputing the contract ID for their guesses to check whether they guessed right. So you need the node seed to check whether the discriminator was computed correctly, but the node seed is not exposed over the ledger API. So this approach is a dead end.

Instead, there is also the undocumented suffix of the contract ID. The format of the suffix changes with the different Daml versions that the contract was created with; more precisely with the protocol version of the synchronization domain where the creating transaction was processed. For a contract created with protocol version 4, for example, the suffix starts with 0xCA01 and contains a SHA256 hash of a salt, the ledger time of the creating transaction, and a hash of the contract arguments. For protocol version 5, the suffix starts with 0xCA02 and the SHA256 hash additionally covers the signatories, the observers, the contract key and the maintainers, if any. The salt serves the same purpose of masking the contract ID suffix as the node seed. However, the participants do store the salts and expose them as part of the opaque metadata field. So it is possible to recompute the hash and check whether everything fits together. Since the suffix format is not part of the public API, it may change any time. So I advise against building your own contract ID checker by reverse-engineering these formats.

DA may at some point provide a maintained library to perform these checks for offline usage. For now, you have to live explicit disclosure.

4 Likes