NUT10/NUT11 canonical serialization of secret. #278

Open
opened 2025-08-06 09:19:33 +00:00 by Toporin · 1 comment
Toporin commented 2025-08-06 09:19:33 +00:00 (Migrated from github.com)

For NUT10 (), the proof.secret is a JSON of the format:

[
kind <str>,
  {
    "nonce": <str>,
    "data": <str>,
    "tags": [[ "key", "value1", "value2", ...],  ... ], // (optional)
  }
]

The recipient who owns the private key of the public key Secret.data can spend the proof by providing a signature on the serialized Proof.secret. If this proof.secret is deserialized and reserialized between the sender and the receiver, the signature might not match the original proof.secret serialization (the hash of serialized secrets could differ).

This can be a problem if the proof.secret is processed between sender and receiver. For example, in the Satocash smartcard ecash wallet, the P2PK proofs are deserialized for efficient storage and processing. The proof.secret is then reserialized for signature, which can create issue if the reconstructed proof.secret string does not match the original one.

For illustration, the NUT11 provides an example of serialized proof.secret:

"secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]"

Using Nutshell send command with the lock argument, we get a decoded proof.secret of the following format:

"s": "[\"P2PK\", {\"data\": \"0355fff0a785c2236b42390f9dd91c30538aa4be70053dba78b344285d7fabad46\", \"nonce\": \"b43b07688f58668a4c0f0a1aff6517f3\", \"tags\": [[\"sigflag\", \"SIG_INPUTS\"]]}]"

Where the data and nonce positions are different, and additional whitespaces are added. Even if the data and nonce values were the same, the hashes would not match, so signature or proof verification would fail.

To mitigate this issue, the 'proof.secret' serialization should be required to use canonical serialization such as RFC8785. This basically defines rules for transforming JSON into a canonical form:

  • Object key ordering: Sort object keys lexicographically
  • Number normalization: Remove unnecessary decimal points and leading zeros
  • String escaping: Use minimal necessary escape sequences
  • No whitespace: Remove all insignificant whitespace
For NUT10 (), the `proof.secret` is a JSON of the format: ```json [ kind <str>, { "nonce": <str>, "data": <str>, "tags": [[ "key", "value1", "value2", ...], ... ], // (optional) } ] ``` The recipient who owns the private key of the public key Secret.data can spend the proof by providing a signature on the serialized Proof.secret. If this `proof.secret` is deserialized and reserialized between the sender and the receiver, the signature might not match the original proof.secret serialization (the hash of serialized secrets could differ). This can be a problem if the proof.secret is processed between sender and receiver. For example, in the Satocash smartcard ecash wallet, the P2PK proofs are deserialized for efficient storage and processing. The proof.secret is then reserialized for signature, which can create issue if the reconstructed proof.secret string does not match the original one. For illustration, the NUT11 provides an example of serialized `proof.secret`: ``` "secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]" ``` Using Nutshell `send` command with the `lock` argument, we get a decoded `proof.secret` of the following format: ``` "s": "[\"P2PK\", {\"data\": \"0355fff0a785c2236b42390f9dd91c30538aa4be70053dba78b344285d7fabad46\", \"nonce\": \"b43b07688f58668a4c0f0a1aff6517f3\", \"tags\": [[\"sigflag\", \"SIG_INPUTS\"]]}]" ``` Where the `data` and `nonce` positions are different, and additional whitespaces are added. Even if the `data` and `nonce` values were the same, the hashes would not match, so signature or proof verification would fail. To mitigate this issue, the 'proof.secret' serialization should be required to use canonical serialization such as [RFC8785](https://www.rfc-editor.org/rfc/rfc8785). This basically defines rules for transforming JSON into a canonical form: * Object key ordering: Sort object keys lexicographically * Number normalization: Remove unnecessary decimal points and leading zeros * String escaping: Use minimal necessary escape sequences * No whitespace: Remove all insignificant whitespace
Toporin commented 2026-05-19 09:32:07 +00:00 (Migrated from github.com)

Any updates on this issue?

Any updates on this issue?
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo-admin/nuts#278
No description provided.