# Envelope CLI Tool
Authors: Wolf McNally, Christopher Allen, Blockchain Commons
Revised: August 3, 2022
Status: DRAFT
## Introduction
`envelope` is a proposed command-line tool used to test, exercise, and facilitate the understanding and use of [Blockchain Commons Secure Components](https://github.com/BlockchainCommons/BCSwiftSecureComponents/blob/master/Docs/8-ENVELOPE-SSKR-TEST-VECTORS.md), and [`crypto-envelope`](https://github.com/BlockchainCommons/BCSwiftSecureComponents/blob/master/Docs/2-ENVELOPE.md) in particular.
This proposal primarily uses explanation-by-example. It introductions various use-cases one by one in order of increasing complexity, and describes the inputs and outputs of the tool.
Like many Unix command line tools its inputs and outputs are designed to be composible with other tools and with repeated invocations of itself, facilitating building up complex uses from simpler ones.
In some cases, several ways of achieving the same outcome are shown, usually using different forms of inputs and outputs.
As this is a draft proposal and not a specification, everything here is open to feedback and subject to change.
## Crypto-Envelope Review
A detailed introduction to `crypto-envelope` can be found [here](https://github.com/BlockchainCommons/BCSwiftSecureComponents/blob/master/Docs/2-ENVELOPE.md).
`crypto-envelope` is a simple data structure that can encompass complex concepts. It consists of a single `Subject` and zero or more `Assertions` on that `Subject`.
```swift
struct Envelope {
let subject: Subject
let assertions: [Assertion]
}
```
A `Subject` is an enumerated type that can be one of: a CBOR object, another `Envelope`, an `EncryptedMessage`, or the `Digest` of an object that has been redacted.
```swift
enum Subject {
case leaf(CBOR)
case envelope(Envelope)
case encrypted(EncryptedMessage)
case redacted(Digest)
}
```
An `Assertion` has a `predicate` and an `object`, both of which are themselves `Envelope`s.
```swift
struct Assertion {
let predicate: Envelope
let object: Envelope
}
```
* Because `Envelope`s can contain other `Envelope`s, they form a tree structure.
* Because every object inside an `Envelope` has a unique `Digest`, every envelope is a Merkle tree.
* Any object in an `Envelope` can be transformed in a number of ways while preserving the Merkle tree:
* Encrypt
* Decrypt
* Elide
* Unelide
* Replace Object with Digest
* Replace Digest with Object
## Enclosing a String
Enclose a UTF-8 string as the `subject` in an `Envelope`. The default output format is `ur:envelope`.
Types that can be used include:
* `--string`
* `--int`
* `--cbor` (any valid hex-encoded CBOR)
* `--date` (ISO-8601 format)
* `--uuid`
* `--cid` (CID: Common Identifier)
* `--digest` (The BLAKE3 digest of a specific binary object)
* `--envelope` (an Envelope in UR format)
* `--ur` (any UR)
```
$ envelope --string Hello.
ur:crypto-envelope/tpfniyfdihjzjzjldmtklproam
```
Decode the previously created envelope into Envelope Notation using `--format`. Since the envelope only has a `subject`, and since the `subject` is a string, the result is simply the string in double quotes.
```
$ envelope --format ur:crypto-envelope/tpfniyfdihjzjzjldmtklproam
"Hello."
```
Decode just the `subject` of the envelope, outputting it directly as an unquoted string.
```
$ envelope --subject ur:crypto-envelope/tpfniyfdihjzjzjldmtklproam
Hello.
```
Perform the previous encoding and printing of the envelope by piping the result of encoding directly to another invocation of `envelope` that prints it in Envelope Notation.
```
$ envelope --string Hello. | envelope --format
"Hello."
```
Perform the same operations in sequence without needing pipes.
```
$ envelope --string Hello. --format
"Hello."
```
Perform the same operations using a Shell variable to hold the Envelope.
```
$ ENVELOPE=`envelope --string Hello.`
$ echo $ENVELOPE
ur:crypto-envelope/tpfniyfdihjzjzjldmtklproam
$ envelope --format $ENVELOPE
"Hello."
$ envelope --subject $ENVELOPE
Hello.
```
**ℹ️ NOTE:** For brevity, the bodies of most URs are truncated to an ellipsis below.
**⚠️ WARNING:** Many of the examples below depict the handling of sensitive data in the Unix shell. Before you work with live data, be sure you understand [best practices for handling sensitive data in the shell](https://smallstep.com/blog/command-line-secrets/), including shell variables and history.
## Generate a Seed for Signing and Encryption
Generate a secure seed using `seedtool`. This will be used in examples below.
```
$ SEED=`seedtool --count 32`
$ echo $SEED
94d5c144c39a6e4fbb4159d2f570be14ae51a822549c6da6f1bc45894abffe26
```
## Encrypting an Envelope
Create a symmetric key from a seed. This returns a `ur:crypto-key`.
```
$ SYMMETRIC_KEY=`envelope --symmetric-key $SEED`
$ echo $SYMMETRIC_KEY
ur:crypto-key/...
```
Create an envelope with a string subject and encrypt it using the private key above as a symmetric key.
```
$ ENCRYPTED_ENVELOPE=`envelope --string Hello. --encrypt $SYMMETRIC_KEY`
$ envelope --format $ENCRYPTED_ENVELOPE
EncryptedMessage
```
Decrypt the envelope and display its subject.
```
$ envelope --decrypt $ENCRYPTED_ENVELOPE $SYMMETRIC_KEY --subject
Hello.
```
## Signing an Envelope
Derive a PrivateKeyBase from the seed. This produces a `ur:crypto-prvkeys` which includes key material that can be used for signing and public key decyption.
```
$ PRIVATE_KEY=`envelope --private-key $SEED`
$ echo $PRIVATE_KEY
ur:crypto-prvkeys/...
```
Derive a PublicKeyBase from the PrivateKeyBase. A `ur:crypto-pubkeys` is returned which includes public keys for signature verification and public key encryption.
```
$ PUBLIC_KEY=`envelope --public-key $PRIVATE_KEY`
$ echo $PUBLIC_KEY
ur:crypto-pubkeys/...
```
Create an envelope with a string subject and sign it using the private key.
```
$ SIGNED_ENVELOPE=`envelope --string Hello. --sign $PRIVATE_KEY`
```
Output the signed envelope in Envelope Notation.
```
$ envelope --format $SIGNED_ENVELOPE
"Hello." [
verifiedBy: Signature
]
```
Verify the signature using the public key.
```
$ envelope --verify $SIGNED_ENVELOPE $PUBLIC_KEY
OK
```
Output the envelope's `subject`:
```
$ envelope --subject $SIGNED_ENVELOPE
Hello.
```
## Checking a Signature against a threshold set of signatures
Alice signs an envelope.
```
$ SIGNED_ENVELOPE=`envelope --string Hello. --sign $ALICE_PRIVATE_KEY`
$ envelope --format $ENVELOPE
"Hello." [
verifiedBy: Signature
]
```
Bob receives the envelope and check's Alice's signature, then reads the message.
```
$ envelope --verify $SIGNED_ENVELOPE $ALICE_PUBLIC_KEY
OK
$ envelope --subject $SIGNED_ENVELOPE
Hello.
```
Bob checks the envelope against Carol's public key, which fails.
```
$ envelope --verify $SIGNED_ENVELOPE $CAROL_PUBLIC_KEY
FAIL
```
Bob checks whether Alice OR Carol has signed the envelope, which succeeds.
```
$ envelope --verify $SIGNED_ENVELOPE \
--threshold 1 \
$ALICE_PUBLIC_KEY \
$CAROL_PUBLIC_KEY
OK
```
Bob checks whether Alice AND Carol have signed the envelope, which fails.
```
$ envelope --verify $SIGNED_ENVELOPE \
--threshold 2 \
$ALICE_PUBLIC_KEY \
$CAROL_PUBLIC_KEY
FAIL
```
## Encrypt then Sign
Alice encrypts a message using a symmetric key she shares with Bob, then signs it.
```
$ ENVELOPE=`envelope --string Hello. \
--encrypt $SYMMETRIC_KEY \
--sign $ALICE_PRIVATE_KEY`
$ envelope --format $ENVELOPE
EncryptedMessage [
verifiedBy: Signature
]
```
Bob checks Alice's signature, then decrypts and reads the message.
```
$ envelope --verify $ENVELOPE $ALICE_PUBLIC_KEY \
--decrypt $SYMMETRIC_KEY \
--subject
Hello.
```
## Sign then Encrypt
Alice signs a message with her private key, then encrypts it with a symmetric key she shares with Bob.
After the `sign` step, which adds a `verifiedBy` assertion, an `enclose` step is performed, which encloses the *entire* envelope, including its assertions, in another envelope. Finally the `encrypt` step is performed, which only transforms the subject of the envelope by encrypting it. The `enclose` step ensures that everything including assertions (the signature) are encrypted.
```
$ ENVELOPE=`envelope --string Hello. \
--sign $ALICE_PRIVATE_KEY \
--enclose \
--encrypt $SYMMETRIC_KEY`
$ envelope --format $ENVELOPE
EncryptedMessage
```
Bob decrypts the message, then checks Alice's signature, then reads the message.
The `extract` step reverses the `enclose` step above, stripping off the outer envelope that was previously encrypted.
```
$ envelope --decrypt $ENVELOPE $SYMMETRIC_KEY \
--extract \
--verify $ALICE_PUBLIC_KEY \
--subject
Hello.
```
## Multi-Recipient
Alice encrypts a message so it can only be decrypted by Bob or Carol.
```
$ CONTENT_KEY=`envelope --symmetric-key $SEED`
$ ENVELOPE=`envelope --string Hello. \
--encrypt $CONTENT_KEY \
--recipient $BOB_PUBLIC_KEY $CONTENT_KEY \
--recipient $CAROL_PUBLIC_KEY $CONTENT_KEY`
$ envelope --format $ENVELOPE
EncryptedMessage [
hasRecipient: SealedMessage
hasRecipient: SealedMessage
]
```
If the encryption step is omitted, `envelope` performs it automatically using an ephemeral content key.
```
$ ENVELOPE=`envelope --string Hello. \
--recipient $BOB_PUBLIC_KEY \
--recipient $CAROL_PUBLIC_KEY`
$ envelope --format $ENVELOPE
EncryptedMessage [
hasRecipient: SealedMessage
hasRecipient: SealedMessage
]
```
Bob receives the message, decrypts it using his private key, and reads it.
```
$ envelope --decrypt $ENVELOPE $BOB_PRIVATE_KEY --subject
Hello.
```
Alice cannot decrypt her own message, because she didn't make herself a recipient.
```
$ envelope --decrypt $ENVELOPE $ALICE_PRIVATE_KEY --subject
FAIL
```
## SSKR
Dan has a cryptographic seed he wants to backup using a social recovery scheme. The seed includes metadata he wants to back up also, making it too large to fit into a basic SSKR share.
```
$ DAN_SEED=ur:crypto-seed/...
```
Dan uses `seedtool` and `envelope` to generate an ephemeral content key.
```
$ CONTENT_KEY=`seedtool --ur | envelope --symmetric-key`
$ echo $CONTENT_KEY
ur:crypto-key/...
```
Dan encloses his seed in an envelope. His seed UR is converted into CBOR as the subject of the envelope, and because it's not yet encrypted or sharded, it can easily be read back.
```
$ SEED_ENVELOPE=`envelope --ur $DAN_SEED`
$ echo $SEED_ENVELOPE
ur:crypto-envelope/...
$ envelope --format $SEED_ENVELOPE
CBOR
$ envelope --subject $SEED_ENVELOPE
ur:crypto-seed/...
```
Dan encrypts the seed using the content key.
```
$ ENCRYPTED_SEED_ENVELOPE=`envelope --encrypt $SEED_ENVELOPE`
$ echo $ENCRYPTED_SEED_ENVELOPE
ur:crypto-envelope/...
$ envelope --format $ENCRYPTED_SEED_ENVELOPE
EncryptedMessage
$ envelope --subject $ENCRYPTED_SEED_ENVELOPE
EncryptedMessage
```
Dan splits encrypted envelope into shares, a single group 2-of-3. Each share is itself an envelope containing the encrypted message and an SSKR share as an assertion. The options for SSKR splits are the same as defined by `seedtool`.
```
$ envelope --split $ENCRYPTED_SEED_ENVELOPE $CONTENT_KEY --group 2-of-3
ur:crypto-envelope/...
ur:crypto-envelope/...
ur:crypto-envelope/...
```
Display the first of the three shares in Envelope Notation.
```
$ envelope --format ur:crypto-envelope/...
EncryptedMessage [
sskrShare: SSKRShare
]
```
Join two of the three shares to reconstruct the original unencrypted envelope.
```
$ RECONSTRUCTED_SEED_ENVELOPE=`envelope --join ur:crypto-envelope/... ur:crypto-envelope/...`
$ envelope --subject $RECONSTRUCTED_SEED_ENVELOPE
ur:crypto-seed/...
```
Only one of the shares won't work.
```
$ envelope --join ur:crypto-envelope/...
FAIL
```
## Complex Metadata
Create an envelope representing the metadata of particular work.
```
$ WORK_SCID=`seedtool --count 32`
$ WORK_ENVELOPE=`envelope --scid $WORK_SCID`
$ envelope --format $WORK_ENVELOPE
SCID(7fb90a9d96c07f39f75ea6acf392d79f241fac4ec0be2120f7c82489711e3e80)
```
Update the envelope by adding an assertion.
```
$ WORK_ENVELOPE=`envelope $WORK_ENVELOPE --assert --predicate isA --string Novel`
$ envelope --format $WORK_ENVELOPE
SCID(7fb90a9d96c07f39f75ea6acf392d79f241fac4ec0be2120f7c82489711e3e80) [
isA: "Novel"
]
```
Add two more assertions. Note that the predicates `isA` and `dereferenceVia` are "well-known" and therefore encoded as integers, while the predicate `"isbn"` is a user-defined string used as a predicate, so is shown surrounded with quotes.
```
$ WORK_ENVELOPE=`envelope $WORK_ENVELOPE \
--assert --string isbn --string 9780451191144 \
--assert --predicate dereferenceVia --string LibraryOfCongress`
$ envelope --format $WORK_ENVELOPE
SCID(7fb90a9d96c07f39f75ea6acf392d79f241fac4ec0be2120f7c82489711e3e80) [
isA: "Novel"
"isbn": "9780451191144"
dereferenceVia: "LibraryOfCongress"
]
```
Create an envelope that will be used to represent the author of the work.
```
$ AUTHOR_SCID=`seedtool --count 32`
$ AUTHOR_ENVELOPE=`envelope --scid $AUTHOR_SCID \
--assert --predicate hasName --string "Ayn Rand" \
--assert --predicate dereferenceVia --string LibraryOfCongress`
$ envelope --format $AUTHOR_ENVELOPE
"author": SCID(9c747ace78a4c826392510dd6285551e7df4e5164729a1b36198e56e017666c8) [
dereferenceVia: "LibraryOfCongress"
hasName: "Ayn Rand"
]
```
Create two envelopes that will be used as objects in the assertions that define the work's name, in two different languages.
```
$ NAME_EN_ENVELOPE=`envelope --string 'Atlas Shrugged' --assert --predicate language --string en`
$ NAME_ES_ENVELOPE=`envelope --string 'La rebelión de Atlas' --assert --predicate language --string es`
$ envelope --format $NAME_EN_ENVELOPE
"Atlas Shrugged" [
language: "en"
]
$ envelope --format $NAME_ES_ENVELOPE
"La rebelión de Atlas" [
language: "es"
]
```
Add the information about the author and the name of the work to the envelope that represents the work.
```
$ WORK_ENVELOPE=`envelope $WORK_ENVELOPE \
--assert --string author --envelope $AUTHOR_ENVELOPE \
--assert --predicate hasName --envelope $NAME_EN_ENVELOPE \
--assert --predicate hasName --envelope $NAME_ES_ENVELOPE`
$ envelope --format $WORK_ENVELOPE
SCID(7fb90a9d96c07f39f75ea6acf392d79f241fac4ec0be2120f7c82489711e3e80) [
"author": SCID(9c747ace78a4c826392510dd6285551e7df4e5164729a1b36198e56e017666c8) [
dereferenceVia: "LibraryOfCongress"
hasName: "Ayn Rand"
]
"isbn": "9780451191144"
dereferenceVia: "LibraryOfCongress"
hasName: "Atlas Shrugged" [
language: "en"
]
hasName: "La rebelión de Atlas" [
language: "es"
]
isA: "novel"
]
```
Calculate the BLAKE3 digest of a specific binary object we want to reference.
```
$ ATLAS_SHRUGGED_DIGEST=`b3sum IPFS/AtlasShrugged.epub`
$ echo $ATLAS_SHRUGGED_DIGEST
e8aa201db4044168d05b77d7b36648fb7a97db2d3e72f5babba9817911a52809
```
Now add the metadata to an envelope where the subject is a `Digest` and which therefore points to a specific, immutable binary object. The final envelope points to an exact binary object, tells you what format it's in, how to retrieve it, and various metadata about it.
```
$ ENVELOPE=`envelope --digest $ATLAS_SHRUGGED_DIGEST \
--assert --string work --envelope $WORK_ENVELOPE \
--assert --string format --string EPUB \
--assert --predicate dereferenceVia --string IPFS`
$ envelope --format $ENVELOPE
Digest(e8aa201db4044168d05b77d7b36648fb7a97db2d3e72f5babba9817911a52809) [
"format": "EPUB"
"work": SCID(7fb90a9d96c07f39f75ea6acf392d79f241fac4ec0be2120f7c82489711e3e80) [
"author": SCID(9c747ace78a4c826392510dd6285551e7df4e5164729a1b36198e56e017666c8) [
dereferenceVia: "LibraryOfCongress"
hasName: "Ayn Rand"
]
"isbn": "9780451191144"
dereferenceVia: "LibraryOfCongress"
hasName: "Atlas Shrugged" [
language: "en"
]
hasName: "La rebelión de Atlas" [
language: "es"
]
isA: "novel"
]
dereferenceVia: "IPFS"
]
```
## Exploring the Structure of an Envelope
Create a simple envelope.
```
$ ENVELOPE=`envelope --string Hello. --sign $PRIVATE_KEY`
$ envelope --format $ENVELOPE
"Hello." [
verifiedBy: Signature
]
```
Get the digest of the entire envelope. This is the root of the envelope's Merkle tree.
```
$ envelope $ENVELOPE --digest
ur:crypto-digest/...
```
Get the subject, its type, and its digest.
```
$ envelope $ENVELOPE --subject --subject-type --subject-digest
Hello.
string
ur:crypto-digest/...
```
Count the number assertions in the envelope.
```
$ envelope $ENVELOPE --assertions-count
1
```
Get the digest that uniquely identifies the first assertion.
```
$ envelope $ENVELOPE --assertion 0 --assertion-digest
ur:crypto-digest/...
```
For the first assertion, get its predicate, its type, and its digest.
```
$ envelope $ENVELOPE --assertion 0 --predicate --predicate-type --predicate-digest
verifiedBy
predicate
ur:crypto-digest/...
```
For the first assertion, get its object, its type, and its digest.
```
$ envelope $ENVELOPE --assertion 0 --object --object-type --object-digest
ur:crypto-sig/...
Signature
ur:crypto-digest/...
```
## Merkle Invariance under Encryption and Redaction
Create a simple signed envelope.
```
$ ENVELOPE=`envelope --string Hello. --sign $PRIVATE_KEY`
$ envelope --format $ENVELOPE
"Hello." [
verifiedBy: Signature
]
```
Note the digest of the envelope.
```
$ envelope $ENVELOPE --digest
ur:crypto-digest/abcd...
```
Encrypt the envelope and note it still has the same digest, and that the signature still verifies.
```
$ ENCRYPTED_ENVELOPE=`envelope $ENVELOPE --encrypt $SYMMETRIC_KEY`
$ envelope --format $ENCRYTED_ENVELOPE
EncryptedMessage [
verifiedBy: Signature
]
$ envelope $ENCRYPTED_ENVELOPE --digest
ur:crypto-digest/abcd...
$ envelope $ENCRYPTED_ENVELOPE --verify $PUBLIC_KEY
OK
```
Redaction is accomplished by specifying one or more digests in the Merkel tree of an envelope, called the target. When used with `--redact` all items with targeted digests are redacted. When used with `--reveal` all items **WITHOUT** targeted digests are redacted.
Redact the subject of the envelope and note it still has the same digest. Also note that the signature on the redacted envelope still verifies.
```
$ TARGET=`envelope $ENVELOPE --subject-digest`
$ REDACTED_ENVELOPE=`envelope $ENVELOPE --redact $TARGET`
$ envelope --format $REDACTED_ENVELOPE
REDACTED [
verifiedBy: Signature
]
$ envelope $REDACTED_ENVELOPE --digest
ur:crypto-digest/abcd...
$ envelope $REDACTED_ENVELOPE --verify $PUBLIC_KEY
OK
```
Redact everything *except* the subject of the envelope using `--reveal` instead of `--redact`. Note that it still has the same digest. The signature is redacted and therefore can't be verified, but the merkle tree remains intact.
```
$ TARGET=`envelope $ENVELOPE --subject-digest`
$ REDACTED_ENVELOPE=`envelope $ENVELOPE --reveal $TARGET`
$ envelope --format $REDACTED_ENVELOPE
"Hello." [
REDACTED: REDACTED
]
$ envelope $REDACTED_ENVELOPE --digest
ur:crypto-digest/abcd...
$ envelope $REDACTED_ENVELOPE --verify $PUBLIC_KEY
FAIL
```