# 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 ```