This is a hands-on command-line introduction to Gordian Envelope in
the style of Blockchain Commons’ from the Command Line
courses. It makes use of the Rust-based
envelope-cli.
The focus of this course is on salting: it demonstrates how salting can protect the privacy of data stored in an envelope and then elided.
Also see “Learning Envelope from the Command Line” for basics such as assertions and signing.
Overview
Hash-based elision is one of the core features of Gordian Envelope. It allows data to be selectively redacted by the envelope holder, supporting data minimization, while also maintaining signatures, which are made across the hash rather than the original data.
However, hash-based elision has one notable vulnerability: if someone can guess the contents of an elided item, they can verify its existence using the hash.
The following examples use this envelope:
ur:envelope/tpsplrtpsoihfpjziniaihoytpsoisihjnjojzjlkkihjptpsoimfekshsjnjojzihcxfxjloytpsoinidinjpjyisfyhsjyihtpsosecydmesmkaeoytpsoiyieihiojpihihtpsojefwguiacxgdiskkjkiniajkwfnnzmtl
Which was generated as follows with the envelope-cli:
ALICE=$(envelope subject type string "Alice" | envelope assertion add pred-obj string "birthDate" date 1994-07-30 | envelope assertion add pred-obj string "degree" string "BSc Physics" | envelope assertion add pred-obj string "employer" string "Example Co" | envelope subject type wrapped)
It contains the following wrapped data:
envelope format $ALICE
| {
| "Alice" [
| "birthDate": 1994-07-30
| "degree": "BSc Physics"
| "employer": "Example Co"
| ]
| }
(Why wrapped? Because the next step would be to sign it, but we’ll work with the simpler, unsigned data here.)
Revealing the Problem
You can view the hashes of the envelope with the envelope format
--type tree command:
envelope format --type tree $ALICE
| a5d7df2d WRAPPED
| 3f0da808 cont NODE
| 13941b48 subj "Alice"
| 46209a0d ASSERTION
| a61e96a8 pred "employer"
| b7927b5c obj "Example Co"
| a6a5f777 ASSERTION
| 22daf383 pred "birthDate"
| 9733441e obj 1994-07-30
| c5ce91c6 ASSERTION
| 80ce7253 pred "degree"
| 506b2ec4 obj "BSc Physics"
When you elide part of the data, such as Alice’s birth date, the hash remains (which is the point!)
BIRTHDATE=$(envelope extract wrapped $ALICE | envelope assertion find predicate string "birthDate" | envelope extract object | envelope digest)
ALICE_MINIMIZED=$(envelope elide removing $BIRTHDATE $ALICE)
envelope format $ALICE_MINIMIZED
| {
| "Alice" [
| "birthDate": ELIDED
| "degree": "BSc Physics"
| "employer": "Example Co"
| ]
| }
envelope format --type tree $ALICE_MINIMIZED
| a5d7df2d WRAPPED
| 3f0da808 cont NODE
| 13941b48 subj "Alice"
| 46209a0d ASSERTION
| a61e96a8 pred "employer"
| b7927b5c obj "Example Co"
| a6a5f777 ASSERTION
| 22daf383 pred "birthDate"
| 9733441e obj ELIDED
| c5ce91c6 ASSERTION
| 80ce7253 pred "degree"
| 506b2ec4 obj "BSc Physics"
Here’s what the birthdate looked like before elision:
9733441e obj 1994-07-30
And here’s what it looks like afterward:
9733441e obj ELIDED
The hash remains the same, which is what allows the holder to later
write an inclusion proof that
demonstrates that the birthdate of 1994-07-30 is in the credential.
However, dates have a limited data space. If someone knows the format
of the data (which is here just the date type), they could calculate
all of the possible entries. That’s just 365 or 366 entries per year,
times the number of years. Not a lot in the scope of computing power!
| Hash | Date |
|---|---|
| … | … |
| 6df068fe | 1994-07-26 |
| 770424db | 1994-07-27 |
| 82594a76 | 1994-07-28 |
| adc767ef | 1994-07-29 |
| 9733441e | 1994-07-30 |
| … | … |
It would take mere seconds to crunch dates until you found the hash of an elided entry in an envelope, and worse an attacker could have a rainbow table of all the dates for the last 100 years that they could check against instaneously.
Solving with Salts
The answer to this problem is 🧂 salt. This is a random value that can be added to any subject, predicate, object, assertion, or sub-envelope within an envelope.
The following adds salt to the date object:
SALT_DATE=$(envelope subject type date 1994-07-30 | envelope salt)
The date now has an assertion of salt:
envelope format $SALT_DATE
| 1994-07-30 [
| 'salt': Salt
| ]
Examining the hashes demonstrates that the date’s hash stays the same
(9733441e), but it and the salt are now incorporated into a node
with a hash that isn’t guessable (71b7042d).
envelope format --type tree $SALT_DATE
| 71b7042d NODE
| 9733441e subj 1994-07-30
| 2a26b1d7 ASSERTION
| 618975ce pred 'salt'
| 8a53fe12 obj Salt
You can then rebuild Alice’s envelope by substituting the date with the date-with-salt envelope:
SALTY_ALICE=$(envelope subject type string "Alice" | envelope assertion add pred-obj string "birthDate" envelope $SALT_DATE | envelope assertion add pred-obj string "degree" string "BSc Physics" | envelope assertion add pred-obj string "employer" string "Example Co" | envelope subject type wrapped)
envelope format $SALTY_ALICE
| {
| "Alice" [
| "birthDate": 1994-07-30 [
| 'salt': Salt
| ]
| "degree": "BSc Physics"
| "employer": "Example Co"
| ]
| }
You can then use the same technique you used before to elide the date:
BIRTHDATE_WITH_SALT=$(envelope extract wrapped $SALTY_ALICE | envelope assertion find predicate string "birthDate" | envelope extract object | envelope digest)
SALTY_ALICE_MINIMIZED=$(envelope elide removing $BIRTHDATE_WITH_SALT $SALTY_ALICE)
The elided envelope looks just the same to the naked eye:
envelope format $SALTY_ALICE_MINIMIZED
| {
| "Alice" [
| "birthDate": ELIDED
| "degree": "BSc Physics"
| "employer": "Example Co"
| ]
| }
But the content is no longer guessable from the digest because of the salt that was part of the elided content:
envelope format --type tree $SALTY_ALICE_MINIMIZED
| 2d28261e WRAPPED
| ad885cbb cont NODE
| 13941b48 subj "Alice"
| 46209a0d ASSERTION
| a61e96a8 pred "employer"
| b7927b5c obj "Example Co"
| 64b92257 ASSERTION
| 22daf383 pred "birthDate"
| 71b7042d obj ELIDED
| c5ce91c6 ASSERTION
| 80ce7253 pred "degree"
| 506b2ec4 obj "BSc Physics"