1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#![cfg(all(
feature = "signature",
feature = "sskr",
feature = "secret",
feature = "types"
))]
mod common;
use bc_components::{
KeyDerivationMethod, SSKRGroupSpec, SSKRSpec, SymmetricKey, keypair,
};
use bc_envelope::prelude::*;
use indoc::indoc;
#[test]
fn test_multi_permit() -> EnvelopeResult<()> {
bc_components::register_tags();
//
// Alice composes a poem.
//
let poem_text =
"At midnight, the clocks sang lullabies to the wandering teacups.";
//
// Alice creates a new envelope and assigns the text as the envelope's
// subject. She also adds some metadata assertions to the envelope,
// including that the subject is a "poem", the title, the author, and
// the date.
//
let original_envelope = Envelope::new(poem_text)
.add_type("poem")
.add_assertion("title", "A Song of Ice Cream")
.add_assertion("author", "Plonkus the Iridescent")
.add_assertion(known_values::DATE, Date::from_ymd(2025, 5, 15));
//
// Alice signs the envelope with her private key.
//
let (alice_private_keys, alice_public_keys) = keypair();
let signed_envelope = original_envelope.sign(&alice_private_keys);
// expected-text-output-rubric:
#[rustfmt::skip]
let expected_format = indoc! {r#"
{
"At midnight, the clocks sang lullabies to the wandering teacups." [
'isA': "poem"
"author": "Plonkus the Iridescent"
"title": "A Song of Ice Cream"
'date': 2025-05-15
]
} [
'signed': Signature
]
"#}.trim();
assert_actual_expected!(signed_envelope.format(), expected_format);
//
// Alice picks a random symmetric "content key" and uses it to encrypt the
// signed envelope. She will provide several different methods ("permits")
// that can be used to unlock it. Each permit encrypts the same content key
// using a different method.
//
let content_key = SymmetricKey::new();
let encrypted_envelope = signed_envelope.encrypt(&content_key);
// expected-text-output-rubric:
#[rustfmt::skip]
let expected_format = indoc! {r#"
ENCRYPTED
"#}.trim();
assert_actual_expected!(encrypted_envelope.format(), expected_format);
//
// Alice wants to be able to recover the envelope later using a password she
// can remember, So she adds the first permit to the envelope by using the
// `add_secret()` method, providing a derivation method `Argon2id`, her
// password, and the content key. The `add_secret()` method encrypts the
// content key with a key derived from her password, and adds it to the
// envelope as a `'hasSecret'` assertion.
//
let password = b"unicorns_dance_on_mars_while_eating_pizza";
let locked_envelope = encrypted_envelope
.add_secret(KeyDerivationMethod::Argon2id, password, &content_key)
.unwrap();
// println!("{}", locked_envelope.format());
// expected-text-output-rubric:
#[rustfmt::skip]
let expected_format = indoc! {r#"
ENCRYPTED [
'hasSecret': EncryptedKey(Argon2id)
]
"#}.trim();
assert_actual_expected!(locked_envelope.format(), expected_format);
//
// Next, Alice wants to be able to unlock her envelope using her private
// key, and she also wants Bob to be able to unlock it using his private
// key. To do this, she uses the `add_recipient()` method, which
// encrypts the content key with the public keys of Alice and Bob.
let (bob_private_keys, bob_public_keys) = keypair();
let locked_envelope = locked_envelope
.add_recipient(&alice_public_keys, &content_key)
.add_recipient(&bob_public_keys, &content_key);
// expected-text-output-rubric:
#[rustfmt::skip]
let expected_format = indoc! {r#"
ENCRYPTED [
'hasRecipient': SealedMessage
'hasRecipient': SealedMessage
'hasSecret': EncryptedKey(Argon2id)
]
"#}.trim();
assert_actual_expected!(locked_envelope.format(), expected_format);
//
// An SSKR share is a kind of permit defined by the characteristic that one
// share by itself is not enough to unlock the envelope: some quorum of
// shares is required.
//
// Alice wants to back up her poem using a social recovery scheme, So even
// if she forgets her password and loses her private key, she can still
// recover the envelope by finding two of the three friends she entrusted
// with the shares.
//
// So Alice creates a 2-of-3 SSKR group and "shards" the envelope into three
// envelopes, each containing a unique SSKR share.
//
let sskr_group = SSKRGroupSpec::new(2, 3)?;
let spec = SSKRSpec::new(1, vec![sskr_group])?;
let sharded_envelopes =
locked_envelope.sskr_split_flattened(&spec, &content_key)?;
//
// Every envelope looks the same including the previous permits Alice added,
// but each one contains a different SSKR share, so we only show the first
// one here.
//
// expected-text-output-rubric:
#[rustfmt::skip]
let expected_format = indoc! {r#"
ENCRYPTED [
'hasRecipient': SealedMessage
'hasRecipient': SealedMessage
'hasSecret': EncryptedKey(Argon2id)
'sskrShare': SSKRShare
]
"#}.trim();
assert_actual_expected!(sharded_envelopes[0].format(), expected_format);
//
// So now there are three envelopes, and five different ways to unlock
// them:
//
// 1. Using her original content key (usually not saved, but could be stored
// in a safe place)
// 2. Using her password
// 3. Using her private key
// 4. Using Bob's private key
// 5. Using any two of the three SSKR shares
//
//
// Using the content key.
//
let received_envelope = &sharded_envelopes[0];
let unlocked_envelope = received_envelope.decrypt(&content_key)?;
assert_eq!(unlocked_envelope, signed_envelope);
//
// Using the password and the Argon2id method.
//
let unlocked_envelope = received_envelope.unlock(password)?;
assert_eq!(unlocked_envelope, signed_envelope);
//
// Using Alice's private key.
//
let unlocked_envelope =
received_envelope.decrypt_to_recipient(&alice_private_keys)?;
assert_eq!(unlocked_envelope, signed_envelope);
//
// Using Bob's private key.
//
let unlocked_envelope =
received_envelope.decrypt_to_recipient(&bob_private_keys)?;
assert_eq!(unlocked_envelope, signed_envelope);
//
// Using any two of the three SSKR shares.
//
let unlocked_envelope =
Envelope::sskr_join(&[&sharded_envelopes[0], &sharded_envelopes[2]])?
.try_unwrap()?;
// println!("{}", unlocked_envelope.format());
assert_eq!(unlocked_envelope, signed_envelope);
unlocked_envelope.verify(&alice_public_keys)?;
Ok(())
}