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
//! A pure Rust implementation of Selection-Sensitive Attribute-Based Key
//! Derivation, providing a novel scheme where cryptographic keys are derived
//! from a collection of attributes, with sensitivity to selection within the
//! collection.
//!
//! An **attribute** is a piece of data that contributes to key derivation.
//! A collection of attributes is first hardened using a
//! [password hasher](password_hash::PasswordHasher), then organised into a
//! keyspace from which attribute-based keys can be selected.
//!
//! A **keyspace** consists of:
//! - A collection of attribute values hardened through parallel password
//! hashing.
//! - Proof-of-work constraint.
//! - Optional application context for domain separation.
//!
//! Key derivation requires selecting a subset of attribute indices, which
//! partitions the collection into two sets: selected and remaining attributes.
//! These are each concatenated to form keying and antikeying material for an
//! [annihilative pair](annihilation::AnnihlKey::new_pair), where selected
//! attributes become keying material and remaining attributes become
//! antikeying material. The derived annihilative pair's
//! [annihilation key](annihilation::AnnihlKey::to_annihilation) is computed
//! and used as a [seed for RNG expansion](rand_core::SeedableRng::from_seed)
//! to produce the final derived key under this scheme.
//!
//! Derived keys depend on both which attributes are selected and which are
//! not. Different selections produce cryptographically independent keys, even
//! when there is overlap in selection. Per-attribute password hashing makes
//! brute-force attacks computationally expensive, and the derived key's
//! dependence on all attributes prevents an attacker with knowledge of
//! attribute selection from optimising by discarding remaining attributes.
//!
//! The requirement to select at least one attribute, but not all attributes,
//! ensures that both partition sets are non-empty and the annihilative pair
//! can be constructed.
//!
//! # Example
//! ```
//! use attrkey::Attributes;
//! use chacha20::ChaCha20Rng;
//! use scrypt::{Params, Scrypt};
//!
//! fn main() {
//! let arr = vec![
//! b"Pigs on the Wing I".to_vec(),
//! b"Dogs".to_vec(),
//! b"Pigs (Three Different Ones)".to_vec(),
//! b"Sheep".to_vec(),
//! b"Pigs on the Wing II".to_vec(),
//! ];
//!
//! let params = Params::new(4, 8, 1).unwrap();
//! let scrypt = Scrypt::new_with_params(params);
//!
//! // Create a collection of attributes with Scrypt and ChaCha20
//! let attributes = Attributes::<_, ChaCha20Rng>::new(arr, scrypt)
//! .expect("valid attributes should produce collection");
//!
//! // Harden attributes into a keyspace
//! let keyspace = attributes
//! .harden(4, None)
//! .expect("scrypt is a valid password hasher for hardening");
//!
//! // Derive keys from two selections within the same keyspace
//! let selection_1 = vec![0, 2, 4];
//! let selection_2 = vec![1, 3];
//!
//! let dk = keyspace
//! .derive_key(&selection_1, None)
//! .expect("selection is valid");
//!
//! let mut dst = [0u8; 32];
//! keyspace
//! .derive_key_into(&selection_2, &mut dst)
//! .expect("selection is valid");
//!
//! // Different selections produce different keys
//! assert_ne!(dk, dst);
//! }
//! ```
pub use Attributes;
pub use AttrkeyError;
pub use Keyspace;