A Rust crate providing traits and data types to define self-signing data. Inspired by the Self-Addressing Identifier concept in KERI. This implementation is not necessarily compatible with KERI. At the moment, it's a very initial implementation, and is a work in progress.
To run the tests, which include printouts demonstrating various features and data structures, run
cargo test --all-features -- --nocapture
The --all-features
is necessary for now.
In the context of cryptography, it's not possible to directly include a digital signature within the message being signed. However, it is possible to define slightly altered signing and verification procedures for a given data structure which in a way does contain a signature over itself.
The idea is that the data structure has at least one "slot" for the verifying key and at least one "slot" for the signature. Hereafter, these will be referred to as self-signature verifier slots and self-signature slots respectively. Each self-signature slot is meant to represent the same value within the data structure. Each self-signature verifier slot is meant to represent the same value within the data structure. The purpose for having multiple of each kind of slot is to be able to have the signature or verifier data appear in multiple places within the data structure (e.g. in the definition of a DID document, in which the self-signature-containing DID itself must appear several places within the DID document). The signature and verification procedures are as follows:
Let the signing key be S
and the verifying key be V
. In asymmetric cryptography, these will be different (the private and public keys respectively), but they could be same as in the case of an HMAC. Let the data to be self-signed be D
. The self-signature slots and self-signature verifier slots of D
are well-defined and enumerable.
The steps to self-sign are:
- Set all of
D
's self-signature verifier slots toV
. - Set all of
D
's self-signature slots to the "placeholder" value for that signature type (this encodes the signature algorithm and an all-zeros dummy signature value). - Serialize
D
with an agreed-upon and deterministic serialization format, producing messagemsg
. - Sign
msg
usingS
, producing signaturesig
. - Set all of
D
's self-signature slots tosig
.
At this point, D
is self-signed, and can be self-verified successfully.
When verifying self-signed data D
, the signing key is not known and the verifying key isn't known ahead of time but rather is determined from D
.
The steps to self-verify are:
- Check that all of
D
's self-signature slots are equal to each other. Let this value besig
. - Check that all of
D
's self-signature verifier slots are equal to each other. Let this value beV
. - Set all of
D
's self-signature slots to the "placeholder" value for that signature type (this is the same as in step 2 of self-signing). - Serialize
D
with an agreed-upon and deterministic serialization format, producing messagemsg
. - Use
V
to verify thatsig
is a valid signature overmsg
.
D
is defined to be self-verified if and only if step 5 succeeded.
Note that JSON isn't the only usable serialization format (and it's not even a good one, in particular because it doesn't have a canonical form and so may have interoperability issues between different implementations), but it does make for human-readable examples. CESR is the intended solution to this problem within the KERI ecosystem. There are a wide range of possible solutions, each fitting different needs. One that will be elaborated upon later within this git repository will be a process for computing the message digest on a binary serialization of the data in a streaming manner, thereby eliminating allocations and other representational issues that can plague human-readable serialization formats.
The examples come from the tests. To run them:
cargo test --all-features -- --nocapture
The --all-features
is necessary for now.
Here is a simple example in which a data structure has a single self-signature slot and a single self-signature verifier slot. Here is the primary data, with self-signature and self-signature verifier slots unpopulated:
{"name":"hippodonkey","stuff_count":42,"data_byte_v":[1,2,3],"self_signature_verifier":null,"self_signature":null}
During the self-signing process, the self_signature_verifier
field is populated with the verifying key (in this case, an Ed25519 public key, indicated via prefix "D"
and the base64url-encoding of the 256 bits of the public key), and the self_signature
field is populated with the appropriate placeholder (in this case, indicating that the signature algorithm is Ed25519_SHA_512 via prefix "0B"
and the base64url-encoding of 512 bits of 0):
{"name":"hippodonkey","stuff_count":42,"data_byte_v":[1,2,3],"self_signature_verifier":"Dgcq4FvGStfSTNNXwYv2t-NiwGG1TtiRz9fgmE1FH26M","self_signature":"0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
After the self-signing process, the self_signature
field has been populated:
{"name":"hippodonkey","stuff_count":42,"data_byte_v":[1,2,3],"self_signature_verifier":"Dgcq4FvGStfSTNNXwYv2t-NiwGG1TtiRz9fgmE1FH26M","self_signature":"0BwR50D76a5DGVCFkh1LmhhiUipno3DPwkI89sB6hZdmfSNYNdSQz06n0LXmO4wTELnxrROyNnpxwU8poTUnpuAw"}
Here is an example involving a much-simplified version of the DID document data model, a data structure which will be called KeyMaterial here. NOTE that this just an example, it is not intended to be a complete analog of DIDs/DID documents. Note that the JSON has been prettified for readability, but what was signed is its compact form.
Root KeyMaterial -- initial definition of the identity, whose name is defined by the uri
field. Note here that the value of the self_signature
field also shows up as a portion of the uri
field. This is where having multiple self-signature slots comes in handy. There are a number of validity checks for this KeyMaterial data model, including, for the root KeyMaterial, that the self_signature_verifier
field is present within the capability_invocation_v
array.
{
"uri": "https://example.com/identity/0BUbRvOl-8QVb-_9OiJdZ_B9zXASfpUrypkLrjFbAVAHQNeVappFDINEtbcAZIK1yo3v0f34iLdTcEQc848oLcAQ",
"version_id": 0,
"valid_from": "2023-09-14T07:51:07.084308507Z",
"authentication_v": [
"DqK5AA_3VwLeSKqN_QpJXaZDZUbIeX8QN4W3TEZGpt8Q"
],
"assertion_v": [
"DPn1nUeg7Ykk3ylQ0WaRS7KxiwzfJQR0eXZ24BeOPLuM"
],
"key_exchange_v": [
"D_1sWlHwfr5ZulnF9oGKPLaZCEA28ZMNI566BrIlKDA0"
],
"capability_invocation_v": [
"D74m5EuWs6gXS7vBGYTKxHfn3gaQZsP3lV9GbsyodgaA"
],
"capability_delegation_v": [
"Dwhyexo5BKbQdZrLMqcg2FD9QM17zOQJJJaYBpZeu1ys"
],
"self_signature_verifier": "D74m5EuWs6gXS7vBGYTKxHfn3gaQZsP3lV9GbsyodgaA",
"self_signature": "0BUbRvOl-8QVb-_9OiJdZ_B9zXASfpUrypkLrjFbAVAHQNeVappFDINEtbcAZIK1yo3v0f34iLdTcEQc848oLcAQ"
}
First update to the KeyMaterial -- key rotation. Note that the uri
field has not changed. The controller of the identity must sign the update using a key listed in capability_invocation_v
when the update action was taken; the self_signature_verifier
field listed here is present in the previous KeyMaterial's capability_invocation_v
array, and is NOT present in this one's (because the capability_invocation_v
key was rotated).
{
"uri": "https://example.com/identity/0BUbRvOl-8QVb-_9OiJdZ_B9zXASfpUrypkLrjFbAVAHQNeVappFDINEtbcAZIK1yo3v0f34iLdTcEQc848oLcAQ",
"previous_key_material_self_signature": "0BUbRvOl-8QVb-_9OiJdZ_B9zXASfpUrypkLrjFbAVAHQNeVappFDINEtbcAZIK1yo3v0f34iLdTcEQc848oLcAQ",
"version_id": 1,
"valid_from": "2023-09-14T07:51:07.103411189Z",
"authentication_v": [
"DLPM9gqlbJPzs0kjAgYC3GbxGbmHPyBFkMJ5ZjSfU95w"
],
"assertion_v": [
"DsgGc1SXTBn9JTNs2lmSNEczsQ3jFOiBhnaEGuKcajPI"
],
"key_exchange_v": [
"D8si7YCUVUdV-2CuaCwJLqia1ckCVC6DPhSdOn2mQ51U"
],
"capability_invocation_v": [
"D3IZLJBDHWkjNOYsXlI0QXQldiK5OiohSGjbbwHVNqAo"
],
"capability_delegation_v": [
"Dp44hsc9w8xhmfOgbaXW-MP7TWENU9KjJ_TlcDlb6S0Q"
],
"self_signature_verifier": "D74m5EuWs6gXS7vBGYTKxHfn3gaQZsP3lV9GbsyodgaA",
"self_signature": "0BSxb-syWD56wtrvYA0ia8YgMBa23HCQoPcx_XLZ8n57MkxdUe-WaI5KNIChDI0UJx0o8e-wU6FewUEaSXvzf_CA"
}
Second update to the KeyMaterial -- adding some keys.
{
"uri": "https://example.com/identity/0BUbRvOl-8QVb-_9OiJdZ_B9zXASfpUrypkLrjFbAVAHQNeVappFDINEtbcAZIK1yo3v0f34iLdTcEQc848oLcAQ",
"previous_key_material_self_signature": "0BSxb-syWD56wtrvYA0ia8YgMBa23HCQoPcx_XLZ8n57MkxdUe-WaI5KNIChDI0UJx0o8e-wU6FewUEaSXvzf_CA",
"version_id": 2,
"valid_from": "2023-09-14T07:51:07.130640532Z",
"authentication_v": [
"DLPM9gqlbJPzs0kjAgYC3GbxGbmHPyBFkMJ5ZjSfU95w",
"D71LakXJUwafcGHxSqHsXJ_KdxAFbAFoThwTTHStTpig"
],
"assertion_v": [
"DsgGc1SXTBn9JTNs2lmSNEczsQ3jFOiBhnaEGuKcajPI",
"DBycfMsPpm7tm7nRvCC-oqk-kO1iVvjPstXMA1uAwSDQ"
],
"key_exchange_v": [
"D8si7YCUVUdV-2CuaCwJLqia1ckCVC6DPhSdOn2mQ51U",
"DFa5I_KaKxIWV4sc1O44GW63sKGNO9s7ZA2JxCDAVWEo"
],
"capability_invocation_v": [
"D3IZLJBDHWkjNOYsXlI0QXQldiK5OiohSGjbbwHVNqAo"
],
"capability_delegation_v": [
"Dp44hsc9w8xhmfOgbaXW-MP7TWENU9KjJ_TlcDlb6S0Q"
],
"self_signature_verifier": "D3IZLJBDHWkjNOYsXlI0QXQldiK5OiohSGjbbwHVNqAo",
"self_signature": "0BNue25ookArn-LNbrQMMKVwyL8oCsrH6jDfscNnLftbyhe7rQknjpEPjs7p3I670Ed9-1m0fiygGgEZPpxPSGCw"
}
- https://github.com/SmithSamuelM/Papers/blob/master/whitepapers/KERI_WP_2.x.web.pdf
- https://github.com/THCLab/cesrox
- https://github.com/WebOfTrust/keriox
- https://www.ietf.org/archive/id/draft-ssmith-cesr-03.html
Copyright 2023 LedgerDomain
Apache 2.0