diff --git a/.gitignore b/.gitignore index 9a8f92d..2014ef1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target Cargo.lock /play +vars.mk diff --git a/src/crypto/key.rs b/src/crypto/key.rs index 00cf8ff..33a9288 100644 --- a/src/crypto/key.rs +++ b/src/crypto/key.rs @@ -25,12 +25,11 @@ use crate::{ sign::Signable, }, }; -use ed25519_dalek::{Signer}; +use ed25519_dalek::{Digest as Ed25519Digest, Signer, generic_array::GenericArray}; use hmac::{ Mac, digest::{ FixedOutput, - crypto_common::generic_array::GenericArray, }, }; use rand::{RngCore, rngs::OsRng}; @@ -63,25 +62,23 @@ pub enum KeyID { SignKeypair(SignKeypairPublic), #[rasn(tag(explicit(1)))] CryptoKeypair(CryptoKeypairPublic), + #[rasn(tag(explicit(2)))] + SecretKey(Hmac), } impl KeyID { pub fn as_string(&self) -> String { - fn get_bytes(ty_prefix: u8, key_prefix: u8, bytes: &[u8]) -> Vec { - let mut res = vec![ty_prefix, key_prefix]; - let mut bytes_vec = Vec::from(bytes); - res.append(&mut bytes_vec); - res - } - let bytes = match self { + match self { Self::SignKeypair(SignKeypairPublic::Ed25519(pubkey)) => { - get_bytes(0, 0, pubkey.as_ref()) + ser::base64_encode(pubkey.as_ref()) } Self::CryptoKeypair(CryptoKeypairPublic::Curve25519XChaCha20Poly1305(pubkey)) => { - get_bytes(1, 0, pubkey.as_ref()) + ser::base64_encode(pubkey.as_ref()) } - }; - ser::base64_encode(&bytes) + Self::SecretKey(hmac) => { + ser::base64_encode(hmac.deref()) + } + } } #[cfg(test)] @@ -97,6 +94,18 @@ impl KeyID { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); Self::CryptoKeypair(CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap().into()) } + + #[cfg(test)] + #[allow(dead_code)] + pub(crate) fn random_secret() -> Self { + Self::SecretKey(Hmac::new_sha512(&HmacKey::new_sha512().unwrap(), b"get a job").unwrap()) + } +} + +impl std::fmt::Display for KeyID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_string()) + } } /// A symmetric encryption key nonce @@ -284,7 +293,7 @@ impl SignKeypair { /// Hash a value then sign it, returning the hash and the signature. This is /// already how signing works, but we basically control the hash process /// ourselves so we can return the hash. - pub fn sign_and_hash(&self, master_key: &SecretKey, data: &[u8]) -> Result<(Binary<64>, SignKeypairSignature)> { + pub fn sign(&self, master_key: &SecretKey, data: &[u8]) -> Result { match self { Self::Ed25519 { secret: ref sec_locked_opt, .. } => { let sec_locked = sec_locked_opt.as_ref().ok_or(Error::CryptoKeyMissing)?; @@ -292,60 +301,30 @@ impl SignKeypair { .map_err(|_| Error::CryptoSignatureFailed)?; let pubkey: ed25519_dalek::PublicKey = (&seckey).into(); let keypair = ed25519_dalek::Keypair { public: pubkey, secret: seckey }; - let mut prehashed = ed25519_dalek::Sha512::new(); - prehashed.update(data); - // i don't like this clone but oh well...? - let sig_obj = keypair.sign_prehashed(prehashed.clone(), Some(b"stamp-protocol")); + let sig_obj = keypair.sign(data); let sig = SignKeypairSignature::Ed25519(Binary::new(sig_obj.to_bytes())); - let hash_vec = Vec::from(prehashed.finalize().as_slice()); - let hash_arr: [u8; 64] = hash_vec.as_slice().try_into() - .map_err(|_| Error::CryptoSignatureFailed)?; - let hash = Sha512(Binary(hash_arr)); - Ok((hash, sig)) + Ok(sig) } } } - /// Sign a value with our secret signing key. - /// - /// Must be unlocked via our master key. - pub fn sign(&self, master_key: &SecretKey, data: &[u8]) -> Result { - // sign and hash but throw away the hash - self.sign_and_hash(master_key, data) - .map(|x| x.1) - } - - /// Make sure a set of data has the appropriate hash and also verifies - /// against its signature. - pub fn verify_and_hash(&self, hash: Sha512, signature: &SignKeypairSignature, data: &[u8]) -> Result { + /// Verify a value with a detached signature given the public key of the + /// signer. + pub fn verify(&self, signature: &SignKeypairSignature, data: &[u8]) -> Result<()> { match (self, signature) { (Self::Ed25519 { public: ref pubkey_bytes, .. }, SignKeypairSignature::Ed25519(ref sig_bytes)) => { let pubkey = ed25519_dalek::PublicKey::from_bytes(&pubkey_bytes[..]) .map_err(|_| Error::CryptoSignatureVerificationFailed)?; let sig_arr: [u8; 64] = sig_bytes.deref().clone().try_into() .map_err(|_| Error::CryptoSignatureVerificationFailed)?; - let prehashed = ed25519_dalek::Sha512::default().chain(data); - let body_hash_vec = Vec::from(prehashed.clone().finalize().as_slice()); - let body_hash_arr: [u8; 64] = hash_vec.as_slice().try_into() - .map_err(|_| Error::CryptoSignatureFailed)?; - let body_hash = Sha512(Binary(body_hash_arr)); - - if hash != body_hash { - Err(Error::CryptoSignatureVerificationFailed)?; - } - let sig = ed25519_dalek::Signature::from(sig_arr); - pubkey.verify_prehashed_strict(data, &sig) - .map_err(|_| Error::CryptoSignatureVerificationFailed) + pubkey.verify_strict(data, &sig) + .map_err(|_| Error::CryptoSignatureVerificationFailed)?; + Ok(()) } } } - /// Verify a value with a detached signature given the public key of the - /// signer. - pub fn verify(&self, signature: &SignKeypairSignature, data: &[u8]) -> Result<()> { - } - /// Re-encrypt this signing keypair with a new master key. pub fn reencrypt(self, previous_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { match self { @@ -657,35 +636,57 @@ impl From for CryptoKeypairPublic { } } +pub const SHA512_LEN: usize = 64; + /// Holds a sha512 signature. We could just use Binary<64> directly but this /// sets a more clear intention, and these days I'm all about clear intentions /// and open communication (as long as it's securely encrypted between /// participants, of course). -#[derive(Debug, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] -pub struct Sha512(Binary<64>); +#[derive(Clone, Debug, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] +pub struct Sha512(Binary); impl Sha512 { - /// Create a new Sha512 from a byte array - pub fn new(bytes: [u8; 64]) -> Self { - Self(Binary::new(bytes)) + /// How bany bytes are in this Sha512? HMMM I WONDER + pub fn len() -> usize { + SHA512_LEN + } + + /// Create a new SHA512 hash from binary data + pub fn hash(input: &[u8]) -> Result { + let genarr = ed25519_dalek::Sha512::digest(input); + let hash_arr: [u8; 64] = genarr.as_slice().try_into() + .map_err(|_| Error::CryptoHashFailed)?; + Ok(Self(Binary::new(hash_arr))) } #[cfg(test)] pub(crate) fn random() -> Self { let mut randbuf = [0u8; 64]; OsRng.fill_bytes(&mut randbuf); - Self::new(randbuf) + Self(Binary::new(randbuf)) + } +} + +impl From<[u8; 64]> for Sha512 { + fn from(arr: [u8; 64]) -> Self { + Self(Binary::new(arr)) } } impl Deref for Sha512 { - type Target = [u8]; + type Target = [u8; 64]; fn deref(&self) -> &Self::Target { &self.0.deref() } } +impl std::fmt::Display for Sha512 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", ser::base64_encode(self.deref())) + } +} + /// A key for deriving an HMAC #[derive(Debug, AsnType, Encode, Decode, Serialize, Deserialize)] #[rasn(choice)] diff --git a/src/crypto/message.rs b/src/crypto/message.rs index 7753fb1..95c1c4a 100644 --- a/src/crypto/message.rs +++ b/src/crypto/message.rs @@ -57,11 +57,11 @@ impl ser::SerdeBinary for Message {} /// because an identity could have many CryptoKeypairs). pub fn send(sender_master_key: &SecretKey, sender_identity_id: &IdentityID, sender_key: &Subkey, recipient_key: &Subkey, message: &[u8]) -> Result { let sender_crypto = sender_key.key().as_cryptokey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let recipient_crypto = recipient_key.key().as_cryptokey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let sealed = recipient_crypto.seal(sender_master_key, sender_crypto, message)?; - let key_id = sender_key.key_id().ok_or(Error::IdentitySubkeyWrongType)?; + let key_id = sender_key.key_id(); let signed_msg = SignedObject::new(sender_identity_id.clone(), key_id, sealed); Ok(Message::Signed(signed_msg)) } @@ -72,9 +72,9 @@ pub fn send(sender_master_key: &SecretKey, sender_identity_id: &IdentityID, send /// message, which signs the *outside* of the message (not the inside). pub fn open(recipient_master_key: &SecretKey, recipient_key: &Subkey, sender_key: &Subkey, sealed: &Message) -> Result> { let sender_crypto = sender_key.key().as_cryptokey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let recipient_crypto = recipient_key.key().as_cryptokey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let signed_message = match sealed { Message::Signed(SignedObject { ref body, .. }) => body, _ => Err(Error::CryptoWrongMessageType)?, @@ -88,7 +88,7 @@ pub fn open(recipient_master_key: &SecretKey, recipient_key: &Subkey, sender_key /// cryptographically verified. pub fn send_anonymous(recipient_key: &Subkey, message: &[u8]) -> Result { let recipient_crypto = recipient_key.key().as_cryptokey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let sealed = recipient_crypto.seal_anonymous(message)?; Ok(Message::Anonymous(sealed)) } @@ -96,7 +96,7 @@ pub fn send_anonymous(recipient_key: &Subkey, message: &[u8]) -> Result /// Open an anonymous message send with [send_anonymous]. pub fn open_anonymous(recipient_master_key: &SecretKey, recipient_key: &Subkey, sealed: &Message) -> Result> { let recipient_crypto = recipient_key.key().as_cryptokey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let anon_message = match sealed { Message::Anonymous(ref data) => data, _ => Err(Error::CryptoWrongMessageType)?, diff --git a/src/crypto/sign.rs b/src/crypto/sign.rs index 3cb5e30..fc93b3d 100644 --- a/src/crypto/sign.rs +++ b/src/crypto/sign.rs @@ -60,9 +60,9 @@ impl ser::SerdeBinary for Signature {} /// Sign a message with a private key, returning the detached signature. pub fn sign(master_key: &SecretKey, signing_identity_id: &IdentityID, signing_key: &Subkey, message: &[u8]) -> Result { let sign_key = signing_key.key().as_signkey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let signature = sign_key.sign(master_key, message)?; - let key_id = signing_key.key_id().ok_or(Error::IdentitySubkeyWrongType)?; + let key_id = signing_key.key_id(); Ok(Signature::Detached { sig: SignedObject::new(signing_identity_id.clone(), key_id, signature) }) @@ -74,16 +74,16 @@ pub fn verify(signing_key: &Subkey, signature: &Signature, message: &[u8]) -> Re .map(|x| x.body()) .ok_or(Error::CryptoWrongSignatureType)?; let sign_key = signing_key.key().as_signkey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; sign_key.verify(detached, message) } /// Sign a message with a private key, returning the detached signature. pub fn sign_attached(master_key: &SecretKey, signing_identity_id: &IdentityID, signing_key: &Subkey, message: &[u8]) -> Result { let sign_key = signing_key.key().as_signkey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; let signature = sign_key.sign(master_key, message)?; - let key_id = signing_key.key_id().ok_or(Error::IdentitySubkeyWrongType)?; + let key_id = signing_key.key_id(); Ok(Signature::Attached { sig: SignedObject::new(signing_identity_id.clone(), key_id, signature), data: message.to_vec().into(), @@ -96,7 +96,7 @@ pub fn verify_attached(signing_key: &Subkey, signature: &Signature) -> Result<() .map(|x| (x.0.body(), x.1)) .ok_or(Error::CryptoWrongSignatureType)?; let sign_key = signing_key.key().as_signkey() - .ok_or(Error::IdentitySubkeyWrongType)?; + .ok_or(Error::KeychainSubkeyWrongType)?; sign_key.verify(attached.0, attached.1) } diff --git a/src/dag.rs b/src/dag.rs deleted file mode 100644 index b740cf9..0000000 --- a/src/dag.rs +++ /dev/null @@ -1,2354 +0,0 @@ -//! A DAG, or directed acyclic graph, allows us to represent our identity as an -//! ordered list of signed changes, as opposed to a singular object. There are -//! pros and cons to both methods, but for the purposes of this project, a -//! tree of signed transactions that link back to previous changes provides a -//! good amount of security, auditability, and syncability. - -use crate::{ - error::{Error, Result}, - crypto::{ - key::{SecretKey, Sha512, SignKeypairSignature}, - }, - identity::{ - claim::{ - ClaimID, - ClaimSpec, - }, - identity::{ - IdentityID, - ForwardType, - Identity, - }, - keychain::{ - ExtendKeypair, - AdminKeypair, - AdminKeypairSignature, - Key, - RevocationReason, - }, - recovery::{ - PolicyID, - PolicyCondition, - PolicyRequest, - PolicyRequestAction, - }, - stamp::{ - StampID, - Stamp, - }, - }, - util::{ - Public, - Timestamp, - ser::{self, SerdeBinary}, - }, -}; -use getset; -use rasn::{Encode, Decode, AsnType}; -use serde_derive::{Serialize, Deserialize}; -use std::collections::HashMap; -use std::hash::{Hash, Hasher}; -use std::ops::Deref; - -/// This is all of the possible transactions that can be performed on an -/// identity, including the data they require. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize)] -#[rasn(choice)] -pub enum TransactionBody { - /// Create a new identity. The [ID][TranscationID] of this transaction will - /// be the identity's public ID forever after. - #[rasn(tag(explicit(0)))] - CreateIdentityV1 { - #[rasn(tag(explicit(0)))] - alpha: AdminKeypair, - }, - /// Make a new claim on this identity. The [ID][TransactionID] if this - /// transaction will be the claim's ID. - #[rasn(tag(explicit(1)))] - MakeClaimV1 { - #[rasn(tag(explicit(0)))] - spec: ClaimSpec, - }, - /// Delete/remove a claim by ID. - #[rasn(tag(explicit(2)))] - DeleteClaimV1 { - #[rasn(tag(explicit(0)))] - claim_id: ClaimID, - }, - /// Make a stamp that is saved and advertised with this identity. - #[rasn(tag(explicit(3)))] - MakeStampV1 { - #[rasn(tag(explicit(0)))] - stamp: Stamp, - }, - /// Revoke a stamp we previously created and store this revocation with the - /// identity. - #[rasn(tag(explicit(4)))] - RevokeStampV1 { - #[rasn(tag(explicit(0)))] - revocation: StampRevocation, - } - /// Accept a stamp on one of our claims into our identity. This allows those - /// who have our identity to see the trust others have put into us. - #[rasn(tag(explicit(5)))] - AcceptStampV1 { - #[rasn(tag(explicit(0)))] - stamp: Stamp, - }, - /// Delete a stamp on one of our claims. - #[rasn(tag(explicit(6)))] - DeleteStampV1 { - #[rasn(tag(explicit(0)))] - stamp_id: StampID, - }, - /// Add a new subkey to our keychain. - #[rasn(tag(explicit(7)))] - AddSubkeyV1 { - #[rasn(tag(explicit(0)))] - key: Key, - #[rasn(tag(explicit(1)))] - name: String, - #[rasn(tag(explicit(2)))] - desc: Option, - }, - /// Edit the name/description of a subkey by its unique name. - #[rasn(tag(explicit(8)))] - EditSubkeyV1 { - #[rasn(tag(explicit(0)))] - name: String, - #[rasn(tag(explicit(1)))] - new_name: String, - #[rasn(tag(explicit(2)))] - new_desc: Option, - }, - /// Mark a subkey as revoked, allowing old signatures to be validated but - /// without permitting new signatures to be created. - #[rasn(tag(explicit(9)))] - RevokeSubkeyV1 { - #[rasn(tag(explicit(0)))] - name: String, - #[rasn(tag(explicit(1)))] - reason: RevocationReason, - #[rasn(tag(explicit(2)))] - new_name: Option, - }, - /// Delete a subkey entirely from the identity. - #[rasn(tag(explicit(10)))] - DeleteSubkeyV1 { - #[rasn(tag(explicit(0)))] - name: String, - }, - /// Set this identity's nickname. - #[rasn(tag(explicit(11)))] - SetNicknameV1 { - #[rasn(tag(explicit(0)))] - nickname: Option, - }, - /// Add a forward to another location. - #[rasn(tag(explicit(12)))] - AddForwardV1 { - #[rasn(tag(explicit(0)))] - name: String, - #[rasn(tag(explicit(1)))] - #[serde(rename = "type")] - ty: ForwardType, - #[rasn(tag(explicit(2)))] - default: bool, - }, - /// Delete a forward. - #[rasn(tag(explicit(13)))] - DeleteForwardV1 { - #[rasn(tag(explicit(0)))] - name: String, - }, -} - -impl TransactionBody { - /// Reencrypt this transaction body - fn reencrypt(self, old_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { - let new_self = match self { - Self::CreateIdentityV1 { admin } => { - let new_admin = admin.reencrypt(old_master_key, new_master_key)?; - Self::CreateIdentityV1 { - admin: new_admin - } - } - Self::MakeClaimV1 { spec } => Self::MakeClaimV1 { - spec: spec.reencrypt(old_master_key, new_master_key)?, - }, - Self::DeleteClaimV1 { claim_id } => Self::DeleteClaimV1 { claim_id }, - Self::AcceptStampV1 { stamp } => Self::AcceptStampV1 { stamp }, - Self::DeleteStampV1 { stamp_id } => Self::DeleteStampV1 { stamp_id }, - Self::AddSubkeyV1 { key, name, desc } => { - let new_subkey = key.reencrypt(old_master_key, new_master_key)?; - Self::AddSubkeyV1 { key: new_subkey, name, desc } - } - Self::EditSubkeyV1 { name, new_name, desc } => Self::EditSubkeyV1 { name, new_name, desc }, - Self::RevokeSubkeyV1 { name, reason, new_name } => Self::RevokeSubkeyV1 { name, reason, new_name }, - Self::DeleteSubkeyV1 { name } => Self::DeleteSubkeyV1 { name }, - Self::SetNicknameV1 { nickname } => Self::SetNicknameV1 { nickname }, - Self::AddForwardV1 { name, ty, default } => Self::AddForwardV1 { name, ty, default }, - Self::DeleteForwardV1 { name } => Self::DeleteForwardV1 { name }, - }; - Ok(new_self) - } -} - -impl Public for TransactionBody { - fn strip_private(&self) -> Self { - match self.clone() { - Self::CreateIdentityV1 { admin } => Self::CreateIdentityV1 { admin: admin.strip_private() }, - Self::MakeClaimV1 { spec } => Self::MakeClaimV1 { spec: spec.strip_private() }, - Self::DeleteClaimV1 { claim_id } => Self::DeleteClaimV1 { claim_id }, - Self::AcceptStampV1 { stamp } => Self::AcceptStampV1 { stamp: stamp.strip_private() }, - Self::DeleteStampV1 { stamp_id } => Self::DeleteStampV1 { stamp_id }, - Self::AddSubkeyV1 { key, name, desc } => Self::AddSubkeyV1 { key: key.strip_private(), name, desc }, - Self::EditSubkeyV1 { name, new_name, desc: new_desc } => Self::EditSubkeyV1 { name, new_name, desc: new_desc }, - Self::RevokeSubkeyV1 { name, reason, new_name } => Self::RevokeSubkeyV1 { name, reason, new_name }, - Self::DeleteSubkeyV1 { name } => Self::DeleteSubkeyV1 { name }, - Self::SetNicknameV1 { nickname } => Self::SetNicknameV1 { nickname }, - Self::AddForwardV1 { name, ty, default } => Self::AddForwardV1 { name, ty, default }, - Self::DeleteForwardV1 { name } => Self::DeleteForwardV1 { name }, - } - } - - fn has_private(&self) -> bool { - match self { - Self::CreateIdentityV1 { admin } => admin.has_private(), - Self::SetRecoveryPolicyV1 { .. } => false, - Self::ExecuteRecoveryPolicyV1 { request } => request.has_private(), - Self::MakeClaimV1 { spec } => spec.has_private(), - Self::DeleteClaimV1 { .. } => false, - Self::AcceptStampV1 { .. } => false, - Self::DeleteStampV1 { .. } => false, - Self::SetPolicyKeyV1 { keypair, .. } => keypair.has_private(), - Self::SetPublishKeyV1 { keypair, .. } => keypair.has_private(), - Self::SetRootKeyV1 { keypair, .. } => keypair.has_private(), - Self::AddSubkeyV1 { key, .. } => key.has_private(), - Self::EditSubkeyV1 { .. } => false, - Self::RevokeSubkeyV1 { .. } => false, - Self::DeleteSubkeyV1 { .. } => false, - Self::SetNicknameV1 { .. } => false, - Self::AddForwardV1 { .. } => false, - Self::DeleteForwardV1 { .. } => false, - } - } -} - -/// The TransactionID is a SHA512 hash of the transaction body -#[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] -pub struct TransactionID(Sha512); - -impl Deref for TransactionID { - type Target = [u8]; - fn deref(&self) -> &Self::Target { - self.0.deref() - } -} - -impl From for String { - fn from(id: TransactionID) -> Self { - ser::base64_encode(id.deref()) - } -} - -impl From<&TransactionID> for String { - fn from(id: &TransactionID) -> Self { - ser::base64_encode(id.deref()) - } -} - -impl Hash for TransactionID { - fn hash(&self, state: &mut H) { - self.deref().hash(state); - } -} - -impl Eq for TransactionID {} - -#[cfg(test)] -impl TransactionID { - pub(crate) fn random() -> Self { - Self(Sha512::random()) - } -} - -/// The body of an identity transaction. Holds the transaction's references to -/// its previous transactions and the transaction type/data itself. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] -#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct TransactionEntry { - /// When this transaction was created. - #[rasn(tag(explicit(0)))] - created: Timestamp, - /// This is a list of previous transactions that are not already listed by - /// another transaction. - /// - /// In general, this will only list the last transaction, but it's possible - /// that you might make two separate changes on two separate devices and - /// when they sync, you will have two leading transactions. The next change - /// you make to your identity would sign both of those transactions, and - /// merge the "tree" back into a single trunk. - /// - /// Note that when listing previous transactions, their `created` times must - /// be *less than* this transaction's created time. Future transactions - /// cannot be signed into a past one. - #[rasn(tag(explicit(1)))] - previous_transactions: Vec, - /// This holds the actual transaction data. - #[rasn(tag(explicit(2)))] - body: TransactionBody, -} - -impl TransactionEntry { - /// Create a new entry. - fn new>(created: T, previous_transactions: Vec, body: TransactionBody) -> Self { - Self { - created: created.into(), - previous_transactions, - body, - } - } -} - -impl Public for TransactionEntry { - fn strip_private(&self) -> Self { - let mut clone = self.clone(); - clone.set_body(self.body().strip_private()); - clone - } - - fn has_private(&self) -> bool { - self.body().has_private() - } -} - -/// A transaction represents a single change on an identity object. In order to -/// build an identity, all transactions are played in order from start to finish. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] -#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct Transaction { - /// This is a SHA512 hash of the transaction's `entry` - #[rasn(tag(explicit(0)))] - id: TransactionID, - /// This holds our transaction body: any references to previous - /// transactions as well as the transaction type/data. - #[rasn(tag(explicit(1)))] - entry: TransactionEntry, - /// The signatures on this transaction's ID. - #[rasn(tag(explicit(2)))] - signatures: Vec, -} - -impl Transaction { - pub(crate) fn new(master_key: &SecretKey, identity_maybe: &Option, sign_with: SignWith, entry: TransactionEntry) -> Result { - let serialized = ser::serialize(&entry.strip_private())?; - let id = match identity_maybe.as_ref() { - // we have an identity, meaning this is NOT a create/genesis trans - // and we can pull the keys directly from the identity object itself... - Some(identity) => { - let regular_ol_sign = || -> Result { - Ok(match sign_with { - SignWith::Alpha => TransactionID::Alpha(identity.keychain().alpha().sign(master_key, serialized.as_slice())?), - SignWith::Policy => TransactionID::Policy(identity.keychain().policy().sign(master_key, serialized.as_slice())?), - SignWith::Root => TransactionID::Root(identity.keychain().root().sign(master_key, serialized.as_slice())?), - }) - }; - // ...unless we're executing a recovery policy, in which case - // the signature must come from the POLICY key - match entry.body() { - TransactionBody::CreateIdentityV1 { .. } => Err(Error::DagCreateIdentityOnExistingChain)?, - TransactionBody::ExecuteRecoveryPolicyV1 { request } => { - match request.entry().action() { - PolicyRequestAction::ReplaceKeys { policy, .. } => { - match sign_with { - SignWith::Policy => TransactionID::Policy(policy.sign(master_key, serialized.as_slice())?), - // recovery transactions must be signed by - // new root key - _ => Err(Error::DagKeyNotFound)?, - } - } - } - } - _ => regular_ol_sign()? - } - } - // we do NOT have an identity, meaning this transaction is likely - // the one creating the identity. so we search for the key we need - // (always the alpha for the creation) within the body of the - // transaction itself. - None => { - match entry.body() { - TransactionBody::CreateIdentityV1 { ref alpha, .. } => { - match sign_with { - SignWith::Alpha => TransactionID::Alpha(alpha.sign(master_key, serialized.as_slice())?), - // you can only sign a blank identity with the alpha key - _ => Err(Error::DagKeyNotFound)?, - } - } - _ => Err(Error::DagKeyNotFound)?, - } - } - }; - Ok(Self { - id, - entry, - }) - } - - /// Verify this transaction's signature against its public data. - pub(crate) fn verify(&self, identity_maybe: Option<&Identity>) -> Result<()> { - let serialized = ser::serialize(&self.entry().strip_private())?; - match identity_maybe.as_ref() { - // if we have an identity, we can verify this transaction using the - // public keys contained in the identity - Some(identity) => { - let regular_ol_verify = || { - match self.id() { - TransactionID::Alpha(ref sig) => identity.keychain().alpha().verify(sig, &serialized), - TransactionID::Policy(ref sig) => identity.keychain().policy().verify(sig, &serialized), - TransactionID::Root(ref sig) => identity.keychain().root().verify(sig, &serialized), - } - }; - match self.entry().body() { - TransactionBody::ExecuteRecoveryPolicyV1 { request } => { - match request.entry().action() { - PolicyRequestAction::ReplaceKeys { policy, .. } => { - match self.id() { - TransactionID::Policy(ref sig) => policy.verify(sig, &serialized), - // recovery transactions must be signed by - // new root key - _ => Err(Error::DagKeyNotFound)?, - } - } - } - } - _ => regular_ol_verify(), - } - } - // we don't have an identity, so this is necessarily the genesis - // transaction that creates it. - None => { - match self.entry().body() { - TransactionBody::CreateIdentityV1 { ref alpha, .. } => { - match self.id() { - TransactionID::Alpha(ref sig) => alpha.verify(sig, &serialized), - // genesis transaction must be signed by alpha - _ => Err(Error::DagKeyNotFound)?, - } - } - _ => Err(Error::DagKeyNotFound)?, - } - } - } - } - - /// Reencrypt this transaction. - fn reencrypt(mut self, old_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { - let new_body = self.entry().body().clone().reencrypt(old_master_key, new_master_key)?; - self.entry_mut().set_body(new_body); - Ok(self) - } -} - -impl Public for Transaction { - fn strip_private(&self) -> Self { - let mut clone = self.clone(); - clone.set_entry(self.entry().strip_private()); - clone - } - - fn has_private(&self) -> bool { - self.entry().has_private() - } -} - -/// A trait used for grabbing information about transactions that allows us to -/// build a DAG or ordered list of transactions from them. -pub trait GraphInfo { - /// Grab the ID for this transaction. - fn id(&self) -> &TransactionID; - - /// Grab the creation date for this transactions. - fn created(&self) -> &Timestamp; - - /// Grab the transactions that this transaction signs. - fn previous_transactions(&self) -> &Vec; -} - -/// This enum helps us version our transactions so that we can make dumb -/// mistakes that don't haunt us until the end of time. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize)] -#[rasn(choice)] -pub enum TransactionVersioned { - #[rasn(tag(explicit(0)))] - V1(Transaction), -} - -impl TransactionVersioned { - /// Reencrypt this transaction. - fn reencrypt(self, old_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { - match self { - Self::V1(trans) => Ok(Self::V1(trans.reencrypt(old_master_key, new_master_key)?)), - } - } -} - -// NOTE: if we ever add more versions, this will need to be removed and we'll -// have to add a more dedicated interface. but for now, we do the lazy way and -// just point all versioned transactions to their inner transaction and call it -// a day. this is still useful, because although adding a version will require -// changing an assload of code, it makes sure our storage layer is future-proof -// which is what's really important. code can change, dealing with unversioned -// protocols is a bit trickier. -impl Deref for TransactionVersioned { - type Target = Transaction; - fn deref(&self) -> &Self::Target { - match self { - Self::V1(trans) => trans, - } - } -} - -impl GraphInfo for TransactionVersioned { - fn id(&self) -> &TransactionID { - self.deref().id() - } - - fn created(&self) -> &Timestamp { - self.deref().entry().created() - } - - fn previous_transactions(&self) -> &Vec { - self.deref().entry().previous_transactions() - } -} - -impl From for TransactionVersioned { - fn from(trans: Transaction) -> Self { - Self::V1(trans) - } -} - -impl Public for TransactionVersioned { - fn strip_private(&self) -> Self { - match self { - Self::V1(trans) => Self::V1(trans.strip_private()), - } - } - - fn has_private(&self) -> bool { - self.deref().has_private() - } -} - -/// Used to tell the transaction system which key to sign the transaction with. -pub(crate) enum SignWith { - Alpha, - Policy, - Root, -} - -/// A container that holds a set of transactions. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] -#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct Transactions { - /// The actual transactions. - #[rasn(tag(explicit(0)))] - transactions: Vec, -} - -impl Transactions { - /// Create a new, empty transaction set. - pub fn new() -> Self { - Self {transactions: vec![]} - } - - /// Returns an iterator over these transactions - pub fn iter(&self) -> core::slice::Iter<'_, TransactionVersioned> { - self.transactions().iter() - } - - /// Run a transaction and return the output - fn apply_transaction(identity: Option, transaction: &TransactionVersioned) -> Result { - match transaction { - TransactionVersioned::V1(trans) => { - match trans.entry().body().clone() { - // if this is a private transaction, just pass the identity - // back as-is - TransactionBody::CreateIdentityV1 { alpha, policy, publish, root } => { - let identity_id = IdentityID(trans.id().deref().clone()); - Ok(Identity::create(identity_id, alpha, policy, publish, root, trans.entry().created().clone())) - } - TransactionBody::SetRecoveryPolicyV1 { policy: policy_condition } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .set_recovery(PolicyID(trans.id().deref().clone()), policy_condition); - Ok(identity_mod) - } - TransactionBody::ExecuteRecoveryPolicyV1 { request } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .execute_recovery(request)?; - Ok(identity_mod) - } - TransactionBody::MakeClaimV1 { spec } => { - let claim_id = ClaimID(trans.id().deref().clone()); - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .make_claim(claim_id, spec, trans.entry().created().clone()); - Ok(identity_mod) - } - TransactionBody::DeleteClaimV1 { claim_id } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .delete_claim(&claim_id)?; - Ok(identity_mod) - } - TransactionBody::AcceptStampV1 { stamp } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .accept_stamp(stamp)?; - Ok(identity_mod) - } - TransactionBody::DeleteStampV1 { stamp_id } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .delete_stamp(&stamp_id)?; - Ok(identity_mod) - } - TransactionBody::SetPolicyKeyV1 { keypair, reason } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .set_policy_key(keypair, reason)?; - Ok(identity_mod) - } - TransactionBody::SetPublishKeyV1 { keypair, reason } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .set_publish_key(keypair, reason)?; - Ok(identity_mod) - } - TransactionBody::SetRootKeyV1 { keypair, reason } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .set_root_key(keypair, reason)?; - Ok(identity_mod) - } - TransactionBody::AddSubkeyV1 { key, name, desc } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .add_subkey(key, name, desc)?; - Ok(identity_mod) - } - TransactionBody::EditSubkeyV1 { name, new_name, desc } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .edit_subkey(&name, new_name, desc)?; - Ok(identity_mod) - } - TransactionBody::RevokeSubkeyV1 { name, reason, new_name } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .revoke_subkey(&name, reason, new_name)?; - Ok(identity_mod) - } - TransactionBody::DeleteSubkeyV1 { name } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .delete_subkey(&name)?; - Ok(identity_mod) - } - TransactionBody::SetNicknameV1 { nickname } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .set_nickname(nickname); - Ok(identity_mod) - } - TransactionBody::AddForwardV1 { name, ty, default } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .add_forward(name, ty, default)?; - Ok(identity_mod) - } - TransactionBody::DeleteForwardV1 { name } => { - let identity_mod = identity.ok_or(Error::DagMissingIdentity)? - .delete_forward(&name)?; - Ok(identity_mod) - } - } - } - } - } - - /// Build an identity from our heroic transactions. - /// - /// Sounds easy, but it's actually a bit...odd. First we reverse our tree - /// of transactions so it's forward-looking. This means for any transaction - /// we can see which transactions come directly after it (as opposed to - /// directly before it). - /// - /// Then, we walk the tree and assign a unique branch number any time the - /// transactions branch or merge. This branch number can be looked up by - /// txid. - /// - /// Lastly, instead of trying to order the transactions what we do is push - /// the first one onto a "pending transactions" list, then run it. Once run, - /// we add any transactions that come after it to the pending list. The - /// pending list is them sorted by the transactions dates ascending (oldest - /// first) and we loop again, plucking the oldest transaction off the list - /// and running it. Now, for each transaction we run, we also apply it to - /// an identity that is specific to each previous branch the transaction - /// descended from. This allows us to easily merge identities from many - /// trees as we move along, but also has the benefit that the branch- - /// specific identity for our first branch (0) is also our *final* identity - /// because ALL transactions have been applied to it. It's a big, burly mess - /// but it works... - /// - /// NOTE: this algorithm handles signing key conflicts by only using the - /// nearest branch-level identity to *validate* the current transaction, - /// although the transaction is applied to all identities from previous - /// branches as well. However, this algorithm does not handle other - /// conflicts (such as duplicate entries). - pub fn build_identity(&self) -> Result { - if self.transactions().len() == 0 { - Err(Error::DagEmpty)?; - } - let transactions = self.transactions.clone(); - if transactions.len() == 0 { - Err(Error::DagEmpty)?; - } - - // use the `previous_transactions` collection to build a feed-forward - // index for transactions (basically, reverse the order of our tree). - // also, index our transactions by id. - let mut transaction_idx: HashMap = HashMap::new(); - let mut next_transactions_idx: HashMap> = HashMap::new(); - for trans in &transactions { - transaction_idx.insert(trans.id().clone(), trans); - let prev = trans.entry().previous_transactions(); - if prev.len() == 0 { continue; } - for trans_prev in prev { - let entry = next_transactions_idx.entry(trans_prev.clone()).or_insert(Vec::new()); - (*entry).push(trans.id().clone()); - } - } - - for trans in &transactions { - // make sure we don't have any orphaned transactions - for prev in trans.entry().previous_transactions() { - if !transaction_idx.contains_key(prev) { - Err(Error::DagOrphanedTransaction(String::from(trans.id())))?; - } - } - } - - // populate a transaction_id -> branchnum index - let mut transaction_branch_idx: HashMap> = HashMap::new(); - fn walker_identity_ranger(transaction_idx: &HashMap, next_transactions_idx: &HashMap>, transaction_branch_idx: &mut HashMap>, transaction: &TransactionVersioned, cur_branch: Vec) -> Result<()> { - fn push_branch(list: &Vec, branch_num: u32) -> Vec { - let mut list = list.clone(); - if list.contains(&branch_num) { - return list; - } - list.append(&mut vec![branch_num]); - list - } - let mut new_branch = 0; - // if this transaction merges one or more branches, it gets its own - // branch id - if transaction.previous_transactions().len() > 1 { - new_branch += 1; - } - let default = Vec::new(); - let next = next_transactions_idx.get(transaction.id()).unwrap_or(&default); - transaction_branch_idx.insert(transaction.id().clone(), push_branch(&cur_branch, new_branch)); - // if this is a branch, give each branch a unique id - if next.len() > 1 { - new_branch += 1; - } - for trans_id in next { - let trans = transaction_idx.get(trans_id).ok_or(Error::DagBuildError)?; - walker_identity_ranger(transaction_idx, next_transactions_idx, transaction_branch_idx, trans, push_branch(&cur_branch, new_branch))?; - new_branch += 1; - } - Ok(()) - } - walker_identity_ranger(&transaction_idx, &next_transactions_idx, &mut transaction_branch_idx, &transactions[0], vec![0])?; - - #[derive(Debug, Default)] - struct WalkState<'a> { - // tracks our current run list - transactions_to_run: Vec<&'a TransactionVersioned>, - // tracks merge transactions, and how many ancestors have been run. - // when this number reaches previous_transactions().len(), then the - // merge is free to run. - pending_merges: HashMap, - } - - impl<'a> WalkState<'a> { - fn next(&self) -> Option<&TransactionVersioned> { - self.transactions_to_run.get(0).map(|x| *x) - } - - fn remove_first(&mut self) { - let tx_id = self.transactions_to_run[0].id(); - self.transactions_to_run.retain(|tx| tx.id() != tx_id); - } - - fn pop_transaction(&mut self, transaction_idx: &HashMap, next_transactions_idx: &HashMap>) -> Result { - if self.transactions_to_run.len() == 0 { - return Err(Error::DagBuildError)?; - } - let trans = self.transactions_to_run[0]; - self.remove_first(); - if let Some(next) = next_transactions_idx.get(trans.id()) { - for next_trans_id in next { - let entry = self.pending_merges.entry(next_trans_id.clone()).or_insert(0); - (*entry) += 1; - self.transactions_to_run.push(transaction_idx.get(next_trans_id).ok_or(Error::DagBuildError)?); - } - // TODO: optimize. sorting on every loop, tsk tsk. - self.transactions_to_run.sort_by_key(|t| t.created()); - } - Ok(true) - } - } - - let mut state = WalkState::default(); - state.transactions_to_run.push( - transactions.iter().find(|x| x.previous_transactions().len() == 0).ok_or(Error::DagNoGenesis)? - ); - let first_trans = match state.next() { - Some(trans) => trans, - None => Err(Error::DagBuildError)?, - }; - first_trans.verify(None)?; - - // tracks our per-branch identities - let mut branch_identities: HashMap = HashMap::new(); - branch_identities.insert(0, Transactions::apply_transaction(None, first_trans)?); - state.pop_transaction(&transaction_idx, &next_transactions_idx)?; - loop { - if let Some(trans) = state.next() { - let root_identity = branch_identities.get(&0).ok_or(Error::DagMissingIdentity)?.clone(); - let ancestors = transaction_branch_idx.get(trans.id()).ok_or(Error::DagBuildError)?; - let previous_len = trans.previous_transactions().len(); - if previous_len > 1 { - let pending_count = state.pending_merges.get(trans.id()).unwrap_or(&0); - // ONLY run a merge transaction if all of its children have - // run!!1 - if *pending_count >= previous_len { - let ancestor_collection = trans.previous_transactions().iter() - .map(|x| { - transaction_branch_idx.get(x) - .map(|ancestors| ancestors.clone().into_iter().rev().collect::>()) - .ok_or(Error::DagBuildError) - }) - .collect::>>>()?; - // now find the highest (ie, youngest) branch that is the - // common ancestor to the N branches we're merging right now - let first = ancestor_collection.get(0).ok_or(Error::DagBuildError)?; - let mut found_branch = None; - for branch in first { - let mut has = true; - for anc in &ancestor_collection[1..] { - if !has || !anc.contains(branch) { - has = false; - } - } - if has { - found_branch = Some(branch); - break; - } - } - let found_branch = found_branch.ok_or(Error::DagBuildError)?; - { - let common_ancestor_identity = branch_identities.get_mut(found_branch).ok_or(Error::DagBuildError)?; - trans.verify(Some(&common_ancestor_identity))?; - } - // apply this transaction to all of its ancestor branches - let mut tracker = HashMap::new(); - for ancestors in ancestor_collection { - for branch in &ancestors { - if tracker.get(branch).is_some() { - continue; - } - let branch_identity = branch_identities.entry(*branch).or_insert(root_identity.clone()); - (*branch_identity) = Transactions::apply_transaction(Some((*branch_identity).clone()), trans)?; - tracker.insert(*branch, true); - } - } - } else { - state.remove_first(); - continue; - } - } else { - let current_branch_identity = branch_identities.entry(*(*ancestors).last().unwrap()).or_insert(root_identity.clone()); - trans.verify(Some(¤t_branch_identity))?; - // apply this transaction to all of its ancestor branches - for branch in ancestors { - let branch_identity = branch_identities.entry(*branch).or_insert(root_identity.clone()); - (*branch_identity) = Transactions::apply_transaction(Some((*branch_identity).clone()), trans)?; - } - } - state.pop_transaction(&transaction_idx, &next_transactions_idx)?; - } else { - break; - } - } - Ok(branch_identities.get(&0).ok_or(Error::DagMissingIdentity)?.clone()) - } - - /// Find any transactions that are not referenced as previous transactions. - /// Effectively, the leaves of our graph. - fn find_leaf_transactions(transaction_list: &Vec) -> Vec { - let mut seen: HashMap = HashMap::new(); - for trans in transaction_list { - for prev in trans.previous_transactions() { - seen.insert(prev.clone(), true); - } - } - transaction_list.iter() - .filter_map(|t| { - if seen.get(t.id()).is_some() { - None - } else { - Some(t.id().clone()) - } - }) - .collect::>() - } - - /// Push a transaction into the transactions list, and return the resulting - /// identity object from running all transactions in order. - fn push_transaction>(&mut self, master_key: &SecretKey, sign_with: SignWith, now: T, body: TransactionBody) -> Result { - let leaves = Self::find_leaf_transactions(self.transactions()); - let entry = TransactionEntry::new(now, leaves, body); - let identity_maybe = match self.build_identity() { - Ok(id) => Some(id), - Err(Error::DagEmpty) => None, - Err(e) => Err(e)?, - }; - let trans = Transaction::new(master_key, &identity_maybe, sign_with, entry)?; - let versioned = trans.into(); - let identity = Self::apply_transaction(identity_maybe, &versioned)?; - self.transactions_mut().push(versioned); - Ok(identity) - } - - /// Push a raw transaction onto this transaction set. Generally, this might - /// come from a syncing source (StampNet's private syncing) that passes - /// around singular transactions. We verify this transactions by building - /// the identity after pushing. - pub fn push_transaction_raw(&mut self, versioned: TransactionVersioned) -> Result { - let identity_maybe = match self.build_identity() { - Ok(id) => Some(id), - Err(Error::DagEmpty) => None, - Err(e) => Err(e)?, - }; - let identity = Self::apply_transaction(identity_maybe, &versioned)?; - self.transactions_mut().push(versioned); - // build it again - let _identity_maybe = match self.build_identity() { - Ok(id) => Some(id), - Err(Error::DagEmpty) => None, - Err(e) => Err(e)?, - }; - Ok(identity) - } - - /// Merge the transactions from two transaction sets together. - pub fn merge(mut branch1: Self, branch2: Self) -> Result { - for trans2 in branch2.transactions() { - // if it already exists, don't merge it - if branch1.transactions().iter().find(|t| t.id() == trans2.id()).is_some() { - continue; - } - branch1.transactions_mut().push(trans2.clone()); - } - // make sure it's all copasetic. - branch1.build_identity()?; - Ok(branch1) - } - - /// Reset a set of transactions to a previous state. - /// - /// Effectively, we take a transaction ID and remove any transactions that - /// came after it. This may create many trailing transactions, which will be - /// connected the next time a new transaction is created. - pub fn reset(mut self, txid: &TransactionID) -> Result { - // recursively find all transactions referencing the given one - fn find_tx_to_rm(transactions: &Vec, txid: &TransactionID) -> Vec { - let mut to_remove = Vec::new(); - for trans in transactions { - if trans.entry().previous_transactions().contains(txid) { - to_remove.push(trans.id().clone()); // i hate this clone, but w/e - to_remove.append(&mut find_tx_to_rm(transactions, trans.id())); - } - } - to_remove - } - let remove_tx = find_tx_to_rm(self.transactions(), txid); - self.transactions_mut().retain(|t| !remove_tx.contains(t.id())); - Ok(self) - } - - // ------------------------------------------------------------------------- - - /// Create an identity. - pub fn create_identity + Clone>(mut self, master_key: &SecretKey, now: T, alpha: AdminKeypair) -> Result { - if self.transactions().len() > 0 { - Err(Error::DagCreateIdentityOnExistingChain)?; - } - let body = TransactionBody::CreateIdentityV1 { alpha }; - self.push_transaction(master_key, SignWith::Alpha, now.clone(), body)?; - Ok(self) - } - - /// Set a recovery policy. - pub fn set_recovery_policy + Clone>(mut self, master_key: &SecretKey, now: T, policy: Option) -> Result { - let body = TransactionBody::SetRecoveryPolicyV1 { policy }; - self.push_transaction(master_key, SignWith::Policy, now, body)?; - Ok(self) - } - - /// Execute a recovery policy (replace your keys via a policy). - pub fn execute_recovery_policy + Clone>(mut self, master_key: &SecretKey, now: T, request: PolicyRequest) -> Result { - let body = TransactionBody::ExecuteRecoveryPolicyV1 { request }; - self.push_transaction(master_key, SignWith::Policy, now, body)?; - Ok(self) - } - - /// Make a new claim. - pub fn make_claim>(mut self, master_key: &SecretKey, now: T, spec: ClaimSpec) -> Result { - let body = TransactionBody::MakeClaimV1 { spec }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Delete an existing claim. - pub fn delete_claim>(mut self, master_key: &SecretKey, now: T, claim_id: ClaimID) -> Result { - let body = TransactionBody::DeleteClaimV1 { claim_id }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Accept a stamp someone, or some*thing*, has made on a claim of ours. - pub fn accept_stamp>(mut self, master_key: &SecretKey, now: T, stamp: Stamp) -> Result { - let body = TransactionBody::AcceptStampV1 { stamp }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Delete an existing stamp. - pub fn delete_stamp>(mut self, master_key: &SecretKey, now: T, stamp_id: StampID) -> Result { - let body = TransactionBody::DeleteStampV1 { stamp_id }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Assign a new policy key to this identity. Requires an alpha sig. - pub fn set_policy_key>(mut self, master_key: &SecretKey, now: T, keypair: PolicyKeypair, revocation_reason: RevocationReason) -> Result { - let body = TransactionBody::SetPolicyKeyV1 { keypair, reason: revocation_reason }; - self.push_transaction(master_key, SignWith::Alpha, now, body)?; - Ok(self) - } - - /// Assign a new publish key to this identity. Requires an alpha sig. - pub fn set_publish_key>(mut self, master_key: &SecretKey, now: T, keypair: PublishKeypair, revocation_reason: RevocationReason) -> Result { - let body = TransactionBody::SetPublishKeyV1 { keypair, reason: revocation_reason }; - self.push_transaction(master_key, SignWith::Alpha, now, body)?; - Ok(self) - } - - /// Assign a new root key to this identity. Requires an alpha sig. - pub fn set_root_key>(mut self, master_key: &SecretKey, now: T, keypair: RootKeypair, revocation_reason: RevocationReason) -> Result { - let body = TransactionBody::SetRootKeyV1 { keypair, reason: revocation_reason }; - self.push_transaction(master_key, SignWith::Alpha, now, body)?; - Ok(self) - } - - /// Add a new subkey to our keychain. - pub fn add_subkey(mut self, master_key: &SecretKey, now: T, key: Key, name: S, description: Option) -> Result - where T: Into, - S: Into, - { - let body = TransactionBody::AddSubkeyV1 { - key, - name: name.into(), - desc: description.map(|x| x.into()), - }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Edit a subkey. - pub fn edit_subkey(mut self, master_key: &SecretKey, now: T, name: S, new_name: S, description: Option) -> Result - where T: Into, - S: Into, - { - let body = TransactionBody::EditSubkeyV1 { - name: name.into(), - new_name: new_name.into(), - desc: description.map(|x| x.into()), - }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Revoke a subkey. - pub fn revoke_subkey(mut self, master_key: &SecretKey, now: T, name: S, revocation_reason: RevocationReason, new_name: Option) -> Result - where T: Into, - S: Into, - { - let body = TransactionBody::RevokeSubkeyV1 { - name: name.into(), - reason: revocation_reason, - new_name: new_name.map(|x| x.into()), - }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Delete a subkey. - pub fn delete_subkey(mut self, master_key: &SecretKey, now: T, name: S) -> Result - where T: Into, - S: Into, - { - let body = TransactionBody::DeleteSubkeyV1 { name: name.into() }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Set the nickname on this identity. - pub fn set_nickname(mut self, master_key: &SecretKey, now: T, nickname: Option) -> Result - where T: Into, - S: Into, - { - let body = TransactionBody::SetNicknameV1 { nickname: nickname.map(|x| x.into()) }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Add a new forward. - pub fn add_forward(mut self, master_key: &SecretKey, now: T, name: S, ty: ForwardType, is_default: bool) -> Result - where T: Into, - S: Into, - { - let body = TransactionBody::AddForwardV1 { name: name.into(), ty, default: is_default }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Delete an existing forward. - pub fn delete_forward(mut self, master_key: &SecretKey, now: T, name: S) -> Result - where T: Into, - S: Into, - { - let body = TransactionBody::DeleteForwardV1 { name: name.into() }; - self.push_transaction(master_key, SignWith::Root, now, body)?; - Ok(self) - } - - /// Reencrypt this transaction set with a new master key. - pub fn reencrypt(mut self, old_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { - for trans in self.transactions_mut() { - *trans = trans.clone().reencrypt(old_master_key, new_master_key)?; - } - Ok(self) - } - - /// Determine if this identity is owned (ie, we have the private keys stored - /// locally) or it is imported (ie, someone else's identity). - pub fn is_owned(&self) -> bool { - let mut has_private = false; - for trans in self.transactions() { - has_private = match trans { - TransactionVersioned::V1(trans) => { - match trans.entry().body() { - TransactionBody::CreateIdentityV1 { .. } => trans.entry().body().has_private(), - TransactionBody::SetPolicyKeyV1 { .. } => trans.entry().body().has_private(), - TransactionBody::SetPublishKeyV1 { .. } => trans.entry().body().has_private(), - TransactionBody::SetRootKeyV1 { .. } => trans.entry().body().has_private(), - _ => false, - } - } - }; - if has_private { - break; - } - } - has_private - } - - /// Test if a master key is correct. - pub fn test_master_key(&self, master_key: &SecretKey) -> Result<()> { - if !self.is_owned() { - Err(Error::IdentityNotOwned)?; - } - - let identity = self.build_identity()?; - identity.test_master_key(master_key) - } -} - -impl Public for Transactions { - fn strip_private(&self) -> Self { - let mut clone = self.clone(); - let stripped = self.transactions().iter().map(|x| x.strip_private()).collect::>(); - clone.set_transactions(stripped); - clone - } - - fn has_private(&self) -> bool { - self.transactions().iter().find(|x| x.has_private()).is_some() - } -} - -impl IntoIterator for Transactions { - type Item = TransactionVersioned; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - let Transactions { transactions } = self; - transactions.into_iter() - } -} - -impl SerdeBinary for Transactions {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - crypto::key::{SignKeypair, CryptoKeypair}, - identity::{ - claim::{ClaimContainer, Relationship, RelationshipType}, - recovery::PolicyRequestEntry, - stamp::Confidence, - }, - private::{Private, MaybePrivate}, - util::{Date, Url, ser::BinaryVec}, - }; - use std::str::FromStr; - - macro_rules! assert_signkey { - ($trans:expr, $keyty:ident) => { - match $trans.id() { - TransactionID::$keyty(..) => {} - _ => panic!("Expected sign key type {}, found {:?}", stringify!($keyty), $trans.id()), - } - } - } - - #[test] - fn trans_body_strip_has_private() { - fn test_privates(body: &TransactionBody) { - match body { - TransactionBody::CreateIdentityV1 { alpha, policy, publish, root } => { - assert!(body.has_private()); - let body2 = TransactionBody::CreateIdentityV1 { - alpha: alpha.strip_private(), - policy: policy.clone(), - publish: publish.clone(), - root: root.clone(), - }; - assert!(body2.has_private()); - let body3 = TransactionBody::CreateIdentityV1 { - alpha: alpha.strip_private(), - policy: policy.strip_private(), - publish: publish.clone(), - root: root.clone(), - }; - assert!(body3.has_private()); - let body4 = TransactionBody::CreateIdentityV1 { - alpha: alpha.strip_private(), - policy: policy.strip_private(), - publish: publish.strip_private(), - root: root.clone(), - }; - assert!(body4.has_private()); - let body5 = TransactionBody::CreateIdentityV1 { - alpha: alpha.strip_private(), - policy: policy.strip_private(), - publish: publish.strip_private(), - root: root.strip_private(), - }; - assert!(!body5.has_private()); - let body6 = body.strip_private(); - assert!(!body6.has_private()); - let body7 = body6.strip_private(); - assert!(!body7.has_private()); - } - TransactionBody::SetRecoveryPolicyV1 { .. } => {} - TransactionBody::ExecuteRecoveryPolicyV1 { request } => { - assert!(body.has_private()); - let body2 = TransactionBody::ExecuteRecoveryPolicyV1 { request: request.strip_private() }; - assert!(!body2.has_private()); - let body3 = body.strip_private(); - assert!(!body3.has_private()); - let body4 = body3.strip_private(); - assert!(!body4.has_private()); - } - TransactionBody::MakeClaimV1 { spec } => { - assert_eq!(body.has_private(), spec.has_private()); - let body2 = TransactionBody::MakeClaimV1 { spec: spec.strip_private() }; - assert!(!body2.has_private()); - let body3 = body.strip_private(); - assert!(!body3.has_private()); - let body4 = body3.strip_private(); - assert!(!body4.has_private()); - } - TransactionBody::DeleteClaimV1 { .. } => {} - TransactionBody::AcceptStampV1 { stamp } => { - assert!(!body.has_private()); - let body2 = TransactionBody::AcceptStampV1 { stamp: stamp.strip_private() }; - assert!(!body2.has_private()); - let body3 = body.strip_private(); - assert!(!body3.has_private()); - let body4 = body3.strip_private(); - assert!(!body4.has_private()); - } - TransactionBody::DeleteStampV1 { .. } => {} - TransactionBody::SetPolicyKeyV1 { keypair, reason } => { - assert!(body.has_private()); - let body2 = TransactionBody::SetPolicyKeyV1 { - keypair: keypair.strip_private(), - reason: reason.clone(), - }; - assert!(!body2.has_private()); - let body3 = body.strip_private(); - assert!(!body3.has_private()); - let body4 = body3.strip_private(); - assert!(!body4.has_private()); - } - TransactionBody::SetPublishKeyV1 { keypair, reason } => { - assert!(body.has_private()); - let body2 = TransactionBody::SetPublishKeyV1 { - keypair: keypair.strip_private(), - reason: reason.clone(), - }; - assert!(!body2.has_private()); - let body3 = body.strip_private(); - assert!(!body3.has_private()); - let body4 = body3.strip_private(); - assert!(!body4.has_private()); - } - TransactionBody::SetRootKeyV1 { keypair, reason } => { - assert!(body.has_private()); - let body2 = TransactionBody::SetRootKeyV1 { - keypair: keypair.strip_private(), - reason: reason.clone(), - }; - assert!(!body2.has_private()); - let body3 = body.strip_private(); - assert!(!body3.has_private()); - let body4 = body3.strip_private(); - assert!(!body4.has_private()); - } - TransactionBody::AddSubkeyV1 { key, name, desc } => { - assert!(body.has_private()); - match key.strip_private_maybe() { - Some(stripped) => { - let body2 = TransactionBody::AddSubkeyV1 { - key: stripped, - name: name.clone(), - desc: desc.clone(), - }; - assert!(!body2.has_private()); - let body3 = body.strip_private(); - assert!(!body3.has_private()); - let body4 = body3.strip_private(); - assert!(!body4.has_private()); - } - None => {} - } - } - TransactionBody::EditSubkeyV1 { .. } => {} - TransactionBody::RevokeSubkeyV1 { .. } => {} - TransactionBody::DeleteSubkeyV1 { .. } => {} - TransactionBody::SetNicknameV1 { .. } => {} - TransactionBody::AddForwardV1 { .. } => {} - TransactionBody::DeleteForwardV1 { .. } => {} - } - } - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let body = TransactionBody::CreateIdentityV1 { - alpha: alpha_keypair.clone(), - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - test_privates(&body); - - test_privates(&TransactionBody::SetRecoveryPolicyV1 { policy: Some(PolicyCondition::Deny) }); - - let action = PolicyRequestAction::ReplaceKeys { - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - let entry = PolicyRequestEntry::new(IdentityID::random(), PolicyID::random(), action); - let req = PolicyRequest::new(&master_key, &policy_keypair, entry).unwrap(); - test_privates(&TransactionBody::ExecuteRecoveryPolicyV1 { request: req }); - - test_privates(&TransactionBody::MakeClaimV1 { spec: ClaimSpec::Name(MaybePrivate::new_public(String::from("Negative Nancy"))) }); - test_privates(&TransactionBody::MakeClaimV1 { spec: ClaimSpec::Name(MaybePrivate::new_private(&master_key, String::from("Positive Pyotr")).unwrap()) }); - test_privates(&TransactionBody::DeleteClaimV1 { claim_id: ClaimID::random() }); - - let claim_con = ClaimContainer::new(ClaimID::random(), ClaimSpec::Name(MaybePrivate::new_private(&master_key, String::from("Hangry Hank")).unwrap()), Timestamp::now()); - let stamp = Stamp::stamp(&master_key, &root_keypair, &IdentityID::random(), &IdentityID::random(), Confidence::Low, Timestamp::now(), claim_con.claim(), Some(Timestamp::now())).unwrap(); - test_privates(&TransactionBody::AcceptStampV1 { stamp }); - test_privates(&TransactionBody::DeleteStampV1 { stamp_id: StampID::random() }); - test_privates(&TransactionBody::SetPolicyKeyV1 { - keypair: policy_keypair.clone(), - reason: RevocationReason::Unspecified, - }); - test_privates(&TransactionBody::SetPublishKeyV1 { - keypair: publish_keypair.clone(), - reason: RevocationReason::Compromised, - }); - test_privates(&TransactionBody::SetRootKeyV1 { - keypair:root_keypair.clone(), - reason: RevocationReason::Recovery, - }); - - let key = Key::new_sign(root_keypair.deref().clone()); - test_privates(&TransactionBody::AddSubkeyV1 { - key, - name: "MY DOGECOIN KEY".into(), - desc: Some("plz send doge".into()), - }); - test_privates(&TransactionBody::EditSubkeyV1 { - name: "MY DOGECOIN KEY".into(), - new_name: "MAI DOGE KEY".into(), - desc: None, - }); - test_privates(&TransactionBody::RevokeSubkeyV1 { - name: "MAI DOGE KEY".into(), - reason: RevocationReason::Compromised, - new_name: Some("REVOKED DOGE KEY".into()), - }); - test_privates(&TransactionBody::DeleteSubkeyV1 { name: "REVOKED DOGE KEY".into() }); - test_privates(&TransactionBody::SetNicknameV1 { nickname: Some("wreck-dum".into()) }); - test_privates(&TransactionBody::AddForwardV1 { - name: "EMAIL".into(), - ty: ForwardType::Social { ty: "mobile".into(), handle: "web2.0".into() }, - default: true, - }); - test_privates(&TransactionBody::DeleteForwardV1 { name: "EMAIL".into() }); - } - - #[test] - fn trans_entry_strip_has_private() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let body = TransactionBody::MakeClaimV1 { - spec: ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Jackie Chrome".into()).unwrap()), - }; - let entry = TransactionEntry::new(Timestamp::now(), vec![TransactionID::random_alpha()], body); - assert!(entry.has_private()); - assert!(entry.body().has_private()); - let entry2 = entry.strip_private(); - assert!(!entry2.has_private()); - assert!(!entry2.body().has_private()); - } - - #[test] - fn trans_new_verify() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let identity = Identity::create(IdentityID::random(), alpha_keypair.clone(), policy_keypair.clone(), publish_keypair.clone(), root_keypair.clone(), Timestamp::now()); - - let body = TransactionBody::CreateIdentityV1 { - alpha: alpha_keypair.clone(), - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - let now = Timestamp::now(); - let entry = TransactionEntry::new(now.clone(), vec![], body); - let trans = Transaction::new(&master_key, &None, SignWith::Alpha, entry.clone()).unwrap(); - trans.verify(None).unwrap(); - - let res = Transaction::new(&master_key, &None, SignWith::Policy, entry.clone()); - assert_eq!(res.err(), Some(Error::DagKeyNotFound)); - - let res = Transaction::new(&master_key, &None, SignWith::Root, entry.clone()); - assert_eq!(res.err(), Some(Error::DagKeyNotFound)); - - let body2 = TransactionBody::DeleteForwardV1 { name: "blassssstodon".into() }; - let entry2 = TransactionEntry::new(Timestamp::now(), vec![], body2); - let res = Transaction::new(&master_key, &None, SignWith::Alpha, entry2.clone()); - assert_eq!(res.err(), Some(Error::DagKeyNotFound)); - - let res = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Root, entry.clone()); - assert_eq!(res.err(), Some(Error::DagCreateIdentityOnExistingChain)); - - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - assert!(new_policy_keypair != policy_keypair); - let action = PolicyRequestAction::ReplaceKeys { - policy: new_policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - let entry = PolicyRequestEntry::new(IdentityID::random(), PolicyID::random(), action); - let req = PolicyRequest::new(&master_key, &new_policy_keypair, entry).unwrap(); - let body_recover = TransactionBody::ExecuteRecoveryPolicyV1 { request: req }; - let entry_recover = TransactionEntry::new(Timestamp::now(), vec![], body_recover); - let trans_recover = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Policy, entry_recover.clone()).unwrap(); - trans_recover.verify(Some(&identity)).unwrap(); - let res = Transaction::new(&master_key, &None, SignWith::Alpha, entry_recover.clone()); - assert_eq!(res.err(), Some(Error::DagKeyNotFound)); - let res = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Alpha, entry_recover.clone()); - assert_eq!(res.err(), Some(Error::DagKeyNotFound)); - let res = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Root, entry_recover.clone()); - assert_eq!(res.err(), Some(Error::DagKeyNotFound)); - - let mut trans2 = trans.clone(); - trans2.set_id(TransactionID::random_alpha()); - assert_eq!(trans2.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); - - let mut trans3 = trans.clone(); - let then = Timestamp::from(now.deref().clone() - chrono::Duration::seconds(2)); - trans3.entry_mut().set_created(then); - assert_eq!(trans3.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); - - let mut trans4 = trans.clone(); - trans4.entry_mut().set_previous_transactions(vec![TransactionID::random_alpha()]); - assert_eq!(trans4.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); - - let mut trans5 = trans.clone(); - let root_keypair2 = RootKeypair::new_ed25519(&master_key).unwrap(); - assert!(root_keypair != root_keypair2); - let body = TransactionBody::CreateIdentityV1 { - alpha: alpha_keypair.clone(), - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair2.clone(), - }; - trans5.entry_mut().set_body(body); - assert_eq!(trans5.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); - } - - #[test] - fn trans_strip_has_private() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - - let body = TransactionBody::CreateIdentityV1 { - alpha: alpha_keypair.clone(), - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - let entry = TransactionEntry::new(Timestamp::now(), vec![], body); - let trans = Transaction::new(&master_key, &None, SignWith::Alpha, entry.clone()).unwrap(); - - assert!(trans.has_private()); - assert!(trans.entry().has_private()); - assert!(trans.entry().body().has_private()); - let trans2 = trans.strip_private(); - assert!(!trans2.has_private()); - assert!(!trans2.entry().has_private()); - assert!(!trans2.entry().body().has_private()); - } - - #[test] - fn trans_versioned_deref() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - - let body = TransactionBody::CreateIdentityV1 { - alpha: alpha_keypair.clone(), - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - let entry = TransactionEntry::new(Timestamp::now(), vec![], body); - let trans = Transaction::new(&master_key, &None, SignWith::Alpha, entry.clone()).unwrap(); - let versioned = TransactionVersioned::from(trans.clone()); - - match &versioned { - TransactionVersioned::V1(ref trans) => { - assert!(std::ptr::eq(trans, versioned.deref())); - } - } - } - - #[test] - fn trans_versioned_graphinfo() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - - let body = TransactionBody::CreateIdentityV1 { - alpha: alpha_keypair.clone(), - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - let entry = TransactionEntry::new(Timestamp::now(), vec![], body); - let trans = Transaction::new(&master_key, &None, SignWith::Alpha, entry.clone()).unwrap(); - let versioned = TransactionVersioned::from(trans.clone()); - - assert_eq!(versioned.id(), trans.id()); - assert_eq!(versioned.created(), trans.entry().created()); - assert_eq!(versioned.previous_transactions(), trans.entry().previous_transactions()); - } - - #[test] - fn trans_versioned_strip_has_private() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - - let body = TransactionBody::CreateIdentityV1 { - alpha: alpha_keypair.clone(), - policy: policy_keypair.clone(), - publish: publish_keypair.clone(), - root: root_keypair.clone(), - }; - let entry = TransactionEntry::new(Timestamp::now(), vec![], body); - let trans = Transaction::new(&master_key, &None, SignWith::Alpha, entry.clone()).unwrap(); - let versioned = TransactionVersioned::from(trans.clone()); - assert!(versioned.has_private()); - assert!(versioned.deref().has_private()); - assert!(versioned.deref().entry().has_private()); - assert!(versioned.deref().entry().body().has_private()); - - let versioned2 = versioned.strip_private(); - assert!(!versioned2.has_private()); - assert!(!versioned2.deref().has_private()); - assert!(!versioned2.deref().entry().has_private()); - assert!(!versioned2.deref().entry().body().has_private()); - } - - fn genesis_time(now: Timestamp) -> (SecretKey, Transactions) { - let transactions = Transactions::new(); - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root = RootKeypair::new_ed25519(&master_key).unwrap(); - let transactions2 = transactions.create_identity(&master_key, now, alpha.clone(), policy.clone(), publish.clone(), root.clone()).unwrap(); - (master_key, transactions2) - } - - fn genesis() -> (SecretKey, Transactions) { - genesis_time(Timestamp::now()) - } - - #[test] - fn transactions_push_raw() { - let now = Timestamp::from_str("2021-04-20T00:00:10Z").unwrap(); - let (master_key_1, mut transactions_1) = genesis_time(now.clone()); - let (_master_key_2, mut transactions_2) = genesis_time(now.clone()); - let transactions_1_2 = transactions_1.clone() - .make_claim(&master_key_1, now.clone(), ClaimSpec::Name(MaybePrivate::new_public("Hooty McOwl".to_string()))).unwrap(); - let raw = transactions_1_2.transactions()[1].clone(); - transactions_1.push_transaction_raw(raw.clone()).unwrap(); - transactions_2.build_identity().unwrap(); - match transactions_2.push_transaction_raw(raw.clone()) { - Ok(_) => panic!("pushed a bad raw transaction: {}", String::from(raw.id())), - Err(e) => assert_eq!(e, Error::DagOrphanedTransaction(String::from(raw.id()))), - } - } - - #[test] - fn transactions_merge_reset() { - let (master_key, transactions) = genesis_time(Timestamp::from_str("2021-04-20T00:00:00Z").unwrap()); - // make some claims on my smart refrigerator - let new_root1 = RootKeypair::new_ed25519(&master_key).unwrap(); - let new_root2 = RootKeypair::new_ed25519(&master_key).unwrap(); - let branch1 = transactions.clone() - .make_claim(&master_key, Timestamp::from_str("2021-04-20T00:00:10Z").unwrap(), ClaimSpec::Name(MaybePrivate::new_public("Hooty McOwl".to_string()))).unwrap() - .set_root_key(&master_key, Timestamp::from_str("2021-04-20T00:01:00Z").unwrap(), new_root1.clone(), RevocationReason::Unspecified).unwrap() - .set_nickname(&master_key, Timestamp::from_str("2021-04-20T00:01:33Z").unwrap(), Some("dirk-delta")).unwrap(); - // make some claims on my Facebook (TM) (R) (C) Brain (AND NOW A WORD FROM OUR SPONSORS) Implant - let branch2 = transactions.clone() - .add_forward(&master_key, Timestamp::from_str("2021-04-20T00:00:30Z").unwrap(), "my-website", ForwardType::Url("https://www.cactus-petes.com/yeeeehawwww".into()), false).unwrap() - .set_root_key(&master_key, Timestamp::from_str("2021-04-20T00:01:36Z").unwrap(), new_root2.clone(), RevocationReason::Unspecified).unwrap() - .set_nickname(&master_key, Timestamp::from_str("2021-04-20T00:01:45Z").unwrap(), Some("liberal hokes")).unwrap() - .make_claim(&master_key, Timestamp::from_str("2021-04-20T00:01:56Z").unwrap(), ClaimSpec::Email(MaybePrivate::new_public(String::from("dirk.delta@hollywood.com")))).unwrap(); - let identity1 = branch1.build_identity().unwrap(); - let identity2 = branch2.build_identity().unwrap(); - assert_eq!(identity1.extra_data().nickname(), &Some(String::from("dirk-delta"))); - assert_eq!(identity1.keychain().root(), &new_root1); - assert_eq!(identity2.extra_data().nickname(), &Some(String::from("liberal hokes"))); - assert!(identity2.keychain().root() != &new_root1); - assert_eq!(identity2.keychain().root(), &new_root2); - let transactions2 = Transactions::merge(branch1.clone(), branch2.clone()).unwrap(); - assert_eq!(branch1.transactions().len(), 4); - assert_eq!(branch2.transactions().len(), 5); - assert_eq!(transactions2.transactions().len(), 8); - let transactions3 = transactions2.clone() - .add_forward(&master_key, Timestamp::from_str("2021-04-20T00:05:22Z").unwrap(), "get-a-job", ForwardType::Url("https://www.cactus-petes.com/yeeeehawwww".into()), false).unwrap(); - assert_eq!(transactions3.transactions().len(), 9); - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.extra_data().nickname(), &Some(String::from("liberal hokes"))); - assert_eq!(identity3.claims().len(), 2); - assert_eq!(identity3.extra_data().forwards().len(), 2); - assert_eq!(identity3.keychain().root(), &new_root2); - } - - #[test] - fn transactions_genesis() { - let (master_key, transactions) = genesis(); - - let alpha = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root = RootKeypair::new_ed25519(&master_key).unwrap(); - let res = transactions.create_identity(&master_key, Timestamp::now(), alpha, policy, publish, root); - assert_eq!(res.err(), Some(Error::DagCreateIdentityOnExistingChain)); - - let transactions2 = Transactions::new(); - let res = transactions2.make_claim(&master_key, Timestamp::now(), ClaimSpec::Name(MaybePrivate::new_public("Stinky Wizzleteets".into()))); - assert_eq!(res.err(), Some(Error::DagKeyNotFound)); - } - - #[test] - fn transactions_create_identity() { - let (_master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert_eq!(identity.id(), &IdentityID(transactions.transactions()[0].id().deref().clone())); - match transactions.transactions()[0].entry().body() { - TransactionBody::CreateIdentityV1{ ref alpha, ref policy, ref publish, ref root } => { - assert_eq!(identity.keychain().alpha(), alpha); - assert_eq!(identity.keychain().policy(), policy); - assert_eq!(identity.keychain().publish(), publish); - assert_eq!(identity.keychain().root(), root); - } - _ => panic!("bad transaction type"), - } - assert_signkey! { transactions.transactions()[0], Alpha } - } - - #[test] - fn transactions_set_recovery() { - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert!(identity.recovery_policy().is_none()); - assert_eq!(transactions.transactions().len(), 1); - - let transactions2 = transactions.clone().set_recovery_policy(&master_key, Timestamp::now(), None).unwrap(); - let identity2 = transactions2.build_identity().unwrap(); - assert!(identity2.recovery_policy().is_none()); - assert_eq!(transactions2.transactions().len(), 2); - assert_signkey! { transactions2.transactions()[1], Policy } - - let transactions3 = transactions.clone().set_recovery_policy(&master_key, Timestamp::now(), Some(PolicyCondition::Deny)).unwrap(); - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.recovery_policy().as_ref().unwrap().id(), &PolicyID(transactions3.transactions[1].id().deref().clone())); - assert_eq!(identity3.recovery_policy().as_ref().unwrap().conditions(), &PolicyCondition::Deny); - assert_eq!(transactions3.transactions().len(), 2); - - let transactions4 = transactions3.clone().set_recovery_policy(&master_key, Timestamp::now(), None).unwrap(); - let identity4 = transactions4.build_identity().unwrap(); - assert!(identity4.recovery_policy().is_none()); - assert_eq!(transactions4.transactions().len(), 3); - } - - #[test] - fn transactions_execute_recovery() { - fn id_with_subkey() -> (SecretKey, Transactions) { - let (master_key, transactions) = genesis(); - let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); - let transactions2 = transactions - .add_subkey(&master_key, Timestamp::now(), Key::new_sign(sign_keypair), "sign", None).unwrap(); - (master_key, transactions2) - } - let (gus_master, gus) = id_with_subkey(); - let (marty_master, marty) = id_with_subkey(); - let (jackie_master, jackie) = id_with_subkey(); - - let gus_sign = gus.build_identity().unwrap().keychain().subkey_by_name("sign").unwrap().as_signkey().unwrap().clone(); - let marty_sign = marty.build_identity().unwrap().keychain().subkey_by_name("sign").unwrap().as_signkey().unwrap().clone(); - let jackie_sign = jackie.build_identity().unwrap().keychain().subkey_by_name("sign").unwrap().as_signkey().unwrap().clone(); - - let (master_key, transactions) = genesis(); - - let transactions2 = transactions.clone() - .set_recovery_policy( - &master_key, - Timestamp::now(), - Some(PolicyCondition::OfN { - must_have: 3, - pubkeys: vec![ - gus_sign.clone().into(), - marty_sign.clone().into(), - jackie_sign.clone().into(), - ], - }) - ) - .unwrap(); - - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let new_publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let new_root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let action = PolicyRequestAction::ReplaceKeys { - policy: new_policy_keypair.clone(), - publish: new_publish_keypair.clone(), - root: new_root_keypair.clone(), - }; - - // cannot open a request unless you have an actual recovery policy - let res = transactions.build_identity().unwrap() - .create_recovery_request(&master_key, &new_policy_keypair, action.clone()); - assert_eq!(res.err(), Some(Error::IdentityMissingRecoveryPolicy)); - - let identity2 = transactions2.build_identity().unwrap(); - let req = identity2.create_recovery_request(&master_key, &new_policy_keypair, action.clone()).unwrap(); - - let res = transactions.clone().execute_recovery_policy(&master_key, Timestamp::now(), req.clone()); - assert_eq!(res.err(), Some(Error::IdentityMissingRecoveryPolicy)); - - let res = transactions2.clone().execute_recovery_policy(&master_key, Timestamp::now(), req.clone()); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - let req_signed_1 = gus.build_identity().unwrap() - .sign_recovery_request(&gus_master, &gus_sign, req.clone()).unwrap(); - let req_signed_2 = marty.build_identity().unwrap() - .sign_recovery_request(&marty_master, &marty_sign, req_signed_1.clone()).unwrap(); - let req_signed_3 = jackie.build_identity().unwrap() - .sign_recovery_request(&jackie_master, &jackie_sign, req_signed_2.clone()).unwrap(); - - let res = transactions2.clone() - .execute_recovery_policy(&master_key, Timestamp::now(), req_signed_1.clone()); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - let res = transactions2.clone() - .execute_recovery_policy(&master_key, Timestamp::now(), req_signed_2.clone()); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - let transactions3 = transactions2.clone() - .execute_recovery_policy(&master_key, Timestamp::now(), req_signed_3.clone()).unwrap(); - let identity3 = transactions3.build_identity().unwrap(); - - assert_signkey! { transactions3.transactions()[2], Policy } - assert!(identity2.keychain().policy() != identity3.keychain().policy()); - assert!(identity2.keychain().publish() != identity3.keychain().publish()); - assert!(identity2.keychain().root() != identity3.keychain().root()); - assert_eq!(identity2.keychain().subkeys().len(), 0); - assert_eq!(identity3.keychain().policy(), &new_policy_keypair); - assert_eq!(identity3.keychain().publish(), &new_publish_keypair); - assert_eq!(identity3.keychain().root(), &new_root_keypair); - assert_eq!(identity3.keychain().subkeys().len(), 3); - assert_eq!(identity3.keychain().subkeys()[0].name(), &format!("revoked:policy:{}", identity2.keychain().policy().key_id().as_string())); - assert_eq!(identity3.keychain().subkeys()[1].name(), &format!("revoked:publish:{}", identity2.keychain().publish().key_id().as_string())); - assert_eq!(identity3.keychain().subkeys()[2].name(), &format!("revoked:root:{}", identity2.keychain().root().key_id().as_string())); - } - - #[test] - fn transactions_make_claim() { - let (master_key, transactions) = genesis(); - - macro_rules! make_specs { - ($master:expr, $claimmaker:expr, $val:expr) => {{ - let val = $val.clone(); - let maybe_private = MaybePrivate::new_private(&$master, val.clone()).unwrap(); - let maybe_public = MaybePrivate::new_public(val.clone()); - let spec_private = $claimmaker(maybe_private, val.clone()); - let spec_public = $claimmaker(maybe_public, val.clone()); - (spec_private, spec_public) - }} - } - - macro_rules! assert_claim { - (raw, $claimmaker:expr, $val:expr, $get_maybe:expr) => { - let val = $val; - let (spec_private, spec_public) = make_specs!(master_key, $claimmaker, val); - - let transactions2 = transactions.clone().make_claim(&master_key, Timestamp::now(), spec_private).unwrap(); - let identity2 = transactions2.build_identity().unwrap(); - let maybe = $get_maybe(identity2.claims()[0].claim().spec().clone()); - assert_eq!(maybe.open(&master_key).unwrap(), val); - assert_eq!(identity2.claims().len(), 1); - assert_eq!(transactions2.transactions().len(), 2); - - let transactions2 = transactions.clone().make_claim(&master_key, Timestamp::now(), spec_public).unwrap(); - let identity2 = transactions2.build_identity().unwrap(); - let maybe = $get_maybe(identity2.claims()[0].claim().spec().clone()); - assert_eq!(maybe.open(&master_key).unwrap(), val); - assert_eq!(identity2.claims().len(), 1); - assert_eq!(transactions2.transactions().len(), 2); - assert_signkey! { transactions2.transactions()[1], Root } - }; - - ($claimty:ident, $val:expr) => { - assert_claim! { - raw, - |maybe, _| ClaimSpec::$claimty(maybe), - $val, - |spec: ClaimSpec| if let ClaimSpec::$claimty(maybe) = spec { maybe } else { panic!("bad claim type: {}", stringify!($claimty)) } - } - }; - } - - let identity = transactions.build_identity().unwrap(); - assert_eq!(identity.claims().len(), 0); - assert_eq!(transactions.transactions().len(), 1); - - let val = identity.id().clone(); - let spec = ClaimSpec::Identity(val.clone()); - let transactions2 = transactions.clone().make_claim(&master_key, Timestamp::now(), spec).unwrap(); - let identity2 = transactions2.build_identity().unwrap(); - match identity2.claims()[0].claim().spec() { - ClaimSpec::Identity(val2) => { - assert_eq!(&val, val2); - } - _ => panic!("bad claim type {:?}", identity2.claims()[0].claim().spec()), - } - assert_eq!(identity2.claims().len(), 1); - assert_eq!(transactions2.transactions().len(), 2); - - assert_claim!{ Name, String::from("Marty Malt") } - assert_claim!{ Birthday, Date::from_str("2010-01-03").unwrap() } - assert_claim!{ Email, String::from("marty@sids.com") } - assert_claim!{ Photo, BinaryVec::from(vec![1, 2, 3]) } - assert_claim!{ Pgp, String::from("12345") } - assert_claim!{ Domain, String::from("slappy.com") } - assert_claim!{ Url, Url::parse("https://killtheradio.net/").unwrap() } - assert_claim!{ Address, String::from("111 blumps ln") } - assert_claim!{ Relation, Relationship::new(RelationshipType::OrganizationMember, IdentityID::random()) } - assert_claim!{ RelationExtension, Relationship::new(RelationshipType::OrganizationMember, BinaryVec::from(vec![1, 2, 3, 4, 5])) } - assert_claim!{ - raw, - |maybe, _| ClaimSpec::Extension { key: String::from("id:state:ca"), value: maybe }, - BinaryVec::from(vec![7, 3, 2, 90]), - |spec: ClaimSpec| if let ClaimSpec::Extension { value: maybe, .. } = spec { maybe } else { panic!("bad claim type: {}", stringify!($claimtype)) } - } - } - - #[test] - fn transactions_delete_claim() { - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert_eq!(identity.claims().len(), 0); - assert_eq!(transactions.transactions().len(), 1); - - let identity_id = IdentityID(transactions.transactions()[0].deref().id().deref().clone()); - let transactions2 = transactions.make_claim(&master_key, Timestamp::now(), ClaimSpec::Identity(identity_id)).unwrap(); - assert_eq!(transactions2.transactions().len(), 2); - - let identity = transactions2.build_identity().unwrap(); - let claim_id = identity.claims()[0].claim().id().clone(); - let transactions3 = transactions2.clone().delete_claim(&master_key, Timestamp::now(), claim_id.clone()).unwrap(); - assert_eq!(transactions3.transactions().len(), 3); - assert_signkey! { transactions3.transactions()[2], Root } - - let res = transactions2.clone().delete_claim(&master_key, Timestamp::now(), ClaimID::random()); - assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); - let res = transactions3.clone().delete_claim(&master_key, Timestamp::now(), claim_id.clone()); - assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); - } - - #[test] - fn transactions_accept_stamp() { - let (master_key, transactions) = genesis(); - let identity_id = IdentityID(transactions.transactions()[0].deref().id().deref().clone()); - let transactions2 = transactions.make_claim(&master_key, Timestamp::now(), ClaimSpec::Identity(identity_id)).unwrap(); - let identity = transactions2.build_identity().unwrap(); - assert_eq!(identity.claims()[0].stamps().len(), 0); - let claim = identity.claims()[0].claim().clone(); - - let (master_key_stamper, transactions_stamper) = genesis(); - let identity_stamper = transactions_stamper.build_identity().unwrap(); - let stamp = identity_stamper.stamp(&master_key_stamper, Confidence::Low, Timestamp::now(), identity.id(), &claim, Some(Timestamp::from_str("2060-01-01T06:59:00Z").unwrap())).unwrap(); - - let transactions3 = transactions2.accept_stamp(&master_key, Timestamp::now(), stamp.clone()).unwrap(); - assert_eq!(transactions3.transactions().len(), 3); - assert_signkey! { transactions3.transactions()[2], Root } - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.claims()[0].stamps().len(), 1); - - let res = transactions3.clone().accept_stamp(&master_key, Timestamp::now(), stamp.clone()); - assert_eq!(res.err(), Some(Error::IdentityStampAlreadyExists)); - - let transactions4 = transactions3.clone().delete_claim(&master_key, Timestamp::now(), claim.id().clone()).unwrap(); - let res = transactions4.accept_stamp(&master_key, Timestamp::now(), stamp.clone()); - assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); - } - - #[test] - fn transactions_delete_stamp() { - let (master_key, transactions) = genesis(); - let identity_id = IdentityID(transactions.transactions()[0].deref().id().deref().clone()); - let transactions2 = transactions.make_claim(&master_key, Timestamp::now(), ClaimSpec::Identity(identity_id)).unwrap(); - let identity = transactions2.build_identity().unwrap(); - assert_eq!(identity.claims()[0].stamps().len(), 0); - let claim = identity.claims()[0].claim().clone(); - - let (master_key_stamper, transactions_stamper) = genesis(); - let identity_stamper = transactions_stamper.build_identity().unwrap(); - let stamp = identity_stamper.stamp(&master_key_stamper, Confidence::Low, Timestamp::now(), identity.id(), &claim, Some(Timestamp::from_str("2060-01-01T06:59:00Z").unwrap())).unwrap(); - - let transactions3 = transactions2.accept_stamp(&master_key, Timestamp::now(), stamp.clone()).unwrap(); - assert_eq!(transactions3.transactions().len(), 3); - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.claims()[0].stamps().len(), 1); - - let transactions4 = transactions3.clone().delete_stamp(&master_key, Timestamp::now(), stamp.id().clone()).unwrap(); - let identity4 = transactions4.build_identity().unwrap(); - assert_eq!(identity4.claims()[0].stamps().len(), 0); - assert_signkey! { transactions4.transactions()[3], Root } - - let res = transactions4.clone().delete_stamp(&master_key, Timestamp::now(), stamp.id().clone()); - assert_eq!(res.err(), Some(Error::IdentityStampNotFound)); - } - - macro_rules! key_setter { - ($ty:ident, $keychain_getter:ident, $transaction_fn:ident, $key_getter:ident, $strname:expr) => {{ - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - let current_keypair = identity.keychain().$keychain_getter(); - assert_eq!(identity.keychain().subkeys().len(), 0); - - let new_keypair = $ty::new_ed25519(&master_key).unwrap(); - assert!(&new_keypair != current_keypair); - let transactions2 = transactions.$transaction_fn(&master_key, Timestamp::now(), new_keypair.clone(), RevocationReason::Superseded).unwrap(); - assert_signkey! { transactions2.transactions()[1], Alpha } - let identity2 = transactions2.build_identity().unwrap(); - assert_eq!(identity2.keychain().$keychain_getter(), &new_keypair); - assert_eq!(identity2.keychain().subkeys()[0].key().$key_getter().as_ref().unwrap(), ¤t_keypair); - assert_eq!(identity2.keychain().subkeys()[0].name(), &format!($strname, current_keypair.key_id().as_string())); - - let transactions3 = transactions2.$transaction_fn(&master_key, Timestamp::now(), new_keypair.clone(), RevocationReason::Superseded).unwrap(); - assert_signkey! { transactions3.transactions()[2], Alpha } - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.keychain().$keychain_getter(), &new_keypair); - assert_eq!(identity3.keychain().subkeys()[0].key().$key_getter().as_ref().unwrap(), ¤t_keypair); - assert_eq!(identity3.keychain().subkeys()[0].name(), &format!($strname, current_keypair.key_id().as_string())); - assert_eq!(identity3.keychain().subkeys()[1].key().$key_getter().unwrap(), &new_keypair); - assert_eq!(identity3.keychain().subkeys()[1].name(), &format!($strname, new_keypair.key_id().as_string())); - transactions3 - }} - } - - #[test] - fn transactions_set_policy_key() { - key_setter! { - PolicyKeypair, - policy, - set_policy_key, - as_policykey, - "revoked:policy:{}" - }; - } - - #[test] - fn transactions_set_publish_key() { - key_setter! { - PublishKeypair, - publish, - set_publish_key, - as_publishkey, - "revoked:publish:{}" - }; - } - - #[test] - fn transactions_set_root_key() { - key_setter! { - RootKeypair, - root, - set_root_key, - as_rootkey, - "revoked:root:{}" - }; - } - - #[test] - fn transactions_add_subkey() { - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert_eq!(identity.keychain().subkeys().len(), 0); - - let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); - let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); - let secret_key = Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); - let transactions2 = transactions - .add_subkey(&master_key, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key")).unwrap(); - assert_signkey! { transactions2.transactions()[1], Root } - assert_signkey! { transactions2.transactions()[2], Root } - assert_signkey! { transactions2.transactions()[3], Root } - let identity2 = transactions2.build_identity().unwrap(); - assert_eq!(identity2.keychain().subkeys()[0].name(), "default:sign"); - assert_eq!(identity2.keychain().subkeys()[1].name(), "default:crypto"); - assert_eq!(identity2.keychain().subkeys()[2].name(), "default:secret"); - let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); - let res = transactions2.clone() - .add_subkey(&master_key, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things")); - assert_eq!(res.err(), Some(Error::DuplicateName)); - let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); - let res = transactions2.clone() - .add_subkey(&master_key, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails")); - assert_eq!(res.err(), Some(Error::DuplicateName)); - let secret_key = Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); - let res = transactions2.clone() - .add_subkey(&master_key, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key")); - assert_eq!(res.err(), Some(Error::DuplicateName)); - } - - #[test] - fn transactions_revoke_subkey() { - let (master_key, transactions) = genesis(); - - let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); - let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); - let secret_key = Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); - let transactions2 = transactions - .add_subkey(&master_key, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key")).unwrap(); - let transactions3 = transactions2.clone() - .revoke_subkey(&master_key, Timestamp::now(), "default:crypto", RevocationReason::Superseded, Some("revoked:default:crypto")).unwrap(); - assert_signkey! { transactions3.transactions()[4], Root } - let identity3 = transactions3.build_identity().unwrap(); - assert!(identity3.keychain().subkeys()[0].revocation().is_none()); - assert_eq!(identity3.keychain().subkeys()[1].revocation().as_ref().map(|x| x.reason()), Some(&RevocationReason::Superseded)); - assert!(identity3.keychain().subkeys()[2].revocation().is_none()); - - let res = transactions3.clone() - .revoke_subkey(&master_key, Timestamp::now(), "default:crypto", RevocationReason::Superseded, Some("revoked:default:crypto")); - assert_eq!(res.err(), Some(Error::IdentitySubkeyNotFound)); - let res = transactions3.clone() - .revoke_subkey(&master_key, Timestamp::now(), "revoked:default:crypto", RevocationReason::Superseded, Some("revoked:default:crypto")); - assert_eq!(res.err(), Some(Error::IdentitySubkeyAlreadyRevoked)); - } - - #[test] - fn transactions_delete_subkey() { - let (master_key, transactions) = genesis(); - - let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); - let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); - let secret_key = Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); - let transactions2 = transactions - .add_subkey(&master_key, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key")).unwrap(); - - let transactions3 = transactions2.clone() - .delete_subkey(&master_key, Timestamp::now(), "default:sign").unwrap(); - assert_signkey! { transactions3.transactions()[4], Root } - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.keychain().subkeys()[0].name(), "default:crypto"); - assert_eq!(identity3.keychain().subkeys()[1].name(), "default:secret"); - assert_eq!(identity3.keychain().subkeys().len(), 2); - - let res = transactions3.clone() - .delete_subkey(&master_key, Timestamp::now(), "default:sign"); - assert_eq!(res.err(), Some(Error::IdentitySubkeyNotFound)); - } - - #[test] - fn transactions_set_nickname() { - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert_eq!(identity.extra_data().nickname(), &None); - - let transactions2 = transactions - .set_nickname(&master_key, Timestamp::now(), Some("dirk-delta")).unwrap(); - assert_signkey! { transactions2.transactions()[1], Root } - let identity2 = transactions2.build_identity().unwrap(); - assert_eq!(identity2.extra_data().nickname(), &Some("dirk-delta".into())); - - let no_name: Option = None; - let transactions3 = transactions2 - .set_nickname(&master_key, Timestamp::now(), no_name).unwrap(); - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.extra_data().nickname(), &None); - } - - #[test] - fn transactions_add_forward() { - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert_eq!(identity.extra_data().forwards().len(), 0); - - let transactions2 = transactions - .add_forward(&master_key, Timestamp::now(), "email", ForwardType::Email("jackie@chrome.com".into()), true).unwrap() - .add_forward(&master_key, Timestamp::now(), "my-website", ForwardType::Url("https://www.cactus-petes.com/yeeeehawwww".into()), false).unwrap() - .add_forward(&master_key, Timestamp::now(), "twitter", ForwardType::Social { ty: "twitter".into(), handle: "lol_twitter_sux".into() }, false).unwrap(); - assert_signkey! { transactions2.transactions()[1], Root } - assert_signkey! { transactions2.transactions()[2], Root } - assert_signkey! { transactions2.transactions()[3], Root } - let identity2 = transactions2.build_identity().unwrap(); - assert_eq!(identity2.extra_data().forwards().len(), 3); - assert_eq!(identity2.extra_data().forwards()[0].name(), "email"); - assert_eq!(identity2.extra_data().forwards()[1].name(), "my-website"); - assert_eq!(identity2.extra_data().forwards()[2].name(), "twitter"); - - let res = transactions2.clone() - .add_forward(&master_key, Timestamp::now(), "email", ForwardType::Email("jack.mama@highland.edu".into()), true); - assert_eq!(res.err(), Some(Error::DuplicateName)); - } - - #[test] - fn transactions_delete_forward() { - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert_eq!(identity.extra_data().forwards().len(), 0); - - let transactions2 = transactions - .add_forward(&master_key, Timestamp::now(), "email", ForwardType::Email("jackie@chrome.com".into()), true).unwrap() - .add_forward(&master_key, Timestamp::now(), "my-website", ForwardType::Url("https://www.cactus-petes.com/yeeeehawwww".into()), false).unwrap() - .add_forward(&master_key, Timestamp::now(), "twitter", ForwardType::Social { ty: "twitter".into(), handle: "lol_twitter_sux".into() }, false).unwrap(); - let transactions3 = transactions2 - .delete_forward(&master_key, Timestamp::now(), "my-website").unwrap(); - assert_signkey! { transactions3.transactions()[4], Root } - let identity3 = transactions3.build_identity().unwrap(); - assert_eq!(identity3.extra_data().forwards().len(), 2); - assert_eq!(identity3.extra_data().forwards()[0].name(), "email"); - assert_eq!(identity3.extra_data().forwards()[1].name(), "twitter"); - - let res = transactions3.clone() - .delete_forward(&master_key, Timestamp::now(), "my-website"); - assert_eq!(res.err(), Some(Error::IdentityForwardNotFound)); - } - - #[test] - fn transactions_reencrypt() { - let (master_key, transactions) = genesis(); - let transactions = transactions - .make_claim(&master_key, Timestamp::now(), ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Hooty McOwl".to_string()).unwrap())).unwrap() - .set_root_key(&master_key, Timestamp::now(), RootKeypair::new_ed25519(&master_key).unwrap(), RevocationReason::Unspecified).unwrap() - .set_nickname(&master_key, Timestamp::now(), Some("dirk-delta")).unwrap() - .add_forward(&master_key, Timestamp::now(), "my-website", ForwardType::Url("https://www.cactus-petes.com/yeeeehawwww".into()), false).unwrap(); - transactions.test_master_key(&master_key).unwrap(); - let identity = transactions.build_identity().unwrap(); - match identity.claims()[0].claim().spec() { - ClaimSpec::Name(maybe) => { - let val = maybe.open(&master_key).unwrap(); - assert_eq!(val, "Hooty McOwl".to_string()); - } - _ => panic!("bad claim type"), - } - let sig = identity.keychain().root().sign(&master_key, "KILL...ME....".as_bytes()).unwrap(); - - let master_key_new = SecretKey::new_xchacha20poly1305().unwrap(); - let transactions2 = transactions.reencrypt(&master_key, &master_key_new).unwrap(); - transactions2.test_master_key(&master_key_new).unwrap(); - let res = transactions2.test_master_key(&master_key); - assert_eq!(res.err(), Some(Error::CryptoOpenFailed)); - let identity2 = transactions2.build_identity().unwrap(); - let sig2 = identity2.keychain().root().sign(&master_key_new, "KILL...ME....".as_bytes()).unwrap(); - assert_eq!(sig, sig2); - match identity2.claims()[0].claim().spec() { - ClaimSpec::Name(maybe) => { - let val = maybe.open(&master_key_new).unwrap(); - assert_eq!(val, "Hooty McOwl".to_string()); - let res = maybe.open(&master_key); - assert_eq!(res.err(), Some(Error::CryptoOpenFailed)); - } - _ => panic!("bad claim type"), - } - } - - #[test] - fn transactions_is_owned() { - let (master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - assert!(transactions.is_owned()); - assert!(identity.is_owned()); - - let mut transactions2 = transactions.clone(); - transactions2.transactions_mut()[0] = transactions2.transactions_mut()[0].strip_private(); - let identity2 = transactions2.build_identity().unwrap(); - assert!(!transactions2.is_owned()); - assert!(!identity2.is_owned()); - - let policy = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root = RootKeypair::new_ed25519(&master_key).unwrap(); - let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); - let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); - let secret_key = Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); - let transactions3 = transactions.clone() - .add_subkey(&master_key, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key")).unwrap() - .set_policy_key(&master_key, Timestamp::now(), policy.clone(), RevocationReason::Unspecified).unwrap() - .set_publish_key(&master_key, Timestamp::now(), publish.clone(), RevocationReason::Unspecified).unwrap() - .set_root_key(&master_key, Timestamp::now(), root.clone(), RevocationReason::Unspecified).unwrap(); - let identity3 = transactions3.build_identity().unwrap(); - assert!(transactions3.is_owned()); - assert!(identity3.is_owned()); - - let mut transactions4 = transactions3.clone(); - for trans in transactions4.transactions_mut() { - let entry = trans.entry().clone(); - match entry.body() { - TransactionBody::CreateIdentityV1 { .. } | TransactionBody::SetPolicyKeyV1 { .. } | TransactionBody::SetPublishKeyV1 { .. } | TransactionBody::SetRootKeyV1 { .. } => { - match trans { - TransactionVersioned::V1(ref mut inner) => { - inner.set_entry(entry.strip_private()); - } - } - } - _ => {} - } - } - let identity4 = transactions4.build_identity().unwrap(); - assert!(!transactions4.is_owned()); - assert!(!identity4.is_owned()); - } - - #[test] - fn transactions_test_master_key() { - let (master_key, transactions) = genesis(); - transactions.test_master_key(&master_key).unwrap(); - let master_key_fake = SecretKey::new_xchacha20poly1305().unwrap(); - assert!(master_key_fake != master_key); - let res = transactions.test_master_key(&master_key_fake); - assert_eq!(res.err(), Some(Error::CryptoOpenFailed)); - } - - #[test] - fn transactions_strip_has_private() { - let (master_key, transactions) = genesis(); - - let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); - let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); - let secret_key = Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); - let transactions2 = transactions - .add_subkey(&master_key, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails")).unwrap() - .add_subkey(&master_key, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key")).unwrap() - .make_claim(&master_key, Timestamp::now(), ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Danny Dinkel".to_string()).unwrap())).unwrap() - .make_claim(&master_key, Timestamp::now(), ClaimSpec::Email(MaybePrivate::new_public("twinkie.doodle@amateur-spotlight.net".to_string()))).unwrap(); - - let mut has_priv: Vec = Vec::new(); - for trans in transactions2.transactions() { - has_priv.push(trans.has_private()); - } - assert_eq!(has_priv.iter().filter(|x| **x).count(), 5); - - assert!(transactions2.has_private()); - let transactions3 = transactions2.strip_private(); - assert!(!transactions3.has_private()); - - let mut has_priv: Vec = Vec::new(); - for trans in transactions3.transactions() { - has_priv.push(trans.has_private()); - } - assert_eq!(has_priv.iter().filter(|x| **x).count(), 0); - } - - #[test] - fn transactions_serde_binary() { - let (_master_key, transactions) = genesis(); - let identity = transactions.build_identity().unwrap(); - let ser = transactions.serialize_binary().unwrap(); - let des = Transactions::deserialize_binary(ser.as_slice()).unwrap(); - let identity2 = des.build_identity().unwrap(); - // quick and dirty. oh well. - assert_eq!(identity.id(), identity2.id()); - } -} - diff --git a/src/dag/mod.rs b/src/dag/mod.rs new file mode 100644 index 0000000..52d808f --- /dev/null +++ b/src/dag/mod.rs @@ -0,0 +1,21 @@ +//! A DAG, or directed acyclic graph, allows us to represent our identity as an +//! ordered list of signed changes, as opposed to a singular object. There are +//! pros and cons to both methods, but for the purposes of this project, a +//! tree of signed transactions that link back to previous changes provides a +//! good amount of security, auditability, and syncability. + +mod transaction; +mod transactions; + +pub use crate::dag::{ + transaction::{ + TransactionBody, + TransactionID, + TransactionEntry, + Transaction, + }, + transactions::{ + Transactions, + }, +}; + diff --git a/src/dag/transaction.rs b/src/dag/transaction.rs new file mode 100644 index 0000000..784bc7c --- /dev/null +++ b/src/dag/transaction.rs @@ -0,0 +1,870 @@ +//! A `Transaction` models a single change against an identity, and is one node +//! inside of the identity DAG. +//! +//! Transactions have a [TransactionBody], an ID (sha512 of the transaction's body, +//! timestamp, and previously-referenced transactions), and a collection of one or +//! more signatures on the transaction's ID that validate that transaction. + +use crate::{ + error::{Error, Result}, + crypto::{ + key::{KeyID, SecretKey, Sha512}, + }, + dag::Transactions, + identity::{ + claim::{ + ClaimID, + ClaimSpec, + }, + identity::{ + IdentityID, + Identity, + }, + keychain::{ + AdminKey, + ExtendKeypair, + Key, + RevocationReason, + }, + stamp::{ + StampID, + StampEntry, + StampRevocationEntry, + }, + }, + policy::{CapabilityPolicy, Context, PolicySignature}, + util::{ + Public, + Timestamp, + ser::{self}, + }, +}; +use getset; +use rasn::{Encode, Decode, AsnType}; +use serde_derive::{Serialize, Deserialize}; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; + +/// This is all of the possible transactions that can be performed on an +/// identity, including the data they require. +#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize)] +#[rasn(choice)] +pub enum TransactionBody { + /// Create a new identity. The [ID][TranscationID] of this transaction will + /// be the identity's public ID forever after. + #[rasn(tag(explicit(0)))] + CreateIdentityV1 { + #[rasn(tag(explicit(0)))] + admin_keys: Vec, + #[rasn(tag(explicit(1)))] + capabilities: Vec, + }, + /// Replace optionally both the [admin keys][AdminKey] in the [Keychain] and the + /// [capabilities][CapabilityPolicy] attached to the identity. + /// + /// This is more or less a hailmary recovery option that allows gaining + /// access to identity after some kind of catastrophic event. + #[rasn(tag(explicit(1)))] + ResetIdentityV1 { + #[rasn(tag(explicit(0)))] + admin_keys: Option>, + #[rasn(tag(explicit(1)))] + capabilities: Option>, + }, + /// Add a new [admin key][AdminKey] to the [Keychain]. + #[rasn(tag(explicit(2)))] + AddAdminKeyV1 { + #[rasn(tag(explicit(0)))] + admin_key: AdminKey, + }, + /// Edit an admin key + #[rasn(tag(explicit(3)))] + EditAdminKeyV1 { + #[rasn(tag(explicit(0)))] + id: KeyID, + #[rasn(tag(explicit(1)))] + name: Option, + #[rasn(tag(explicit(2)))] + description: Option>, + }, + /// Revokes an [AdminKey] key and moves it into the subkeys, optionally + /// renaming it. + #[rasn(tag(explicit(4)))] + RevokeAdminKeyV1 { + #[rasn(tag(explicit(0)))] + id: KeyID, + #[rasn(tag(explicit(1)))] + reason: RevocationReason, + #[rasn(tag(explicit(2)))] + new_name: Option, + }, + /// Add a new [capability policy][CapabilityPolicy] to the identity. + #[rasn(tag(explicit(5)))] + AddCapabilityPolicyV1 { + #[rasn(tag(explicit(0)))] + capability: CapabilityPolicy, + }, + /// Delete (by name) a capability policy from the identity. + #[rasn(tag(explicit(6)))] + DeleteCapabilityPolicyV1 { + #[rasn(tag(explicit(0)))] + name: String, + }, + /// Make a new claim on this identity. The [ID][TransactionID] of this + /// transaction will be the claim's ID. + #[rasn(tag(explicit(7)))] + MakeClaimV1 { + #[rasn(tag(explicit(0)))] + spec: ClaimSpec, + #[rasn(tag(explicit(1)))] + name: Option, + }, + /// Edit a claim's name + #[rasn(tag(explicit(8)))] + EditClaimV1 { + #[rasn(tag(explicit(0)))] + claim_id: ClaimID, + #[rasn(tag(explicit(1)))] + name: Option, + }, + /// Delete/remove a claim by ID. + #[rasn(tag(explicit(9)))] + DeleteClaimV1 { + #[rasn(tag(explicit(0)))] + claim_id: ClaimID, + }, + /// Make a stamp that is saved and advertised with this identity. + #[rasn(tag(explicit(10)))] + MakeStampV1 { + #[rasn(tag(explicit(0)))] + stamp: StampEntry, + }, + /// Revoke a stamp we previously created and store this revocation with the + /// identity. + #[rasn(tag(explicit(11)))] + RevokeStampV1 { + #[rasn(tag(explicit(0)))] + revocation: StampRevocationEntry, + }, + /// Accept a stamp on one of our claims into our identity. This allows those + /// who have our identity to see the trust others have put into us. + #[rasn(tag(explicit(12)))] + AcceptStampV1 { + #[rasn(tag(explicit(0)))] + stamp_transaction: Box, + }, + /// Delete a stamp on one of our claims. + #[rasn(tag(explicit(13)))] + DeleteStampV1 { + #[rasn(tag(explicit(0)))] + stamp_id: StampID, + }, + /// Add a new subkey to our keychain. + #[rasn(tag(explicit(14)))] + AddSubkeyV1 { + #[rasn(tag(explicit(0)))] + key: Key, + #[rasn(tag(explicit(1)))] + name: String, + #[rasn(tag(explicit(2)))] + desc: Option, + }, + /// Edit the name/description of a subkey by its unique name. + #[rasn(tag(explicit(15)))] + EditSubkeyV1 { + #[rasn(tag(explicit(0)))] + id: KeyID, + #[rasn(tag(explicit(1)))] + new_name: Option, + #[rasn(tag(explicit(2)))] + new_desc: Option>, + }, + /// Mark a subkey as revoked, allowing old signatures to be validated but + /// without permitting new signatures to be created. + #[rasn(tag(explicit(16)))] + RevokeSubkeyV1 { + #[rasn(tag(explicit(0)))] + id: KeyID, + #[rasn(tag(explicit(1)))] + reason: RevocationReason, + #[rasn(tag(explicit(2)))] + new_name: Option, + }, + /// Delete a subkey entirely from the identity. + #[rasn(tag(explicit(17)))] + DeleteSubkeyV1 { + #[rasn(tag(explicit(0)))] + id: KeyID, + }, + /// Set this identity's nickname. + #[rasn(tag(explicit(18)))] + SetNicknameV1 { + #[rasn(tag(explicit(0)))] + nickname: Option, + }, + /// Publish this identity. This transaction cannot be saved with the identity, but + /// rather should be published to a public medium (like StampNet!!!!1) + #[rasn(tag(explicit(19)))] + PublishV1 { + #[rasn(tag(explicit(0)))] + transactions: Box, + } +} + +impl TransactionBody { + /// Reencrypt this transaction body + fn reencrypt(self, old_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { + let new_self = match self { + Self::CreateIdentityV1 { admin_keys, capabilities } => { + let admin_reenc = admin_keys.into_iter() + .map(|x| x.reencrypt(old_master_key, new_master_key)) + .collect::>>()?; + Self::CreateIdentityV1 { + admin_keys: admin_reenc, + capabilities, + } + } + Self::ResetIdentityV1 { admin_keys, capabilities } => { + let admin_keys_reenc = admin_keys + .map(|keyvec| { + keyvec.into_iter() + .map(|k| k.reencrypt(old_master_key, new_master_key)) + .collect::>>() + }) + .transpose()?; + Self::ResetIdentityV1 { + admin_keys: admin_keys_reenc, + capabilities, + } + } + Self::AddAdminKeyV1 { admin_key } => Self::AddAdminKeyV1 { + admin_key: admin_key.reencrypt(old_master_key, new_master_key)?, + }, + Self::EditAdminKeyV1 { id, name, description } => Self::EditAdminKeyV1 { id, name, description }, + Self::RevokeAdminKeyV1 { id, reason, new_name } => Self::RevokeAdminKeyV1 { id, reason, new_name }, + Self::AddCapabilityPolicyV1 { capability } => Self::AddCapabilityPolicyV1 { capability }, + Self::DeleteCapabilityPolicyV1 { name } => Self::DeleteCapabilityPolicyV1 { name }, + Self::MakeClaimV1 { spec, name } => Self::MakeClaimV1 { + spec: spec.reencrypt(old_master_key, new_master_key)?, + name, + }, + Self::EditClaimV1 { claim_id, name} => Self::EditClaimV1 { claim_id, name }, + Self::DeleteClaimV1 { claim_id } => Self::DeleteClaimV1 { claim_id }, + Self::MakeStampV1 { stamp } => Self::MakeStampV1 { stamp }, + Self::RevokeStampV1 { revocation } => Self::RevokeStampV1 { revocation }, + Self::AcceptStampV1 { stamp_transaction } => Self::AcceptStampV1 { stamp_transaction }, + Self::DeleteStampV1 { stamp_id } => Self::DeleteStampV1 { stamp_id }, + Self::AddSubkeyV1 { key, name, desc } => { + let new_subkey = key.reencrypt(old_master_key, new_master_key)?; + Self::AddSubkeyV1 { key: new_subkey, name, desc } + } + Self::EditSubkeyV1 { id, new_name, new_desc } => Self::EditSubkeyV1 { id, new_name, new_desc }, + Self::RevokeSubkeyV1 { id, reason, new_name } => Self::RevokeSubkeyV1 { id, reason, new_name }, + Self::DeleteSubkeyV1 { id } => Self::DeleteSubkeyV1 { id }, + Self::SetNicknameV1 { nickname } => Self::SetNicknameV1 { nickname }, + Self::PublishV1 { transactions } => Self::PublishV1 { + transactions: Box::new(transactions.reencrypt(old_master_key, new_master_key)?), + }, + }; + Ok(new_self) + } +} + +impl Public for TransactionBody { + fn strip_private(&self) -> Self { + match self.clone() { + Self::CreateIdentityV1 { admin_keys, capabilities } => { + let admin_stripped = admin_keys.into_iter() + .map(|k| k.strip_private()) + .collect::>(); + Self::CreateIdentityV1 { admin_keys: admin_stripped, capabilities} + } + Self::ResetIdentityV1 { admin_keys, capabilities } => { + let stripped_admin = admin_keys + .map(|keys| { + keys.into_iter() + .map(|k| k.strip_private()) + .collect::>() + }); + Self::ResetIdentityV1 { admin_keys: stripped_admin, capabilities } + } + Self::AddAdminKeyV1 { admin_key } => Self::AddAdminKeyV1 { admin_key: admin_key.strip_private() }, + Self::EditAdminKeyV1 { id, name, description } => Self::EditAdminKeyV1 { id, name, description }, + Self::RevokeAdminKeyV1 { id, reason, new_name } => Self::RevokeAdminKeyV1 { id, reason, new_name }, + Self::AddCapabilityPolicyV1 { capability } => Self::AddCapabilityPolicyV1 { capability }, + Self::DeleteCapabilityPolicyV1 { name } => Self::DeleteCapabilityPolicyV1 { name }, + Self::MakeClaimV1 { spec, name } => Self::MakeClaimV1 { spec: spec.strip_private(), name }, + Self::EditClaimV1 { claim_id, name } => Self::EditClaimV1 { claim_id, name }, + Self::DeleteClaimV1 { claim_id } => Self::DeleteClaimV1 { claim_id }, + Self::MakeStampV1 { stamp } => Self::MakeStampV1 { stamp }, + Self::RevokeStampV1 { revocation } => Self::RevokeStampV1 { revocation }, + Self::AcceptStampV1 { stamp_transaction } => Self::AcceptStampV1 { stamp_transaction: Box::new(stamp_transaction.strip_private()) }, + Self::DeleteStampV1 { stamp_id } => Self::DeleteStampV1 { stamp_id }, + Self::AddSubkeyV1 { key, name, desc } => Self::AddSubkeyV1 { key: key.strip_private(), name, desc }, + Self::EditSubkeyV1 { id, new_name, new_desc } => Self::EditSubkeyV1 { id, new_name, new_desc }, + Self::RevokeSubkeyV1 { id, reason, new_name } => Self::RevokeSubkeyV1 { id, reason, new_name }, + Self::DeleteSubkeyV1 { id } => Self::DeleteSubkeyV1 { id }, + Self::SetNicknameV1 { nickname } => Self::SetNicknameV1 { nickname }, + Self::PublishV1 { transactions } => Self::PublishV1 { transactions: Box::new(transactions.strip_private()) }, + } + } + + fn has_private(&self) -> bool { + match self { + Self::CreateIdentityV1 { admin_keys, .. } => admin_keys.iter().find(|k| k.has_private()).is_some(), + Self::ResetIdentityV1 { admin_keys, .. } => { + admin_keys + .as_ref() + .map(|keys| keys.iter().find(|x| x.key().has_private()).is_some()) + .unwrap_or(false) + } + Self::AddAdminKeyV1 { admin_key } => admin_key.has_private(), + Self::EditAdminKeyV1 { .. } => false, + Self::RevokeAdminKeyV1 { .. } => false, + Self::AddCapabilityPolicyV1 { .. } => false, + Self::DeleteCapabilityPolicyV1 { .. } => false, + Self::MakeClaimV1 { spec, .. } => spec.has_private(), + Self::EditClaimV1 { .. } => false, + Self::DeleteClaimV1 { .. } => false, + Self::MakeStampV1 { .. } => false, + Self::RevokeStampV1 { .. } => false, + Self::AcceptStampV1 { .. } => false, + Self::DeleteStampV1 { .. } => false, + Self::AddSubkeyV1 { key, .. } => key.has_private(), + Self::EditSubkeyV1 { .. } => false, + Self::RevokeSubkeyV1 { .. } => false, + Self::DeleteSubkeyV1 { .. } => false, + Self::SetNicknameV1 { .. } => false, + Self::PublishV1 { transactions } => transactions.has_private(), + } + } +} + +/// The TransactionID is a SHA512 hash of the transaction body +#[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] +pub struct TransactionID(Sha512); + +impl From for TransactionID { + fn from(sha: Sha512) -> Self { + Self(sha) + } +} + +impl Deref for TransactionID { + type Target = Sha512; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for String { + fn from(id: TransactionID) -> Self { + ser::base64_encode(&id.deref().deref()) + } +} + +impl From<&TransactionID> for String { + fn from(id: &TransactionID) -> Self { + ser::base64_encode(&id.deref().deref()) + } +} + +impl Hash for TransactionID { + fn hash(&self, state: &mut H) { + self.deref().hash(state); + } +} + +impl Eq for TransactionID {} + +impl std::fmt::Display for TransactionID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", ser::base64_encode(self.deref().deref())) + } +} + +#[cfg(test)] +impl TransactionID { + pub(crate) fn random() -> Self { + Self(Sha512::random()) + } +} + +/// The body of an identity transaction. Holds the transaction's references to +/// its previous transactions and the transaction type/data itself. +#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] +#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] +pub struct TransactionEntry { + /// When this transaction was created. + #[rasn(tag(explicit(0)))] + created: Timestamp, + /// This is a list of previous transactions that are not already listed by + /// another transaction. + /// + /// In general, this will only list the last transaction, but it's possible + /// that you might make two separate changes on two separate devices and + /// when they sync, you will have two leading transactions. The next change + /// you make to your identity would sign both of those transactions, and + /// merge the "tree" back into a single trunk. + /// + /// Note that when listing previous transactions, their `created` times must + /// be *less than* this transaction's created time. Future transactions + /// cannot be signed into a past one. + #[rasn(tag(explicit(1)))] + previous_transactions: Vec, + /// This holds the actual transaction data. + #[rasn(tag(explicit(2)))] + body: TransactionBody, +} + +impl TransactionEntry { + /// Create a new entry. + pub(crate) fn new>(created: T, previous_transactions: Vec, body: TransactionBody) -> Self { + Self { + created: created.into(), + previous_transactions, + body, + } + } +} + +impl Public for TransactionEntry { + fn strip_private(&self) -> Self { + let mut clone = self.clone(); + clone.set_body(self.body().strip_private()); + clone + } + + fn has_private(&self) -> bool { + self.body().has_private() + } +} + +/// A transaction represents a single change on an identity object. In order to +/// build an identity, all transactions are played in order from start to finish. +#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] +#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] +pub struct Transaction { + /// This is a SHA512 hash of the transaction's `entry` + #[rasn(tag(explicit(0)))] + id: TransactionID, + /// This holds our transaction body: any references to previous + /// transactions as well as the transaction type/data. + #[rasn(tag(explicit(1)))] + entry: TransactionEntry, + /// The signatures on this transaction's ID. + #[rasn(tag(explicit(2)))] + signatures: Vec, +} + +impl Transaction { + /// Create a new Transaction from a [TransactionEntry]. + pub(crate) fn new(entry: TransactionEntry) -> Result { + let serialized = ser::serialize(&entry.strip_private())?; + let id = TransactionID::from(Sha512::hash(&serialized)?); + Ok(Self { + id, + entry, + signatures: Vec::new(), + }) + } + + /// Sign this transaction. This consumes the transaction, adds the signature + /// to the `signatures` list, then returns the new transaction. + pub(crate) fn sign(mut self, master_key: &SecretKey, admin_key: &AdminKey) -> Result { + let sig = admin_key.key().sign(master_key, self.id().deref().deref())?; + let policy_sig = PolicySignature::Key { + key: admin_key.key().clone().into(), + signature: sig, + }; + self.signatures_mut().push(policy_sig); + Ok(self) + } + + /// Verify that the signatures on this transaction match the transaction. + pub(crate) fn verify_signatures(&self) -> Result<()> { + if self.signatures().len() == 0 { + Err(Error::TransactionNoSignatures)?; + } + for sig in self.signatures() { + match sig { + PolicySignature::Key { key, signature } => { + match key.verify(signature, self.id().deref().deref()) { + Err(_) => Err(Error::TransactionSignatureInvalid(key.clone()))?, + _ => {} + } + } + } + } + Ok(()) + } + + /// Verify this transaction's validity. We have to make sure its ID matches + /// the hash of its public contents, and we have to make sure the signatures + /// satisfy a policy which has the capabilities the transaction requires. + pub(crate) fn verify(&self, identity_maybe: Option<&Identity>) -> Result<()> { + let serialized = ser::serialize(&self.entry().strip_private())?; + // first verify the transaction's hash. + let transaction_hash = Sha512::hash(&serialized[..])?; + if &transaction_hash != self.id().deref() { + Err(Error::TransactionIDMismatch(self.id().clone()))?; + } + + // now verify the signatures on the stinkin transaction + self.verify_signatures()?; + + macro_rules! search_capabilities { + ($identity:expr) => { + let mut found_match = false; + let contexts = Context::contexts_from_transaction(self, $identity); + for capability in $identity.capabilities() { + if capability.validate_transaction(self, &contexts).is_ok() { + found_match = true; + break; + } + } + if !found_match { + Err(Error::PolicyNotFound)?; + } + } + } + + // if we got here, the transaction, and all the signatures on it, are + // valid. now we need to figure out if the transaction/signatures match + // any policy within the identity (if it exists). + match identity_maybe.as_ref() { + // if we have an identity, we can verify this transaction using the + // public keys contained in the identity + Some(identity) => { + search_capabilities! { identity } + Ok(()) + } + // we don't have an identity, so this is necessarily the genesis + // transaction that creates it. + None => { + match self.entry().body() { + TransactionBody::CreateIdentityV1 { admin_keys, capabilities } => { + // create an identity with the given keys/capabilities + // and see if it will validate its own genesis transaction + let identity = Identity::create(IdentityID::from(self.id().clone()), admin_keys.clone(), capabilities.clone(), self.entry().created().clone()); + search_capabilities! { &identity } + Ok(()) + } + _ => Err(Error::DagNoGenesis)?, + } + } + } + } + + /// Reencrypt this transaction. + pub(crate) fn reencrypt(mut self, old_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { + let new_body = self.entry().body().clone().reencrypt(old_master_key, new_master_key)?; + self.entry_mut().set_body(new_body); + Ok(self) + } +} + +impl Public for Transaction { + fn strip_private(&self) -> Self { + let mut clone = self.clone(); + clone.set_entry(self.entry().strip_private()); + clone + } + + fn has_private(&self) -> bool { + self.entry().has_private() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + crypto::key::{SignKeypair, CryptoKeypair}, + identity::{ + claim::{Relationship, RelationshipType}, + keychain::AdminKeypair, + stamp::Confidence, + }, + policy::Policy, + private::{Private, MaybePrivate}, + util::{Date, Url, ser::BinaryVec, test}, + }; + use std::str::FromStr; + + macro_rules! assert_signkey { + ($trans:expr, $keyty:ident) => { + match $trans.id() { + TransactionID::$keyty(..) => {} + _ => panic!("Expected sign key type {}, found {:?}", stringify!($keyty), $trans.id()), + } + } + } + + #[test] + fn trans_body_strip_has_private() { + fn test_privates(body: &TransactionBody) { + match body { + TransactionBody::CreateIdentityV1 { admin_keys, capabilities } => { + assert!(body.has_private()); + assert!(!body.strip_private().has_private()); + let body2 = TransactionBody::CreateIdentityV1 { + admin_keys: admin_keys.clone().into_iter().map(|x| x.strip_private()).collect::>(), + capabilities: capabilities.clone(), + }; + assert!(!body2.has_private()); + } + TransactionBody::ResetIdentityV1 { admin_keys, capabilities } => { + assert!(body.has_private()); + assert!(!body.strip_private().has_private()); + let body2 = TransactionBody::ResetIdentityV1 { + admin_keys: admin_keys.clone().map(|x| x.into_iter().map(|y| y.strip_private()).collect::>()), + capabilities: capabilities.clone(), + }; + assert!(!body2.has_private()); + } + TransactionBody::AddAdminKeyV1 { admin_key } => { + assert!(body.has_private()); + let body2 = TransactionBody::AddAdminKeyV1 { admin_key: admin_key.strip_private() }; + assert!(!body2.has_private()); + } + TransactionBody::EditAdminKeyV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::RevokeAdminKeyV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::AddCapabilityPolicyV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::DeleteCapabilityPolicyV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::MakeClaimV1 { spec, name } => { + assert_eq!(body.has_private(), spec.has_private()); + let body2 = TransactionBody::MakeClaimV1 { spec: spec.strip_private(), name: name.clone() }; + assert!(!body2.has_private()); + let body3 = body.strip_private(); + assert!(!body3.has_private()); + let body4 = body3.strip_private(); + assert!(!body4.has_private()); + } + TransactionBody::EditClaimV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::DeleteClaimV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::MakeStampV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::RevokeStampV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::AcceptStampV1 { stamp_transaction } => { + assert!(!body.has_private()); + assert!(!stamp_transaction.has_private()); + } + TransactionBody::DeleteStampV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::AddSubkeyV1 { key, name, desc } => { + assert!(body.has_private()); + let body2 = TransactionBody::AddSubkeyV1 { + key: key.strip_private(), + name: name.clone(), + desc: desc.clone(), + }; + assert!(!body2.has_private()); + let body3 = body.strip_private(); + assert!(!body3.has_private()); + } + TransactionBody::EditSubkeyV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::RevokeSubkeyV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::DeleteSubkeyV1 { .. } => { + assert!(!body.has_private()); + } + TransactionBody::SetNicknameV1 { .. } => { + assert!(!body.has_private()); + } + // blehhhh... + TransactionBody::PublishV1 { .. } => { } + } + } + + let (master_key, transactions, admin_key) = test::create_fake_identity(Timestamp::now()); + + test_privates(&TransactionBody::CreateIdentityV1 { admin_keys: vec![admin_key.clone()], capabilities: Vec::new() }); + test_privates(&TransactionBody::ResetIdentityV1 { admin_keys: Some(vec![admin_key.clone()]), capabilities: None }); + test_privates(&TransactionBody::AddAdminKeyV1 { admin_key: admin_key.clone() }); + test_privates(&TransactionBody::EditAdminKeyV1 { id: admin_key.key().key_id(), name: Some("poopy".into()), description: None }); + test_privates(&TransactionBody::RevokeAdminKeyV1 { id: admin_key.key().key_id(), reason: RevocationReason::Compromised, new_name: Some("old key".into()) }); + + let policy = CapabilityPolicy::new("omg".into(), vec![], Policy::MOfN { must_have: 0, participants: vec![] }); + test_privates(&TransactionBody::AddCapabilityPolicyV1 { capability: policy }); + test_privates(&TransactionBody::DeleteCapabilityPolicyV1 { name: "omg".into() }); + test_privates(&TransactionBody::MakeClaimV1 { spec: ClaimSpec::Name(MaybePrivate::new_public(String::from("Negative Nancy"))), name: None }); + test_privates(&TransactionBody::MakeClaimV1 { spec: ClaimSpec::Name(MaybePrivate::new_private(&master_key, String::from("Positive Pyotr")).unwrap()), name: Some("Grover".into()) }); + test_privates(&TransactionBody::DeleteClaimV1 { claim_id: ClaimID::random() }); + + let entry = StampEntry::new::(IdentityID::random(), IdentityID::random(), ClaimID::random(), Confidence::Low, None); + test_privates(&TransactionBody::MakeStampV1 { stamp: entry.clone() }); + let revocation = StampRevocationEntry::new(IdentityID::random(), IdentityID::random(), StampID::random()); + test_privates(&TransactionBody::RevokeStampV1 { revocation }); + let stamp_transaction = transactions.make_stamp(Timestamp::now(), entry.clone()).unwrap(); + test_privates(&TransactionBody::AcceptStampV1 { stamp_transaction: Box::new(stamp_transaction) }); + test_privates(&TransactionBody::DeleteStampV1 { stamp_id: StampID::random() }); + + let key = Key::new_sign(admin_key.key().deref().clone()); + let key_id = key.key_id(); + test_privates(&TransactionBody::AddSubkeyV1 { + key, + name: "MY DOGECOIN KEY".into(), + desc: Some("plz send doge".into()), + }); + test_privates(&TransactionBody::EditSubkeyV1 { + id: key_id.clone(), + new_name: Some("MAI DOGE KEY".into()), + new_desc: Some(None), + }); + test_privates(&TransactionBody::RevokeSubkeyV1 { + id: key_id.clone(), + reason: RevocationReason::Compromised, + new_name: Some("REVOKED DOGE KEY".into()), + }); + test_privates(&TransactionBody::DeleteSubkeyV1 { id: key_id.clone() }); + test_privates(&TransactionBody::SetNicknameV1 { nickname: Some("wreck-dum".into()) }); + } + + #[test] + fn trans_entry_strip_has_private() { + let master_key = SecretKey::new_xchacha20poly1305().unwrap(); + let body = TransactionBody::MakeClaimV1 { + spec: ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Jackie Chrome".into()).unwrap()), + name: None, + }; + let entry = TransactionEntry::new(Timestamp::now(), vec![TransactionID::from(Sha512::random())], body); + assert!(entry.has_private()); + assert!(entry.body().has_private()); + let entry2 = entry.strip_private(); + assert!(!entry2.has_private()); + assert!(!entry2.body().has_private()); + } + + #[test] + fn trans_new_verify() { + todo!(); + /* + let master_key = SecretKey::new_xchacha20poly1305().unwrap(); + let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); + let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); + let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); + let root_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); + let identity = Identity::create(IdentityID::random(), alpha_keypair.clone(), policy_keypair.clone(), publish_keypair.clone(), root_keypair.clone(), Timestamp::now()); + + let body = TransactionBody::CreateIdentityV1 { + alpha: alpha_keypair.clone(), + policy: policy_keypair.clone(), + publish: publish_keypair.clone(), + root: root_keypair.clone(), + }; + let now = Timestamp::now(); + let entry = TransactionEntry::new(now.clone(), vec![], body); + let trans = Transaction::new(&master_key, &None, SignWith::Alpha, entry.clone()).unwrap(); + trans.verify(None).unwrap(); + + let res = Transaction::new(&master_key, &None, SignWith::Policy, entry.clone()); + assert_eq!(res.err(), Some(Error::DagKeyNotFound)); + + let res = Transaction::new(&master_key, &None, SignWith::Root, entry.clone()); + assert_eq!(res.err(), Some(Error::DagKeyNotFound)); + + let body2 = TransactionBody::DeleteForwardV1 { name: "blassssstodon".into() }; + let entry2 = TransactionEntry::new(Timestamp::now(), vec![], body2); + let res = Transaction::new(&master_key, &None, SignWith::Alpha, entry2.clone()); + assert_eq!(res.err(), Some(Error::DagKeyNotFound)); + + let res = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Root, entry.clone()); + assert_eq!(res.err(), Some(Error::DagCreateIdentityOnExistingChain)); + + let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); + assert!(new_policy_keypair != policy_keypair); + let action = PolicyRequestAction::ReplaceKeys { + policy: new_policy_keypair.clone(), + publish: publish_keypair.clone(), + root: root_keypair.clone(), + }; + let entry = PolicyRequestEntry::new(IdentityID::random(), PolicyID::random(), action); + let req = PolicyRequest::new(&master_key, &new_policy_keypair, entry).unwrap(); + let body_recover = TransactionBody::ExecuteRecoveryPolicyV1 { request: req }; + let entry_recover = TransactionEntry::new(Timestamp::now(), vec![], body_recover); + let trans_recover = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Policy, entry_recover.clone()).unwrap(); + trans_recover.verify(Some(&identity)).unwrap(); + let res = Transaction::new(&master_key, &None, SignWith::Alpha, entry_recover.clone()); + assert_eq!(res.err(), Some(Error::DagKeyNotFound)); + let res = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Alpha, entry_recover.clone()); + assert_eq!(res.err(), Some(Error::DagKeyNotFound)); + let res = Transaction::new(&master_key, &Some(identity.clone()), SignWith::Root, entry_recover.clone()); + assert_eq!(res.err(), Some(Error::DagKeyNotFound)); + + let mut trans2 = trans.clone(); + trans2.set_id(TransactionID::random_alpha()); + assert_eq!(trans2.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); + + let mut trans3 = trans.clone(); + let then = Timestamp::from(now.deref().clone() - chrono::Duration::seconds(2)); + trans3.entry_mut().set_created(then); + assert_eq!(trans3.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); + + let mut trans4 = trans.clone(); + trans4.entry_mut().set_previous_transactions(vec![TransactionID::random_alpha()]); + assert_eq!(trans4.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); + + let mut trans5 = trans.clone(); + let root_keypair2 = AdminKeypair::new_ed25519(&master_key).unwrap(); + assert!(root_keypair != root_keypair2); + let body = TransactionBody::CreateIdentityV1 { + alpha: alpha_keypair.clone(), + policy: policy_keypair.clone(), + publish: publish_keypair.clone(), + root: root_keypair2.clone(), + }; + trans5.entry_mut().set_body(body); + assert_eq!(trans5.verify(None).err(), Some(Error::CryptoSignatureVerificationFailed)); + */ + } + + #[test] + fn trans_strip_has_private() { + todo!(); + /* + let master_key = SecretKey::new_xchacha20poly1305().unwrap(); + let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); + let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); + let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); + let root_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); + + let body = TransactionBody::CreateIdentityV1 { + alpha: alpha_keypair.clone(), + policy: policy_keypair.clone(), + publish: publish_keypair.clone(), + root: root_keypair.clone(), + }; + let entry = TransactionEntry::new(Timestamp::now(), vec![], body); + let trans = Transaction::new(&master_key, &None, SignWith::Alpha, entry.clone()).unwrap(); + + assert!(trans.has_private()); + assert!(trans.entry().has_private()); + assert!(trans.entry().body().has_private()); + let trans2 = trans.strip_private(); + assert!(!trans2.has_private()); + assert!(!trans2.entry().has_private()); + assert!(!trans2.entry().body().has_private()); + */ + } +} + diff --git a/src/dag/transactions.rs b/src/dag/transactions.rs new file mode 100644 index 0000000..5f791df --- /dev/null +++ b/src/dag/transactions.rs @@ -0,0 +1,1669 @@ +//! This module holds the logic of the DAG and also assists in the create of +//! valid [Transaction] objects. + +use crate::{ + error::{Error, Result}, + crypto::{ + key::{KeyID, SecretKey}, + }, + dag::{TransactionBody, TransactionID, TransactionEntry, Transaction}, + identity::{ + claim::{ + ClaimID, + ClaimSpec, + }, + identity::{ + IdentityID, + Identity, + }, + keychain::{ + AdminKey, + Key, + RevocationReason, + }, + stamp::{ + Stamp, + StampID, + StampEntry, + StampRevocation, + StampRevocationEntry, + StampRevocationID, + }, + }, + policy::{CapabilityPolicy}, + util::{ + Public, + Timestamp, + ser::{SerdeBinary}, + }, +}; +use getset; +use rasn::{Encode, Decode, AsnType}; +use serde_derive::{Serialize, Deserialize}; +use std::collections::HashMap; + +/// A container that holds a set of transactions. +#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] +#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] +pub struct Transactions { + /// The actual transactions. + #[rasn(tag(explicit(0)))] + transactions: Vec, +} + +impl Transactions { + /// Create a new, empty transaction set. + pub fn new() -> Self { + Self {transactions: vec![]} + } + + /// Returns an iterator over these transactions + pub fn iter(&self) -> core::slice::Iter<'_, Transaction> { + self.transactions().iter() + } + + /// Takes a master key, [Timestamp], and a [TransactionBody] and returns a + /// [Transaction] that can be signed and then pushed onto the list. + pub(crate) fn stage_transaction + Clone>(&self, now: T, body: TransactionBody) -> Result { + let leaves = Self::find_leaf_transactions(self.transactions()); + Transaction::new(TransactionEntry::new(now, leaves, body)) + } + + /// Run a transaction and return the output + fn apply_transaction(identity: Option, transaction: &Transaction) -> Result { + match transaction.entry().body().clone() { + // if this is a private transaction, just pass the identity + // back as-is + TransactionBody::CreateIdentityV1 { admin_keys, capabilities } => { + if identity.is_some() { + Err(Error::DagCreateIdentityOnExistingChain)?; + } + let identity_id = IdentityID::from(transaction.id().clone()); + Ok(Identity::create(identity_id, admin_keys, capabilities, transaction.entry().created().clone())) + } + TransactionBody::ResetIdentityV1 { admin_keys, capabilities } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .reset(admin_keys, capabilities)?; + Ok(identity_mod) + } + TransactionBody::AddAdminKeyV1 { admin_key } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .add_admin_key(admin_key)?; + Ok(identity_mod) + } + TransactionBody::EditAdminKeyV1 { id, name, description } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .edit_admin_key(&id, name, description)?; + Ok(identity_mod) + } + TransactionBody::RevokeAdminKeyV1 { id, reason, new_name } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .revoke_admin_key(&id, reason, new_name)?; + Ok(identity_mod) + } + TransactionBody::AddCapabilityPolicyV1 { capability } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .add_capability_policy(capability)?; + Ok(identity_mod) + } + TransactionBody::DeleteCapabilityPolicyV1 { name } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .delete_capability_policy(&name)?; + Ok(identity_mod) + } + TransactionBody::MakeClaimV1 { spec, name } => { + let claim_id = ClaimID(transaction.id().clone()); + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .make_claim(claim_id, spec, name)?; + Ok(identity_mod) + } + TransactionBody::EditClaimV1 { claim_id, name } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .edit_claim(&claim_id, name)?; + Ok(identity_mod) + } + TransactionBody::DeleteClaimV1 { claim_id } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .delete_claim(&claim_id)?; + Ok(identity_mod) + } + TransactionBody::MakeStampV1 { stamp: entry } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .make_stamp(Stamp::new(StampID::from(transaction.id().clone()), entry))?; + Ok(identity_mod) + } + TransactionBody::RevokeStampV1 { revocation: entry } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .revoke_stamp(StampRevocation::new(StampRevocationID::from(transaction.id().clone()), entry))?; + Ok(identity_mod) + } + TransactionBody::AcceptStampV1 { stamp_transaction } => { + stamp_transaction.verify_signatures()?; + let identity_mod = match stamp_transaction.entry().body() { + TransactionBody::MakeStampV1 { stamp: entry } => { + let stamp = Stamp::new(StampID::from(stamp_transaction.id().clone()), entry.clone()); + identity.ok_or(Error::DagMissingIdentity)? + .accept_stamp(stamp)? + } + _ => Err(Error::TransactionMismatch)?, + }; + Ok(identity_mod) + } + TransactionBody::DeleteStampV1 { stamp_id } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .delete_stamp(&stamp_id)?; + Ok(identity_mod) + } + TransactionBody::AddSubkeyV1 { key, name, desc } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .add_subkey(key, name, desc)?; + Ok(identity_mod) + } + TransactionBody::EditSubkeyV1 { id, new_name, new_desc } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .edit_subkey(&id, new_name, new_desc)?; + Ok(identity_mod) + } + TransactionBody::RevokeSubkeyV1 { id, reason, new_name } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .revoke_subkey(&id, reason, new_name)?; + Ok(identity_mod) + } + TransactionBody::DeleteSubkeyV1 { id } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .delete_subkey(&id)?; + Ok(identity_mod) + } + TransactionBody::SetNicknameV1 { nickname } => { + let identity_mod = identity.ok_or(Error::DagMissingIdentity)? + .set_nickname(nickname); + Ok(identity_mod) + } + TransactionBody::PublishV1 { .. } => { + // NOPE + Err(Error::TransactionInvalid("Publish transactions cannot be applied to identities".into())) + } + } + } + + /// Build an identity from our heroic transactions. + /// + /// Sounds easy, but it's actually a bit...odd. First we reverse our tree + /// of transactions so it's forward-looking. This means for any transaction + /// we can see which transactions come directly after it (as opposed to + /// directly before it). + /// + /// Then, we walk the tree and assign a unique branch number any time the + /// transactions branch or merge. This branch number can be looked up by + /// txid. + /// + /// Lastly, instead of trying to order the transactions what we do is push + /// the first one onto a "pending transactions" list, then run it. Once run, + /// we add any transactions that come after it to the pending list. The + /// pending list is them sorted by the transactions dates ascending (oldest + /// first) and we loop again, plucking the oldest transaction off the list + /// and running it. Now, for each transaction we run, we also apply it to + /// an identity that is specific to each previous branch the transaction + /// descended from. This allows us to easily merge identities from many + /// trees as we move along, but also has the benefit that the branch- + /// specific identity for our first branch (0) is also our *final* identity + /// because ALL transactions have been applied to it. It's a big, burly mess + /// but it works... + /// + /// NOTE: this algorithm handles signing key conflicts by only using the + /// nearest branch-level identity to *validate* the current transaction, + /// although the transaction is applied to all identities from previous + /// branches as well. However, this algorithm does not handle other + /// conflicts (such as duplicate entries). + pub fn build_identity(&self) -> Result { + if self.transactions().len() == 0 { + Err(Error::DagEmpty)?; + } + let transactions = self.transactions.clone(); + if transactions.len() == 0 { + Err(Error::DagEmpty)?; + } + + // use the `previous_transactions` collection to build a feed-forward + // index for transactions (basically, reverse the order of our tree). + // also, index our transactions by id. + let mut transaction_idx: HashMap = HashMap::new(); + let mut next_transactions_idx: HashMap> = HashMap::new(); + for trans in &transactions { + transaction_idx.insert(trans.id().clone(), trans); + let prev = trans.entry().previous_transactions(); + if prev.len() == 0 { continue; } + for trans_prev in prev { + let entry = next_transactions_idx.entry(trans_prev.clone()).or_insert(Vec::new()); + (*entry).push(trans.id().clone()); + } + } + + for trans in &transactions { + // make sure we don't have any orphaned transactions + for prev in trans.entry().previous_transactions() { + if !transaction_idx.contains_key(prev) { + Err(Error::DagOrphanedTransaction(String::from(trans.id())))?; + } + } + } + + // populate a transaction_id -> branchnum index + let mut transaction_branch_idx: HashMap> = HashMap::new(); + fn walker_identity_ranger(transaction_idx: &HashMap, next_transactions_idx: &HashMap>, transaction_branch_idx: &mut HashMap>, transaction: &Transaction, cur_branch: Vec) -> Result<()> { + fn push_branch(list: &Vec, branch_num: u32) -> Vec { + let mut list = list.clone(); + if list.contains(&branch_num) { + return list; + } + list.append(&mut vec![branch_num]); + list + } + let mut new_branch = 0; + // if this transaction merges one or more branches, it gets its own + // branch id + if transaction.entry().previous_transactions().len() > 1 { + new_branch += 1; + } + let default = Vec::new(); + let next = next_transactions_idx.get(transaction.id()).unwrap_or(&default); + transaction_branch_idx.insert(transaction.id().clone(), push_branch(&cur_branch, new_branch)); + // if this is a branch, give each branch a unique id + if next.len() > 1 { + new_branch += 1; + } + for trans_id in next { + let trans = transaction_idx.get(trans_id).ok_or(Error::DagBuildError)?; + walker_identity_ranger(transaction_idx, next_transactions_idx, transaction_branch_idx, trans, push_branch(&cur_branch, new_branch))?; + new_branch += 1; + } + Ok(()) + } + walker_identity_ranger(&transaction_idx, &next_transactions_idx, &mut transaction_branch_idx, &transactions[0], vec![0])?; + + #[derive(Debug, Default)] + struct WalkState<'a> { + // tracks our current run list + transactions_to_run: Vec<&'a Transaction>, + // tracks merge transactions, and how many ancestors have been run. + // when this number reaches previous_transactions().len(), then the + // merge is free to run. + pending_merges: HashMap, + } + + impl<'a> WalkState<'a> { + fn next(&self) -> Option<&Transaction> { + self.transactions_to_run.get(0).map(|x| *x) + } + + fn remove_first(&mut self) { + let tx_id = self.transactions_to_run[0].id(); + self.transactions_to_run.retain(|tx| tx.id() != tx_id); + } + + fn pop_transaction(&mut self, transaction_idx: &HashMap, next_transactions_idx: &HashMap>) -> Result { + if self.transactions_to_run.len() == 0 { + return Err(Error::DagBuildError)?; + } + let trans = self.transactions_to_run[0]; + self.remove_first(); + if let Some(next) = next_transactions_idx.get(trans.id()) { + for next_trans_id in next { + let entry = self.pending_merges.entry(next_trans_id.clone()).or_insert(0); + (*entry) += 1; + self.transactions_to_run.push(transaction_idx.get(next_trans_id).ok_or(Error::DagBuildError)?); + } + // TODO: optimize. sorting on every loop, tsk tsk. + self.transactions_to_run.sort_by_key(|t| t.entry().created()); + } + Ok(true) + } + } + + let mut state = WalkState::default(); + state.transactions_to_run.push( + transactions.iter().find(|x| x.entry().previous_transactions().len() == 0).ok_or(Error::DagNoGenesis)? + ); + let first_trans = match state.next() { + Some(trans) => trans, + None => Err(Error::DagBuildError)?, + }; + first_trans.verify(None)?; + + // tracks our per-branch identities + let mut branch_identities: HashMap = HashMap::new(); + branch_identities.insert(0, Transactions::apply_transaction(None, first_trans)?); + state.pop_transaction(&transaction_idx, &next_transactions_idx)?; + loop { + if let Some(trans) = state.next() { + let root_identity = branch_identities.get(&0).ok_or(Error::DagMissingIdentity)?.clone(); + let ancestors = transaction_branch_idx.get(trans.id()).ok_or(Error::DagBuildError)?; + let previous_len = trans.entry().previous_transactions().len(); + if previous_len > 1 { + let pending_count = state.pending_merges.get(trans.id()).unwrap_or(&0); + // ONLY run a merge transaction if all of its children have + // run!!1 + if *pending_count >= previous_len { + let ancestor_collection = trans.entry().previous_transactions().iter() + .map(|x| { + transaction_branch_idx.get(x) + .map(|ancestors| ancestors.clone().into_iter().rev().collect::>()) + .ok_or(Error::DagBuildError) + }) + .collect::>>>()?; + // now find the highest (ie, youngest) branch that is the + // common ancestor to the N branches we're merging right now + let first = ancestor_collection.get(0).ok_or(Error::DagBuildError)?; + let mut found_branch = None; + for branch in first { + let mut has = true; + for anc in &ancestor_collection[1..] { + if !has || !anc.contains(branch) { + has = false; + } + } + if has { + found_branch = Some(branch); + break; + } + } + let found_branch = found_branch.ok_or(Error::DagBuildError)?; + { + let common_ancestor_identity = branch_identities.get_mut(found_branch).ok_or(Error::DagBuildError)?; + trans.verify(Some(&common_ancestor_identity))?; + } + // apply this transaction to all of its ancestor branches + let mut tracker = HashMap::new(); + for ancestors in ancestor_collection { + for branch in &ancestors { + if tracker.get(branch).is_some() { + continue; + } + let branch_identity = branch_identities.entry(*branch).or_insert(root_identity.clone()); + (*branch_identity) = Transactions::apply_transaction(Some((*branch_identity).clone()), trans)?; + tracker.insert(*branch, true); + } + } + } else { + state.remove_first(); + continue; + } + } else { + let current_branch_identity = branch_identities.entry(*(*ancestors).last().unwrap()).or_insert(root_identity.clone()); + trans.verify(Some(¤t_branch_identity))?; + // apply this transaction to all of its ancestor branches + for branch in ancestors { + let branch_identity = branch_identities.entry(*branch).or_insert(root_identity.clone()); + (*branch_identity) = Transactions::apply_transaction(Some((*branch_identity).clone()), trans)?; + } + } + state.pop_transaction(&transaction_idx, &next_transactions_idx)?; + } else { + break; + } + } + Ok(branch_identities.get(&0).ok_or(Error::DagMissingIdentity)?.clone()) + } + + /// Find any transactions that are not referenced as previous transactions. + /// Effectively, the leaves of our graph. + fn find_leaf_transactions(transaction_list: &Vec) -> Vec { + let mut seen: HashMap = HashMap::new(); + for trans in transaction_list { + for prev in trans.entry().previous_transactions() { + seen.insert(prev.clone(), true); + } + } + transaction_list.iter() + .filter_map(|t| { + if seen.get(t.id()).is_some() { + None + } else { + Some(t.id().clone()) + } + }) + .collect::>() + } + + /// Push a transaction into the transactions list, and return the resulting + /// identity object from running all transactions in order. + pub fn push_transaction(mut self, transaction: Transaction) -> Result { + self.push_transaction_raw(transaction)?; + Ok(self) + } + + /// Push a raw transaction onto this transaction set. Generally, this might + /// come from a syncing source (StampNet's private syncing) that passes + /// around singular transactions. We verify this transactions by building + /// the identity after pushing. + pub fn push_transaction_raw(&mut self, transaction: Transaction) -> Result { + let identity_maybe = match self.build_identity() { + Ok(id) => Some(id), + Err(Error::DagEmpty) => None, + Err(e) => Err(e)?, + }; + let identity = Self::apply_transaction(identity_maybe, &transaction)?; + self.transactions_mut().push(transaction); + // build it again + let _identity_maybe = match self.build_identity() { + Ok(id) => Some(id), + Err(Error::DagEmpty) => None, + Err(e) => Err(e)?, + }; + Ok(identity) + } + + /// Merge the transactions from two transaction sets together. + pub fn merge(mut branch1: Self, branch2: Self) -> Result { + for trans2 in branch2.transactions() { + // if it already exists, don't merge it + if branch1.transactions().iter().find(|t| t.id() == trans2.id()).is_some() { + continue; + } + branch1.transactions_mut().push(trans2.clone()); + } + // make sure it's all copasetic. + branch1.build_identity()?; + Ok(branch1) + } + + /// Reset a set of transactions to a previous state. + /// + /// Effectively, we take a transaction ID and remove any transactions that + /// came after it. This may create many trailing transactions, which will be + /// connected the next time a new transaction is created. + pub fn reset(mut self, txid: &TransactionID) -> Result { + // recursively find all transactions referencing the given one + fn find_tx_to_rm(transactions: &Vec, txid: &TransactionID) -> Vec { + let mut to_remove = Vec::new(); + for trans in transactions { + if trans.entry().previous_transactions().contains(txid) { + to_remove.push(trans.id().clone()); // i hate this clone, but w/e + to_remove.append(&mut find_tx_to_rm(transactions, trans.id())); + } + } + to_remove + } + let remove_tx = find_tx_to_rm(self.transactions(), txid); + self.transactions_mut().retain(|t| !remove_tx.contains(t.id())); + Ok(self) + } + + /// Reencrypt this transaction set with a new master key. + pub fn reencrypt(mut self, old_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { + for trans in self.transactions_mut() { + *trans = trans.clone().reencrypt(old_master_key, new_master_key)?; + } + Ok(self) + } + + /// Determine if this identity is owned (ie, we have the private keys stored + /// locally) or it is imported (ie, someone else's identity). + pub fn is_owned(&self) -> bool { + self.transactions().iter().find(|trans| { + match trans.entry().body() { + TransactionBody::CreateIdentityV1 { .. } => trans.entry().body().has_private(), + TransactionBody::AddAdminKeyV1 { .. } => trans.entry().body().has_private(), + _ => false, + } + }).is_some() + } + + /// Test if a master key is correct. + pub fn test_master_key(&self, master_key: &SecretKey) -> Result<()> { + if !self.is_owned() { + Err(Error::IdentityNotOwned)?; + } + + let identity = self.build_identity()?; + identity.test_master_key(master_key) + } + + // ------------------------------------------------------------------------- + // The actual transaction builder methods + // ------------------------------------------------------------------------- + + /// Create a new identity. The [ID][TranscationID] of this transaction will + /// be the identity's public ID forever after. + pub fn create_identity + Clone>(&self, now: T, admin_keys: Vec, capabilities: Vec) -> Result { + let body = TransactionBody::CreateIdentityV1 { + admin_keys, + capabilities, + }; + self.stage_transaction(now, body) + } + + /// Replace optionally both the [admin keys][AdminKey] in the [Keychain] and the + /// [capabilities][CapabilityPolicy] attached to the identity. + /// + /// This is more or less a hailmary recovery option that allows gaining + /// access to identity after some kind of catastrophic event. + pub fn reset_identity + Clone>(&self, now: T, admin_keys: Option>, capabilities: Option>) -> Result { + let body = TransactionBody::ResetIdentityV1 { + admin_keys, + capabilities, + }; + self.stage_transaction(now, body) + } + + /// Add a new [admin key][AdminKey] to the [Keychain]. + pub fn add_admin_key + Clone>(&self, now: T, admin_key: AdminKey) -> Result { + let body = TransactionBody::AddAdminKeyV1 { + admin_key, + }; + self.stage_transaction(now, body) + } + + /// Edit an [admin key][AdminKey]. + pub fn edit_admin_key(&self, now: T, id: KeyID, name: Option, description: Option>) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::EditAdminKeyV1 { + id, + name: name.map(|x| x.into()), + description: description.map(|x| x.map(|y| y.into())), + }; + self.stage_transaction(now, body) + } + + /// Revokes an [AdminKey] key and moves it into the subkeys, optionally + /// renaming it. + pub fn revoke_admin_key(&self, now: T, id: KeyID, reason: RevocationReason, new_name: Option) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::RevokeAdminKeyV1 { + id, + reason, + new_name: new_name.map(|x| x.into()), + }; + self.stage_transaction(now, body) + } + + /// Add a new [capability policy][CapabilityPolicy] to the identity. + pub fn add_capability_policy + Clone>(&self, now: T, capability: CapabilityPolicy) -> Result { + let body = TransactionBody::AddCapabilityPolicyV1 { + capability, + }; + self.stage_transaction(now, body) + } + + /// Delete (by name) a capability policy from the identity. + pub fn delete_capability_policy(&self, now: T, name: S) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::DeleteCapabilityPolicyV1 { + name: name.into(), + }; + self.stage_transaction(now, body) + } + + /// Make a new claim. + pub fn make_claim(&self, now: T, spec: ClaimSpec, name: Option) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::MakeClaimV1 { + spec, + name: name.map(|x| x.into()), + }; + self.stage_transaction(now, body) + } + + /// Edit a claim. + pub fn edit_claim(&self, now: T, claim_id: ClaimID, name: Option) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::EditClaimV1 { + claim_id, + name: name.map(|x| x.into()), + }; + self.stage_transaction(now, body) + } + + /// Delete an existing claim. + pub fn delete_claim + Clone>(&self, now: T, claim_id: ClaimID) -> Result { + let body = TransactionBody::DeleteClaimV1 { + claim_id, + }; + self.stage_transaction(now, body) + } + + /// Make a transaction that stamps a claim. This transaction can be saved + /// with the stemping identity (stamper) in order to advertise it as a public + /// stamp. + /// + /// It can also not be added to the identity and sent directly to the stampee. + pub fn make_stamp + Clone>(&self, now: T, stamp: StampEntry) -> Result { + let body = TransactionBody::MakeStampV1 { + stamp, + }; + self.stage_transaction(now, body) + } + + /// Revoke a stamp we previously created and store this revocation with the + /// identity. + pub fn revoke_stamp + Clone>(&self, now: T, revocation: StampRevocationEntry) -> Result { + let body = TransactionBody::RevokeStampV1 { + revocation, + }; + self.stage_transaction(now, body) + } + + /// Accept a stamp someone, or some*thing*, has made on a claim of ours. + pub fn accept_stamp + Clone>(&self, now: T, stamp_transaction: Transaction) -> Result { + if !matches!(stamp_transaction.entry().body(), TransactionBody::MakeStampV1 { .. }) { + Err(Error::TransactionMismatch)?; + } + let body = TransactionBody::AcceptStampV1 { + stamp_transaction: Box::new(stamp_transaction), + }; + self.stage_transaction(now, body) + } + + /// Delete an existing stamp. + pub fn delete_stamp + Clone>(&self, now: T, stamp_id: StampID) -> Result { + let body = TransactionBody::DeleteStampV1 { + stamp_id, + }; + self.stage_transaction(now, body) + } + + /// Add a new subkey to our keychain. + pub fn add_subkey(&self, now: T, key: Key, name: S, desc: Option) -> Result + where T: Into + Clone, + S: Into, + { + if matches!(key, Key::Admin(_)) { + Err(Error::TransactionInvalid("Admin keys cannot be added as subkeys".into()))?; + } + let body = TransactionBody::AddSubkeyV1 { + key, + name: name.into(), + desc: desc.map(|x| x.into()), + }; + self.stage_transaction(now, body) + } + + /// Edit a subkey. + pub fn edit_subkey(&self, now: T, id: KeyID, new_name: Option, new_desc: Option>) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::EditSubkeyV1 { + id, + new_name: new_name.map(|x| x.into()), + new_desc: new_desc.map(|x| x.map(|y| y.into())), + }; + self.stage_transaction(now, body) + } + + /// Revoke a subkey. + pub fn revoke_subkey(&self, now: T, id: KeyID, reason: RevocationReason, new_name: Option) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::RevokeSubkeyV1 { + id, + reason, + new_name: new_name.map(|x| x.into()), + }; + self.stage_transaction(now, body) + } + + /// Delete a subkey. + pub fn delete_subkey + Clone>(&self, now: T, id: KeyID) -> Result { + let body = TransactionBody::DeleteSubkeyV1 { + id, + }; + self.stage_transaction(now, body) + } + + /// Set the nickname on this identity. + pub fn set_nickname(&self, now: T, nickname: Option) -> Result + where T: Into + Clone, + S: Into, + { + let body = TransactionBody::SetNicknameV1 { + nickname: nickname.map(|x| x.into()), + }; + self.stage_transaction(now, body) + } +} + +impl Public for Transactions { + fn strip_private(&self) -> Self { + let mut clone = self.clone(); + let stripped = self.transactions().iter().map(|x| x.strip_private()).collect::>(); + clone.set_transactions(stripped); + clone + } + + fn has_private(&self) -> bool { + self.transactions().iter().find(|x| x.has_private()).is_some() + } +} + +impl IntoIterator for Transactions { + type Item = Transaction; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let Transactions { transactions } = self; + transactions.into_iter() + } +} + +impl SerdeBinary for Transactions {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + crypto::key::{SignKeypair, CryptoKeypair}, + identity::{ + claim::{Relationship, RelationshipType}, + keychain::{AdminKeypair, ExtendKeypair}, + stamp::Confidence, + }, + policy::{Capability, Context, Participant, Policy, TransactionBodyType}, + private::{PrivateWithHmac, MaybePrivate}, + util::{Date, Url, ser::BinaryVec, test}, + }; + use std::str::FromStr; + + macro_rules! sign_and_push { + ($master_key:expr, $admin_key:expr, $transactions:expr, $([ $fn:ident, $($args:expr),* ])*) => {{ + let mut trans_tmp = $transactions; + $( + let trans = trans_tmp.$fn($($args),*).unwrap(); + let trans_signed = trans.sign($master_key, $admin_key).unwrap(); + trans_tmp = trans_tmp.push_transaction(trans_signed).unwrap(); + )* + trans_tmp + }}; + } + + fn genesis_time(now: Timestamp) -> (SecretKey, Transactions, AdminKey) { + test::create_fake_identity(now) + } + + fn genesis() -> (SecretKey, Transactions, AdminKey) { + genesis_time(Timestamp::now()) + } + + #[test] + fn transactions_identity_id_is_genesis_transaction() { + let (_master_key, transactions, _admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + assert_eq!(IdentityID::from(transactions.transactions()[0].id().clone()), identity.id().clone()); + } + + #[test] + fn transactions_push() { + let now = Timestamp::from_str("2021-04-20T00:00:10Z").unwrap(); + let (master_key_1, mut transactions_1, admin_key_1) = genesis_time(now.clone()); + let (_master_key_2, mut transactions_2, _admin_key_2) = genesis_time(now.clone()); + let trans_claim_signed = transactions_1 + .make_claim(now.clone(), ClaimSpec::Name(MaybePrivate::new_public("Hooty McOwl".to_string())), None::).unwrap() + .sign(&master_key_1, &admin_key_1).unwrap(); + transactions_1.push_transaction(trans_claim_signed.clone()).unwrap(); + transactions_2.build_identity().unwrap(); + match transactions_2.push_transaction_raw(trans_claim_signed.clone()) { + Ok(_) => panic!("pushed a bad raw transaction: {}", String::from(trans_claim_signed.id())), + Err(e) => assert_eq!(e, Error::DagOrphanedTransaction(String::from(trans_claim_signed.id()))), + } + } + + #[test] + fn transactions_merge_reset() { + todo!(); + /* + let (master_key, transactions, admin_key) = genesis_time(Timestamp::from_str("2021-04-20T00:00:00Z").unwrap()); + // make some claims on my smart refrigerator + let new_root1 = RootKeypair::new_ed25519(&master_key).unwrap(); + let new_root2 = RootKeypair::new_ed25519(&master_key).unwrap(); + let admin_key_2 = AdminKey::new(AdminKeypair::from(SignKeypair::new_ed25519(&master_key).unwrap()), "Alpha", None); + let admin_key_3 = AdminKey::new(AdminKeypair::from(SignKeypair::new_ed25519(&master_key).unwrap()), "Alpha", None); + let branch1 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [ make_claim, Timestamp::from_str("2021-04-20T00:00:10Z").unwrap(), ClaimSpec::Name(MaybePrivate::new_public("Hooty McOwl".to_string())), None:: ] + [ add_admin_key, Timestamp::from_str("2021-04-20T00:01:00Z").unwrap(), admin_key_2 ] + [ revoke_admin_key, Timestamp::("2021-04-20T00:01:01Z").unwrap(), "Alpha" ] + [ set_nickname, Timestamp::from_str("2021-04-20T00:01:33Z").unwrap(), Some("dirk-delta") ] + }; + // make some claims on my Facebook (TM) (R) (C) Brain (AND NOW A WORD FROM OUR SPONSORS) Implant + let branch2 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [ make_claim, Timestamp::from_str("2021-04-20T00:00:30Z").unwrap(), ClaimSpec::Url(MaybePrivate::new_public(Url::try_from("https://www.cactus-petes.com/yeeeehawwww").unwrap())), None:: ] + [ set_root_key, Timestamp::from_str("2021-04-20T00:01:36Z").unwrap(), new_root2.clone(), RevocationReason::Unspecified ] + [ set_nickname, Timestamp::from_str("2021-04-20T00:01:45Z").unwrap(), Some("liberal hokes") ] + [ make_claim, Timestamp::from_str("2021-04-20T00:01:56Z").unwrap(), ClaimSpec::Email(MaybePrivate::new_public(String::from("dirk.delta@hollywood.com"))), None:: ] + }; + let identity1 = branch1.build_identity().unwrap(); + let identity2 = branch2.build_identity().unwrap(); + assert_eq!(identity1.nickname(), Some(&String::from("dirk-delta"))); + assert_eq!(identity1.keychain().root(), &new_root1); + assert_eq!(identity2.nickname(), Some(&String::from("liberal hokes"))); + assert!(identity2.keychain().root() != &new_root1); + assert_eq!(identity2.keychain().root(), &new_root2); + let transactions2 = Transactions::merge(branch1.clone(), branch2.clone()).unwrap(); + assert_eq!(branch1.transactions().len(), 4); + assert_eq!(branch2.transactions().len(), 5); + assert_eq!(transactions2.transactions().len(), 8); + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2.clone(), + [ make_claim, Timestamp::from_str("2021-04-20T00:05:22Z").unwrap(), ClaimSpec::Url(MaybePrivate::new_public(Url::try_from("https://www.cactus-petes.com/yeeeehawwww").unwrap())), None:: ] + }; + assert_eq!(transactions3.transactions().len(), 9); + let identity3 = transactions3.build_identity().unwrap(); + assert_eq!(identity3.nickname(), Some(&String::from("liberal hokes"))); + assert_eq!(identity3.claims().len(), 2); + assert_eq!(identity3.keychain().root(), &new_root2); + */ + } + + #[test] + fn transactions_genesis() { + let (master_key, transactions, admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + let res = transactions.clone().push_transaction( + transactions + .create_identity(Timestamp::now(), identity.keychain().admin_keys().clone(), identity.capabilities().clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DagCreateIdentityOnExistingChain)); + + let transactions2 = Transactions::new(); + let res = transactions2.clone().push_transaction( + transactions2 + .make_claim(Timestamp::now(), ClaimSpec::Name(MaybePrivate::new_public("Stinky Wizzleteets".into())), None::).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DagMissingIdentity)); + } + + #[test] + fn transactions_create_identity() { + let (master_key, transactions, admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + assert_eq!(identity.id(), &IdentityID::from(transactions.transactions()[0].id().clone())); + assert_eq!(identity.keychain().admin_keys().len(), 1); + assert_eq!(identity.capabilities().len(), 1); + + let res = transactions.clone().push_transaction( + transactions + .create_identity(Timestamp::now(), vec![], vec![]).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DagCreateIdentityOnExistingChain)); + } + + #[test] + fn transactions_reset_identity() { + let (master_key, transactions, admin_key) = genesis(); + let admin_key2 = AdminKey::new(AdminKeypair::new_ed25519(&master_key).unwrap(), "Alpha", None); + let admin_key3 = AdminKey::new(AdminKeypair::new_ed25519(&master_key).unwrap(), "Zing", None); + let capability2 = Capability::Transaction { body_type: TransactionBodyType::ResetIdentityV1, context: Context::Permissive }; + let capability3 = Capability::Transaction { body_type: TransactionBodyType::AcceptStampV1, context: Context::IdentityID(IdentityID::random()) }; + let policy2 = CapabilityPolicy::new( + "WOW".into(), + vec![capability2], + Policy::MOfN { must_have: 0, participants: vec![] } + ); + let policy3 = CapabilityPolicy::new( + "ZOMG".into(), + vec![capability3], + Policy::MOfN { must_have: 1, participants: vec![] } + ); + let identity1 = transactions.build_identity().unwrap(); + assert_eq!(identity1.keychain().admin_keys().len(), 1); + assert!(identity1.keychain().admin_key_by_name("Alpha").is_some()); + assert_eq!(identity1.capabilities().len(), 1); + assert_eq!(identity1.capabilities()[0].name(), "default"); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ reset_identity, Timestamp::now(), Some(vec![admin_key2.clone(), admin_key3.clone()]), Some(vec![policy2, policy3]) ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.keychain().admin_keys().len(), 2); + assert_eq!(identity2.keychain().admin_key_by_name("Alpha").unwrap().key(), admin_key2.key()); + assert!(identity2.keychain().admin_key_by_name("Zing").is_some()); + assert_eq!(identity2.capabilities().len(), 2); + assert_eq!(identity2.capabilities()[0].name(), "WOW"); + assert_eq!(identity2.capabilities()[1].name(), "ZOMG"); + } + + #[test] + fn transactions_add_admin_key() { + let (master_key, transactions, admin_key) = genesis(); + let identity1 = transactions.build_identity().unwrap(); + assert_eq!(identity1.keychain().admin_keys().len(), 1); + assert_eq!(identity1.keychain().admin_key_by_keyid(&admin_key.key_id()).map(|x| x.key()), Some(admin_key.key())); + + let admin_key2 = AdminKey::new(AdminKeypair::new_ed25519(&master_key).unwrap(), "publish key lol", None); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [add_admin_key, Timestamp::now(), admin_key2.clone() ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.keychain().admin_keys().len(), 2); + assert_eq!(identity2.keychain().admin_key_by_name("Alpha").map(|x| x.key()), Some(admin_key.key())); + assert_eq!(identity2.keychain().admin_key_by_name("publish key lol").map(|x| x.key()), Some(admin_key2.key())); + + let res = transactions2.clone().push_transaction( + transactions2 + .add_admin_key(Timestamp::now(), admin_key2.clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DuplicateName)); + } + + #[test] + fn transactions_edit_admin_key() { + let (master_key, transactions, admin_key) = genesis(); + let identity1 = transactions.build_identity().unwrap(); + assert_eq!(identity1.keychain().admin_keys().len(), 1); + assert_eq!(identity1.keychain().subkeys().len(), 0); + assert_eq!(identity1.keychain().admin_key_by_keyid(&admin_key.key_id()).map(|x| x.key()), Some(admin_key.key())); + + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [ revoke_admin_key, Timestamp::now(), admin_key.key_id(), RevocationReason::Compromised, Some("rotten") ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.keychain().admin_keys().len(), 0); + assert!(identity2.keychain().admin_key_by_keyid(&admin_key.key_id()).is_none()); + assert_eq!(identity2.keychain().subkeys().len(), 1); + assert!(identity2.keychain().subkey_by_name("Alpha").is_none()); + assert!(matches!(identity2.keychain().subkey_by_name("rotten").unwrap().key(), Key::Admin(_))); + + let res = transactions2.clone().push_transaction( + transactions2 + .revoke_admin_key(Timestamp::now(), admin_key.key_id(), RevocationReason::Compromised, Some("rotten")).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::KeychainKeyNotFound(admin_key.key_id()))) + } + + #[test] + fn transactions_revoke_admin_key() { + let (master_key, transactions, admin_key) = genesis(); + let identity1 = transactions.build_identity().unwrap(); + assert_eq!(identity1.keychain().admin_keys().len(), 1); + assert_eq!(identity1.keychain().subkeys().len(), 0); + assert_eq!(identity1.keychain().admin_key_by_keyid(&admin_key.key_id()).map(|x| x.key()), Some(admin_key.key())); + + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [ revoke_admin_key, Timestamp::now(), admin_key.key_id(), RevocationReason::Compromised, Some("rotten") ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.keychain().admin_keys().len(), 0); + assert!(identity2.keychain().admin_key_by_name("Alpha").is_none()); + assert_eq!(identity2.keychain().subkeys().len(), 1); + assert!(identity2.keychain().subkey_by_name("Alpha").is_none()); + assert!(matches!(identity2.keychain().subkey_by_name("rotten").unwrap().key(), Key::Admin(_))); + + let res = transactions2.clone().push_transaction( + transactions2 + .revoke_admin_key(Timestamp::now(), admin_key.key_id(), RevocationReason::Compromised, Some("rotten")).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::KeychainKeyNotFound(admin_key.key_id()))) + } + + #[test] + fn transactions_add_capability_policy() { + let (master_key, transactions, admin_key) = genesis(); + let capability2 = Capability::Transaction { body_type: TransactionBodyType::ResetIdentityV1, context: Context::Permissive }; + let policy2 = CapabilityPolicy::new( + "WOW".into(), + vec![capability2], + Policy::MOfN { must_have: 0, participants: vec![] } + ); + + let identity1 = transactions.build_identity().unwrap(); + assert_eq!(identity1.capabilities().len(), 1); + assert_eq!(identity1.capabilities()[0].name(), "default"); + + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ add_capability_policy, Timestamp::now(), policy2.clone() ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.capabilities().len(), 2); + assert_eq!(identity2.capabilities()[0].name(), "default"); + assert_eq!(identity2.capabilities()[1].name(), "WOW"); + + let res = transactions2.clone().push_transaction( + transactions2 + .add_capability_policy(Timestamp::now(), policy2.clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DuplicateName)); + } + + #[test] + fn transactions_delete_capability_policy() { + let (master_key, transactions, admin_key) = genesis(); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ delete_capability_policy, Timestamp::now(), "default" ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.capabilities().len(), 0); + + let res = transactions2.clone().push_transaction( + transactions2 + .delete_capability_policy(Timestamp::now(), "default").unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::IdentityCapabilityPolicyNotFound("default".into()))); + } + + #[test] + fn transactions_make_claim() { + let (master_key, transactions, admin_key) = genesis(); + + macro_rules! make_specs { + ($master:expr, $claimmaker:expr, $val:expr) => {{ + let val = $val.clone(); + let maybe_private = MaybePrivate::new_private(&$master, val.clone()).unwrap(); + let maybe_public = MaybePrivate::new_public(val.clone()); + let spec_private = $claimmaker(maybe_private, val.clone()); + let spec_public = $claimmaker(maybe_public, val.clone()); + (spec_private, spec_public) + }} + } + + macro_rules! assert_claim { + (raw, $claimmaker:expr, $val:expr, $get_maybe:expr) => { + let val = $val; + let (spec_private, spec_public) = make_specs!(master_key, $claimmaker, val); + + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [ make_claim, Timestamp::now(), spec_private, None:: ] + }; + let identity2 = transactions2.build_identity().unwrap(); + let maybe = $get_maybe(identity2.claims()[0].spec().clone()); + assert_eq!(maybe.open(&master_key).unwrap(), val); + assert_eq!(identity2.claims().len(), 1); + assert_eq!(transactions2.transactions().len(), 2); + + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [ make_claim, Timestamp::now(), spec_public, None:: ] + }; + let identity2 = transactions2.build_identity().unwrap(); + let maybe = $get_maybe(identity2.claims()[0].spec().clone()); + assert_eq!(maybe.open(&master_key).unwrap(), val); + assert_eq!(identity2.claims().len(), 1); + assert_eq!(transactions2.transactions().len(), 2); + }; + + ($claimty:ident, $val:expr) => { + assert_claim! { + raw, + |maybe, _| ClaimSpec::$claimty(maybe), + $val, + |spec: ClaimSpec| if let ClaimSpec::$claimty(maybe) = spec { maybe } else { panic!("bad claim type: {}", stringify!($claimty)) } + } + }; + } + + let identity = transactions.build_identity().unwrap(); + assert_eq!(identity.claims().len(), 0); + assert_eq!(transactions.transactions().len(), 1); + + assert_claim!{ Identity, identity.id().clone() } + assert_claim!{ Name, String::from("Marty Malt") } + assert_claim!{ Birthday, Date::from_str("2010-01-03").unwrap() } + assert_claim!{ Email, String::from("marty@sids.com") } + assert_claim!{ Photo, BinaryVec::from(vec![1, 2, 3]) } + assert_claim!{ Pgp, String::from("12345") } + assert_claim!{ Domain, String::from("slappy.com") } + assert_claim!{ Url, Url::parse("https://killtheradio.net/").unwrap() } + assert_claim!{ Address, String::from("111 blumps ln") } + assert_claim!{ Relation, Relationship::new(RelationshipType::OrganizationMember, IdentityID::random()) } + assert_claim!{ RelationExtension, Relationship::new(RelationshipType::OrganizationMember, BinaryVec::from(vec![1, 2, 3, 4, 5])) } + assert_claim!{ + raw, + |maybe, _| ClaimSpec::Extension { key: String::from("id:state:ca"), value: maybe }, + BinaryVec::from(vec![7, 3, 2, 90]), + |spec: ClaimSpec| if let ClaimSpec::Extension { value: maybe, .. } = spec { maybe } else { panic!("bad claim type: {}", stringify!($claimtype)) } + } + } + + #[test] + fn transactions_edit_claim() { + todo!("test editing claims"); + } + + #[test] + fn transactions_delete_claim() { + let (master_key, transactions, admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + assert_eq!(identity.claims().len(), 0); + assert_eq!(transactions.transactions().len(), 1); + + let identity_id = IdentityID::from(transactions.transactions()[0].id().clone()); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ make_claim, Timestamp::now(), ClaimSpec::Identity(MaybePrivate::new_public(identity_id)), None:: ] + }; + assert_eq!(transactions2.transactions().len(), 2); + + let identity = transactions2.build_identity().unwrap(); + let claim_id = identity.claims()[0].id().clone(); + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2.clone(), + [delete_claim, Timestamp::now(), claim_id.clone()] + }; + assert_eq!(transactions3.transactions().len(), 3); + + let res = transactions2.clone() + .push_transaction( + transactions2 + .delete_claim(Timestamp::now(), ClaimID::random()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); + let res = transactions3.clone() + .push_transaction( + transactions3 + .delete_claim(Timestamp::now(), claim_id.clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); + } + + #[test] + fn transactions_make_stamp() { + let (master_key, transactions, admin_key) = genesis(); + let identity_id = IdentityID::from(transactions.transactions()[0].id().clone()); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ make_claim, Timestamp::now(), ClaimSpec::Identity(MaybePrivate::new_public(identity_id)), None:: ] + }; + let identity = transactions2.build_identity().unwrap(); + let claim = identity.claims()[0].clone(); + + let (master_key_stamper, transactions_stamper, admin_key_stamper) = genesis(); + + let identity_stamper1 = transactions_stamper.build_identity().unwrap(); + assert_eq!(identity_stamper1.stamps().stamps().len(), 0); + + let entry = StampEntry::new( + IdentityID::from(transactions_stamper.transactions()[0].id().clone()), + identity.id().clone(), + claim.id().clone(), + Confidence::Low, + Some(Timestamp::from_str("2060-01-01T06:59:00Z").unwrap()) + ); + + let make_stamp_trans = transactions_stamper + .make_stamp(Timestamp::now(), entry).unwrap() + .sign(&master_key_stamper, &admin_key_stamper).unwrap(); + let transactions_stamper2 = transactions_stamper + .push_transaction(make_stamp_trans.clone()) + .unwrap(); + let identity_stamper2 = transactions_stamper2.build_identity().unwrap(); + assert_eq!(identity_stamper2.stamps().stamps().len(), 1); + + let res = transactions_stamper2.clone().push_transaction(make_stamp_trans.clone()); + assert_eq!(res.err(), Some(Error::IdentityStampAlreadyExists)); + } + + #[test] + fn transactions_revoke_stamp() { + let (master_key, transactions, admin_key) = genesis(); + let identity_id = IdentityID::from(transactions.transactions()[0].id().clone()); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ make_claim, Timestamp::now(), ClaimSpec::Identity(MaybePrivate::new_public(identity_id)), None:: ] + }; + + let (master_key_stamper, transactions_stamper, admin_key_stamper) = genesis(); + let identity_stamper1 = transactions_stamper.build_identity().unwrap(); + assert_eq!(identity_stamper1.stamps().stamps().len(), 0); + + let identity_stampee2 = transactions2.build_identity().unwrap(); + let claim = identity_stampee2.claims()[0].clone(); + let entry = StampEntry::new( + IdentityID::from(transactions_stamper.transactions()[0].id().clone()), + identity_stampee2.id().clone(), + claim.id().clone(), + Confidence::Low, + Some(Timestamp::from_str("2060-01-01T06:59:00Z").unwrap()) + ); + + let make_stamp_trans = transactions_stamper + .make_stamp(Timestamp::now(), entry).unwrap() + .sign(&master_key_stamper, &admin_key_stamper).unwrap(); + let transactions_stamper2 = transactions_stamper + .push_transaction(make_stamp_trans.clone()) + .unwrap(); + let identity_stamper2 = transactions_stamper2.build_identity().unwrap(); + assert_eq!(identity_stamper2.stamps().stamps().len(), 1); + + let revocation = StampRevocationEntry::new( + identity_stamper2.id().clone(), + identity_stampee2.id().clone(), + identity_stamper2.stamps().stamps()[0].id().clone() + ); + let revoke_trans = transactions_stamper2 + .revoke_stamp(Timestamp::now(), revocation.clone()).unwrap() + .sign(&master_key_stamper, &admin_key_stamper).unwrap(); + let transactions_stamper3 = transactions_stamper2.clone() + .push_transaction(revoke_trans.clone()).unwrap(); + + let res = transactions_stamper3.clone() + .push_transaction(revoke_trans.clone()); + assert_eq!(res.err(), Some(Error::IdentityStampRevocationAlreadyExists)); + + // same revocation, different id, should work fine + sign_and_push! { &master_key_stamper, &admin_key_stamper, transactions_stamper3.clone(), + [ revoke_stamp, Timestamp::now(), revocation.clone() ] + }; + } + + #[test] + fn transactions_accept_stamp() { + let (master_key, transactions, admin_key) = genesis(); + let identity_id = IdentityID::from(transactions.transactions()[0].id().clone()); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ make_claim, Timestamp::now(), ClaimSpec::Identity(MaybePrivate::new_public(identity_id)), None:: ] + }; + let identity = transactions2.build_identity().unwrap(); + assert_eq!(identity.claims()[0].stamps().len(), 0); + let claim = identity.claims()[0].clone(); + + let (master_key_stamper, transactions_stamper, admin_key_stamper) = genesis(); + let entry = StampEntry::new( + IdentityID::from(transactions_stamper.transactions()[0].id().clone()), + identity.id().clone(), + claim.id().clone(), + Confidence::Low, + Some(Timestamp::from_str("2060-01-01T06:59:00Z").unwrap()) + ); + let stamp_transaction_unsigned = transactions_stamper + .make_stamp(Timestamp::now(), entry).unwrap(); + let stamp_transaction = stamp_transaction_unsigned.clone() + .sign(&master_key_stamper, &admin_key_stamper).unwrap(); + let not_stamp_transaction = transactions_stamper + .make_claim(Timestamp::now(), ClaimSpec::Name(MaybePrivate::new_public("Butch".into())), None::).unwrap() + .sign(&master_key_stamper, &admin_key_stamper).unwrap(); + + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2, + [ accept_stamp, Timestamp::now(), stamp_transaction.clone() ] + }; + assert_eq!(transactions3.transactions().len(), 3); + let identity3 = transactions3.build_identity().unwrap(); + assert_eq!(identity3.claims()[0].stamps().len(), 1); + + let res = transactions3.clone().push_transaction( + transactions3 + .accept_stamp(Timestamp::now(), stamp_transaction_unsigned.clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::TransactionNoSignatures)); + + let res = transactions3 + .accept_stamp(Timestamp::now(), not_stamp_transaction.clone()); + assert_eq!(res.err(), Some(Error::TransactionMismatch)); + + let res = transactions3.clone().push_transaction( + transactions3 + .accept_stamp(Timestamp::now(), stamp_transaction.clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::IdentityStampAlreadyExists)); + + let transactions4 = sign_and_push! { &master_key, &admin_key, transactions3.clone(), + [ delete_claim, Timestamp::now(), claim.id().clone() ] + }; + let res = transactions4.clone().push_transaction( + transactions4 + .accept_stamp(Timestamp::now(), stamp_transaction.clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); + } + + #[test] + fn transactions_delete_stamp() { + let (master_key, transactions, admin_key) = genesis(); + let identity_id = IdentityID::from(transactions.transactions()[0].id().clone()); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ make_claim, Timestamp::now(), ClaimSpec::Identity(MaybePrivate::new_public(identity_id)), None:: ] + }; + let identity = transactions2.build_identity().unwrap(); + assert_eq!(identity.claims()[0].stamps().len(), 0); + let claim = identity.claims()[0].clone(); + + let (master_key_stamper, transactions_stamper, admin_key_stamper) = genesis(); + let entry = StampEntry::new( + IdentityID::from(transactions_stamper.transactions()[0].id().clone()), + identity.id().clone(), + claim.id().clone(), + Confidence::Low, + Some(Timestamp::from_str("2060-01-01T06:59:00Z").unwrap()) + ); + let stamp_transaction = transactions_stamper + .make_stamp(Timestamp::now(), entry).unwrap() + .sign(&master_key_stamper, &admin_key_stamper).unwrap(); + + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2, + [ accept_stamp, Timestamp::now(), stamp_transaction.clone() ] + }; + assert_eq!(transactions3.transactions().len(), 3); + let identity3 = transactions3.build_identity().unwrap(); + assert_eq!(identity3.claims()[0].stamps().len(), 1); + + let transactions4 = sign_and_push! { &master_key, &admin_key, transactions3.clone(), + [ delete_stamp, Timestamp::now(), StampID::from(stamp_transaction.id().clone()) ] + }; + let identity4 = transactions4.build_identity().unwrap(); + assert_eq!(identity4.claims()[0].stamps().len(), 0); + + let res = transactions4.clone().push_transaction( + transactions4 + .delete_stamp(Timestamp::now(), StampID::from(stamp_transaction.id().clone())).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::IdentityStampNotFound)); + } + + #[test] + fn transactions_add_subkey() { + let (master_key, transactions, admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + assert_eq!(identity.keychain().subkeys().len(), 0); + + let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); + let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ add_subkey, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things") ] + [ add_subkey, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails") ] + [ add_subkey, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key") ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.keychain().subkeys()[0].name(), "default:sign"); + assert_eq!(identity2.keychain().subkeys()[1].name(), "default:crypto"); + assert_eq!(identity2.keychain().subkeys()[2].name(), "default:secret"); + let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); + let res = transactions2.clone().push_transaction( + transactions2 + .add_subkey(Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things")).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DuplicateName)); + let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); + let res = transactions2.clone().push_transaction( + transactions2 + .add_subkey(Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails")).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DuplicateName)); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let res = transactions2.clone().push_transaction( + transactions2 + .add_subkey(Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key")).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::DuplicateName)); + } + + #[test] + fn transactions_edit_subkey() { + let (master_key, transactions, admin_key) = genesis(); + + let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); + let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ add_subkey, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things") ] + [ add_subkey, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails") ] + [ add_subkey, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key") ] + }; + + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.keychain().subkeys().len(), 3); + + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2.clone(), + [ edit_subkey, Timestamp::now(), identity2.keychain().subkey_by_name("default:crypto").unwrap().key_id(), Some("default:MYLITTLEPONY"), Some(Some("Tonga")) ] + [ edit_subkey, Timestamp::now(), identity2.keychain().subkey_by_name("default:secret").unwrap().key_id(), Some("default:secret"), None ] + }; + let identity3 = transactions3.build_identity().unwrap(); + assert_eq!(identity3.keychain().subkeys().len(), 3); + assert!(identity3.keychain().subkey_by_name("default:sign").is_some()); + assert!(identity3.keychain().subkey_by_name("default:MYLITTLEPONY").is_some()); + assert!(identity3.keychain().subkey_by_name("default:crypto").is_none()); + assert_eq!(identity3.keychain().subkey_by_name("default:MYLITTLEPONY").unwrap().description(), &Some("Tonga".into())); + assert_eq!(identity3.keychain().subkey_by_name("default:secret").unwrap().description(), &None); + + let randkey = KeyID::random_secret(); + let res = transactions3.clone().push_transaction( + transactions3 + .edit_subkey(Timestamp::now(), randkey.clone(), Some("you want a push i'll show you a push"), None).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::KeychainKeyNotFound(randkey.clone()))); + } + + #[test] + fn transactions_revoke_subkey() { + let (master_key, transactions, admin_key) = genesis(); + + let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); + let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ add_subkey, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things") ] + [ add_subkey, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails") ] + [ add_subkey, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key") ] + }; + let identity2 = transactions2.build_identity().unwrap(); + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2.clone(), + [ revoke_subkey, Timestamp::now(), identity2.keychain().subkey_by_name("default:crypto").unwrap().key_id(), RevocationReason::Superseded, Some("revoked:default:crypto") ] + }; + let identity3 = transactions3.build_identity().unwrap(); + assert!(identity3.keychain().subkeys()[0].revocation().is_none()); + assert_eq!(identity3.keychain().subkeys()[1].revocation().as_ref(), Some(&RevocationReason::Superseded)); + assert!(identity3.keychain().subkeys()[2].revocation().is_none()); + + let res = transactions3.clone().push_transaction( + transactions3 + .revoke_subkey(Timestamp::now(), identity2.keychain().subkey_by_name("default:crypto").unwrap().key_id(), RevocationReason::Superseded, Some("revoked:default:crypto")).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::KeychainSubkeyAlreadyRevoked)); + } + + #[test] + fn transactions_delete_subkey() { + let (master_key, transactions, admin_key) = genesis(); + + let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); + let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ add_subkey, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things") ] + [ add_subkey, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails") ] + [ add_subkey, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key") ] + }; + let identity2 = transactions2.build_identity().unwrap(); + let sign_id = identity2.keychain().subkey_by_name("default:sign").unwrap().key_id(); + + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2.clone(), + [ delete_subkey, Timestamp::now(), sign_id.clone() ] + }; + let identity3 = transactions3.build_identity().unwrap(); + assert_eq!(identity3.keychain().subkeys()[0].name(), "default:crypto"); + assert_eq!(identity3.keychain().subkeys()[1].name(), "default:secret"); + assert_eq!(identity3.keychain().subkeys().len(), 2); + + let res = transactions3.clone().push_transaction( + transactions3 + .delete_subkey(Timestamp::now(), sign_id.clone()).unwrap() + .sign(&master_key, &admin_key).unwrap() + ); + assert_eq!(res.err(), Some(Error::KeychainKeyNotFound(sign_id.clone()))); + } + + #[test] + fn transactions_set_nickname() { + let (master_key, transactions, admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + assert_eq!(identity.nickname(), None); + + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ set_nickname, Timestamp::now(), Some("dirk-delta") ] + }; + let identity2 = transactions2.build_identity().unwrap(); + assert_eq!(identity2.nickname(), Some(&"dirk-delta".into())); + + let no_name: Option = None; + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions2, + [ set_nickname, Timestamp::now(), no_name ] + }; + let identity3 = transactions3.build_identity().unwrap(); + assert_eq!(identity3.nickname(), None); + } + + #[test] + fn transactions_push_invalid_sig() { + todo!("Make sure transactions with bad sigs cannot be added to the list"); + } + + #[test] + fn transactions_policy_multisign_verify() { + todo!("Make sure transactions verify against policies before being accepted"); + } + + #[test] + fn transactions_reencrypt() { + let (master_key, transactions, admin_key) = genesis(); + let admin_key2 = AdminKey::new(AdminKeypair::new_ed25519(&master_key).unwrap(), "Second", None); + let transactions = sign_and_push! { &master_key, &admin_key, transactions, + [ make_claim, Timestamp::now(), ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Hooty McOwl".to_string()).unwrap()), None:: ] + [ add_admin_key, Timestamp::now(), admin_key2 ] + [ set_nickname, Timestamp::now(), Some("dirk-delta") ] + }; + transactions.test_master_key(&master_key).unwrap(); + let identity = transactions.build_identity().unwrap(); + match identity.claims()[0].spec() { + ClaimSpec::Name(maybe) => { + let val = maybe.open(&master_key).unwrap(); + assert_eq!(val, "Hooty McOwl".to_string()); + } + _ => panic!("bad claim type"), + } + let sig = identity.keychain().admin_keys()[0].key().sign(&master_key, b"KILL...ME....").unwrap(); + + let master_key_new = SecretKey::new_xchacha20poly1305().unwrap(); + let transactions2 = transactions.reencrypt(&master_key, &master_key_new).unwrap(); + transactions2.test_master_key(&master_key_new).unwrap(); + let res = transactions2.test_master_key(&master_key); + assert_eq!(res.err(), Some(Error::CryptoOpenFailed)); + let identity2 = transactions2.build_identity().unwrap(); + let sig2 = identity2.keychain().admin_keys()[0].key().sign(&master_key_new, b"KILL...ME....").unwrap(); + assert_eq!(sig, sig2); + match identity2.claims()[0].spec() { + ClaimSpec::Name(maybe) => { + let val = maybe.open(&master_key_new).unwrap(); + assert_eq!(val, "Hooty McOwl".to_string()); + let res = maybe.open(&master_key); + assert_eq!(res.err(), Some(Error::CryptoOpenFailed)); + } + _ => panic!("bad claim type"), + } + } + + #[test] + fn transactions_is_owned() { + let (master_key, transactions, admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + assert!(transactions.is_owned()); + assert!(identity.is_owned()); + + let mut transactions2 = transactions.clone(); + transactions2.transactions_mut()[0] = transactions2.transactions_mut()[0].strip_private(); + let identity2 = transactions2.build_identity().unwrap(); + assert!(!transactions2.is_owned()); + assert!(!identity2.is_owned()); + + let admin_key2 = AdminKey::new(AdminKeypair::new_ed25519(&master_key).unwrap(), "Second", None); + let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); + let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let transactions3 = sign_and_push! { &master_key, &admin_key, transactions.clone(), + [ add_subkey, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things") ] + [ add_subkey, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails") ] + [ add_subkey, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key") ] + [ add_admin_key, Timestamp::now(), admin_key2 ] + }; + let identity3 = transactions3.build_identity().unwrap(); + assert!(transactions3.is_owned()); + assert!(identity3.is_owned()); + + let mut transactions4 = transactions3.clone(); + for trans in transactions4.transactions_mut() { + let entry = trans.entry().clone(); + match entry.body() { + TransactionBody::CreateIdentityV1 { .. } | TransactionBody::AddAdminKeyV1 { .. } => { + trans.set_entry(entry.strip_private()); + } + _ => {} + } + } + let identity4 = transactions4.build_identity().unwrap(); + assert!(!transactions4.is_owned()); + assert!(!identity4.is_owned()); + } + + #[test] + fn transactions_test_master_key() { + let (master_key, transactions, _admin_key) = genesis(); + transactions.test_master_key(&master_key).unwrap(); + let master_key_fake = SecretKey::new_xchacha20poly1305().unwrap(); + assert!(master_key_fake != master_key); + let res = transactions.test_master_key(&master_key_fake); + assert_eq!(res.err(), Some(Error::CryptoOpenFailed)); + } + + #[test] + fn transactions_strip_has_private() { + let (master_key, transactions, admin_key) = genesis(); + + let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); + let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let transactions2 = sign_and_push! { &master_key, &admin_key, transactions, + [ add_subkey, Timestamp::now(), Key::new_sign(sign_keypair), "default:sign", Some("The key I use to sign things") ] + [ add_subkey, Timestamp::now(), Key::new_crypto(crypto_keypair), "default:crypto", Some("Use this to send me emails") ] + [ add_subkey, Timestamp::now(), Key::new_secret(secret_key), "default:secret", Some("Encrypt/decrypt things locally with this key") ] + [ make_claim, Timestamp::now(), ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Danny Dinkel".to_string()).unwrap()), None:: ] + [ make_claim, Timestamp::now(), ClaimSpec::Email(MaybePrivate::new_public("twinkie.doodle@amateur-spotlight.net".to_string())), None:: ] + }; + + let mut has_priv: Vec = Vec::new(); + for trans in transactions2.transactions() { + has_priv.push(trans.has_private()); + } + assert_eq!(has_priv.iter().filter(|x| **x).count(), 5); + + assert!(transactions2.has_private()); + let transactions3 = transactions2.strip_private(); + assert!(!transactions3.has_private()); + + let mut has_priv: Vec = Vec::new(); + for trans in transactions3.transactions() { + has_priv.push(trans.has_private()); + } + assert_eq!(has_priv.iter().filter(|x| **x).count(), 0); + } + + #[test] + fn transactions_serde_binary() { + let (_master_key, transactions, _admin_key) = genesis(); + let identity = transactions.build_identity().unwrap(); + let ser = transactions.serialize_binary().unwrap(); + let des = Transactions::deserialize_binary(ser.as_slice()).unwrap(); + let identity2 = des.build_identity().unwrap(); + // quick and dirty. oh well. + assert_eq!(identity.id(), identity2.id()); + } +} + diff --git a/src/error.rs b/src/error.rs index 37ec51c..dc4c7eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,17 +37,9 @@ pub enum Error { #[error("incorrect seed given for keypair")] CryptoBadSeed, - /// Error creating hash digest - #[error("could not create hash digest")] - CryptoHashStateDigestError, - - /// Error creating hash state - #[error("could not init hash state")] - CryptoHashStateInitError, - - /// Error updating hash state - #[error("could not update hash state")] - CryptoHashStateUpdateError, + /// Failed to create a hash + #[error("failed to create a hash")] + CryptoHashFailed, /// HMAC failed to build properly #[error("HMAC failed to build properly")] @@ -112,10 +104,6 @@ pub enum Error { #[error("cannot build an identity from an empty transaction set")] DagEmpty, - /// A key wasn't found when running a DAG operation - #[error("key missing while processing transaction")] - DagKeyNotFound, - /// The DAG chain looped (so this is more of a DG or G than DAG) #[error("an endless loop occurred while processing the transaction set")] DagLoop, @@ -146,6 +134,10 @@ pub enum Error { #[error("the given name is already in use (names must be unique)")] DuplicateName, + /// Tried to find a capability policy but it wasn't there. + #[error("capability policy not found {0}")] + IdentityCapabilityPolicyNotFound(String), + /// The claim being operated on cannot be verified automatically #[error("this claim cannot be automatically verified")] IdentityClaimVerificationNotAllowed, @@ -154,10 +146,6 @@ pub enum Error { #[error("identity claim not found")] IdentityClaimNotFound, - /// The forward with that name couldn't be found - #[error("that forward couldn't be found")] - IdentityForwardNotFound, - /// An operation is being performed on an object not owned by the current /// identity #[error("identity ID mismatch")] @@ -176,22 +164,14 @@ pub enum Error { #[error("identity stamp already exists")] IdentityStampAlreadyExists, + /// This stamp revocation is being duplicated + #[error("identity stamp revocation already exists")] + IdentityStampRevocationAlreadyExists, + /// The stamp being operated on wasn't found #[error("identity stamp not found")] IdentityStampNotFound, - /// This subkey is already revoked. - #[error("subkey is already revoked")] - IdentitySubkeyAlreadyRevoked, - - /// The subkey being operated on wasn't found - #[error("identity subkey not found")] - IdentitySubkeyNotFound, - - /// The subkey being operated on is the wrong type - #[error("the given subkey cannot be used for the requested operation")] - IdentitySubkeyWrongType, - /// Verification of an identity failed. #[error("Verification of identity failed: {0}")] IdentityVerificationFailed(String), @@ -200,14 +180,35 @@ pub enum Error { #[error("io error {0:?}")] IoError(#[from] std::io::Error), + /// Key not found in [Keychain][crate::identity::keychain::Keychain]. + #[error("keychain key not found: {0}")] + KeychainKeyNotFound(crate::crypto::key::KeyID), + + /// This subkey is already revoked. + #[error("subkey is already revoked")] + KeychainSubkeyAlreadyRevoked, + + /// The subkey being operated on is the wrong type + #[error("the given subkey cannot be used for the requested operation")] + KeychainSubkeyWrongType, + /// Keygen failed #[error("keygen failed")] KeygenFailed, + /// A given policy does not have the capabilities required to perform the + /// requested action. + #[error("the policy does not have the capabilities required to perform that action")] + PolicyCapabilityMismatch, + /// The request doesn't satisfy the policy. 20 beats your 5. I'm sorry, sir. #[error("the recovery request does not meet the policy's conditions")] PolicyConditionMismatch, + /// No policy/capability matched the transaction + #[error("no matching policy/capability found for the given transaction")] + PolicyNotFound, + /// A key cannot be verified against the executed recovery policy chain. #[error("policy verification of key failed")] PolicyVerificationFailure, @@ -238,6 +239,27 @@ pub enum Error { #[error("signature missing on a value")] SignatureMissing, + /// The SHA512 of a transaction's body does not match its ID. He's tampered + /// with it. + #[error("transaction ID mismatch: {0}")] + TransactionIDMismatch(crate::dag::TransactionID), + + /// Expected one transaction, got another. + #[error("transaction mismatch")] + TransactionMismatch, + + /// This transaction cannot be saved + #[error("transaction cannot be saved: {0}")] + TransactionInvalid(String), + + /// This transaction has no signatures. Why don't you try and get one? + #[error("transaction has no signatures")] + TransactionNoSignatures, + + /// A signature on a transaction is not valid. + #[error("transaction signature invalid: {0:?}")] + TransactionSignatureInvalid(crate::identity::keychain::AdminKeypairPublic), + /// Error parsing a URL #[error("URL parse error")] Url(#[from] url::ParseError) diff --git a/src/identity/claim.rs b/src/identity/claim.rs index 141fbb8..584b91d 100644 --- a/src/identity/claim.rs +++ b/src/identity/claim.rs @@ -9,16 +9,14 @@ use crate::{ error::{Error, Result}, identity::{ - Public, stamp::Stamp, identity::IdentityID, }, crypto::key::SecretKey, - private::MaybePrivate, - util::{Timestamp, Date, Url, ser::BinaryVec}, + private::{MaybePrivate, PrivateWithHmac}, + util::{Public, Date, Url, ser::BinaryVec}, }; use getset; -#[cfg(test)] use rand::RngCore; use rasn::{AsnType, Encode, Decode}; use serde_derive::{Serialize, Deserialize}; use std::convert::TryInto; @@ -81,14 +79,14 @@ impl Relationship { #[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize)] #[rasn(choice)] pub enum ClaimSpec { - /// A claim that this identity is mine (always public). + /// A claim that this identity is mine. /// - /// This claim should be made any time a new identity is created. + /// This claim should be made *publicly* any time a new identity is created. /// - /// This can also be used to claim ownership of another identity, hopefully - /// stamped by that identity. + /// This can also be used to claim ownership of another identity, for instance + /// if you lost your keys and need to move to a new identity. #[rasn(tag(explicit(0)))] - Identity(IdentityID), + Identity(MaybePrivate), /// A claim that the name attached to this identity is mine. #[rasn(tag(explicit(1)))] Name(MaybePrivate), @@ -303,24 +301,28 @@ impl Public for ClaimSpec { #[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct Claim { - /// The unique ID of this claim. + /// The ID of this claim (the [transaction id][crate::dag::TransactionID] that created it). #[rasn(tag(explicit(0)))] id: ClaimID, /// The data we're claiming. #[rasn(tag(explicit(1)))] spec: ClaimSpec, - /// The date we created this claim. + /// Stamps that have been made on our claim. #[rasn(tag(explicit(2)))] - created: Timestamp, + stamps: Vec, + /// This claim's name, can be used for forwarding/redirection. + #[rasn(tag(explicit(3)))] + name: Option, } impl Claim { /// Create a new claim. - fn new(id: ClaimID, spec: ClaimSpec, created: Timestamp) -> Self { + pub(crate) fn new(id: ClaimID, spec: ClaimSpec, name: Option) -> Self { Self { id, spec, - created, + stamps: Vec::new(), + name, } } @@ -386,48 +388,13 @@ impl Public for Claim { } } -/// A wrapper around a `Claim` that stores its stamps. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] -#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct ClaimContainer { - /// The actual claim data - #[rasn(tag(explicit(0)))] - claim: Claim, - /// Stamps that have been made on our claim. - #[rasn(tag(explicit(1)))] - stamps: Vec, -} - -impl ClaimContainer { - /// Create a new claim, sign it with our signing key, and return a container - /// that holds the claim (with an empty set of stamps). - pub fn new(claim_id: ClaimID, spec: ClaimSpec, created: Timestamp) -> Self { - let claim = Claim::new(claim_id, spec, created); - Self { - claim, - stamps: Vec::new(), - } - } -} - -impl Public for ClaimContainer { - fn strip_private(&self) -> Self { - let mut clone = self.clone(); - clone.set_claim(clone.claim().strip_private()); - clone - } - - fn has_private(&self) -> bool { - self.claim().spec().has_private() - } -} - #[cfg(test)] pub(crate) mod tests { use super::*; use crate::{ error::Error, identity::{IdentityID}, + util::Timestamp, }; use std::convert::TryFrom; use std::str::FromStr; @@ -477,14 +444,7 @@ pub(crate) mod tests { }; } - let (master_key, _, spec) = make_specs!(|_, val| ClaimSpec::Identity(val), IdentityID::random()); - let master_key2 = SecretKey::new_xchacha20poly1305().unwrap(); - let spec2 = spec.clone().reencrypt(&master_key, &master_key2).unwrap(); - match (spec, spec2) { - (ClaimSpec::Identity(id), ClaimSpec::Identity(id2)) => assert_eq!(id, id2), - _ => panic!("Bad claim type: Identity"), - } - + claim_reenc!{ Identity, IdentityID::random() } claim_reenc!{ Name, String::from("Marty Malt") } claim_reenc!{ Birthday, Date::from_str("2010-01-03").unwrap() } claim_reenc!{ Email, String::from("marty@sids.com") } @@ -510,14 +470,14 @@ pub(crate) mod tests { let (_master_key, spec, spec2) = make_specs!($claimmaker, $val); assert_eq!(spec.has_private(), true); match $getmaybe(spec.clone()) { - MaybePrivate::Private { data: Some(_), .. } => {}, + MaybePrivate::Private(PrivateWithHmac { data: Some(_), .. }) => {}, _ => panic!("bad maybe val: {}", stringify!($claimtype)), } - let claim = ClaimContainer::new(ClaimID::random(), spec, Timestamp::now()); + let claim = Claim::new(ClaimID::random(), spec, None); assert_eq!(claim.has_private(), true); assert_eq!(spec2.has_private(), false); - let claim2 = ClaimContainer::new(ClaimID::random(), spec2, Timestamp::now()); + let claim2 = Claim::new(ClaimID::random(), spec2, None); assert_eq!(claim2.has_private(), false); }; ($claimty:ident, $val:expr) => { @@ -530,10 +490,7 @@ pub(crate) mod tests { }; } - // as usual, Identity is special - let spec = ClaimSpec::Identity(IdentityID::random()); - assert_eq!(spec.has_private(), false); - + claim_pub_priv!{ Identity, IdentityID::random() } claim_pub_priv!{ Name, String::from("I LIKE FOOTBALL") } claim_pub_priv!{ Birthday, Date::from_str("1990-03-04").unwrap() } claim_pub_priv!{ Email, String::from("IT@IS.FUN") } @@ -578,6 +535,7 @@ pub(crate) mod tests { }; } + thtrip!{ Identity, IdentityID::random() } thtrip!{ Name, String::from("I LIKE FOOTBALL") } thtrip!{ Birthday, Date::from_str("1967-12-03").unwrap() } thtrip!{ Email, String::from("IT.MAKES@ME.GLAD") } @@ -593,16 +551,6 @@ pub(crate) mod tests { BinaryVec::from(vec![42, 17, 86]), |maybe| { ClaimSpec::Extension { key: "best poem ever".into(), value: maybe } } } - - // for Identity, nothing will fundamentally change. - let claimspec = ClaimSpec::Identity(IdentityID::random()); - let claimspec2 = claimspec.clone().strip_private(); - match (&claimspec, &claimspec2) { - (ClaimSpec::Identity(id), ClaimSpec::Identity(id2)) => { - assert_eq!(id, id2); - } - _ => panic!("Bad claim type: Identity"), - } } #[test] @@ -612,11 +560,11 @@ pub(crate) mod tests { let identity_id = IdentityID::random(); let identity_id_str = String::try_from(&identity_id).unwrap(); let identity_id_str_short = IdentityID::short(&identity_id_str); - let claim_id_str = String::try_from($container.claim().id()).unwrap(); + let claim_id_str = String::try_from($container.id()).unwrap(); let claim_id_str_short = ClaimID::short(&claim_id_str); - match $container.claim().spec() { + match $container.spec() { ClaimSpec::Domain(..) | ClaimSpec::Url(..) => { - let instant_vals = $container.claim().instant_verify_allowed_values(&identity_id).unwrap(); + let instant_vals = $container.instant_verify_allowed_values(&identity_id).unwrap(); let compare: Vec = $expected.into_iter() .map(|x: String| { x @@ -629,7 +577,7 @@ pub(crate) mod tests { assert_eq!(instant_vals, compare); } _ => { - let res = $container.claim().instant_verify_allowed_values(&identity_id); + let res = $container.instant_verify_allowed_values(&identity_id); assert_eq!(res, Err(Error::IdentityClaimVerificationNotAllowed)); } } @@ -638,8 +586,8 @@ pub(crate) mod tests { macro_rules! assert_instant { (raw, $claimmaker:expr, $val:expr, $expected:expr) => { let (_master_key, spec_private, spec_public) = make_specs!($claimmaker, $val); - let container_private = ClaimContainer::new(ClaimID::random(), spec_private, Timestamp::now()); - let container_public = ClaimContainer::new(ClaimID::random(), spec_public, Timestamp::now()); + let container_private = Claim::new(ClaimID::random(), spec_private, None); + let container_public = Claim::new(ClaimID::random(), spec_public, None); match_container! { container_public, $expected } match_container! { container_private, $expected } @@ -648,7 +596,7 @@ pub(crate) mod tests { assert_instant!{ raw, |maybe, _| ClaimSpec::$claimty(maybe), $val, $expected } }; } - assert_instant!{ raw, |_, val| ClaimSpec::Identity(val), IdentityID::random(), vec![] } + assert_instant!{ Identity, IdentityID::random(), vec![] } assert_instant!{ Name, String::from("I LIKE FOOTBALL"), vec![] } assert_instant!{ Birthday, Date::from_str("1967-12-03").unwrap(), vec![] } assert_instant!{ Email, String::from("IT.MAKES@ME.GLAD"), vec![] } @@ -681,14 +629,14 @@ pub(crate) mod tests { (raw, $claimmaker:expr, $val:expr, $getmaybe:expr) => { let (master_key, spec_private, spec_public) = make_specs!($claimmaker, $val); let fake_master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let container_private = ClaimContainer::new(ClaimID::random(), spec_private, Timestamp::now()); - let container_public = ClaimContainer::new(ClaimID::random(), spec_public, Timestamp::now()); - let opened_claim = container_private.claim().as_public(&master_key).unwrap(); + let container_private = Claim::new(ClaimID::random(), spec_private, None); + let container_public = Claim::new(ClaimID::random(), spec_public, None); + let opened_claim = container_private.as_public(&master_key).unwrap(); assert_eq!(container_private.has_private(), true); assert_eq!(container_public.has_private(), false); assert_eq!(opened_claim.spec().has_private(), false); - assert_eq!($getmaybe(opened_claim.spec().clone()), $getmaybe(container_public.claim().spec().clone())); - assert_eq!(container_private.claim().as_public(&fake_master_key).err(), Some(Error::CryptoOpenFailed)); + assert_eq!($getmaybe(opened_claim.spec().clone()), $getmaybe(container_public.spec().clone())); + assert_eq!(container_private.as_public(&fake_master_key).err(), Some(Error::CryptoOpenFailed)); }; ($claimty:ident, $val:expr) => { as_pub!{ @@ -700,15 +648,7 @@ pub(crate) mod tests { }; } - let (master_key, spec_private, _) = make_specs!(|_, val| ClaimSpec::Identity(val), IdentityID::random()); - let container_private = ClaimContainer::new(ClaimID::random(), spec_private, Timestamp::now()); - match (container_private.claim().spec(), container_private.claim().as_public(&master_key).unwrap().spec()) { - (ClaimSpec::Identity(val1), ClaimSpec::Identity(val2)) => { - assert_eq!(val1, val2); - } - _ => panic!("weird"), - } - + as_pub!{ Identity, IdentityID::random() } as_pub!{ Name, String::from("Sassafrass Stevens") } as_pub!{ Birthday, Date::from_str("1990-03-04").unwrap() } as_pub!{ Email, String::from("MEGATRON@nojerrystopjerry.net") } @@ -732,8 +672,8 @@ pub(crate) mod tests { macro_rules! has_priv { (raw, $claimmaker:expr, $val:expr, $haspriv:expr) => { let (_master_key, spec_private, spec_public) = make_specs!($claimmaker, $val); - let container_private = ClaimContainer::new(ClaimID::random(), spec_private, Timestamp::now()); - let container_public = ClaimContainer::new(ClaimID::random(), spec_public, Timestamp::now()); + let container_private = Claim::new(ClaimID::random(), spec_private, None); + let container_public = Claim::new(ClaimID::random(), spec_public, None); assert_eq!(container_private.has_private(), $haspriv); assert_eq!(container_public.has_private(), false); @@ -747,7 +687,7 @@ pub(crate) mod tests { has_priv! { raw, |maybe, _| ClaimSpec::$claimty(maybe), $val, $haspriv } }; } - has_priv! { raw, |_, val| ClaimSpec::Identity(val), IdentityID::random(), false } + has_priv! { Identity, IdentityID::random(), true } has_priv! { Name, String::from("Goleen Jundersun"), true } has_priv! { Birthday, Date::from_str("1969-12-03").unwrap(), true } has_priv! { Email, String::from("jerry@karate.com"), true } diff --git a/src/identity/identity.rs b/src/identity/identity.rs index b94252e..ff54520 100644 --- a/src/identity/identity.rs +++ b/src/identity/identity.rs @@ -1,24 +1,24 @@ //! This module holds the identity structures and methods. //! -//! Here we define what an identity looks like and how all the pieces -//! ([claims](crate::identity::claim), [stamps](crate::identity::stamp), and -//! [forwards](crate::identity::Forward)) all tie together. +//! The identity object holds claims, public stamps/revoations, a keychain +//! of administrative and third-party keys, and a set of capability policies +//! which dictate what signatures from various key are allowed to create +//! valid transactions against the identity. use crate::{ error::{Error, Result}, - crypto::key::{SecretKey, SignKeypair}, - dag::TransactionID, + crypto::key::{KeyID, SecretKey}, identity::{ - claim::{ClaimID, Claim, ClaimSpec, ClaimContainer}, - keychain::{ExtendKeypair, AlphaKeypair, PolicyKeypair, PublishKeypair, RootKeypair, RevocationReason, Key, Keychain}, - recovery::{PolicyCondition, PolicyID, PolicyRequestAction, PolicyRequestEntry, PolicyRequest, RecoveryPolicy}, - stamp::{Confidence, StampID, Stamp, StampRevocation}, + claim::{ClaimID, ClaimSpec, Claim}, + keychain::{ExtendKeypair, AdminKey, RevocationReason, Key, Keychain}, + stamp::{StampID, Stamp, StampRevocation}, }, + policy::{CapabilityPolicy}, private::MaybePrivate, util::{ Public, Timestamp, - ser::{self, BinaryVec}, + ser, }, }; use getset; @@ -28,94 +28,82 @@ use serde_derive::{Serialize, Deserialize}; use std::convert::TryInto; use std::ops::Deref; -/// The TransactionID is a SHA512 hash of the transaction body -#[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] -pub struct IdentityID(TransactionID); - -/// A set of forward types. -#[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] -#[rasn(choice)] -pub enum ForwardType { - /// An email address - #[rasn(tag(explicit(0)))] - Email(String), - /// A social identity. This is two strings to represent type and handle/url. - #[rasn(tag(explicit(1)))] - Social { - #[rasn(tag(explicit(0)))] - #[serde(rename = "type")] - ty: String, - #[rasn(tag(explicit(1)))] - handle: String, - }, - /// A PGP keypair ID or URL to a published public key - #[rasn(tag(explicit(2)))] - Pgp(String), - /// A raw url. - #[rasn(tag(explicit(3)))] - Url(String), - /// An extension type, can be used to implement any kind of forward you can - /// think of. - #[rasn(tag(explicit(4)))] - Extension { - #[rasn(tag(explicit(0)))] - #[serde(rename = "type")] - ty: BinaryVec, - #[rasn(tag(explicit(1)))] - data: BinaryVec, - }, +object_id! { + /// The identity's unique ID. This is the SHA512 hash of the + /// [initial transaction][crate::dag::TransactionBody::CreateIdentityV1]. + IdentityID } -/// A pointer to somewhere else. -/// -/// This can be useful for pointing either people or machines to a known -/// location. For instance, if you switch Mastodon servers, you could add a new -/// "Mastodon" forward pointing to your new handle and mark it as the default -/// for that forward type. Or you could forward to your personal domain, or -/// your email address. +/// A container holding our public stamps and public revocations. /// -/// Each forward is signed with your signing secret key. This is a bit different -/// from a claim in that claims help verify your identity, and forwards are -/// assertions you can make that don't require external verification. If people -/// trust that this is *your* identity via the signed claims, then they can -/// trust the forwards you assert and sign. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] +/// Note that stamps/revocations do not have to be publicly stored with the identity, +/// but doing so is an option for easy lookup. +#[derive(Debug, Default, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct Forward { - /// The identity-unique name of this forward. This lets us reference this - /// object by name. +pub struct StampCollection { + /// Turns out the real cryptographic identity system was stamps we made along the way. #[rasn(tag(explicit(0)))] - name: String, - /// The forward type we're creating. + stamps: Vec, + /// Hall of shame. #[rasn(tag(explicit(1)))] - val: ForwardType, - /// Whether or not this forward is a default. For instance, you could have - /// ten emails listed, but only one used as the default. If multiple - /// defaults are given for a particular forward type, then the one with the - /// most recent `Signature::date_signed` date in the `SignedForward::sig` - /// field should be used. - #[rasn(tag(explicit(2)))] - is_default: bool, + revocations: Vec, } -impl Forward { - /// Create a new forward. - pub fn new(name: String, val: ForwardType, is_default: bool) -> Self { - Self { - name, - val, - is_default, +impl StampCollection { + fn add_stamp(&mut self, stamp: Stamp) -> Result<()> { + if self.stamps().iter().find(|s| s.id() == stamp.id()).is_some() { + Err(Error::IdentityStampAlreadyExists)?; + } + self.stamps_mut().push(stamp); + Ok(()) + } + + fn add_revocation(&mut self, revocation: StampRevocation) -> Result<()> { + if self.revocations().iter().find(|r| r.id() == revocation.id()).is_some() { + Err(Error::IdentityStampRevocationAlreadyExists)?; } + self.revocations_mut().push(revocation); + Ok(()) } } -/// Extra public data that is attached to our identity. -/// -/// Each entry in this struct is signed by our root signing key. In the case -/// that our identity is re-keyed, the entries in this struct must be re-signed. +/// An identity. #[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct IdentityExtraData { +pub struct Identity { + /// The unique identifier for this identity. + #[rasn(tag(explicit(0)))] + id: IdentityID, + /// When this identity came into being. + #[rasn(tag(explicit(1)))] + created: Timestamp, + /// A collection of capabilities, each with a key policy attached to it. The + /// idea here is that we can specify a capability/action such as "add subkey" + /// and allow that action to be performed if we have the proper signature(s) + /// as determined by the key policy. + /// + /// This allows us to not only run transactions against this identity, but + /// also allows others to do so as well, given they sign their transactions + /// according to the given policies. + /// + /// Effectively, this allows group/multisig management of identities. + #[rasn(tag(explicit(2)))] + capabilities: Vec, + /// Holds the keys for our identity. + #[rasn(tag(explicit(3)))] + keychain: Keychain, + /// The claims this identity makes. + #[rasn(tag(explicit(4)))] + claims: Vec, + /// The public stamps (and revocations) this identity has made *on other + /// identities.* + /// + /// Note that stamps do NOT have to be publicly saved, but can be transmitted + /// directly to the recipient without advertisement. However, public storage + /// of stamps within the stamper's identity allows for quick verification and + /// for checking of revocation. + #[rasn(tag(explicit(5)))] + stamps: StampCollection, /// An always-public nickname that can be used to look up this identity /// in various indexed locations. This will always have `stamp://` prepended /// to it, so don't include it here. @@ -126,7 +114,7 @@ pub struct IdentityExtraData { /// /// Note that this necessarily cannot be unique, so services that index the /// nickname will need to list *all* known identities using that shortname. - /// Note that it will be possible to specify a hex ID in long or short-form, + /// Note that it will be possible to specify a string ID in long or short-form, /// such as `stamp://zefram-cochrane/s0yB0i-4y822` to narrow down the result /// by the nickname *and* ID. /// @@ -136,114 +124,130 @@ pub struct IdentityExtraData { /// and trust levels. /// /// NOTE that the nickname is only useful for discovery of an identity in - /// the network. It must *not* be included in forwards or claim proofs, - /// because if the nickname changes then the forward or claim proof will + /// the network. It must *not* be included in claim proofs, + /// because if the nickname changes then the claim proof will /// break. It's really meant as a quick way to allow people to find your /// identity, as opposed to a piece of static information used by other /// systems. - #[rasn(tag(explicit(0)))] + #[rasn(tag(explicit(6)))] + #[getset(skip)] nickname: Option, - /// A canonical list of places this identity forwards to. - #[rasn(tag(explicit(1)))] - forwards: Vec, -} - -impl IdentityExtraData { - /// Create a blank identity data container - fn new() -> Self { - Self { - nickname: None, - forwards: Vec::new(), - } - } -} - -/// An identity. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] -#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct Identity { - /// The unique identifier for this identity. - #[rasn(tag(explicit(0)))] - id: IdentityID, - /// When this identity came into being. - #[rasn(tag(explicit(1)))] - created: Timestamp, - /// Holds the keys for our identity. - #[rasn(tag(explicit(2)))] - keychain: Keychain, - /// The claims this identity makes. - #[rasn(tag(explicit(3)))] - claims: Vec, - /// Extra data that can be attached to our identity. - #[rasn(tag(explicit(4)))] - extra_data: IdentityExtraData, } impl Identity { /// Create a new identity. - pub(crate) fn create(id: IdentityID, alpha_keypair: AlphaKeypair, policy_keypair: PolicyKeypair, publish_keypair: PublishKeypair, root_keypair: RootKeypair, created: Timestamp) -> Self { + pub(crate) fn create(id: IdentityID, admin_keys: Vec, capabilities: Vec, created: Timestamp) -> Self { // create a new keychain from our keys above. - let keychain = Keychain::new(alpha_keypair, policy_keypair, publish_keypair, root_keypair); - - // init our extra data - let extra_data = IdentityExtraData::new(); + let keychain = Keychain::new(admin_keys); // create the identity Self { id, created, - recovery_policy: None, + capabilities, keychain, claims: vec![], - extra_data, + stamps: StampCollection::default(), + nickname: None, } } - /// Set the current recovery policy. - pub(crate) fn set_recovery(mut self, policy_id: PolicyID, conditions: Option) -> Self { - if let Some(conditions) = conditions { - self.set_recovery_policy(Some(RecoveryPolicy::new(policy_id, conditions))); - } else { - self.set_recovery_policy(None); + /// Reset the admin keys/capabilities in this identity. + pub(crate) fn reset(mut self, admin_keys_maybe: Option>, capabilities_maybe: Option>) -> Result { + if let Some(admin_keys) = admin_keys_maybe { + let mut keychain = self.keychain().clone(); + keychain.set_admin_keys(admin_keys); + self.set_keychain(keychain); } - self + if let Some(capabilities) = capabilities_maybe { + self.set_capabilities(capabilities); + } + Ok(self) } - /// Execute a recovery against the current policy. - pub(crate) fn execute_recovery(self, request: PolicyRequest) -> Result { - let policy = self.recovery_policy().as_ref().ok_or(Error::IdentityMissingRecoveryPolicy)?; - policy.validate_request(self.id(), &request)?; - match request.entry().action() { - PolicyRequestAction::ReplaceKeys { policy, publish, root } => { - self.set_policy_key(policy.clone(), RevocationReason::Recovery)? - .set_publish_key(publish.clone(), RevocationReason::Recovery)? - .set_root_key(root.clone(), RevocationReason::Recovery) - } + pub(crate) fn add_admin_key(mut self, admin_key: AdminKey) -> Result { + self.set_keychain(self.keychain().clone().add_admin_key(admin_key)?); + Ok(self) + } + + pub(crate) fn edit_admin_key(mut self, id: &KeyID, name: Option, description: Option>) -> Result { + self.set_keychain(self.keychain().clone().edit_admin_key(id, name, description)?); + Ok(self) + } + + pub(crate) fn revoke_admin_key(mut self, id: &KeyID, reason: RevocationReason, new_name: Option) -> Result { + self.set_keychain(self.keychain().clone().revoke_admin_key(id, reason, new_name)?); + Ok(self) + } + + /// Add a new capability policy + pub(crate) fn add_capability_policy(mut self, capability: CapabilityPolicy) -> Result { + if self.capabilities().iter().find(|c| c.name() == capability.name()).is_some() { + Err(Error::DuplicateName)?; + } + self.capabilities_mut().push(capability); + Ok(self) + } + + /// Delete a capability policy by name + pub(crate) fn delete_capability_policy(mut self, name: &str) -> Result { + if self.capabilities().iter().find(|c| c.name() == name).is_none() { + Err(Error::IdentityCapabilityPolicyNotFound(name.into()))?; } + self.capabilities_mut().retain(|c| c.name() != name); + Ok(self) } /// Create a new claim from the given data, sign it, and attach it to this /// identity. - pub(crate) fn make_claim(mut self, claim_id: ClaimID, claim: ClaimSpec, created: Timestamp) -> Self { - let claim_container = ClaimContainer::new(claim_id, claim, created); - self.claims_mut().push(claim_container); - self + pub(crate) fn make_claim(mut self, claim_id: ClaimID, claim: ClaimSpec, name: Option) -> Result { + let claim = Claim::new(claim_id, claim, name); + self.claims_mut().push(claim); + Ok(self) + } + + /// Set a new name for a claim + pub(crate) fn edit_claim(mut self, id: &ClaimID, name: Option) -> Result { + let claim_maybe = self.claims_mut().iter_mut().find(|x| x.id() == id); + if let Some(claim) = claim_maybe { + claim.set_name(name); + } else { + Err(Error::IdentityClaimNotFound)?; + } + Ok(self) } /// Remove a claim from this identity, including any stamps it has received. pub(crate) fn delete_claim(mut self, id: &ClaimID) -> Result { - let exists = self.claims().iter().find(|x| x.claim().id() == id); + let exists = self.claims().iter().find(|x| x.id() == id); if exists.is_none() { Err(Error::IdentityClaimNotFound)?; } - self.claims_mut().retain(|x| x.claim().id() != id); + self.claims_mut().retain(|x| x.id() != id); + Ok(self) + } + + /// MAke a public stamp + pub(crate) fn make_stamp(mut self, stamp: Stamp) -> Result { + if self.stamps().stamps().iter().find(|s| s.id() == stamp.id()).is_some() { + Err(Error::IdentityStampAlreadyExists)?; + } + self.stamps_mut().add_stamp(stamp)?; + Ok(self) + } + + pub(crate) fn revoke_stamp(mut self, revocation: StampRevocation) -> Result { + if self.stamps().revocations().iter().find(|r| r.id() == revocation.id()).is_some() { + Err(Error::IdentityStampRevocationAlreadyExists)?; + } + self.stamps_mut().add_revocation(revocation)?; Ok(self) } /// Accept a stamp on one of our claims. pub(crate) fn accept_stamp(mut self, stamp: Stamp) -> Result { let claim_id = stamp.entry().claim_id(); - let claim = self.claims_mut().iter_mut().find(|x| x.claim().id() == claim_id) + let claim = self.claims_mut().iter_mut().find(|x| x.id() == claim_id) .ok_or(Error::IdentityClaimNotFound)?; if claim.stamps().iter().find(|x| x.id() == stamp.id()).is_some() { Err(Error::IdentityStampAlreadyExists)?; @@ -273,24 +277,6 @@ impl Identity { } } - /// Set the policy signing key on this identity. - pub(crate) fn set_policy_key(mut self, new_policy_keypair: PolicyKeypair, revocation_reason: RevocationReason) -> Result { - self.set_keychain(self.keychain().clone().set_policy_key(new_policy_keypair, revocation_reason)?); - Ok(self) - } - - /// Set the publish signing key on this identity. - pub(crate) fn set_publish_key(mut self, new_publish_keypair: PublishKeypair, revocation_reason: RevocationReason) -> Result { - self.set_keychain(self.keychain().clone().set_publish_key(new_publish_keypair, revocation_reason)?); - Ok(self) - } - - /// Set the root signing key on this identity. - pub(crate) fn set_root_key(mut self, new_root_keypair: RootKeypair, revocation_reason: RevocationReason) -> Result { - self.set_keychain(self.keychain().clone().set_root_key(new_root_keypair, revocation_reason)?); - Ok(self) - } - /// Add a new subkey to our identity. pub(crate) fn add_subkey>(mut self, key: Key, name: T, description: Option) -> Result { self.set_keychain(self.keychain().clone().add_subkey(key, name, description)?); @@ -298,149 +284,77 @@ impl Identity { } /// Update the name/description on a subkey. - pub(crate) fn edit_subkey>(mut self, name: &str, new_name: T, description: Option) -> Result { - self.set_keychain(self.keychain().clone().edit_subkey(name, new_name, description)?); + pub(crate) fn edit_subkey>(mut self, id: &KeyID, new_name: Option, new_desc: Option>) -> Result { + self.set_keychain(self.keychain().clone().edit_subkey(id, new_name, new_desc)?); Ok(self) } /// Revoke one of our subkeys, for instance if it has been compromised. - pub(crate) fn revoke_subkey(mut self, name: &str, reason: RevocationReason, new_name: Option) -> Result { - self.set_keychain(self.keychain().clone().revoke_subkey(name, reason, new_name)?); + pub(crate) fn revoke_subkey(mut self, id: &KeyID, reason: RevocationReason, new_name: Option) -> Result { + self.set_keychain(self.keychain().clone().revoke_subkey(id, reason, new_name)?); Ok(self) } /// Remove a subkey from the keychain. - pub(crate) fn delete_subkey(mut self, name: &str) -> Result { - self.set_keychain(self.keychain().clone().delete_subkey(name)?); + pub(crate) fn delete_subkey(mut self, id: &KeyID) -> Result { + self.set_keychain(self.keychain().clone().delete_subkey(id)?); Ok(self) } - /// Set the nickname on this identity - pub(crate) fn set_nickname(mut self, nickname: Option) -> Self { - self.extra_data_mut().set_nickname(nickname); + /// Set the identity's nickname + pub(crate) fn set_nickname(mut self, name: Option) -> Self { + self.nickname = name; self } - /// Add a forward to this identity - pub(crate) fn add_forward>(mut self, name: T, ty: ForwardType, is_default: bool) -> Result { - let name: String = name.into(); - if self.extra_data().forwards().iter().find(|x| x.name() == &name).is_some() { - Err(Error::DuplicateName)?; - } - let forward = Forward::new(name, ty, is_default); - self.extra_data_mut().forwards_mut().push(forward); - Ok(self) - } - - /// Add a forward to this identity - pub(crate) fn delete_forward(mut self, name: &str) -> Result { - let forwards = self.extra_data_mut().forwards_mut(); - if forwards.iter().find(|x| x.name() == name).is_none() { - Err(Error::IdentityForwardNotFound)?; - } - forwards.retain(|x| x.name() != name); - Ok(self) - } - - /// Create a new recovery request. Once made, we can go out and get all of - /// our little friends to sign it so we can recovery our identity. - pub fn create_recovery_request(&self, master_key: &SecretKey, new_policy_key: &PolicyKeypair, action: PolicyRequestAction) -> Result { - let policy_id = self.recovery_policy().as_ref().ok_or(Error::IdentityMissingRecoveryPolicy)?.id().clone(); - let entry = PolicyRequestEntry::new(self.id().clone(), policy_id, action); - PolicyRequest::new(master_key, new_policy_key, entry) - } - - /// Sign someone else's recovery policy request. This is how signatures are - /// added to the request, possibly allowing for the recovery of an identity. - pub fn sign_recovery_request(&self, master_key: &SecretKey, sign_keypair: &SignKeypair, request: PolicyRequest) -> Result { - request.sign(master_key, sign_keypair) - } - - /// Stamp a claim with our identity. - pub fn stamp>(&self, master_key: &SecretKey, confidence: Confidence, now: T, stampee: &IdentityID, claim: &Claim, expires: Option) -> Result { - Stamp::stamp(master_key, self.keychain().root(), self.id(), stampee, confidence, now, claim, expires) + /// Get this identity's nickname + pub fn nickname(&self) -> Option<&String> { + self.nickname.as_ref() } - /// Verify that the given stamp was actually signed by this identity. - pub fn verify_stamp(&self, stamp: &Stamp) -> Result<()> { - let root_keys = self.keychain().keys_root().into_iter() - .map(|x| x.deref()) - .collect::>(); - Keychain::try_keys(&root_keys, |sign_keypair| stamp.verify(&sign_keypair.clone().into())) - } - - /// Revoke a stamp we've made. - /// - /// For instance if you've stamped the identity for the Miner 49er but it - /// turns out it was just Hank the caretaker all along (who was trying to - /// scare people away from the mines so he could have the oil reserves to - /// himself), you might wish to revoke your stamp on that identity. - /// - /// Note that this doesn't change the claim itself on the identity the claim - /// belongs to, but instead we must publish this revocation on whatever - /// medium we see fit, and it is up to people to check for revocations on - /// that medium before accepting a stamped claim as given. - pub fn revoke_stamp>(&self, master_key: &SecretKey, stamp: &Stamp, date_revoked: T) -> Result { - if self.id() != stamp.entry().stamper() { - Err(Error::IdentityIDMismatch)?; + /// Try to find a [Stamp] on a [Claim] by id. + pub fn find_claim_stamp_by_id(&self, stamp_id: &StampID) -> Option<&Stamp> { + let mut found_stamp = None; + for claim in self.claims() { + for stamp in claim.stamps() { + if stamp.id() == stamp_id { + found_stamp = Some(stamp); + break; + } + } + if found_stamp.is_some() { break; } } - stamp.revoke(master_key, self.keychain().root(), date_revoked) - } - - /// Grab this identity's nickname, if it has one. - pub fn nickname_maybe(&self) -> Option { - self.extra_data().nickname().clone() + found_stamp } /// Return all emails associated with this identity. pub fn emails(&self) -> Vec { - let mut forwards = self.extra_data().forwards().iter() - .filter_map(|x| { - match x.val() { - ForwardType::Email(ref email) => Some(email.clone()), - _ => None, - } - }) - .collect::>(); - let mut claims = self.claims().iter() + self.claims().iter() .filter_map(|x| { - match x.claim().spec() { + match x.spec() { ClaimSpec::Email(MaybePrivate::Public(ref email)) => Some(email.clone()), _ => None, } }) - .collect::>(); - forwards.append(&mut claims); - forwards + .collect::>() } /// Grab this identity's primary email, if it has one. pub fn email_maybe(&self) -> Option { - // first search forwards for a default email. if that fails, check our - // claims department. - self.extra_data().forwards().iter() + self.claims().iter() .find_map(|x| { - match (x.is_default(), x.val()) { - (true, ForwardType::Email(ref email)) => Some(email.clone()), + match x.spec() { + ClaimSpec::Email(MaybePrivate::Public(ref email)) => Some(email.clone()), _ => None, } }) - .or_else(|| { - self.claims().iter() - .find_map(|x| { - match x.claim().spec() { - ClaimSpec::Email(MaybePrivate::Public(ref email)) => Some(email.clone()), - _ => None, - } - }) - }) } /// Return all names associated with this identity. pub fn names(&self) -> Vec { self.claims().iter() .filter_map(|x| { - match x.claim().spec() { + match x.spec() { ClaimSpec::Name(MaybePrivate::Public(ref name)) => Some(name.clone()), _ => None, } @@ -452,7 +366,7 @@ impl Identity { pub fn name_maybe(&self) -> Option { self.claims().iter() .find_map(|x| { - match x.claim().spec() { + match x.spec() { ClaimSpec::Name(MaybePrivate::Public(ref name)) => Some(name.clone()), _ => None, } @@ -462,10 +376,7 @@ impl Identity { /// Determine if this identity is owned (ie, we have the private keys stored /// locally) or it is imported (ie, someone else's identity). pub fn is_owned(&self) -> bool { - self.keychain().alpha().has_private() || - self.keychain().policy().has_private() || - self.keychain().publish().has_private() || - self.keychain().root().has_private() + self.keychain().admin_keys().iter().find(|k| k.has_private()).is_some() } /// Test if a master key is correct. @@ -473,14 +384,11 @@ impl Identity { let mut randbuf = [0u8; 32]; OsRng.fill_bytes(&mut randbuf); let test_bytes = Vec::from(&randbuf[..]); - if self.keychain().alpha().has_private() { - self.keychain().alpha().sign(master_key, test_bytes.as_slice())?; - } else if self.keychain().policy().has_private() { - self.keychain().policy().sign(master_key, test_bytes.as_slice())?; - } else if self.keychain().publish().has_private() { - self.keychain().publish().sign(master_key, test_bytes.as_slice())?; - } else if self.keychain().root().has_private() { - self.keychain().root().sign(master_key, test_bytes.as_slice())?; + if self.keychain().admin_keys().len() == 0 { + Err(Error::IdentityNotOwned)?; + } + for key in self.keychain().admin_keys() { + key.key().sign(master_key, test_bytes.as_slice())?; } Ok(()) } @@ -511,7 +419,13 @@ impl Public for Identity { mod tests { use super::*; use crate::{ - crypto::key::SignKeypair, + crypto::key::{Sha512, SignKeypair}, + dag::TransactionID, + identity::{ + keychain::AdminKeypair, + stamp::{Confidence}, + }, + policy::{Capability, Participant, Policy}, util, }; use std::str::FromStr; @@ -523,12 +437,16 @@ mod tests { fn create_identity() -> (SecretKey, Identity) { let master_key = gen_master_key(); let id = IdentityID::random(); - let alpha = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root = RootKeypair::new_ed25519(&master_key).unwrap(); + let admin_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); + let pubkey = admin_keypair.clone().into(); + let admin_key = AdminKey::new(admin_keypair, "Default", None); + let capability = CapabilityPolicy::new( + "default".into(), + vec![Capability::Permissive], + Policy::MOfN { must_have: 1, participants: vec![Participant::Key(pubkey)] } + ); let created = Timestamp::now(); - let identity = Identity::create(id.clone(), alpha.clone(), policy.clone(), publish.clone(), root.clone(), created.clone()); + let identity = Identity::create(id, vec![admin_key], vec![capability], created); (master_key, identity) } @@ -536,96 +454,20 @@ mod tests { fn identity_create() { let master_key = gen_master_key(); let id = IdentityID::random(); - let alpha = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root = RootKeypair::new_ed25519(&master_key).unwrap(); + let admin_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); + let admin_key = AdminKey::new(admin_keypair, "Default", None); + let capability = CapabilityPolicy::new( + "default".into(), + vec![Capability::Permissive], + Policy::MOfN { must_have: 1, participants: vec![admin_key.key().clone().into()] } + ); let created = Timestamp::now(); - let identity = Identity::create(id.clone(), alpha.clone(), policy.clone(), publish.clone(), root.clone(), created.clone()); + let identity = Identity::create(id.clone(), vec![admin_key.clone()], vec![capability.clone()], created.clone()); assert_eq!(identity.id(), &id); - assert!(identity.recovery_policy().is_none()); assert_eq!(identity.created(), &created); - assert_eq!(identity.keychain().alpha(), &alpha); - assert_eq!(identity.keychain().policy(), &policy); - assert_eq!(identity.keychain().publish(), &publish); - assert_eq!(identity.keychain().root(), &root); - } - - #[test] - fn identity_set_recovery() { - let policy_id = PolicyID::random(); - let conditions = PolicyCondition::Any(vec![PolicyCondition::Deny]); - let (_master_key, identity) = create_identity(); - assert!(identity.recovery_policy().is_none()); - let identity2 = identity.set_recovery(policy_id.clone(), Some(conditions.clone())); - assert_eq!(identity2.recovery_policy().as_ref().unwrap().id(), &policy_id); - assert_eq!(identity2.recovery_policy().as_ref().unwrap().conditions(), &conditions); - } - - #[test] - fn identity_create_sign_execute_recovery() { - let (master_key, identity) = create_identity(); - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let new_publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let new_root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let action = PolicyRequestAction::ReplaceKeys { - policy: new_policy_keypair.clone(), - publish: new_publish_keypair.clone(), - root: new_root_keypair.clone(), - }; - - let res = identity.create_recovery_request(&master_key, &new_policy_keypair, action.clone()); - // you can't triple-stamp a double-stamp - assert_eq!(res.err(), Some(Error::IdentityMissingRecoveryPolicy)); - - let policy_id = PolicyID::random(); - let conditions = PolicyCondition::Deny; - let identity2 = identity.set_recovery(policy_id.clone(), Some(conditions)); - - let req = identity2.create_recovery_request(&master_key, &new_policy_keypair, action.clone()).unwrap(); - let res = identity2.clone().execute_recovery(req); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - let (gus_master, gus) = util::test::setup_identity_with_subkeys(); - let (_marty_master, marty) = util::test::setup_identity_with_subkeys(); - let (jackie_master, jackie) = util::test::setup_identity_with_subkeys(); - let gus_sign = gus.keychain().subkey_by_name("sign").unwrap().as_signkey().unwrap().clone(); - let marty_sign = marty.keychain().subkey_by_name("sign").unwrap().as_signkey().unwrap().clone(); - let jackie_sign = jackie.keychain().subkey_by_name("sign").unwrap().as_signkey().unwrap().clone(); - - let identity3 = identity2.set_recovery(policy_id.clone(), Some(PolicyCondition::OfN { - must_have: 2, - pubkeys: vec![ - gus_sign.clone().into(), - marty_sign.clone().into(), - jackie_sign.clone().into(), - ], - })); - - let req = identity3.create_recovery_request(&master_key, &new_policy_keypair, action.clone()).unwrap(); - let res = identity3.clone().execute_recovery(req); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - let req = identity3.create_recovery_request(&master_key, &new_policy_keypair, action.clone()).unwrap(); - let req_signed_1 = gus.sign_recovery_request(&gus_master, &gus_sign, req.clone()).unwrap(); - let res = identity3.clone().execute_recovery(req_signed_1.clone()); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - let req_signed_2 = jackie.sign_recovery_request(&jackie_master, &jackie_sign, req_signed_1.clone()).unwrap(); - let identity4 = identity3.clone().execute_recovery(req_signed_2.clone()).unwrap(); - - assert!(identity3.keychain().policy() != identity4.keychain().policy()); - assert!(identity3.keychain().publish() != identity4.keychain().publish()); - assert!(identity3.keychain().root() != identity4.keychain().root()); - assert_eq!(identity3.keychain().subkeys().len(), 0); - assert_eq!(identity4.keychain().policy(), &new_policy_keypair); - assert_eq!(identity4.keychain().publish(), &new_publish_keypair); - assert_eq!(identity4.keychain().root(), &new_root_keypair); - assert_eq!(identity4.keychain().subkeys().len(), 3); - assert_eq!(identity4.keychain().subkeys()[0].name(), &format!("revoked:policy:{}", identity3.keychain().policy().key_id().as_string())); - assert_eq!(identity4.keychain().subkeys()[1].name(), &format!("revoked:publish:{}", identity3.keychain().publish().key_id().as_string())); - assert_eq!(identity4.keychain().subkeys()[2].name(), &format!("revoked:root:{}", identity3.keychain().root().key_id().as_string())); + assert_eq!(&identity.keychain().admin_keys().iter().map(|x| x.key()).collect::>(), &vec![admin_key.key()]); + assert_eq!(identity.capabilities(), &vec![capability]); } #[test] @@ -633,67 +475,46 @@ mod tests { let (_master_key, identity) = create_identity(); let claim_id = ClaimID::random(); - let spec = ClaimSpec::Identity(IdentityID::random()); + let spec = ClaimSpec::Identity(MaybePrivate::new_public(IdentityID::random())); assert_eq!(identity.claims().len(), 0); - let identity = identity.make_claim(claim_id.clone(), spec.clone(), Timestamp::now()); + let identity = identity.make_claim(claim_id.clone(), spec.clone(), None).unwrap(); assert_eq!(identity.claims().len(), 1); - assert_eq!(identity.claims()[0].claim().id(), &claim_id); - match (identity.claims()[0].claim().spec(), &spec) { + assert_eq!(identity.claims()[0].id(), &claim_id); + match (identity.claims()[0].spec(), &spec) { (ClaimSpec::Identity(val), ClaimSpec::Identity(val2)) => assert_eq!(val, val2), _ => panic!("bad claim type"), } let claim_id2 = ClaimID::random(); let spec2 = ClaimSpec::Name(MaybePrivate::new_public(String::from("BOND. JAMES BOND."))); - let identity = identity.make_claim(claim_id2.clone(), spec2.clone(), Timestamp::now()); + let identity = identity.make_claim(claim_id2.clone(), spec2.clone(), None).unwrap(); assert_eq!(identity.claims().len(), 2); - assert_eq!(identity.claims()[0].claim().id(), &claim_id); - assert_eq!(identity.claims()[1].claim().id(), &claim_id2); + assert_eq!(identity.claims()[0].id(), &claim_id); + assert_eq!(identity.claims()[1].id(), &claim_id2); let identity = identity.delete_claim(&claim_id).unwrap(); assert_eq!(identity.claims().len(), 1); - assert_eq!(identity.claims()[0].claim().id(), &claim_id2); + assert_eq!(identity.claims()[0].id(), &claim_id2); let res = identity.clone().delete_claim(&claim_id); assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); } #[test] - fn identity_stamp_accept_delete_verify_revoke() { - let (_master_key_stampee, identity_stampee) = create_identity(); - let (master_key_stamper, identity_stamper) = create_identity(); - - let claim_id = ClaimID::random(); - let spec = ClaimSpec::Identity(IdentityID::random()); - let identity_stampee = identity_stampee.make_claim(claim_id.clone(), spec.clone(), Timestamp::now()); - - let stamp = identity_stamper.stamp(&master_key_stamper, Confidence::High, Timestamp::now(), identity_stampee.id(), identity_stampee.claims()[0].claim(), None).unwrap(); - identity_stamper.verify_stamp(&stamp).unwrap(); - - let revocation = identity_stamper.revoke_stamp(&master_key_stamper, &stamp, Timestamp::now()).unwrap(); - revocation.verify(identity_stamper.keychain().root()).unwrap(); - - let mut stamp_mod = stamp.clone(); - stamp_mod.entry_mut().set_confidence(Confidence::None); // very very log energy - let res = identity_stamper.verify_stamp(&stamp_mod); - assert_eq!(res.err(), Some(Error::CryptoSignatureVerificationFailed)); - - let identity_stampee = identity_stampee.clone().accept_stamp(stamp.clone()).unwrap(); - assert_eq!(identity_stampee.claims()[0].stamps().len(), 1); - - let identity_stampee2 = identity_stampee.clone().delete_claim(identity_stampee.claims()[0].claim().id()).unwrap(); - let res = identity_stampee2.clone().accept_stamp(stamp.clone()); - assert_eq!(res.err(), Some(Error::IdentityClaimNotFound)); + fn identity_stamp_make_revoke() { + todo!(); + } - let identity_stampee3 = identity_stampee.clone().delete_stamp(stamp.id()).unwrap(); - assert_eq!(identity_stampee3.claims()[0].stamps().len(), 0); - let res = identity_stampee3.delete_stamp(stamp.id()); - assert_eq!(res.err(), Some(Error::IdentityStampNotFound)); + #[test] + fn identity_stamp_accept_delete() { + todo!(); } #[test] - fn identity_set_keys_brah_whoaaa_shaka_gnargnar_so_pitted_whapow() { + fn identity_add_remove_admin_keys_brah_whoaaa_shaka_gnargnar_so_pitted_whapow() { let (master_key, identity) = create_identity(); + todo!(); + /* macro_rules! keytest { ($keyty:ident, $setter:ident, $getter:ident) => { let old_keypair = identity.keychain().$getter().clone(); @@ -706,7 +527,8 @@ mod tests { } keytest!{ PolicyKeypair, set_policy_key, policy } keytest!{ PublishKeypair, set_publish_key, publish } - keytest!{ RootKeypair, set_root_key, root } + keytest!{ AdminKeypair, set_root_key, root } + */ } #[test] @@ -723,67 +545,43 @@ mod tests { assert_eq!(identity.keychain().subkeys()[0].key().as_signkey(), Some(&signkey)); assert_eq!(identity.keychain().subkeys()[0].revocation().is_some(), false); + let key_id = key.key_id(); let res = identity.clone().add_subkey(key, "default:sign", Some("get a job")); assert_eq!(res.err(), Some(Error::DuplicateName)); assert_eq!(identity.keychain().subkeys()[0].revocation().is_some(), false); - let identity = identity.edit_subkey("default:sign", "sign:shutup-parker-thank-you-shutup", None).unwrap(); + let identity = identity.edit_subkey(&key_id, Some("sign:shutup-parker-thank-you-shutup"), None).unwrap(); assert_eq!(identity.keychain().subkeys().len(), 1); assert_eq!(identity.keychain().subkeys()[0].name(), "sign:shutup-parker-thank-you-shutup"); assert_eq!(identity.keychain().subkeys()[0].description(), &None); assert_eq!(identity.keychain().subkeys()[0].key().as_signkey(), Some(&signkey)); assert_eq!(identity.keychain().subkeys()[0].revocation().is_some(), false); - let identity = identity.revoke_subkey("sign:shutup-parker-thank-you-shutup", RevocationReason::Superseded, Some("thank-you".into())).unwrap(); + let identity = identity.revoke_subkey(&key_id, RevocationReason::Superseded, Some("thank-you".into())).unwrap(); assert_eq!(identity.keychain().subkeys().len(), 1); assert_eq!(identity.keychain().subkeys()[0].name(), "thank-you"); assert_eq!(identity.keychain().subkeys()[0].description(), &None); assert_eq!(identity.keychain().subkeys()[0].key().as_signkey(), Some(&signkey)); assert_eq!(identity.keychain().subkeys()[0].revocation().is_some(), true); - let res = identity.clone().revoke_subkey("thank-you", RevocationReason::Superseded, Some("thank-you".into())); - assert_eq!(res.err(), Some(Error::IdentitySubkeyAlreadyRevoked)); + let res = identity.clone().revoke_subkey(&key_id, RevocationReason::Superseded, Some("thank-you".into())); + assert_eq!(res.err(), Some(Error::KeychainSubkeyAlreadyRevoked)); - let identity = identity.delete_subkey("thank-you").unwrap(); + let identity = identity.delete_subkey(&key_id).unwrap(); assert_eq!(identity.keychain().subkeys().len(), 0); - let res = identity.clone().delete_subkey("thank-you"); - assert_eq!(res.err(), Some(Error::IdentitySubkeyNotFound)); + let res = identity.clone().delete_subkey(&key_id); + assert_eq!(res.err(), Some(Error::KeychainKeyNotFound(key_id.clone()))); } #[test] fn identity_nicknames() { let (_master_key, identity) = util::test::setup_identity_with_subkeys(); - assert_eq!(identity.extra_data().nickname().is_none(), true); + assert_eq!(identity.nickname().is_none(), true); let identity = identity.set_nickname(Some("fascistpig".into())); - assert_eq!(identity.extra_data().nickname().as_ref().unwrap(), "fascistpig"); - assert_eq!(identity.nickname_maybe(), Some("fascistpig".into())); + assert_eq!(identity.nickname(), Some("fascistpig".into()).as_ref()); let identity = identity.set_nickname(None); - assert_eq!(identity.extra_data().nickname(), &None); - assert_eq!(identity.nickname_maybe(), None); - } - - #[test] - fn identity_forward_add_delete() { - let (_master_key, identity) = util::test::setup_identity_with_subkeys(); - assert_eq!(identity.extra_data().forwards().len(), 0); - let forward = ForwardType::Social { - ty: "matrix".into(), - handle: "@jayjay-the-stinky-hippie:matrix.oorg".into(), - }; - - let identity = identity.add_forward("matrix", forward.clone(), true).unwrap(); - assert_eq!(identity.extra_data().forwards().len(), 1); - assert_eq!(identity.extra_data().forwards()[0].name(), "matrix"); - assert_eq!(identity.extra_data().forwards()[0].val(), &forward); - assert_eq!(identity.extra_data().forwards()[0].is_default(), &true); - - let identity = identity.delete_forward("matrix").unwrap(); - assert_eq!(identity.extra_data().forwards().len(), 0); - - let res = identity.clone().delete_forward("matrix"); - assert_eq!(res.err(), Some(Error::IdentityForwardNotFound)); - + assert_eq!(identity.nickname(), None); } #[test] @@ -794,10 +592,13 @@ mod tests { let claim_id = ClaimID::random(); let spec = ClaimSpec::Email(MaybePrivate::new_public(String::from("poopy@butt.com"))); - let identity = identity.make_claim(claim_id.clone(), spec.clone(), Timestamp::now()); + let identity = identity.make_claim(claim_id.clone(), spec.clone(), Some("Zing".into())).unwrap(); assert_eq!(identity.emails(), vec!["poopy@butt.com".to_string()]); assert_eq!(identity.email_maybe(), Some("poopy@butt.com".to_string())); + todo!("convert forwards to claims"); + + /* let forward1 = ForwardType::Social { ty: "matrix".into(), handle: "@jayjay-the-stinky-hippie:matrix.oorg".into(), @@ -824,6 +625,7 @@ mod tests { let identity5 = identity4.delete_forward("email").unwrap(); assert_eq!(identity5.email_maybe(), Some("jabjabjabjab@jabjabjabberjaw.com".to_string())); + */ } #[test] @@ -834,13 +636,13 @@ mod tests { let claim_id = ClaimID::random(); let spec = ClaimSpec::Name(MaybePrivate::new_public(String::from("BOND. JAMES BOND."))); - let identity = identity.make_claim(claim_id.clone(), spec.clone(), Timestamp::now()); + let identity = identity.make_claim(claim_id.clone(), spec.clone(), Some("hvvvv".into())).unwrap(); assert_eq!(identity.names(), vec!["BOND. JAMES BOND.".to_string()]); assert_eq!(identity.name_maybe(), Some("BOND. JAMES BOND.".to_string())); let claim_id2 = ClaimID::random(); let spec = ClaimSpec::Name(MaybePrivate::new_public(String::from("Jack Mama"))); - let identity = identity.make_claim(claim_id2.clone(), spec.clone(), Timestamp::now()); + let identity = identity.make_claim(claim_id2.clone(), spec.clone(), Some("GUHHHH".into())).unwrap(); assert_eq!(identity.names(), vec!["BOND. JAMES BOND.".to_string(), "Jack Mama".to_string()]); assert_eq!(identity.name_maybe(), Some("BOND. JAMES BOND.".to_string())); @@ -879,25 +681,22 @@ mod tests { let now = Timestamp::from_str("1977-06-07T04:32:06Z").unwrap(); let seeds = [ &[33, 90, 159, 88, 22, 24, 84, 4, 237, 121, 198, 195, 71, 238, 107, 91, 235, 93, 9, 129, 252, 221, 2, 149, 250, 142, 49, 36, 161, 184, 44, 156], - &[170, 39, 114, 32, 79, 238, 151, 138, 85, 59, 44, 153, 147, 105, 161, 127, 180, 225, 13, 119, 143, 46, 119, 153, 203, 41, 129, 240, 180, 88, 201, 37], - &[67, 150, 243, 61, 128, 149, 195, 141, 16, 154, 144, 63, 21, 245, 243, 226, 244, 55, 168, 59, 66, 45, 15, 61, 152, 5, 101, 219, 43, 137, 197, 90], - &[179, 112, 207, 116, 174, 196, 118, 123, 235, 202, 236, 69, 169, 209, 65, 238, 204, 235, 194, 187, 37, 246, 180, 124, 8, 116, 207, 175, 95, 87, 159, 137], ]; - let alpha = AlphaKeypair::new_ed25519_from_seed(&master_key, seeds[0]).unwrap(); - let policy = PolicyKeypair::new_ed25519_from_seed(&master_key, seeds[1]).unwrap(); - let publish = PublishKeypair::new_ed25519_from_seed(&master_key, seeds[2]).unwrap(); - let root = RootKeypair::new_ed25519_from_seed(&master_key, seeds[3]).unwrap(); - - let sig = alpha.sign(&master_key, "get a job".as_bytes()).unwrap(); - let id = IdentityID::from(sig.deref().clone()); - - let identity = Identity::create(id.clone(), alpha.clone(), policy.clone(), publish.clone(), root.clone(), now.clone()); + let admin = AdminKeypair::new_ed25519_from_seed(&master_key, seeds[0]).unwrap(); + let admin_key = AdminKey::new(admin.clone(), "alpha", None); + + let id = IdentityID::from(TransactionID::from(Sha512::hash(b"get a job").unwrap())); + let capability = CapabilityPolicy::new( + "default".into(), + vec![Capability::Permissive], + Policy::MOfN { must_have: 1, participants: vec![Participant::Key(admin.into())] } + ); + let identity = Identity::create(id.clone(), vec![admin_key], vec![capability], now); let ser = identity.serialize().unwrap(); assert_eq!(ser.trim(), r#"--- id: Ed25519: fCIX7Z3EiXIanC2819hWhF3oNW9gg6ujZKW8D_Y1lfZJJODmkkjVJlOZKCtM6YMa_fSS4i6Witse0k2UlZ-GAQ created: "1977-06-07T04:32:06Z" -recovery_policy: ~ keychain: alpha: Ed25519: @@ -925,7 +724,7 @@ extra_data: #[test] fn identity_strip_has_private() { let (master_key, identity) = create_identity(); - let identity = identity.make_claim(ClaimID::random(), ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Bozotron".to_string()).unwrap()), Timestamp::now()); + let identity = identity.make_claim(ClaimID::random(), ClaimSpec::Name(MaybePrivate::new_private(&master_key, "Bozotron".to_string()).unwrap()), None).unwrap(); assert!(identity.has_private()); assert!(identity.keychain().has_private()); assert!(identity.claims().iter().find(|c| c.has_private()).is_some()); diff --git a/src/identity/keychain.rs b/src/identity/keychain.rs index c198043..b55a5ab 100644 --- a/src/identity/keychain.rs +++ b/src/identity/keychain.rs @@ -15,9 +15,8 @@ use crate::{ error::{Error, Result}, crypto::key::{KeyID, SecretKey, SignKeypairSignature, SignKeypair, SignKeypairPublic, CryptoKeypair}, - policy::CapabilityPolicy, - private::{Private, PrivateWithHmac}, - util::{Public, sign::Signable, ser, ser::BinaryVec}, + private::{PrivateWithHmac}, + util::{Public, sign::Signable, ser}, }; use getset; use rasn::{AsnType, Encode, Decode}; @@ -99,7 +98,7 @@ macro_rules! make_keytype { impl ExtendKeypairSignature for $signaturetype {} - #[derive(Debug, Clone, Serialize, Deserialize)] + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct $keytype_public(SignKeypairPublic); asn_encdec_newtype! { $keytype_public, SignKeypairPublic } @@ -128,6 +127,12 @@ macro_rules! make_keytype { } } + impl From<$keytype> for $keytype_public { + fn from(key: $keytype) -> Self { + Self(key.deref().clone().into()) + } + } + impl Deref for $keytype { type Target = SignKeypair; fn deref(&self) -> &Self::Target { @@ -200,7 +205,7 @@ pub enum Key { /// An asymmetric crypto key. #[rasn(tag(explicit(2)))] Crypto(CryptoKeypair), - /// Hides our private data (including private claims). + /// A symmetric encryption key. #[rasn(tag(explicit(3)))] Secret(PrivateWithHmac), } @@ -221,7 +226,7 @@ impl Key { Self::Secret(key) } - /// Returns the `SignKeypair` if this is a policy key. + /// Returns the [AdminKeypair] if this is an admin key. pub fn as_adminkey(&self) -> Option<&AdminKeypair> { match self { Self::Admin(ref x) => Some(x), @@ -246,20 +251,20 @@ impl Key { } /// Returns the `SecretKey` if this is a secret key. - pub fn as_secretkey(&self) -> Option<&Private> { + pub fn as_secretkey(&self) -> Option<&PrivateWithHmac> { match self { Self::Secret(ref x) => Some(x), _ => None, } } - /// Returns a KeyID for this key, if possible. - pub fn key_id(&self) -> Option { + /// Returns a KeyID for this key. + pub fn key_id(&self) -> KeyID { match self { - Self::Admin(keypair) => Some(keypair.key_id()), - Self::Sign(keypair) => Some(keypair.key_id()), - Self::Crypto(keypair) => Some(keypair.key_id()), - _ => None, + Self::Admin(keypair) => keypair.key_id(), + Self::Sign(keypair) => keypair.key_id(), + Self::Crypto(keypair) => keypair.key_id(), + Self::Secret(pwh) => KeyID::SecretKey(pwh.hmac().clone()), } } @@ -321,9 +326,6 @@ pub struct Subkey { #[rasn(tag(explicit(0)))] key: Key, /// The key's human-readable name, for example "email". - /// - /// This must be a unique value among all subkeys (even revoked ones). This - /// is used in many places to reference the key. #[rasn(tag(explicit(1)))] name: String, /// The key's human-readable description, for example "Please send me @@ -348,8 +350,7 @@ impl Subkey { /// Revoke this subkey. fn revoke(&mut self, reason: RevocationReason, name_change: Option) { - let revocation = Revocation::new(reason); - self.set_revocation(Some(revocation)); + self.set_revocation(Some(reason)); if let Some(new_name) = name_change { self.set_name(new_name); } @@ -370,8 +371,8 @@ impl Public for Subkey { clone } - fn has_private(&self) => { - self.key().has_private(); + fn has_private(&self) -> bool { + self.key().has_private() } } @@ -385,8 +386,6 @@ pub struct AdminKey { #[rasn(tag(explicit(0)))] key: AdminKeypair, /// The key's human-readable name, for example "claims/manage". - /// - /// This must be a unique value among all admin keys. #[rasn(tag(explicit(1)))] name: String, /// The key's human-readable description, for example "This key is used to @@ -396,11 +395,40 @@ pub struct AdminKey { } impl AdminKey { - fn new(key: AdminKeypair, name: String, description: Option) -> Self { - Self { key, name, description } + /// Create a new AdminKey + pub fn new>(key: AdminKeypair, name: T, description: Option) -> Self { + Self { + key, + name: name.into(), + description: description.map(|x| x.into()), + } + } + + /// Re-encrypt this signing keypair with a new master key. + pub fn reencrypt(self, previous_master_key: &SecretKey, new_master_key: &SecretKey) -> Result { + let Self { key, name, description } = self; + Ok(Self::new(key.reencrypt(previous_master_key, new_master_key)?, name, description)) + } +} + +impl Deref for AdminKey { + type Target = AdminKeypair; + fn deref(&self) -> &Self::Target { + self.key() } } +impl Public for AdminKey { + fn strip_private(&self) -> Self { + let mut clone = self.clone(); + clone.set_key(clone.key().strip_private()); + clone + } + + fn has_private(&self) -> bool { + self.key().has_private() + } +} /// Holds the keys for our identity. /// /// This is a set of administration keys which can be used to manage the @@ -408,79 +436,48 @@ impl AdminKey { /// as well as a collection of subkeys, which can be used by various third /// party applications (including Stamp's CLU/GUI) for cryptography. /// -/// Aside from keys, the keychain also stores a collection of capabilities and -/// policies which control which actions can be performed by which combinations -/// of keys. This generalized setup allows things as easy as "one key for all -/// actions" or as granular as "three signatures from these five keys can add -/// a new subkey if its name matches to glob pattern "turtl/*". The sky is the -/// limit. -/// /// The keys stored here can also be revoked. They can remain stored here for /// the purposes of verifying old signatures or decrypting old messages, but /// revoked keys must not be used to sign or encrypt new data. #[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct Keychain { - /// A collection of capabilities, each eith a key policy attached to it. The - /// idea here is that we can specify a capability/action such as "add subkey" - /// and allow that action to be performed if we have the proper signature(s) - /// as determined by the key policy. - /// - /// This allows use to not only run transactions against this identity, but - /// also allows others to do so as well, given they sign their transactions - /// according to the given policies. - #[rasn(tag(explicit(0)))] - capabilities: Vec, /// Holds this identity's owned administration keypairs. These are keys used /// to manage the identity, although it's entirely possible to manage the /// identity using keys owned by other identities by using the policy system. - #[rasn(tag(explicit(1)))] + #[rasn(tag(explicit(0)))] admin_keys: Vec, /// Holds subkeys, which are non-admin keys owned by this identity. Generally /// these are accessed/used by other systems for things like creating messages /// or accessing encrypted data. For instance, an application that manages /// encrypted notes might store a subkey in the keychain which can be used to /// unlock the note data. - #[rasn(tag(explicit(2)))] + #[rasn(tag(explicit(1)))] subkeys: Vec, } impl Keychain { /// Create a new keychain - pub(crate) fn new() -> Self { + pub(crate) fn new(admin_keys: Vec) -> Self { Self { - capabilities: Vec::new(), - admin_keys: Vec::new(), + admin_keys, subkeys: Vec::new(), } } - /// Verify that a signature and a set of data used to generate that - /// signature can be verified by at least one of our signing keys. - pub(crate) fn try_keys(keylist: &Vec<&SignKeypair>, sigfn: F) -> Result<()> - where F: Fn(&SignKeypair) -> Result<()>, - { - for sign_keypair in keylist { - if sigfn(sign_keypair).is_ok() { - return Ok(()); - } - } - Err(Error::CryptoSignatureVerificationFailed) + /// Find an admin key by key id. + pub fn admin_key_by_keyid(&self, key_id: &KeyID) -> Option<&AdminKey> { + self.admin_keys().iter().find(|x| &x.key_id() == key_id) } - /// Find a subkey by ID. Relieves a bit of tedium. - pub fn subkey_by_keyid(&self, keyid_str: &str) -> Option<&Subkey> { - self.subkeys().iter().find(|x| { - if let Some(key_id) = x.key_id() { - return key_id.as_string().starts_with(keyid_str); - } - false - }) + /// Find an admin key by string key id. + pub fn admin_key_by_keyid_str(&self, keyid_str: &str) -> Option<&AdminKey> { + self.admin_keys().iter().find(|x| x.key().key_id().as_string() == keyid_str) } - /// Find a subkey by name. Relieves a bit of tedium. - pub fn subkey_by_name(&self, name: &str) -> Option<&Subkey> { - self.subkeys().iter().find(|x| x.name() == name) + /// Find an admin key by key id. + pub fn admin_key_by_keyid_mut(&mut self, keyid: &KeyID) -> Option<&mut AdminKey> { + self.admin_keys_mut().iter_mut().find(|x| &x.key().key_id() == keyid) } /// Find an admin key by name. @@ -488,6 +485,26 @@ impl Keychain { self.admin_keys().iter().find(|x| x.name() == name) } + /// Find a subkey by ID. + pub fn subkey_by_keyid(&self, key_id: &KeyID) -> Option<&Subkey> { + self.subkeys().iter().find(|x| &x.key_id() == key_id) + } + + /// Find a subkey by ID string + pub fn subkey_by_keyid_str(&self, keyid_str: &str) -> Option<&Subkey> { + self.subkeys().iter().find(|x| x.key_id().as_string() == keyid_str) + } + + /// Find a subkey mut by ID. Relieves a bit of tedium. + pub fn subkey_by_keyid_mut(&mut self, keyid: &KeyID) -> Option<&mut Subkey> { + self.subkeys_mut().iter_mut().find(|x| &x.key_id() == keyid) + } + + /// Find a subkey by name. Relieves a bit of tedium. + pub fn subkey_by_name(&self, name: &str) -> Option<&Subkey> { + self.subkeys().iter().find(|x| x.name() == name) + } + /// Grab all admin keys (active and revoked). pub fn keys_admin(&self) -> Vec<&AdminKeypair> { let mut search_keys = self.admin_keys() @@ -520,65 +537,46 @@ impl Keychain { /// Add an admin key but check for dupes fn add_admin_key_impl(&mut self, admin_key: AdminKey) -> Result<()> { - if self.admin_key_by_name(admin_key.name()).is_some() { - Err(Error::DuplicateName)?; - } - self.admin_keys_mut().push(subkey); + self.admin_keys_mut().push(admin_key); Ok(()) } /// Add a subkey but check for dupes fn add_subkey_impl(&mut self, subkey: Subkey) -> Result<()> { - if self.subkey_by_name(subkey.name()).is_some() { - Err(Error::DuplicateName)?; - } self.subkeys_mut().push(subkey); Ok(()) } - /// Replace our policy signing key. - /// - /// This moves the current policy key into the subkeys and revokes it. - pub(crate) fn set_policy_key(mut self, new_policy_keypair: PolicyKeypair, reason: RevocationReason) -> Result { - let policy = self.policy().clone(); - let name = format!("revoked:policy:{}", policy.key_id().as_string()); - let mut subkey = Subkey::new(Key::Policy(policy), name, Some("revoked policy key".into())); - subkey.revoke(reason, None); - self.add_subkey_impl(subkey)?; - self.set_policy(new_policy_keypair); + /// Add a new admin keypair. + pub(crate) fn add_admin_key(mut self, admin_key: AdminKey) -> Result { + self.add_admin_key_impl(admin_key)?; Ok(self) } - /// Replace our publish signing key. - /// - /// This moves the current publish key into the subkeys and revokes it. - pub(crate) fn set_publish_key(mut self, new_publish_keypair: PublishKeypair, reason: RevocationReason) -> Result { - let publish = self.publish().clone(); - let name = format!("revoked:publish:{}", publish.key_id().as_string()); - let mut subkey = Subkey::new(Key::Publish(publish), name, Some("revoked publish key".into())); - subkey.revoke(reason, None); - self.add_subkey_impl(subkey)?; - self.set_publish(new_publish_keypair); + /// Update some info about an admin key + pub(crate) fn edit_admin_key>(mut self, id: &KeyID, name: Option, description: Option>) -> Result { + let mut key = self.admin_key_by_keyid_mut(id) + .ok_or_else(|| Error::KeychainKeyNotFound(id.clone()))? + .clone(); + if let Some(set_name) = name { + key.set_name(set_name.into()); + } + if let Some(desc) = description { + key.set_description(desc.map(|x| x.into())); + } Ok(self) } - /// Replace our root signing key. - /// - /// This moves the current root key into the subkeys and revokes it. - pub(crate) fn set_root_key(mut self, new_root_keypair: RootKeypair, reason: RevocationReason) -> Result { - let root = self.root().clone(); - let name = format!("revoked:root:{}", root.key_id().as_string()); - let mut subkey = Subkey::new(Key::Root(root), name, Some("revoked root key".into())); + /// Revoke an [Admin key][AdminKeypair]. + pub(crate) fn revoke_admin_key(mut self, id: &KeyID, reason: RevocationReason, new_name: Option) -> Result { + let key = self.admin_key_by_keyid(id) + .ok_or_else(|| Error::KeychainKeyNotFound(id.clone()))? + .clone(); + self.admin_keys_mut().retain(|k| k.key() != key.key()); + let new_name = new_name.unwrap_or_else(|| format!("revoked/admin/{}", key.key().key_id().as_string())); + let mut subkey = Subkey::new(Key::Admin(key.key().clone()), new_name, Some("revoked admin key".into())); subkey.revoke(reason, None); self.add_subkey_impl(subkey)?; - self.set_root(new_root_keypair); - Ok(self) - } - - /// Add a new admin keypair. - pub(crate) fn add_admin_key>(mut self, key: AdminKeypair, name: T, description: Option) -> Result { - let admin_key = AdminKey::new(key, name.into(), description.map(|x| x.into())); - self.add_admin_key_impl(admin_key)?; Ok(self) } @@ -590,32 +588,36 @@ impl Keychain { } /// Edit a subkey (set name/description). - pub(crate) fn edit_subkey>(mut self, name: &str, new_name: T, description: Option) -> Result { - let key = self.subkeys_mut().iter_mut().find(|x| x.name() == name) - .ok_or(Error::IdentitySubkeyNotFound)?; - key.set_name(new_name.into()); - key.set_description(description.map(|x| x.into())); + pub(crate) fn edit_subkey>(mut self, id: &KeyID, name: Option, description: Option>) -> Result { + let key = self.subkey_by_keyid_mut(id) + .ok_or_else(|| Error::KeychainKeyNotFound(id.clone()))?; + if let Some(set_name) = name { + key.set_name(set_name.into()); + } + if let Some(desc) = description { + key.set_description(desc.map(|x| x.into())); + } Ok(self) } /// Revoke a subkey. - pub(crate) fn revoke_subkey(mut self, name: &str, reason: RevocationReason, new_name: Option) -> Result { - let key = self.subkeys_mut().iter_mut().find(|x| x.name() == name) - .ok_or(Error::IdentitySubkeyNotFound)?; + pub(crate) fn revoke_subkey(mut self, id: &KeyID, reason: RevocationReason, new_name: Option) -> Result { + let mut key = self.subkey_by_keyid_mut(id) + .ok_or_else(|| Error::KeychainKeyNotFound(id.clone()))? + .clone(); if key.revocation().is_some() { - Err(Error::IdentitySubkeyAlreadyRevoked)?; + Err(Error::KeychainSubkeyAlreadyRevoked)?; } key.revoke(reason, new_name); Ok(self) } /// Delete a key from the keychain. - pub(crate) fn delete_subkey(mut self, name: &str) -> Result { - let exists = self.subkey_by_name(name); - if exists.is_none() { - Err(Error::IdentitySubkeyNotFound)?; - } - self.subkeys_mut().retain(|x| x.name() != name); + pub(crate) fn delete_subkey(mut self, id: &KeyID) -> Result { + let key = self.subkey_by_keyid(id) + .ok_or_else(|| Error::KeychainKeyNotFound(id.clone()))? + .clone(); + self.subkeys_mut().retain(|x| &x.key_id() != id); Ok(self) } } @@ -623,30 +625,26 @@ impl Keychain { impl Public for Keychain { fn strip_private(&self) -> Self { let mut keychain_clone = self.clone(); - keychain_clone.set_alpha(self.alpha().strip_private()); - keychain_clone.set_policy(self.policy().strip_private()); - keychain_clone.set_publish(self.publish().strip_private()); - keychain_clone.set_root(self.root().strip_private()); - let subkeys = self.subkeys().clone().into_iter() - .map(|x| { - (x.key().strip_private_maybe(), x) + let admin_stripped = keychain_clone.admin_keys().clone().into_iter() + .map(|mut ak| { + ak.set_key(ak.key().strip_private()); + ak }) - .filter(|x| x.0.is_some()) - .map(|(key, mut subkey)| { - subkey.set_key(key.unwrap()); - subkey + .collect::>(); + let subkeys_stripped = self.subkeys().clone().into_iter() + .map(|mut sk| { + sk.set_key(sk.key().strip_private()); + sk }) .collect::>(); - keychain_clone.set_subkeys(subkeys); + keychain_clone.set_admin_keys(admin_stripped); + keychain_clone.set_subkeys(subkeys_stripped); keychain_clone } fn has_private(&self) -> bool { - self.alpha().has_private() || - self.policy().has_private() || - self.publish().has_private() || - self.root().has_private() || - self.subkeys().iter().find(|x| x.has_private()).is_some() + self.admin_keys().iter().find(|x| x.key().has_private()).is_some() || + self.subkeys().iter().find(|x| x.key().has_private()).is_some() } } @@ -665,67 +663,13 @@ mod tests { } #[test] - fn alpha_ser() { - let master_key = get_master_key(); - let hashbytes = util::hash(b"get a job").unwrap(); - let seed: [u8; 32] = hashbytes[0..32].try_into().unwrap(); - let kp = SignKeypair::new_ed25519_from_seed(&master_key, &seed).unwrap(); - let alpha = AlphaKeypair::from(kp); - let sig = alpha.sign(&master_key, b"who's this, steve??").unwrap(); - let sig_inner: &SignKeypairSignature = sig.deref(); - let ser = util::ser::serialize(&sig).unwrap(); - let ser_inner = util::ser::serialize(sig_inner).unwrap(); - assert_eq!(ser, ser_inner); - assert_eq!( - "oEIEQD1K6VIpwXjFMZdpb8XqMmgV2uRPedKr-AxGicJPqkndk79ryzsBzDmMTh2SYC-cscEng5BP4iqHlbyIxpRH5wI", - util::ser::base64_encode(&ser).as_str() - ); - } - - #[test] - fn policy_ser() { - let master_key = get_master_key(); - let hashbytes = util::hash(b"im detective john kimble").unwrap(); - let seed: [u8; 32] = hashbytes[0..32].try_into().unwrap(); - let kp = SignKeypair::new_ed25519_from_seed(&master_key, &seed).unwrap(); - let policy = PolicyKeypair::from(kp); - let sig = policy.sign(&master_key, b"who's this, steve??").unwrap(); - let sig_inner: &SignKeypairSignature = sig.deref(); - let ser = util::ser::serialize(&sig).unwrap(); - let ser_inner = util::ser::serialize(sig_inner).unwrap(); - assert_eq!(ser, ser_inner); - assert_eq!( - "oEIEQMv99PDoO1W65NLAIxDFKQPdqQKzQWh_ei3tX9Xy088_5m58QpcgfY_2rA0CvC2uKq0pzif5vGw_x4VsfnAAPQI", - util::ser::base64_encode(&ser).as_str() - ); - } - - #[test] - fn publish_ser() { - let master_key = get_master_key(); - let hashbytes = util::hash(b"yeah sure you are").unwrap(); - let seed: [u8; 32] = hashbytes[0..32].try_into().unwrap(); - let kp = SignKeypair::new_ed25519_from_seed(&master_key, &seed).unwrap(); - let publish = PublishKeypair::from(kp); - let sig = publish.sign(&master_key, b"who's this, steve??").unwrap(); - let sig_inner: &SignKeypairSignature = sig.deref(); - let ser = util::ser::serialize(&sig).unwrap(); - let ser_inner = util::ser::serialize(sig_inner).unwrap(); - assert_eq!(ser, ser_inner); - assert_eq!( - "oEIEQBLSREemDtNKdHdG3iog2PJAQ8Sf2JCXrasZqAkteWQUwFx12BbR3oP6guKGLYClgTlr_0f9mC_OoO9yeGtaAQw", - util::ser::base64_encode(&ser).as_str() - ); - } - - #[test] - fn root_ser() { + fn admin_ser() { let master_key = get_master_key(); let hashbytes = util::hash(b"i will, bye").unwrap(); let seed: [u8; 32] = hashbytes[0..32].try_into().unwrap(); let kp = SignKeypair::new_ed25519_from_seed(&master_key, &seed).unwrap(); - let root = RootKeypair::from(kp); - let sig = root.sign(&master_key, b"who's this, steve??").unwrap(); + let admin = AdminKeypair::from(kp); + let sig = admin.sign(&master_key, b"who's this, steve??").unwrap(); let sig_inner: &SignKeypairSignature = sig.deref(); let ser = util::ser::serialize(&sig).unwrap(); let ser_inner = util::ser::serialize(sig_inner).unwrap(); @@ -739,103 +683,82 @@ mod tests { #[test] fn key_as_type() { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); + let admin_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); let secret_key = SecretKey::new_xchacha20poly1305().unwrap(); - let key1 = Key::Policy(policy_keypair.clone()); - let key2 = Key::Publish(publish_keypair.clone()); - let key3 = Key::Root(root_keypair.clone()); + let key3 = Key::Admin(admin_keypair.clone()); let key4 = Key::Sign(sign_keypair.clone()); let key5 = Key::Crypto(crypto_keypair.clone()); - let key6 = Key::Secret(Private::seal(&master_key, &secret_key).unwrap()); + let key6 = Key::Secret(PrivateWithHmac::seal(&master_key, secret_key).unwrap()); - let keys = vec![key1, key2, key3, key4, key5, key6]; + let keys = vec![key3, key4, key5, key6]; macro_rules! keytype { ($keys:ident, $fn:ident) => { $keys.iter().map(|x| x.$fn().is_some()).collect::>() } } - assert_eq!(keytype!(keys, as_policykey), vec![true, false, false, false, false, false]); - assert_eq!(keytype!(keys, as_rootkey), vec![false, false, true, false, false, false]); - assert_eq!(keytype!(keys, as_signkey), vec![false, false, false, true, false, false]); - assert_eq!(keytype!(keys, as_cryptokey), vec![false, false, false, false, true, false]); - assert_eq!(keytype!(keys, as_secretkey), vec![false, false, false, false, false, true]); + assert_eq!(keytype!(keys, as_adminkey), vec![true, false, false, false]); + assert_eq!(keytype!(keys, as_signkey), vec![false, true, false, false]); + assert_eq!(keytype!(keys, as_cryptokey), vec![false, false, true, false]); + assert_eq!(keytype!(keys, as_secretkey), vec![false, false, false, true]); } #[test] fn key_serde() { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); let secret_key = SecretKey::new_xchacha20poly1305().unwrap(); - let key = Key::Secret(Private::seal(&master_key, &secret_key).unwrap()); + let key = Key::Secret(PrivateWithHmac::seal(&master_key, secret_key).unwrap()); let ser = key.serialize().unwrap(); let key2 = Key::deserialize(ser.as_slice()).unwrap(); assert!(key.as_secretkey().is_some()); - let sec1 = util::ser::serialize(&key.as_secretkey().unwrap().open(&master_key).unwrap()).unwrap(); - let sec2 = util::ser::serialize(&key2.as_secretkey().unwrap().open(&master_key).unwrap()).unwrap(); + let sec1 = util::ser::serialize(&key.as_secretkey().unwrap().open_and_verify(&master_key).unwrap()).unwrap(); + let sec2 = util::ser::serialize(&key2.as_secretkey().unwrap().open_and_verify(&master_key).unwrap()).unwrap(); assert_eq!(sec1, sec2); } #[test] fn key_reencrypt() { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); + let admin_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); let secret_key = SecretKey::new_xchacha20poly1305().unwrap(); - let key1 = Key::Policy(policy_keypair.clone()); - let key2 = Key::Publish(publish_keypair.clone()); - let key3 = Key::Root(root_keypair.clone()); + let key3 = Key::Admin(admin_keypair.clone()); let key4 = Key::Sign(sign_keypair.clone()); let key5 = Key::Crypto(crypto_keypair.clone()); - let key6 = Key::Secret(Private::seal(&master_key, &secret_key).unwrap()); + let key6 = Key::Secret(PrivateWithHmac::seal(&master_key, secret_key).unwrap()); - let val1 = key1.as_policykey().unwrap().sign(&master_key, b"hi i'm jerry").unwrap(); - let val2 = key2.as_publishkey().unwrap().sign(&master_key, b"hi i'm barry").unwrap(); - let val3 = key3.as_rootkey().unwrap().sign(&master_key, b"hi i'm larry").unwrap(); + let val3 = key3.as_adminkey().unwrap().sign(&master_key, b"hi i'm larry").unwrap(); let val4 = key4.as_signkey().unwrap().sign(&master_key, b"hi i'm butch").unwrap(); let val5 = key5.as_cryptokey().unwrap().seal_anonymous(b"sufferin succotash").unwrap(); - let val6_key = key6.as_secretkey().unwrap().open(&master_key).unwrap(); + let val6_key = key6.as_secretkey().unwrap().open_and_verify(&master_key).unwrap(); let val6_nonce = val6_key.gen_nonce().unwrap(); let val6 = val6_key.seal(b"and your nose like a delicious slope of cream", &val6_nonce).unwrap(); let master_key2 = SecretKey::new_xchacha20poly1305().unwrap(); assert!(master_key != master_key2); - let key1_2 = key1.reencrypt(&master_key, &master_key2).unwrap(); - let key2_2 = key2.reencrypt(&master_key, &master_key2).unwrap(); let key3_2 = key3.reencrypt(&master_key, &master_key2).unwrap(); let key4_2 = key4.reencrypt(&master_key, &master_key2).unwrap(); let key5_2 = key5.reencrypt(&master_key, &master_key2).unwrap(); let key6_2 = key6.reencrypt(&master_key, &master_key2).unwrap(); - let val1_2 = key1_2.as_policykey().unwrap().sign(&master_key2, b"hi i'm jerry").unwrap(); - let val2_2 = key2_2.as_publishkey().unwrap().sign(&master_key2, b"hi i'm barry").unwrap(); - let val3_2 = key3_2.as_rootkey().unwrap().sign(&master_key2, b"hi i'm larry").unwrap(); + let val3_2 = key3_2.as_adminkey().unwrap().sign(&master_key2, b"hi i'm larry").unwrap(); let val4_2 = key4_2.as_signkey().unwrap().sign(&master_key2, b"hi i'm butch").unwrap(); let val5_2 = key5_2.as_cryptokey().unwrap().open_anonymous(&master_key2, &val5).unwrap(); - let val6_2_key = key6_2.as_secretkey().unwrap().open(&master_key2).unwrap(); + let val6_2_key = key6_2.as_secretkey().unwrap().open_and_verify(&master_key2).unwrap(); let val6_2 = val6_2_key.open(&val6, &val6_nonce).unwrap(); - assert_eq!(val1, val1_2); - assert_eq!(val2, val2_2); assert_eq!(val3, val3_2); assert_eq!(val4, val4_2); assert_eq!(val5_2, b"sufferin succotash"); assert_eq!(val6_2, b"and your nose like a delicious slope of cream"); - let res1 = key1_2.as_policykey().unwrap().sign(&master_key, b"hi i'm jerry"); - let res2 = key2_2.as_publishkey().unwrap().sign(&master_key, b"hi i'm barry"); - let res3 = key3_2.as_rootkey().unwrap().sign(&master_key, b"hi i'm larry"); + let res3 = key3_2.as_adminkey().unwrap().sign(&master_key, b"hi i'm larry"); let res4 = key4_2.as_signkey().unwrap().sign(&master_key, b"hi i'm butch"); let res5 = key5_2.as_cryptokey().unwrap().open_anonymous(&master_key, &val5); - let res6 = key6_2.as_secretkey().unwrap().open(&master_key); + let res6 = key6_2.as_secretkey().unwrap().open_and_verify(&master_key); - assert_eq!(res1, Err(Error::CryptoOpenFailed)); - assert_eq!(res2, Err(Error::CryptoOpenFailed)); assert_eq!(res3, Err(Error::CryptoOpenFailed)); assert_eq!(res4, Err(Error::CryptoOpenFailed)); assert_eq!(res5, Err(Error::CryptoOpenFailed)); @@ -845,35 +768,25 @@ mod tests { #[test] fn key_strip_private_has_private() { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); + let admin_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); let secret_key = SecretKey::new_xchacha20poly1305().unwrap(); - let key1 = Key::Policy(policy_keypair.clone()); - let key2 = Key::Publish(publish_keypair.clone()); - let key3 = Key::Root(root_keypair.clone()); + let key3 = Key::Admin(admin_keypair.clone()); let key4 = Key::Sign(sign_keypair.clone()); let key5 = Key::Crypto(crypto_keypair.clone()); let key6 = Key::Secret(PrivateWithHmac::seal(&master_key, secret_key).unwrap()); - assert!(key1.has_private()); - assert!(key2.has_private()); assert!(key3.has_private()); assert!(key4.has_private()); assert!(key5.has_private()); assert!(key6.has_private()); - let key1_2 = key1.strip_private(); - let key2_2 = key2.strip_private(); let key3_2 = key3.strip_private(); let key4_2 = key4.strip_private(); let key5_2 = key5.strip_private(); let key6_2 = key6.strip_private(); - assert!(!key1_2.has_private()); - assert!(!key2_2.has_private()); assert!(!key3_2.has_private()); assert!(!key4_2.has_private()); assert!(!key5_2.has_private()); @@ -882,49 +795,21 @@ mod tests { fn keychain_new() -> (SecretKey, Keychain) { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); + let admin_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); + let admin_key = AdminKey::new(admin_keypair, "Default", None); - let keychain = Keychain::new(alpha_keypair, policy_keypair, publish_keypair, root_keypair); + let keychain = Keychain::new(vec![admin_key]); (master_key, keychain) } #[test] - fn keychain_set_policy_keys() { - let (master_key, keychain) = keychain_new(); - assert_eq!(keychain.subkeys().len(), 0); - let old_key = keychain.policy().clone(); - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let keychain = keychain.set_policy_key(new_policy_keypair, RevocationReason::Superseded).unwrap(); - assert_eq!(keychain.subkeys().len(), 1); - assert_eq!(keychain.subkeys()[0].key().as_policykey(), Some(&old_key)); - assert_eq!(keychain.subkeys()[0].name(), &format!("revoked:policy:{}", old_key.key_id().as_string())); + fn keychain_add_admin_key() { + todo!(); } #[test] - fn keychain_set_publish() { - let (master_key, keychain) = keychain_new(); - assert_eq!(keychain.subkeys().len(), 0); - let old_key = keychain.publish().clone(); - let new_publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let keychain = keychain.set_publish_key(new_publish_keypair, RevocationReason::Superseded).unwrap(); - assert_eq!(keychain.subkeys().len(), 1); - assert_eq!(keychain.subkeys()[0].key().as_publishkey(), Some(&old_key)); - assert_eq!(keychain.subkeys()[0].name(), &format!("revoked:publish:{}", old_key.key_id().as_string())); - } - - #[test] - fn keychain_set_root() { - let (master_key, keychain) = keychain_new(); - assert_eq!(keychain.subkeys().len(), 0); - let old_key = keychain.root().clone(); - let new_root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let keychain = keychain.set_root_key(new_root_keypair, RevocationReason::Superseded).unwrap(); - assert_eq!(keychain.subkeys().len(), 1); - assert_eq!(keychain.subkeys()[0].key().as_rootkey(), Some(&old_key)); - assert_eq!(keychain.subkeys()[0].name(), &format!("revoked:root:{}", old_key.key_id().as_string())); + fn keychain_remove_admin_key() { + todo!(); } #[test] @@ -932,7 +817,7 @@ mod tests { let (master_key, keychain) = keychain_new(); let sign_keypair = SignKeypair::new_ed25519(&master_key).unwrap(); let crypto_keypair = CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap(); - let secret_key = Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); + let secret_key = PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap(); let sign = Key::new_sign(sign_keypair); let crypto = Key::new_crypto(crypto_keypair); let secret = Key::new_secret(secret_key); @@ -946,19 +831,18 @@ mod tests { let last = keychain.subkeys().iter().last().unwrap(); assert_eq!(last.name(), "MY signing key"); assert_eq!(Some(last.name()), keychain.subkey_by_name("MY signing key").map(|x| x.name())); - assert_eq!(Some(last.name()), keychain.subkey_by_keyid(&last.key_id().unwrap().as_string()).map(|x| x.name())); + assert_eq!(Some(last.name()), keychain.subkey_by_keyid(&last.key_id()).map(|x| x.name())); let keychain = keychain.add_subkey(crypto, "MY crypto key", Some("Send me messages with this key OR ELSE")).unwrap(); let last = keychain.subkeys().iter().last().unwrap(); assert_eq!(last.name(), "MY crypto key"); assert_eq!(Some(last.name()), keychain.subkey_by_name("MY crypto key").map(|x| x.name())); - assert_eq!(Some(last.name()), keychain.subkey_by_keyid(&last.key_id().unwrap().as_string()).map(|x| x.name())); + assert_eq!(Some(last.name()), keychain.subkey_by_keyid(&last.key_id()).map(|x| x.name())); let keychain = keychain.add_subkey(secret, "MY secret key", Some("I use this to encrypt files and shit")).unwrap(); let last = keychain.subkeys().iter().last().unwrap(); assert_eq!(last.name(), "MY secret key"); assert_eq!(Some(last.name()), keychain.subkey_by_name("MY secret key").map(|x| x.name())); - assert!(last.key_id().is_none()); // make sure finding by name does what we expect (first matching key // with that name) @@ -979,13 +863,13 @@ mod tests { let signkey = keychain.subkey_by_name("sign").unwrap().clone(); assert!(signkey.revocation.is_none()); - let keychain2 = keychain.clone().revoke_subkey(signkey.name(), RevocationReason::Unspecified, None).unwrap(); + let keychain2 = keychain.clone().revoke_subkey(&signkey.key_id(), RevocationReason::Unspecified, None).unwrap(); let signkey = keychain2.subkey_by_name("sign").unwrap().clone(); - assert_eq!(signkey.revocation().as_ref().unwrap().reason(), &RevocationReason::Unspecified); + assert_eq!(signkey.revocation().as_ref().unwrap(), &RevocationReason::Unspecified); - let keychain3 = keychain.clone().revoke_subkey(signkey.name(), RevocationReason::Unspecified, Some("revoked:sign".into())).unwrap(); + let keychain3 = keychain.clone().revoke_subkey(&signkey.key_id(), RevocationReason::Unspecified, Some("revoked:sign".into())).unwrap(); let signkey = keychain3.subkey_by_name("revoked:sign").unwrap().clone(); - assert_eq!(signkey.revocation().as_ref().unwrap().reason(), &RevocationReason::Unspecified); + assert_eq!(signkey.revocation().as_ref().unwrap(), &RevocationReason::Unspecified); } #[test] @@ -995,7 +879,7 @@ mod tests { let keychain = keychain.add_subkey(crypto, "crypto", None).unwrap(); // delete a key LOL let cryptokey = keychain.subkey_by_name("crypto").unwrap().clone(); - let keychain = keychain.delete_subkey(cryptokey.name()).unwrap(); + let keychain = keychain.delete_subkey(&cryptokey.key_id()).unwrap(); let cryptokey2 = keychain.subkey_by_name("crypto"); // checkmate, liberals assert!(cryptokey2.is_none()); @@ -1006,25 +890,19 @@ mod tests { let (master_key, keychain) = keychain_new(); let sign = Key::new_sign(SignKeypair::new_ed25519(&master_key).unwrap()); let crypto = Key::new_crypto(CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap()); - let secret = Key::new_secret(Private::seal(&master_key, &SecretKey::new_xchacha20poly1305().unwrap()).unwrap()); + let secret = Key::new_secret(PrivateWithHmac::seal(&master_key, SecretKey::new_xchacha20poly1305().unwrap()).unwrap()); let keychain = keychain .add_subkey(sign, "sign", None).unwrap() .add_subkey(crypto, "crypto", None).unwrap() .add_subkey(secret, "secret", None).unwrap(); - assert_eq!(keychain.alpha().has_private(), true); - assert_eq!(keychain.policy().has_private(), true); - assert_eq!(keychain.publish().has_private(), true); - assert_eq!(keychain.root().has_private(), true); + assert_eq!(keychain.admin_keys().iter().fold(false, |acc, x| acc || x.has_private()), true); assert_eq!(keychain.subkey_by_name("sign").unwrap().key().has_private(), true); assert_eq!(keychain.subkey_by_name("crypto").unwrap().key().has_private(), true); assert!(keychain.subkey_by_name("secret").is_some()); let keychain = keychain.strip_private(); - assert_eq!(keychain.alpha().has_private(), false); - assert_eq!(keychain.policy().has_private(), false); - assert_eq!(keychain.publish().has_private(), false); - assert_eq!(keychain.root().has_private(), false); + assert_eq!(keychain.admin_keys().iter().fold(false, |acc, x| acc || x.has_private()), false); assert_eq!(keychain.subkey_by_name("sign").unwrap().key().has_private(), false); assert_eq!(keychain.subkey_by_name("crypto").unwrap().key().has_private(), false); assert!(keychain.subkey_by_name("secret").is_none()); diff --git a/src/identity/mod.rs b/src/identity/mod.rs index ff751fa..351e163 100644 --- a/src/identity/mod.rs +++ b/src/identity/mod.rs @@ -3,15 +3,14 @@ //! //! An identity is essentially a set of keys (signing and encryption), a set of //! claims made by the identity owner (including the identity itself), any -//! number of signatures that verify those claims, and a set of "forwards" that -//! can point to other locations (for instance, your canonical email address, -//! your personal domain, etc). +//! number of signatures that verify those claims, and a set of [capability +//! policies][crate::policy::CapabilityPolicy] that dictate what public key +//! signatures are required to create valid transactions against this identity. //! //! This system relies heavily on the [key](crate::crypto::key) module, which //! provides all the mechanisms necessary for encryption, decryption, signing, //! and verification of data. - pub mod keychain; pub mod claim; pub mod stamp; @@ -22,407 +21,3 @@ pub use claim::*; pub use stamp::*; pub use identity::*; -use crate::{ - error::Result, - crypto::key::SecretKey, - dag::Transactions, - util::{ - Public, - Timestamp, - ser, - sign::DateSigner, - }, -}; -use serde_derive::{Serialize, Deserialize}; - -/// The container that is used to publish an identity. This is what otherswill -/// import when they verify an identity, stamp the claim for an identity, send -/// the identity a value for signing (for instance for logging in to an online -/// service), etc. -/// -/// The published identity must be signed by our publish keypair, which in turn -/// is signed by our alpha keypair. -#[derive(Debug, Clone, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] -#[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] -pub struct PublishedIdentity { - /// The signature of this published identity, generated using our publish - /// keypair. - publish_signature: PublishKeypairSignature, - /// The date we published on. - publish_date: Timestamp, - /// The versioned identity we're publishing. - identity: Transactions, -} - -impl PublishedIdentity { - /// Takes an identity and creates a signed published identity object from - /// it. - pub fn publish(master_key: &SecretKey, now: Timestamp, transactions: Transactions) -> Result { - let identity = transactions.build_identity()?; - let public_identity = transactions.strip_private(); - let datesigner = DateSigner::new(&now, &public_identity); - let serialized = ser::serialize(&datesigner)?; - let signature = identity.keychain().publish().sign(master_key, &serialized)?; - Ok(Self { - publish_signature: signature, - publish_date: now, - identity: public_identity, - }) - } - - /// Confirm that this published identity has indeed been signed by the - /// publish contained in the identity, and that the identity itself is - /// valid. - pub fn verify(&self) -> Result<()> { - // this verifies each transaction - let identity = self.identity().build_identity()?; - - // now that we know the identity is valid, we can validate the publish - // signature against its publish key - let datesigner = DateSigner::new(self.publish_date(), self.identity()); - let serialized = ser::serialize(&datesigner)?; - identity.keychain().publish().verify(self.publish_signature(), &serialized) - } - - /// Serialize this published identity into a human readable format - pub fn serialize(&self) -> Result { - ser::serialize_human(self) - } - - /// Deserialize this published identity from a byte vector. - pub fn deserialize(slice: &[u8]) -> Result { - let published: Self = ser::deserialize_human(slice)?; - published.verify()?; - Ok(published) - } -} - -impl Public for PublishedIdentity { - fn strip_private(&self) -> Self { - self.clone() - } - - fn has_private(&self) -> bool { - false - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - dag::{TransactionID, TransactionBody, TransactionVersioned}, - error::Error, - identity::{ - claim::ClaimSpec, - }, - private::MaybePrivate, - }; - use std::ops::Deref; - use std::str::FromStr; - - fn get_transactions() -> (SecretKey, Transactions) { - let now = Timestamp::now(); - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let transactions = Transactions::new() - .create_identity(&master_key, now.clone(), alpha_keypair, policy_keypair, publish_keypair, root_keypair).unwrap(); - let identity_id = IdentityID(transactions.transactions()[0].id().deref().clone()); - let transactions = transactions - .make_claim(&master_key, now.clone(), ClaimSpec::Identity(identity_id.clone())).unwrap() - .make_claim(&master_key, now.clone(), ClaimSpec::Name(MaybePrivate::new_public("Von Jonie Himself".to_string()))).unwrap() - .make_claim(&master_key, now.clone(), ClaimSpec::Address(MaybePrivate::new_private(&master_key, "6969 Uhhhhuhuhuhuh Thtreet".to_string()).unwrap())).unwrap(); - (master_key, transactions) - } - - #[test] - fn published_publish_verify() { - let (master_key, transactions) = get_transactions(); - let published = PublishedIdentity::publish(&master_key, Timestamp::now(), transactions.clone()).unwrap(); - - published.verify().unwrap(); - - // modify the publish date and watch it fail - let mut published_mod = published.clone(); - published_mod.set_publish_date(Timestamp::from_str("1968-03-07T13:45:59Z").unwrap()); - let res = published_mod.verify(); - assert_eq!(res.err(), Some(Error::CryptoSignatureVerificationFailed)); - - // modify each transaction and watch it fail - let mut idx = 0; - macro_rules! mod_trans { - ($published:expr, $trans:expr, $inner:ident, $op:expr, $err:expr) => {{ - let mut trans_copy = $trans.clone(); - match trans_copy { - TransactionVersioned::V1(ref mut $inner) => { - $op; - let mut pubclone = $published.clone(); - pubclone.identity_mut().transactions_mut()[idx] = trans_copy; - // should always faily after modificationy - let res = pubclone.verify(); - assert_eq!(res.err(), Some($err)); - } - } - }} - } - for trans in published.identity().transactions() { - published.verify().unwrap(); - let num_prev = match trans { - TransactionVersioned::V1(ref inner) => inner.entry().previous_transactions().len(), - }; - - match published.identity().transactions().get(idx + 1) { - Some(trans_id) => { - let next_id = trans_id.id(); - mod_trans!( published, trans, inner, { - inner.set_id(TransactionID::random_alpha()); - }, Error::DagOrphanedTransaction(String::from(next_id))); - } - None => {} - } - - if num_prev > 0 { - mod_trans!( published, trans, inner, { - inner.entry_mut().previous_transactions_mut().push(TransactionID::random_alpha()); - }, Error::DagOrphanedTransaction(String::from(trans.id()))); - } - - mod_trans!( published, trans, inner, { - inner.entry_mut().set_created(Timestamp::from_str("1719-09-12T13:44:58Z").unwrap()); - }, Error::CryptoSignatureVerificationFailed); - - mod_trans!( published, trans, inner, { - let body = match inner.entry().body().clone() { - TransactionBody::CreateIdentityV1 { alpha, policy, publish, root } => { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let new_alpha = AlphaKeypair::new_ed25519(&master_key).unwrap(); - assert!(new_alpha != alpha); - TransactionBody::CreateIdentityV1 { alpha: new_alpha, policy, publish, root } - } - TransactionBody::Private => { - TransactionBody::MakeClaimV1 { - spec: ClaimSpec::Name(MaybePrivate::new_public(String::from("BAT MAN"))), - } - } - _ => TransactionBody::Private, - }; - inner.entry_mut().set_body(body); - }, Error::CryptoSignatureVerificationFailed); - idx += 1; - } - } - - #[test] - fn published_serde() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let now = Timestamp::from_str("1977-06-07T04:32:06Z").unwrap(); - let seeds = [ - &[33, 90, 159, 88, 22, 24, 84, 4, 237, 121, 198, 195, 71, 238, 107, 91, 235, 93, 9, 129, 252, 221, 2, 149, 250, 142, 49, 36, 161, 184, 44, 156], - &[170, 39, 114, 32, 79, 238, 151, 138, 85, 59, 44, 153, 147, 105, 161, 127, 180, 225, 13, 119, 143, 46, 119, 153, 203, 41, 129, 240, 180, 88, 201, 37], - &[67, 150, 243, 61, 128, 149, 195, 141, 16, 154, 144, 63, 21, 245, 243, 226, 244, 55, 168, 59, 66, 45, 15, 61, 152, 5, 101, 219, 43, 137, 197, 90], - &[179, 112, 207, 116, 174, 196, 118, 123, 235, 202, 236, 69, 169, 209, 65, 238, 204, 235, 194, 187, 37, 246, 180, 124, 8, 116, 207, 175, 95, 87, 159, 137], - ]; - let alpha_keypair = AlphaKeypair::new_ed25519_from_seed(&master_key, seeds[0]).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519_from_seed(&master_key, seeds[1]).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519_from_seed(&master_key, seeds[2]).unwrap(); - let root_keypair = RootKeypair::new_ed25519_from_seed(&master_key, seeds[3]).unwrap(); - let transactions = Transactions::new() - .create_identity(&master_key, now.clone(), alpha_keypair, policy_keypair, publish_keypair, root_keypair).unwrap(); - let identity_id = transactions.build_identity().unwrap().id().clone(); - let transactions = transactions - .make_claim(&master_key, now.clone(), ClaimSpec::Identity(identity_id.clone())).unwrap() - .make_claim(&master_key, now.clone(), ClaimSpec::Name(MaybePrivate::new_public("Von Jonie Himself".to_string()))).unwrap(); - let identity = transactions.build_identity().unwrap(); - - let published = PublishedIdentity::publish(&master_key, now.clone(), transactions.clone()).unwrap(); - let ser = published.serialize().unwrap(); - assert_eq!(ser.trim(), r#"--- -publish_signature: - Ed25519: aV6hwrK42EaRJe9uV705q0wk4H79Bsw9X0i4mGEgDEL1tCYsO5xCR56baanG4PS8-Im-g0_Wx8XSOTBmuGM8AQ -publish_date: "1977-06-07T04:32:06Z" -identity: - transactions: - - V1: - id: - Alpha: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: [] - body: - CreateIdentityV1: - alpha: - Ed25519: - public: dHNopBN3YZrNa52xiVxB1IoY9NsrCz1c9cL8lLTu69U - secret: ~ - policy: - Ed25519: - public: s5YuvOaxr4y1qQBzZyJJ0SduYXf8toYfLa2izUgcT2I - secret: ~ - publish: - Ed25519: - public: B1NXKqP26jGll8tT12CCLbGxo09Do2M-A6VvRJoW87M - secret: ~ - root: - Ed25519: - public: 75w-F9acRAKDCDdeAiOYTAz9BUoky98lO5rHNSeodQg - secret: ~ - - V1: - id: - Root: - Ed25519: OZ33QK8puJeHsMKP1Ag_4ai3s01_v6Z7FADVqCs5UnoeBAWWT4E9ZuuY7Oma2ZnKhw3QM2qLcr8PYXVTsOEPBA - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: - - Alpha: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - body: - MakeClaimV1: - spec: - Identity: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - - V1: - id: - Root: - Ed25519: sgBVjMhJK89W4e8ZxUvVU9XIy0O9dsPOv9TdHv2nuy0Mds0AoFnJMxB2ASZ0EY30wy3DOFHL55BdPaFhOrJPAA - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: - - Root: - Ed25519: OZ33QK8puJeHsMKP1Ag_4ai3s01_v6Z7FADVqCs5UnoeBAWWT4E9ZuuY7Oma2ZnKhw3QM2qLcr8PYXVTsOEPBA - body: - MakeClaimV1: - spec: - Name: - Public: Von Jonie Himself"#); - let published_des = PublishedIdentity::deserialize(ser.as_bytes()).unwrap(); - let identity_des = published_des.identity().build_identity().unwrap(); - assert_eq!(identity.claims().len(), 2); - assert_eq!(identity.claims().len(), identity_des.claims().len()); - for i in 0..identity.claims().len() { - let claim1 = &identity.claims()[i]; - let claim2 = &identity_des.claims()[i]; - assert_eq!(claim1.claim().id(), claim2.claim().id()); - } - - // modify one of our stinkin' claims. this should fail to even build - // the identity, since seach transaction's signature is checked before - // we can even access the publish key to check the publish sig. - let modified_claim = r#"--- -publish_signature: - Ed25519: aV6hwrK42EaRJe9uV705q0wk4H79Bsw9X0i4mGEgDEL1tCYsO5xCR56baanG4PS8-Im-g0_Wx8XSOTBmuGM8AQ -publish_date: "1977-06-07T04:32:06Z" -identity: - transactions: - - V1: - id: - Alpha: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: [] - body: - CreateIdentityV1: - alpha: - Ed25519: - public: dHNopBN3YZrNa52xiVxB1IoY9NsrCz1c9cL8lLTu69U - secret: ~ - policy: - Ed25519: - public: s5YuvOaxr4y1qQBzZyJJ0SduYXf8toYfLa2izUgcT2I - secret: ~ - publish: - Ed25519: - public: B1NXKqP26jGll8tT12CCLbGxo09Do2M-A6VvRJoW87M - secret: ~ - root: - Ed25519: - public: 75w-F9acRAKDCDdeAiOYTAz9BUoky98lO5rHNSeodQg - secret: ~ - - V1: - id: - Root: - Ed25519: OZ33QK8puJeHsMKP1Ag_4ai3s01_v6Z7FADVqCs5UnoeBAWWT4E9ZuuY7Oma2ZnKhw3QM2qLcr8PYXVTsOEPBA - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: - - Alpha: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - body: - MakeClaimV1: - spec: - Identity: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - - V1: - id: - Root: - Ed25519: sgBVjMhJK89W4e8ZxUvVU9XIy0O9dsPOv9TdHv2nuy0Mds0AoFnJMxB2ASZ0EY30wy3DOFHL55BdPaFhOrJPAA - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: - - Root: - Ed25519: OZ33QK8puJeHsMKP1Ag_4ai3s01_v6Z7FADVqCs5UnoeBAWWT4E9ZuuY7Oma2ZnKhw3QM2qLcr8PYXVTsOEPBA - body: - MakeClaimV1: - spec: - Name: - Public: Mr. Bovine Jonie"#; - - let res = PublishedIdentity::deserialize(modified_claim.as_bytes()); - assert_eq!(res.err(), Some(Error::CryptoSignatureVerificationFailed)); - - // modify the chain itself. this should pass the build_identity() - // validation, but fail the publish signature check - let modified_chain = r#"--- -publish_signature: - Ed25519: aV6hwrK42EaRJe9uV705q0wk4H79Bsw9X0i4mGEgDEL1tCYsO5xCR56baanG4PS8-Im-g0_Wx8XSOTBmuGM8AQ -publish_date: "1977-06-07T04:32:06Z" -identity: - transactions: - - V1: - id: - Alpha: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: [] - body: - CreateIdentityV1: - alpha: - Ed25519: - public: dHNopBN3YZrNa52xiVxB1IoY9NsrCz1c9cL8lLTu69U - secret: ~ - policy: - Ed25519: - public: s5YuvOaxr4y1qQBzZyJJ0SduYXf8toYfLa2izUgcT2I - secret: ~ - publish: - Ed25519: - public: B1NXKqP26jGll8tT12CCLbGxo09Do2M-A6VvRJoW87M - secret: ~ - root: - Ed25519: - public: 75w-F9acRAKDCDdeAiOYTAz9BUoky98lO5rHNSeodQg - secret: ~ - - V1: - id: - Root: - Ed25519: OZ33QK8puJeHsMKP1Ag_4ai3s01_v6Z7FADVqCs5UnoeBAWWT4E9ZuuY7Oma2ZnKhw3QM2qLcr8PYXVTsOEPBA - entry: - created: "1977-06-07T04:32:06Z" - previous_transactions: - - Alpha: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg - body: - MakeClaimV1: - spec: - Identity: - Ed25519: eQ5eek-FGJrTdUh_ceurqfGA9C6zth8hKOICmj1Jnsd5hrtKz8_NirwDMDh2bWkcxqaeT0e2CiAb8zqoEDkjBg"#; - let res = PublishedIdentity::deserialize(modified_chain.as_bytes()); - assert_eq!(res.err(), Some(Error::CryptoSignatureVerificationFailed)); - } -} - diff --git a/src/identity/stamp.rs b/src/identity/stamp.rs index a6cbafd..033a2a1 100644 --- a/src/identity/stamp.rs +++ b/src/identity/stamp.rs @@ -7,22 +7,21 @@ use crate::{ error::Result, identity::{ - Public, claim::{Claim, ClaimID}, identity::IdentityID, - keychain::{AdminKeypair, Subkey}, + keychain::{Subkey}, }, crypto::{ key::SecretKey, message::{self, Message}, }, util::{ + Public, Timestamp, ser, }, }; use getset; -#[cfg(test)] use rand::RngCore; use rasn::{AsnType, Encode, Decode}; use serde_derive::{Serialize, Deserialize}; use std::convert::TryInto; @@ -38,9 +37,8 @@ object_id! { StampRevocationID } -/// An object that contains a stamp revocation's inner data. Its signature is -/// what gives the revocation its ID. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] +/// An inner container for creating a stamp revocation. +#[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct StampRevocationEntry { /// The identity ID of the original stamper (which must match the identity @@ -53,36 +51,24 @@ pub struct StampRevocationEntry { /// The ID of the stamp we're revoking. #[rasn(tag(explicit(2)))] stamp_id: StampID, - /// Date revoked - #[rasn(tag(explicit(3)))] - date_revoked: Timestamp, } impl StampRevocationEntry { - /// Create a new stamp revocaiton entry. - fn from_stamp(stamp: &Stamp, date_revoked: Timestamp) -> Self { - let stamp_id = stamp.id().clone(); - let stamper = stamp.entry().stamper().clone(); - let stampee = stamp.entry().stampee().clone(); + /// Create a new stamp revocation + pub fn new(stamper: IdentityID, stampee: IdentityID, stamp_id: StampID) -> Self { Self { stamper, stampee, stamp_id, - date_revoked, } } } /// An object published when a stamper wishes to revoke their stamp. /// -/// If this is not signed by the same identity that made the original stamp, it -/// must be ignored. Note, however, that the original stamper's signing key may -/// have changed since then, so we must look through their revoked keys when -/// checking if this revocation is valid. If any of their signing keys match the -/// original stamp, then it's a valid revocation. -/// -/// Effectively, if the same identity can verify both the original stamp and the -/// revocation, then the revocation is valid. +/// Note that like [`Stamp` objects][Stamp], this must be wrapped in an outer transaction +/// which is what determines its validity (through signatures). A stamp revocation on +/// its own is fairly useless. #[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct StampRevocation { @@ -96,14 +82,18 @@ pub struct StampRevocation { } impl StampRevocation { - fn new(id: StampRevocationID, entry: StampRevocationEntry) -> Self { + pub(crate) fn new(id: StampRevocationID, entry: StampRevocationEntry) -> Self { Self { id, entry } } +} - /// Verify this stamp revocation's integrity. - pub fn verify(&self, sign_keypair: &RootKeypair) -> Result<()> { - let serialized = ser::serialize(self.entry())?; - sign_keypair.verify(self.id(), &serialized) +impl Public for StampRevocation { + fn strip_private(&self) -> Self { + self.clone() + } + + fn has_private(&self) -> bool { + false } } @@ -135,8 +125,9 @@ pub enum Confidence { Extreme, } -/// A set of data that is signed when a stamp is created that is stored -/// alongside the signature itself. +/// An inner struct type created when making a stamp. This is what is wrapped in a +/// [transaction][crate::dag::transaction::Transaction] for signing (and possibly +/// publishing). #[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct StampEntry { @@ -157,25 +148,21 @@ pub struct StampEntry { /// place. #[rasn(tag(explicit(3)))] confidence: Confidence, - /// Filled in by the stamper, the date the claim was stamped. - #[rasn(tag(explicit(4)))] - date_signed: Timestamp, /// The date this stamp expires (if at all). The stamper can choose to set /// this expiration date if they feel their stamp is only good for a set /// period of time. - #[rasn(tag(explicit(5)))] + #[rasn(tag(explicit(4)))] expires: Option, } impl StampEntry { /// Create a new stamp entry. - fn new>(stamper: IdentityID, stampee: IdentityID, claim_id: ClaimID, confidence: Confidence, date_signed: T, expires: Option) -> Self { + pub fn new>(stamper: IdentityID, stampee: IdentityID, claim_id: ClaimID, confidence: Confidence, expires: Option) -> Self { Self { stamper, stampee, claim_id, confidence, - date_signed: date_signed.into(), expires: expires.map(|x| x.into()), } } @@ -190,8 +177,8 @@ impl StampEntry { #[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct Stamp { - /// This stamp's signature, and by remarkable coincidence, also its unique - /// identifier. + /// The [transaction id][crate::dag::TransactionID] of the transaction that created + /// this stamp. #[rasn(tag(explicit(0)))] id: StampID, /// The stamp entry, containing all the actual stamp data. @@ -200,36 +187,8 @@ pub struct Stamp { } impl Stamp { - /// Stamp a claim. - /// - /// This must be created by the identity validating the claim, using their - /// private signing key. - pub fn stamp>(master_key: &SecretKey, sign_keypair: &RootKeypair, stamper: &IdentityID, stampee: &IdentityID, confidence: Confidence, now: T, claim: &Claim, expires: Option) -> Result { - let entry = StampEntry::new(stamper.clone(), stampee.clone(), claim.id().clone(), confidence, now, expires); - let ser = ser::serialize(&entry)?; - let signature = sign_keypair.sign(master_key, &ser)?; - Ok(Self { - id: StampID(signature), - entry: entry, - }) - } - - /// Verify a stamp. - /// - /// Must have the stamper's public key, which can be obtained by querying - /// whatever networks means are accessible for the `IdentityID` in the - /// `entry.stamper` field. - pub fn verify(&self, sign_keypair: &RootKeypair) -> Result<()> { - let ser = ser::serialize(self.entry())?; - sign_keypair.verify(&self.id, &ser) - } - - /// Create a new stamp revocation - pub fn revoke>(&self, master_key: &SecretKey, sign_keypair: &RootKeypair, date_revoked: T) -> Result { - let entry = StampRevocationEntry::from_stamp(self, date_revoked.into()); - let serialized = ser::serialize(&entry)?; - let sig = sign_keypair.sign(master_key, &serialized)?; - Ok(StampRevocation::new(StampRevocationID(sig), entry)) + pub(crate) fn new(id: StampID, entry: StampEntry) -> Self { + Self { id, entry } } /// Serialize this stamp in human-readable form. @@ -313,9 +272,9 @@ mod tests { use crate::{ error::Error, identity::{ - claim::{Relationship, RelationshipType, ClaimSpec, ClaimContainer}, + claim::{Relationship, RelationshipType, ClaimSpec, Claim}, identity::IdentityID, - keychain::{ExtendKeypair, AlphaKeypair, PolicyKeypair, PublishKeypair, RootKeypair, Key, Keychain}, + keychain::{ExtendKeypair, AdminKey, AdminKeypair, Key, Keychain}, stamp::Confidence, }, crypto::key::{self, SecretKey, SignKeypair, CryptoKeypair}, @@ -325,38 +284,14 @@ mod tests { use std::convert::TryFrom; use std::str::FromStr; - fn make_stamp(master_key: &SecretKey, sign_keypair: &RootKeypair, claim_id: ClaimID, stamper: &IdentityID, stampee: &IdentityID, ts: Option) -> Stamp { - assert!(stamper != stampee); - let maybe = MaybePrivate::new_public(String::from("andrew")); - let ts = ts.unwrap_or(Timestamp::now()); - // kind of stupid to sign the claim with the same key creating the stamp - // but it's also not incorrect. - let claim = ClaimContainer::new(claim_id, ClaimSpec::Name(maybe), Timestamp::now()); - Stamp::stamp(&master_key, &sign_keypair, &stamper, &stampee, Confidence::Medium, ts, claim.claim(), None).unwrap() - } - - #[test] - fn stamp_verify() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let sign_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let mut stamp = make_stamp(&master_key, &sign_keypair, ClaimID::random(), &IdentityID::random(), &IdentityID::random(), None); - stamp.verify(&sign_keypair).unwrap(); - - // let's modify the stamp. and set the confidence a bit higher. why not? - // OH BECAUSE NOW IT DOESN'T FUCKING VERIFY YOU DINGLEBERRY - stamp.entry_mut().set_confidence(Confidence::Extreme); - assert_eq!(stamp.verify(&sign_keypair), Err(Error::CryptoSignatureVerificationFailed)); - } - #[test] fn stamp_serde() { let id1 = IdentityID::try_from("RUHyjlNbE7u7BCd9kp3_3jhKiC4w-8fpkox3HiTMD7gQDhGNS6dYCpJiU1C029gpqxjvLUmZmsokeQsjSC9gAAA").unwrap(); let id2 = IdentityID::try_from("izTWRLHDYRY1qwkgxXgxe1D0Ft-TcJS95OpghVsplpu1S-5rpa7tGvCzmAVP9WhxKALZlOCiijAT1q6AMknuAAA").unwrap(); let claim_id = ClaimID::try_from("K9fUQ28tp-azWhlysEyQisdt6qKh4-OEF1-ZYEetSVQuYQpa62DTREgAwtljpOYZZbrrxhBv7XnwBDDd9BFNAAA").unwrap(); - let master_key = key::tests::secret_from_vec(vec![58, 30, 74, 149, 49, 101, 115, 190, 250, 4, 99, 141, 245, 201, 209, 83, 46, 121, 28, 174, 1, 150, 149, 118, 181, 228, 215, 78, 226, 248, 53, 152]); - let root_keypair = RootKeypair::new_ed25519_from_seed(&master_key, &[190, 106, 28, 143, 162, 234, 87, 8, 20, 209, 219, 44, 136, 152, 126, 189, 46, 129, 12, 125, 138, 173, 37, 220, 174, 42, 218, 199, 95, 127, 97, 92]).unwrap(); let ts = Timestamp::from_str("2021-06-06T00:00:00-06:00").unwrap(); - let stamp = make_stamp(&master_key, &root_keypair, claim_id, &id1, &id2, Some(ts)); + let entry = StampEntry::new::(id1, id2, claim_id, Confidence::Low, None); + let stamp = Stamp::new(StampID::try_from("LcfZalEKenbS5ClNRTlBMwDalCF7MEqKY4nF3+w7uK8ZMt4UXCv0hrnuBUtcaiJwNY/MmW0UAqjcyL9gobA/QA==").unwrap(), entry); let ser = stamp.serialize().unwrap(); assert_eq!(ser.trim(), r#"--- id: @@ -369,7 +304,6 @@ entry: claim_id: Ed25519: K9fUQ28tp-azWhlysEyQisdt6qKh4-OEF1-ZYEetSVQuYQpa62DTREgAwtljpOYZZbrrxhBv7XnwBDDd9BFNAA confidence: Medium - date_signed: "2021-06-06T06:00:00Z" expires: ~"#); let des = Stamp::deserialize(ser.as_bytes()).unwrap(); assert_eq!(stamp, des); @@ -378,8 +312,9 @@ entry: #[test] fn stamp_strip() { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let stamp = make_stamp(&master_key, &root_keypair, ClaimID::random(), &IdentityID::random(), &IdentityID::random(), None); + let root_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); + let entry = StampEntry::new::(IdentityID::random(), IdentityID::random(), ClaimID::random(), Confidence::None, None); + let stamp = Stamp::new(StampID::random(), entry); let stamp2 = stamp.strip_private(); // we only really need strip_private() so we can serialized_human, but // stamps don't hold ANY private data at all, so a stripped stamp should @@ -410,28 +345,24 @@ entry: let (sender_master_key, _root_keypair, spec_private, spec_public) = make_specs!($claimmaker, val.clone()); let sender_identity_id = IdentityID::random(); let subkey_key = Key::new_crypto(CryptoKeypair::new_curve25519xchacha20poly1305(&sender_master_key).unwrap()); - let alpha = AlphaKeypair::new_ed25519(&sender_master_key).unwrap(); - let policy = PolicyKeypair::new_ed25519(&sender_master_key).unwrap(); - let publish = PublishKeypair::new_ed25519(&sender_master_key).unwrap(); - let root = RootKeypair::new_ed25519(&sender_master_key).unwrap(); - let sender_keychain = Keychain::new(alpha, policy, publish, root) + let admin = AdminKeypair::new_ed25519(&sender_master_key).unwrap(); + let admin_key = AdminKey::new(admin, "MAIN LOL", None); + let sender_keychain = Keychain::new(vec![admin_key]) .add_subkey(subkey_key, "default:crypto", None).unwrap(); - let container_private = ClaimContainer::new(ClaimID::random(), spec_private, Timestamp::now()); - let container_public = ClaimContainer::new(ClaimID::random(), spec_public, Timestamp::now()); + let container_private = Claim::new(ClaimID::random(), spec_private, None); + let container_public = Claim::new(ClaimID::random(), spec_public, None); let sender_subkey = sender_keychain.subkey_by_name("default:crypto").unwrap(); let recipient_master_key = SecretKey::new_xchacha20poly1305().unwrap(); let subkey_key = Key::new_crypto(CryptoKeypair::new_curve25519xchacha20poly1305(&recipient_master_key).unwrap()); - let alpha = AlphaKeypair::new_ed25519(&sender_master_key).unwrap(); - let policy = PolicyKeypair::new_ed25519(&sender_master_key).unwrap(); - let publish = PublishKeypair::new_ed25519(&sender_master_key).unwrap(); - let root = RootKeypair::new_ed25519(&sender_master_key).unwrap(); - let recipient_keychain = Keychain::new(alpha, policy, publish, root) + let admin = AdminKeypair::new_ed25519(&sender_master_key).unwrap(); + let admin_key = AdminKey::new(admin, "ALPHA MALE BIG HANDS", None); + let recipient_keychain = Keychain::new(vec![admin_key]) .add_subkey(subkey_key, "default:crypto", None).unwrap(); let recipient_subkey = recipient_keychain.subkey_by_name("default:crypto").unwrap(); - let req_msg_priv = StampRequest::new(&sender_master_key, &sender_identity_id, sender_subkey, recipient_subkey, container_private.claim()).unwrap(); - let req_msg_pub = StampRequest::new(&sender_master_key, &sender_identity_id, sender_subkey, recipient_subkey, container_public.claim()).unwrap(); + let req_msg_priv = StampRequest::new(&sender_master_key, &sender_identity_id, sender_subkey, recipient_subkey, &container_private).unwrap(); + let req_msg_pub = StampRequest::new(&sender_master_key, &sender_identity_id, sender_subkey, recipient_subkey, &container_public).unwrap(); let res1 = StampRequest::open(&sender_master_key, recipient_subkey, sender_subkey, &req_msg_priv); let res2 = StampRequest::open(&sender_master_key, recipient_subkey, sender_subkey, &req_msg_pub); @@ -464,17 +395,7 @@ entry: }; } - let val = IdentityID::random(); - let (opened_priv, opened_pub) = req_open!{ raw, |_, val| ClaimSpec::Identity(val), val.clone() }; - match (opened_priv.spec().clone(), opened_pub.spec().clone()) { - (ClaimSpec::Identity(val1), ClaimSpec::Identity(val2)) => { - assert_eq!(val1, val); - assert_eq!(val2, val); - assert_eq!(val1, val2); // probably not needed but w/e - } - _ => panic!("Invalid claim type"), - } - + req_open!{ Identity, IdentityID::random() } req_open!{ Name, String::from("Hippie Steve") } req_open!{ Birthday, Date::from_str("1957-12-03").unwrap() } req_open!{ Email, String::from("decolonizing.decolonialism@decolonize.dclnze") } @@ -500,24 +421,5 @@ entry: _ => panic!("Invalid claim type"), } } - - #[test] - fn stamp_revocation_create_verify() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let stamp = make_stamp(&master_key, &root_keypair, ClaimID::random(), &IdentityID::random(), &IdentityID::random(), None); - - // oh no i stamped superman but meant to stamp batman gee willickers - - // revocation should verify - let rev = stamp.revoke(&master_key, &root_keypair, Timestamp::now()).unwrap(); - rev.verify(&root_keypair).unwrap(); - - // let's modify the revocation. this should invalidate the sig. - let mut rev2 = rev.clone(); - let then = Timestamp::from_str("1999-01-01T00:00:00-06:00").unwrap(); - rev2.entry_mut().set_date_revoked(then); - assert_eq!(rev2.verify(&root_keypair), Err(Error::CryptoSignatureVerificationFailed)); - } } diff --git a/src/lib.rs b/src/lib.rs index 5e5c0f3..131cfbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,37 +1,42 @@ //! Welcome to the Stamp core, a reference implementation of the Stamp protocol. //! -//! The Stamp protocol is essentially a successor to PGP. It seeks to provide -//! a meaningful cryptographic identity for a given person, which can be signed -//! by either their peers or various institutions. +//! The Stamp protocol is a p2p cryptographic system for managing personal and group +//! identities. Like PGP, it allows creating a web of trust when users "stamp" +//! the claims made by peers or institutions. However unlike PGP, Stamp is not +//! limited to a single set of owned keys. A Stamp identity can be managed entirely +//! by third parties via multisig transactions, making it a good option for managing +//! identities for groups or organizations. //! -//! Stamp deviates from PGP in that any number of claims can be made by a -//! Stamp identity, and any of them can be individually signed. For instance, an +//! Stamp allows any number of claims to be created on an identity, and any of them +//! can be individually signed ("stamped"). For instance, an //! identity might claim ownership of an email address, and any person or //! organization might "stamp" (verify) that claim by having the owner of the //! identity sign a random string sent over email and return it to the verifier. //! Any number of claims or types of claims can be made and signed by any other -//! participant. +//! participant. Some claims, such as domain and URL claims, can be verified +//! without any stamps from others. //! -//! The Stamp protocol defines not just methods for encryption, signing, and -//! verification, but also for key recovery among trusted peers or institutions. +//! Because of the robust claim system, Stamp identities can also act as forwarding +//! mechanisms for other p2p (or centralized) systems. For instance, you could claim +//! ownership of an email address, an ActivityPub handle, etc and anyone (or anything) +//! reading your identity would know how to reach you. This allows other systems to +//! follow *your identity* and if you were to, say, switch ActivityPub servers, you +//! could take your network with you. //! -//! Stamp also allows an identity to point (or "forward") to other locations or -//! distributed/decentralized systems. Your identity might be the canonical -//! place that your followers on a decentralized social network might find you: -//! switching servers doesn't mean you have to rebuild your network anymore, -//! because you can update your Stamp identity to point at your new location. -//! You can forward interested parties to websites, email address, social -//! networks, or any custom representation of location. +//! The multisig protocol within Stamp allows for another useful feature: recovery. +//! You can create a policy that allows friends, family, or institutions to replace +//! your keys in the event of loss or theft. Losing a private key no longer has to +//! be a catastrophic loss. //! //! The goals of this protocol are as follows: //! //! 1. To provide a semi-permanent container for a cryptographically-verified -//! online/offline electronic identity. +//! online/offline electronic identity for individuals and groups. //! 1. To allow signing and verification of any number of custom pieces of //! information ("claims") that assert one's identity, including ones that are //! private and only accessible by the identity owner (and those who they choose //! to verify that claim). -//! 1. To allow the identity holder the ultimate control over their identity. +//! 1. To allow the identity holder(s) the ultimate control over their identity. //! 1. To remain as distributed as possible. //! 1. To be easy to use by choosing sensible defaults, providing good UX //! wherever possible, and being opinionated wherever needed. diff --git a/src/policy.rs b/src/policy.rs index 686c6d8..089fdaa 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -10,21 +10,19 @@ //! not just by keys it owns, but by trusted third parties as well. use crate::{ - crypto::key::{SecretKey, SignKeypair, SignKeypairPublic, SignKeypairSignature}, - dag::{TransactionBody, TransactionID}, + crypto::key::{KeyID}, + dag::{TransactionBody, TransactionID, Transaction}, error::{Error, Result}, identity::{ - Public, - identity::{IdentityID, ForwardType}, - keychain::{ExtendKeypair, AdminKeypairPublic, AdminKeypairSignature}, + claim::ClaimSpec, + identity::{Identity, IdentityID}, + keychain::{ExtendKeypair, AdminKeypair, AdminKeypairPublic, AdminKeypairSignature}, }, - util::ser::{self, BinaryVec}, + util::{ser::BinaryVec}, }; use getset; -#[cfg(test)] use rand::RngCore; use rasn::{AsnType, Encode, Decode}; use serde_derive::{Serialize, Deserialize}; -use std::convert::TryInto; use std::ops::Deref; /// Defines a context specifier specific to various claim types @@ -34,40 +32,59 @@ pub enum TransactionBodyType { #[rasn(tag(explicit(0)))] CreateIdentityV1, #[rasn(tag(explicit(1)))] - MakeClaimV1, + ResetIdentityV1, #[rasn(tag(explicit(2)))] - DeleteClaimV1, + AddAdminKeyV1, #[rasn(tag(explicit(3)))] - MakeStampV1, + EditAdminKeyV1, #[rasn(tag(explicit(4)))] - RevokeStampV1, + RevokeAdminKeyV1, #[rasn(tag(explicit(5)))] - AcceptStampV1, + AddCapabilityPolicyV1, #[rasn(tag(explicit(6)))] - DeleteStampV1, + DeleteCapabilityPolicyV1, #[rasn(tag(explicit(7)))] - AddSubkeyV1, + MakeClaimV1, #[rasn(tag(explicit(8)))] - EditSubkeyV1, + EditClaimV1, #[rasn(tag(explicit(9)))] - RevokeSubkeyV1, + DeleteClaimV1, #[rasn(tag(explicit(10)))] - DeleteSubkeyV1, + MakeStampV1, #[rasn(tag(explicit(11)))] - SetNicknameV1, + RevokeStampV1, #[rasn(tag(explicit(12)))] - AddForwrdV1, + AcceptStampV1, #[rasn(tag(explicit(13)))] - DeleteForwardV1, + DeleteStampV1, + #[rasn(tag(explicit(14)))] + AddSubkeyV1, + #[rasn(tag(explicit(15)))] + EditSubkeyV1, + #[rasn(tag(explicit(16)))] + RevokeSubkeyV1, + #[rasn(tag(explicit(17)))] + DeleteSubkeyV1, + #[rasn(tag(explicit(18)))] + SetNicknameV1, + #[rasn(tag(explicit(19)))] + PublishV1, } -impl From for TransactionBodyType { +impl From<&TransactionBody> for TransactionBodyType { // Not sure if this is actually useful as much as it keeps ContextClaimType // in sync with ClaimSpec - fn from(body: TransactionBody) -> Self { - match body { + fn from(body: &TransactionBody) -> Self { + match *body { TransactionBody::CreateIdentityV1 { .. } => Self::CreateIdentityV1, + TransactionBody::ResetIdentityV1 { .. } => Self::ResetIdentityV1, + TransactionBody::AddAdminKeyV1 { .. } => Self::AddAdminKeyV1, + TransactionBody::EditAdminKeyV1 { .. } => Self::EditAdminKeyV1, + TransactionBody::RevokeAdminKeyV1 { .. } => Self::RevokeAdminKeyV1, + TransactionBody::AddCapabilityPolicyV1 { .. } => Self::AddCapabilityPolicyV1, + TransactionBody::DeleteCapabilityPolicyV1 { .. } => Self::DeleteCapabilityPolicyV1, TransactionBody::MakeClaimV1 { .. } => Self::MakeClaimV1, + TransactionBody::EditClaimV1 { .. } => Self::EditClaimV1, TransactionBody::DeleteClaimV1 { .. } => Self::DeleteClaimV1, TransactionBody::MakeStampV1 { .. } => Self::MakeStampV1, TransactionBody::RevokeStampV1 { .. } => Self::RevokeStampV1, @@ -78,8 +95,7 @@ impl From for TransactionBodyType { TransactionBody::RevokeSubkeyV1 { .. } => Self::RevokeSubkeyV1, TransactionBody::DeleteSubkeyV1 { .. } => Self::DeleteSubkeyV1, TransactionBody::SetNicknameV1 { .. } => Self::SetNicknameV1, - TransactionBody::AddForwrdV1 { .. } => Self::AddForwrdV1, - TransactionBody::DeleteForwardV1 { .. } => Self::DeleteForwardV1, + TransactionBody::PublishV1 { .. } => Self::PublishV1, } } } @@ -114,11 +130,11 @@ pub enum ContextClaimType { Extension, } -impl From for ContextClaimType { +impl From<&ClaimSpec> for ContextClaimType { // Not sure if this is actually useful as much as it keeps ContextClaimType // in sync with ClaimSpec - fn from(spec: ClaimSpec) -> Self { - match spec { + fn from(spec: &ClaimSpec) -> Self { + match *spec { ClaimSpec::Identity(..) => Self::Identity, ClaimSpec::Name(..) => Self::Name, ClaimSpec::Birthday(..) => Self::Birthday, @@ -135,36 +151,6 @@ impl From for ContextClaimType { } } -/// Defines a context specifier specific to various forward types -#[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] -#[rasn(choice)] -pub enum ContextForwardType { - #[rasn(tag(explicit(0)))] - Email, - #[rasn(tag(explicit(1)))] - Social, - #[rasn(tag(explicit(2)))] - Pgp, - #[rasn(tag(explicit(3)))] - Url, - #[rasn(tag(explicit(4)))] - Extension, -} - -impl From for ContextForwardType { - // Not sure if this is actually useful as much as it keeps ContextClaimType - // in sync with ClaimSpec - fn from(spec: ForwardType) -> Self { - match spec { - ForwardType::Email(..) => Self::Email, - ForwardType::Social { .. } => Self::Social, - ForwardType::Pgp(..) => Self::Pgp, - ForwardType::Url(..) => Self::Url, - ForwardType::Extension { .. } => Self::Extension, - } - } -} - /// Defines a context under which a transaction can be performed. /// /// This is a recursive structure which allows defining arbitrary combinations @@ -172,31 +158,133 @@ impl From for ContextForwardType { #[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] #[rasn(choice)] pub enum Context { - /// Represents a context in which ALL given contexts must match. + /// Represents a context in which ALL given contexts must match (an AND gate) #[rasn(tag(explicit(0)))] All(Vec), /// Represents a context in which one or more of the given contexts must - /// match. + /// match (an OR gate). #[rasn(tag(explicit(1)))] Any(Vec), + /// Allows an action in any context (ie, context is irrelevant). + #[rasn(tag(explicit(2)))] + Permissive, + /// Allows an action in the context of an identity that has this exact ID. + #[rasn(tag(explicit(3)))] + IdentityID(IdentityID), /// Allows an action in the context of items with an exact ID match (for /// instance, a claim that was created by transaction 0x03fd913) - #[rasn(tag(explicit(2)))] - ID(TransactionID), + #[rasn(tag(explicit(4)))] + ObjectID(TransactionID), + /// Allows an action on a keypair that has a public key matching the given + /// ID. + #[rasn(tag(explicit(5)))] + KeyID(KeyID), /// Allows an action in the context of items with an exact name match. This - /// can be a forward or a subkey generally. - #[rasn(tag(explicit(3)))] + /// can be an admin key, subkey, or capability policy generally. + #[rasn(tag(explicit(6)))] Name(String), /// Allows an action in the context of items with name matching a glob pattern. - /// This can be a forward or a subkey generally. - #[rasn(tag(explicit(4)))] + /// For instance `email-keys/*` + #[rasn(tag(explicit(7)))] NameGlob(String), /// Allows actions on claims where the claim is of a particular type - #[rasn(tag(explicit(5)))] + #[rasn(tag(explicit(8)))] ClaimType(ContextClaimType), - /// Allows actions on forwards where the claim is of a particular type - #[rasn(tag(explicit(6)))] - ForwardType(ContextForwardType), +} + +impl Context { + /// Takes a transaction and returns all the contexts it covers. + pub(crate) fn contexts_from_transaction(transaction: &Transaction, identity: &Identity) -> Vec { + let mut contexts = Vec::new(); + match transaction.entry().body() { + TransactionBody::CreateIdentityV1 { .. } => {} + TransactionBody::ResetIdentityV1 { .. } => {} + TransactionBody::AddAdminKeyV1 { admin_key } => { + contexts.push(Self::KeyID(admin_key.key().key_id())); + contexts.push(Self::Name(admin_key.name().clone())); + } + TransactionBody::EditAdminKeyV1 { id, .. } => { + identity.keychain().admin_key_by_keyid(id) + .map(|admin_key| contexts.push(Self::Name(admin_key.name().clone()))); + contexts.push(Self::KeyID(id.clone())); + } + TransactionBody::RevokeAdminKeyV1 { id, .. } => { + identity.keychain().admin_key_by_keyid(id) + .map(|admin_key| contexts.push(Self::Name(admin_key.name().clone()))); + contexts.push(Self::KeyID(id.clone())); + } + TransactionBody::AddCapabilityPolicyV1 { capability } => { + contexts.push(Self::Name(capability.name().clone())); + } + TransactionBody::DeleteCapabilityPolicyV1 { name } => { + contexts.push(Self::Name(name.clone())); + } + TransactionBody::MakeClaimV1 { spec, name } => { + contexts.push(Self::ClaimType(ContextClaimType::from(spec))); + if let Some(name) = name { + contexts.push(Self::Name(name.clone())); + } + } + TransactionBody::EditClaimV1 { claim_id, .. } => { + contexts.push(Self::ObjectID(claim_id.deref().clone())); + } + TransactionBody::DeleteClaimV1 { claim_id } => { + contexts.push(Self::ObjectID(claim_id.deref().clone())); + } + TransactionBody::MakeStampV1 { stamp } => { + contexts.push(Self::ObjectID(stamp.claim_id().deref().clone())); + contexts.push(Self::IdentityID(stamp.stampee().clone())); + } + TransactionBody::RevokeStampV1 { revocation } => { + contexts.push(Self::ObjectID(revocation.stamp_id().deref().clone())); + contexts.push(Self::IdentityID(revocation.stampee().clone())); + + let stamp_maybe = identity.find_claim_stamp_by_id(revocation.stamp_id()); + if let Some(stamp) = stamp_maybe { + contexts.push(Self::ObjectID(stamp.entry().claim_id().deref().clone())); + } + } + TransactionBody::AcceptStampV1 { stamp_transaction } => { + match stamp_transaction.entry().body() { + TransactionBody::MakeStampV1 { stamp } => { + contexts.push(Self::ObjectID(stamp.claim_id().deref().clone())); + contexts.push(Self::IdentityID(stamp.stamper().clone())); + } + _ => {} + } + } + TransactionBody::DeleteStampV1 { stamp_id } => { + let stamp_maybe = identity.find_claim_stamp_by_id(stamp_id); + if let Some(stamp) = stamp_maybe { + contexts.push(Self::ObjectID(stamp.id().deref().clone())); + contexts.push(Self::ObjectID(stamp.entry().claim_id().deref().clone())); + contexts.push(Self::IdentityID(stamp.entry().stamper().clone())); + } + } + TransactionBody::AddSubkeyV1 { key, name, .. } => { + contexts.push(Self::Name(name.clone())); + contexts.push(Self::KeyID(key.key_id().clone())); + } + TransactionBody::EditSubkeyV1 { id, .. } => { + contexts.push(Self::KeyID(id.clone())); + identity.keychain().subkey_by_keyid(id) + .map(|subkey| contexts.push(Self::Name(subkey.name().clone()))); + } + TransactionBody::RevokeSubkeyV1 { id, .. } => { + contexts.push(Self::KeyID(id.clone())); + identity.keychain().subkey_by_keyid(id) + .map(|subkey| contexts.push(Self::Name(subkey.name().clone()))); + } + TransactionBody::DeleteSubkeyV1 { id } => { + contexts.push(Self::KeyID(id.clone())); + identity.keychain().subkey_by_keyid(id) + .map(|subkey| contexts.push(Self::Name(subkey.name().clone()))); + } + TransactionBody::SetNicknameV1 { .. } => {} + TransactionBody::PublishV1 { .. } => {} + } + contexts + } } /// Defines an action that can be taken on an identity. Effectively, this is the @@ -209,11 +297,14 @@ pub enum Context { #[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] #[rasn(choice)] pub enum Capability { - /// The ability to perform a transaction in a given context + /// A capability that allows all actions #[rasn(tag(explicit(0)))] + Permissive, + /// The ability to perform a transaction in a given context + #[rasn(tag(explicit(1)))] Transaction { #[rasn(tag(explicit(0)))] - transaction: TransactionBodyType, + body_type: TransactionBodyType, #[rasn(tag(explicit(1)))] context: Context, }, @@ -226,16 +317,30 @@ pub enum Capability { /// /// This allows harnessing the identity and its policy system for participating /// in protocols outside of Stamp. - #[rasn(tag(explicit(1)))] + #[rasn(tag(explicit(2)))] Extension { #[rasn(tag(explicit(0)))] - #[serde(rename = "type")] ty: BinaryVec, #[rasn(tag(explicit(1)))] context: BinaryVec, } } +impl Capability { + pub(crate) fn test(&self, test: &Capability) -> Result<()> { + match self { + // allow anything + Self::Permissive => Ok(()), + // tricky... + Self::Transaction { body_type, context } => { + todo!(); + } + // don't validate extensions. you need to do that yourself. + Self::Extension { .. } => Ok(()), + } + } +} + /// A policy participant. Currently this is just an [Admin][AdminKeypair] public key /// but could be expanded later on to allow other participant types. #[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] @@ -247,6 +352,18 @@ pub enum Participant { Key(AdminKeypairPublic), } +impl From for Participant { + fn from(admin_pubkey: AdminKeypairPublic) -> Self { + Participant::Key(admin_pubkey) + } +} + +impl From for Participant { + fn from(admin_pubkey: AdminKeypair) -> Self { + Participant::Key(admin_pubkey.into()) + } +} + /// A signature on a policy transaction. Currently only supports direct signatures /// from admin keys, but could allow expanding to other signature methods. #[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize)] @@ -255,7 +372,10 @@ pub enum PolicySignature { /// A signature on a transaction from a specific key, generally one that's /// listed as a [Participant::Key] in the policy. #[rasn(tag(explicit(0)))] - Key(AdminKeypairSignature), + Key { + key: AdminKeypairPublic, + signature: AdminKeypairSignature, + }, } /// A recursive structure that defines the conditions under which a multisig @@ -284,32 +404,35 @@ pub enum Policy { } impl Policy { - /// Tests whether the given signatures match the policy condition - pub(crate) fn test(&self, signatures: &Vec, serialized_transaction: &[u8]) -> Result<()> - where F: Fn(&Participant, &PolicySignature) -> Result<()>, - { + /// Tests whether the given signatures match the current policy. KEEP IN MIND + /// that the signatures *must* be validated before we get here. We're simply + /// testing that the signatures are from keys that satisfy the policy: WE DO + /// NOT VALIDATE THE SIGNATURES. + pub(crate) fn test(&self, signatures: &Vec) -> Result<()> { match self { - Self::All(conditions) => { - conditions.iter() - .map(|c| c.test(signatures, sigtest)) + Self::All(policies) => { + policies.iter() + .map(|p| p.test(signatures)) .collect::>>()?; } - Self::Any(conditions) => { - conditions.iter() - .find(|c| c.test(signatures, sigtest).is_ok()) + Self::Any(policies) => { + policies.iter() + .find(|p| p.test(signatures).is_ok()) .ok_or(Error::PolicyConditionMismatch)?; } - Self::OfN { must_have, participants } => { + Self::MOfN { must_have, participants } => { let has = participants.iter() .filter_map(|participant| { for sig in signatures { match (participant, sig) { - (Participant::Key(pubkey), PolicySignature::Key(sig)) => { - if pubkey.verify(sig, &serialized_transaction).is_ok() { + (Participant::Key(ref pubkey), PolicySignature::Key { key, .. }) => { + // NOTE: all signatures have already been validated + // upstreeaaaaam so all we need to do is verify that + // the participant's key matches the signing key. + if pubkey == key { return Some(()); } } - _ => {} } } None @@ -326,35 +449,49 @@ impl Policy { /// Matches a set of [Capabilities][Capability] to a multisig [Policy], making /// it so the policy must be fulfilled in order to perform those capabilities. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] +#[derive(Debug, Clone, PartialEq, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct CapabilityPolicy { - /// The capabilities (or actions) this policy can access + /// The *unique* name of this capability policy. #[rasn(tag(explicit(0)))] + name: String, + /// The capabilities (or actions) this policy can access. These are permissive, + /// and combined via OR. + #[rasn(tag(explicit(1)))] capabilities: Vec, /// The signature policy defining which keys are required to perform the /// specified capabilities - #[rasn(tag(explicit(1)))] + #[rasn(tag(explicit(2)))] policy: Policy, } impl CapabilityPolicy { /// Create a new `CapabilityPolicy` - pub fn new(capabilities: Vec, policy: Policy) -> Self { - Self { capabilities, policy } + pub fn new(name: String, capabilities: Vec, policy: Policy) -> Self { + Self { name, capabilities, policy } } /// Determine if this particular `CapabilityPolicy` allows performing the - /// action `capability`. - pub fn can(&self, capability: Capability) -> bool { - todo!(); + /// action `capability` in the given `context`. + pub fn can(&self, capability: &Capability) -> bool { + self.capabilities().iter().find(|c| c.test(capability).is_ok()).is_some() } /// Determine if this particular `CapabilityPolicy` allows performing the /// action `capability`, and also checks the `signatures` against the policy /// to make sure we have the signatures we need to perform this action. - pub fn validate(&self, capability: Capability, signatures: &Vec) -> bool { - todo!(); + pub(crate) fn validate_transaction(&self, transaction: &Transaction, contexts: &Vec) -> Result<()> { + // don't check the signature validity here. just check that we have the signatures + // needed to satisfy the policy. signature checks happen higher level. + self.policy().test(transaction.signatures())?; + let transaction_capability = Capability::Transaction { + body_type: transaction.entry().body().into(), + context: Context::Any(contexts.clone()), + }; + if !self.can(&transaction_capability) { + Err(Error::PolicyCapabilityMismatch)?; + } + Ok(()) } } @@ -362,45 +499,50 @@ impl CapabilityPolicy { mod tests { use super::*; use crate::{ - crypto::key::{SignKeypair}, - error::Error, + crypto::key::{SecretKey}, + identity::keychain::{AdminKeypair}, util, }; #[test] - fn policy_condition_test() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); + fn capability_test() { + todo!(); + } - let gus = SignKeypair::new_ed25519(&master_key).unwrap(); - let marty = SignKeypair::new_ed25519(&master_key).unwrap(); - let jackie = SignKeypair::new_ed25519(&master_key).unwrap(); - let rosarita = SignKeypair::new_ed25519(&master_key).unwrap(); - let dirk = SignKeypair::new_ed25519(&master_key).unwrap(); - let twinkee = SignKeypair::new_ed25519(&master_key).unwrap(); - let syd = SignKeypair::new_ed25519(&master_key).unwrap(); - let scurvy = SignKeypair::new_ed25519(&master_key).unwrap(); - let kitty = SignKeypair::new_ed25519(&master_key).unwrap(); + #[test] + fn policy_test() { + let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let conditions = PolicyCondition::Any(vec![ - PolicyCondition::All(vec![ - PolicyCondition::OfN { + let gus = AdminKeypair::new_ed25519(&master_key).unwrap(); + let marty = AdminKeypair::new_ed25519(&master_key).unwrap(); + let jackie = AdminKeypair::new_ed25519(&master_key).unwrap(); + let rosarita = AdminKeypair::new_ed25519(&master_key).unwrap(); + let dirk = AdminKeypair::new_ed25519(&master_key).unwrap(); + let twinkee = AdminKeypair::new_ed25519(&master_key).unwrap(); + let syd = AdminKeypair::new_ed25519(&master_key).unwrap(); + let scurvy = AdminKeypair::new_ed25519(&master_key).unwrap(); + let kitty = AdminKeypair::new_ed25519(&master_key).unwrap(); + + let conditions = Policy::Any(vec![ + Policy::All(vec![ + Policy::MOfN { must_have: 1, - pubkeys: vec![ + participants: vec![ dirk.clone().into(), jackie.clone().into(), ], }, - PolicyCondition::OfN { + Policy::MOfN { must_have: 1, - pubkeys: vec![ + participants: vec![ syd.clone().into(), twinkee.clone().into(), ], }, ]), - PolicyCondition::OfN { + Policy::MOfN { must_have: 3, - pubkeys: vec![ + participants: vec![ gus.clone().into(), marty.clone().into(), jackie.clone().into(), @@ -441,7 +583,10 @@ mod tests { let fs = |key| { possible_signatures.iter() .find(|ent| ent.0 == key) - .map(|x| x.1.clone()) + .map(|x| PolicySignature::Key { + key: x.0.clone().into(), + signature: x.1.clone(), + }) .unwrap() }; let passing_combinations = vec![ @@ -454,9 +599,6 @@ mod tests { vec!["gus", "jackie", "dirk"], vec!["gus", "marty", "dirk"], ]; - let sigtest = |pubkey: &SignKeypairPublic, sig: &SignKeypairSignature| { - pubkey.verify(sig, obj.as_bytes()) - }; let should_pass = |names: &Vec<&str>| -> bool { if names.len() == 0 { return false; @@ -477,7 +619,7 @@ mod tests { }; for combo in combinations { let combo_sigs = combo.iter().map(|name| fs(kn(name))).collect::>(); - let res = conditions.test(&combo_sigs, &sigtest); + let res = conditions.test(&combo_sigs); match res { Ok(_) => { if !should_pass(&combo) { @@ -493,221 +635,13 @@ mod tests { } } - #[test] - fn policy_sign_request_validate_request() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let gus = SignKeypair::new_ed25519(&master_key).unwrap(); - let marty = SignKeypair::new_ed25519(&master_key).unwrap(); - let jackie = SignKeypair::new_ed25519(&master_key).unwrap(); - let rosarita = SignKeypair::new_ed25519(&master_key).unwrap(); - let dirk = SignKeypair::new_ed25519(&master_key).unwrap(); - - let identity_id = IdentityID::random(); - let policy_id = PolicyID::random(); - - let policy = RecoveryPolicy::new(policy_id.clone(), PolicyCondition::OfN { - must_have: 3, - pubkeys: vec![ - gus.clone().into(), - marty.clone().into(), - jackie.clone().into(), - rosarita.clone().into(), - dirk.clone().into(), - ], - }); - - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let new_publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let new_root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let action = PolicyRequestAction::ReplaceKeys { - policy: new_policy_keypair.clone(), - publish: new_publish_keypair.clone(), - root: new_root_keypair.clone(), - }; - let entry = PolicyRequestEntry::new(identity_id.clone(), policy_id.clone(), action.clone()); - let req = PolicyRequest::new(&master_key, &new_policy_keypair, entry).unwrap(); - - let entry2 = PolicyRequestEntry::new(identity_id.clone(), PolicyID::random(), action.clone()); - let req_random_policy = PolicyRequest::new(&master_key, &new_policy_keypair, entry2).unwrap(); - - macro_rules! sig_failed { - ($req_mod:ident, $setter:expr) => { - let mut $req_mod = req.clone(); - $setter; - let res = policy.validate_request(&identity_id, &$req_mod); - assert_eq!(res.err(), Some(Error::CryptoSignatureVerificationFailed)); - } - } - - sig_failed! { req_mod, req_mod.set_id(RequestID::random()) } - sig_failed! { req_mod, req_mod.entry_mut().set_identity_id(IdentityID::random()) } - sig_failed! { req_mod, req_mod.entry_mut().set_policy_id(PolicyID::random()) } - match req.entry().action().clone() { - PolicyRequestAction::ReplaceKeys { publish, root, .. } => { - let new_policy = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let new_action = PolicyRequestAction::ReplaceKeys { - policy: new_policy, - publish, - root, - }; - sig_failed! { req_mod, req_mod.entry_mut().set_action(new_action) } - } - } - - let res = policy.validate_request(&IdentityID::random(), &req); - assert_eq!(res.err(), Some(Error::RecoveryPolicyRequestIdentityMismatch)); - - let res = policy.validate_request(&identity_id, &req_random_policy); - assert_eq!(res.err(), Some(Error::RecoveryPolicyRequestPolicyMismatch)); - - let res = policy.validate_request(&identity_id, &req); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - // ok, let's get some sigs - let req_signed = req - .sign(&master_key, &gus).unwrap() - .sign(&master_key, &marty).unwrap(); - // almost there... - let res = policy.validate_request(&identity_id, &req_signed); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - // marty signs again...shouldn't count - let req_signed_2 = req_signed.clone() - .sign(&master_key, &marty).unwrap(); - assert_eq!(req_signed_2.signatures().len(), 3); - // nice try - let res = policy.validate_request(&identity_id, &req_signed_2); - assert_eq!(res.err(), Some(Error::PolicyConditionMismatch)); - - // rosarita to the rescue - let req_signed_3 = req_signed.clone() - .sign(&master_key, &rosarita).unwrap(); - // this shoudl get it - let res = policy.validate_request(&identity_id, &req_signed_3); - assert_eq!(res, Ok(())); - } - - #[test] - fn policy_request_new_verify() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let new_publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let new_root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let identity_id = IdentityID::random(); - let policy_id = PolicyID::random(); - let action = PolicyRequestAction::ReplaceKeys { - policy: new_policy_keypair.clone(), - publish: new_publish_keypair.clone(), - root: new_root_keypair.clone(), - }; - let entry = PolicyRequestEntry::new(identity_id.clone(), policy_id.clone(), action); - let req = PolicyRequest::new(&master_key, &new_policy_keypair, entry).unwrap(); - - assert_eq!(req.entry().identity_id(), &identity_id); - assert_eq!(req.entry().policy_id(), &policy_id); - match req.entry().action() { - PolicyRequestAction::ReplaceKeys { policy, publish, root } => { - assert_eq!(policy, &new_policy_keypair); - assert_eq!(publish, &new_publish_keypair); - assert_eq!(root, &new_root_keypair); - } - } - - req.verify(&new_policy_keypair).unwrap(); - - // wrong key won't verify - let res = req.verify(&PolicyKeypair::from(new_root_keypair.deref().clone())); - assert_eq!(res.err(), Some(Error::CryptoSignatureVerificationFailed)); - - // modified request won't verify - let mut req2 = req.clone(); - let identity_id2 = IdentityID::random(); - assert!(identity_id != identity_id2); - req2.entry_mut().set_identity_id(identity_id2); - let res = req2.verify(&new_policy_keypair); - assert_eq!(res.err(), Some(Error::CryptoSignatureVerificationFailed)); - } - - #[test] - fn policy_request_reencrypt() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let new_publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let new_root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let identity_id = IdentityID::random(); - let policy_id = PolicyID::random(); - let action = PolicyRequestAction::ReplaceKeys { - policy: new_policy_keypair.clone(), - publish: new_publish_keypair.clone(), - root: new_root_keypair.clone(), - }; - let entry = PolicyRequestEntry::new(identity_id.clone(), policy_id.clone(), action); - let req = PolicyRequest::new(&master_key, &new_policy_keypair, entry).unwrap(); - - // i'm detective john kimble - let obj = "yeah sure you are."; - - let sig = match req.entry().action() { - PolicyRequestAction::ReplaceKeys { policy, .. } => { - policy.sign(&master_key, obj.as_bytes()).unwrap() - } - }; - - let new_master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let req2 = req.reencrypt(&master_key, &new_master_key).unwrap(); - - match req2.entry().action() { - PolicyRequestAction::ReplaceKeys { policy, .. } => { - let sig2 = policy.sign(&new_master_key, obj.as_bytes()).unwrap(); - assert_eq!(sig, sig2); - let res = policy.sign(&master_key, obj.as_bytes()); - assert_eq!(res.err(), Some(Error::CryptoOpenFailed)); - } - } - } - - #[test] - fn policy_request_strip_private_has_private() { - let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let new_policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let new_publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let new_root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let identity_id = IdentityID::random(); - let policy_id = PolicyID::random(); - let action = PolicyRequestAction::ReplaceKeys { - policy: new_policy_keypair.clone(), - publish: new_publish_keypair.clone(), - root: new_root_keypair.clone(), - }; - let entry = PolicyRequestEntry::new(identity_id.clone(), policy_id.clone(), action); - let req = PolicyRequest::new(&master_key, &new_policy_keypair, entry).unwrap(); - - assert!(req.has_private()); - match req.entry().action() { - PolicyRequestAction::ReplaceKeys{ policy, publish, root } => { - assert!(policy.has_private()); - assert!(publish.has_private()); - assert!(root.has_private()); - } - } - let req2 = req.strip_private(); - assert!(!req2.has_private()); - match req2.entry().action() { - PolicyRequestAction::ReplaceKeys { policy, publish, root } => { - assert!(!policy.has_private()); - assert!(!publish.has_private()); - assert!(!root.has_private()); - } - } - } - #[test] fn capability_policy_can() { todo!(); } #[test] - fn capability_policy_validate() { + fn capability_policy_validate_transaction() { todo!(); } } diff --git a/src/private.rs b/src/private.rs index 87ce2c4..e938605 100644 --- a/src/private.rs +++ b/src/private.rs @@ -134,7 +134,7 @@ struct PrivateVerifiableInner { /// /// This also allows the key that protects the private data to be rotated /// without the HMAC (and therefor the stamps) on that data being deprecated. -#[derive(Debug, Clone, PartialEq, AsnType, Serialize, Deserialize)] +#[derive(Debug, PartialEq, AsnType, Serialize, Deserialize)] pub struct PrivateVerifiable { /// Allows us to cast this container to T without this container ever /// actually storing any T value (because it's encrypted). @@ -235,6 +235,16 @@ impl PrivateVerifiable { } } +impl Clone for PrivateVerifiable { + fn clone(&self) -> Self { + Self { + _phantom: Default::default(), + sealed: self.sealed.clone(), + nonce: self.nonce.clone(), + } + } +} + /// A container that holds an (encrypted) HMAC key, a set of (encrypted) data /// of type `T`, and an [Hmac] of the unencrypted data. /// @@ -245,19 +255,26 @@ impl PrivateVerifiable { /// 1. If multiple people can access the private data of the identity, one /// cannot maliciously replace the private contents of a transaction without /// breaking the signature of the HMAC on that content. -#[derive(Debug, Clone, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] +#[derive(Debug, AsnType, Encode, Decode, Serialize, Deserialize, getset::Getters, getset::MutGetters, getset::Setters)] #[getset(get = "pub", get_mut = "pub(crate)", set = "pub(crate)")] pub struct PrivateWithHmac { /// Holds the HMAC for this private data so it can be verified without /// revealing the data itself #[rasn(tag(explicit(0)))] - hmac: Hmac, + pub(crate) hmac: Hmac, /// The (encrypted) data AND HMAC key. #[rasn(tag(explicit(1)))] - data: Option>, + pub(crate) data: Option>, +} + +impl PrivateWithHmac { + /// Create a new private hmac container + pub fn new(hmac: Hmac, data: Option>) -> Self { + Self { hmac, data } + } } -impl PrivateWithHmac { +impl PrivateWithHmac { /// Create a new `PrivateWithHmac` container around our data. pub fn seal(seal_key: &SecretKey, val: T) -> Result { let (hmac, private_verifiable) = PrivateVerifiable::seal(seal_key, &val)?; @@ -274,15 +291,16 @@ impl PrivateWithHmac { /// Reencrypt this PrivateWithHmac container with a new key. pub(crate) fn reencrypt(self, previous_seal_key: &SecretKey, new_seal_key: &SecretKey) -> Result { - match self { - Self {hmac, Some(prv)} => { + let res = match self { + Self {hmac, data: Some(prv)} => { Self { hmac, data: Some(prv.reencrypt(previous_seal_key, new_seal_key)?), } } - Self {hmac, None} => Self {hmac, data: None}, - } + Self {hmac, data: None} => Self {hmac, data: None}, + }; + Ok(res) } } @@ -299,6 +317,21 @@ impl Public for PrivateWithHmac { } } +impl PartialEq for PrivateWithHmac { + fn eq(&self, other: &Self) -> bool { + self.hmac() == other.hmac() + } +} + +impl Clone for PrivateWithHmac { + fn clone(&self) -> Self { + Self { + hmac: self.hmac.clone(), + data: self.data.clone(), + } + } +} + /// A wrapper that contains either public/plaintext data of type T or encrypted /// data, which can be deserialized to T. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -328,7 +361,7 @@ impl MaybePrivate { /// Get the HMAC for this MaybePrivate, if it has one. pub fn hmac(&self) -> Option<&Hmac> { match self { - Self::Private(container) => Some(container.hmac()) + Self::Private(container) => Some(container.hmac()), _ => None, } } @@ -369,7 +402,7 @@ impl MaybePrivate { Self::Public(x) => Ok(Self::Public(x)), Self::Private(container) => { let unsealed = container.open_and_verify(seal_key)?; - Self::Public(unsealed) + Ok(Self::Public(unsealed)) } } } @@ -378,7 +411,7 @@ impl MaybePrivate { pub(crate) fn reencrypt(self, previous_seal_key: &SecretKey, new_seal_key: &SecretKey) -> Result { let maybe = match self { Self::Public(x) => Self::Public(x), - Self::Private(container) => Self::Private(container.reencrypt(previous_seal_key, new_seal_key)), + Self::Private(container) => Self::Private(container.reencrypt(previous_seal_key, new_seal_key)?), }; Ok(maybe) } @@ -404,7 +437,7 @@ impl Encode for MaybePrivate Self::Public(data) => { encoder.encode_explicit_prefix(Tag::new(Class::Context, 0), data)?; } - Self::Private { ref hmac, ref data } => { + Self::Private(PrivateWithHmac { ref hmac, ref data }) => { let inner = PrivateInner { hmac: hmac.clone(), data: data.clone(), @@ -425,10 +458,7 @@ impl Decode for MaybePrivate .or_else(|_| { decoder.decode_explicit_prefix(Tag::new(Class::Context, 1)) .map(|inner: PrivateInner| { - Self::Private { - hmac: inner.hmac.clone(), - data: inner.data.clone(), - } + Self::Private(PrivateWithHmac::new(inner.hmac.clone(), inner.data.clone())) }) }) } @@ -438,13 +468,13 @@ impl Public for MaybePrivate { fn strip_private(&self) -> Self { match self { Self::Public(x) => Self::Public(x.clone()), - Self::Private { hmac, .. } => Self::Private { hmac: hmac.clone(), data: None }, + Self::Private(PrivateWithHmac { hmac, .. }) => Self::Private(PrivateWithHmac::new(hmac.clone(), None)), } } fn has_private(&self) -> bool { match self { - Self::Private { data: Some(_), .. } => true, + Self::Private(PrivateWithHmac { data: Some(_), .. }) => true, _ => false, } } @@ -528,15 +558,17 @@ mod tests { let maybe1: MaybePrivate = MaybePrivate::Public(String::from("hello")); let maybe2: MaybePrivate = MaybePrivate::new_private(&seal_key, String::from("omg")).unwrap(); - let maybe3: MaybePrivate = MaybePrivate::Private { - hmac: Hmac::new_sha512(&fake_hmac_key, Vec::new().as_slice()).unwrap(), - data: None, - }; + let maybe3: MaybePrivate = MaybePrivate::Private(PrivateWithHmac::new( + Hmac::new_sha512(&fake_hmac_key, Vec::new().as_slice()).unwrap(), + None, + )); let maybe2_tampered = match maybe2.clone() { - MaybePrivate::Private { data, .. } => MaybePrivate::Private { - hmac: Hmac::new_sha512(&fake_hmac_key, String::from("loool").as_bytes()).unwrap(), - data, - }, + MaybePrivate::Private(PrivateWithHmac { data, .. }) => { + MaybePrivate::Private(PrivateWithHmac::new( + Hmac::new_sha512(&fake_hmac_key, String::from("loool").as_bytes()).unwrap(), + data + )) + } _ => panic!("bad maybeprivate given"), }; @@ -566,10 +598,10 @@ mod tests { let maybe1: MaybePrivate = MaybePrivate::Public(String::from("hello")); let maybe2: MaybePrivate = MaybePrivate::new_private(&seal_key, String::from("omg")).unwrap(); - let maybe3: MaybePrivate = MaybePrivate::Private { - hmac: Hmac::new_sha512(&fake_hmac_key, Vec::new().as_slice()).unwrap(), - data: None, - }; + let maybe3: MaybePrivate = MaybePrivate::Private(PrivateWithHmac::new( + Hmac::new_sha512(&fake_hmac_key, Vec::new().as_slice()).unwrap(), + None, + )); assert_eq!(maybe1.open_public().unwrap(), "hello"); assert_eq!(maybe2.open_public(), None); @@ -585,10 +617,10 @@ mod tests { let maybe1: MaybePrivate = MaybePrivate::Public(String::from("hello")); let maybe2: MaybePrivate = MaybePrivate::new_private(&seal_key, String::from("omg")).unwrap(); - let maybe3: MaybePrivate = MaybePrivate::Private { - hmac: Hmac::new_sha512(&fake_hmac_key, Vec::new().as_slice()).unwrap(), - data: None, - }; + let maybe3: MaybePrivate = MaybePrivate::Private(PrivateWithHmac::new( + Hmac::new_sha512(&fake_hmac_key, Vec::new().as_slice()).unwrap(), + None, + )); assert_eq!(maybe1.clone().into_public(&seal_key).unwrap(), MaybePrivate::Public(String::from("hello"))); // fake key works too because who gives a crap if it's public. grind me @@ -637,10 +669,10 @@ mod tests { assert!(maybe.has_data()); let maybe2 = maybe.strip_private(); let hmac = match &maybe { - MaybePrivate::Private { hmac, .. } => hmac.clone(), + MaybePrivate::Private(PrivateWithHmac { hmac, .. }) => hmac.clone(), _ => panic!("weird"), }; - assert_eq!(maybe2, MaybePrivate::Private { hmac, data: None }); + assert_eq!(maybe2, MaybePrivate::Private(PrivateWithHmac { hmac, data: None })); assert!(!maybe2.has_data()); } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 330d40f..43cc16d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -25,9 +25,9 @@ macro_rules! object_id { ) => { #[derive(Debug, Clone, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)] $(#[$meta])* - pub struct $name(pub(crate) crate::crypto::key::SignKeypairSignature); + pub struct $name(pub(crate) crate::dag::TransactionID); - asn_encdec_newtype! { $name, crate::crypto::key::SignKeypairSignature } + asn_encdec_newtype! { $name, crate::dag::TransactionID } impl $name { /// Take a full string id and return the shortened ID @@ -40,58 +40,45 @@ macro_rules! object_id { #[allow(dead_code)] impl $name { pub(crate) fn blank() -> Self { - let sigbytes = [0u8; ed25519_dalek::SIGNATURE_LENGTH]; - let sig = crate::crypto::key::SignKeypairSignature::Ed25519(crate::util::ser::Binary::new(sigbytes)); - $name(sig) + let hash = crate::crypto::key::Sha512::from([0u8; crate::crypto::key::SHA512_LEN]); + $name(crate::dag::TransactionID::from(hash)) } + #[cfg(test)] pub(crate) fn random() -> Self { - let mut sigbytes = [0u8; ed25519_dalek::SIGNATURE_LENGTH]; - rand::rngs::OsRng.fill_bytes(&mut sigbytes); - sigbytes[ed25519_dalek::SIGNATURE_LENGTH - 1] = 0; - let sig = crate::crypto::key::SignKeypairSignature::Ed25519(crate::util::ser::Binary::new(sigbytes)); - $name(sig) + let hash = crate::crypto::key::Sha512::random(); + $name(crate::dag::TransactionID::from(hash)) } } impl Deref for $name { - type Target = crate::crypto::key::SignKeypairSignature; + type Target = crate::dag::TransactionID; fn deref(&self) -> &Self::Target { &self.0 } } - impl std::convert::From for $name { - fn from(sig: crate::crypto::key::SignKeypairSignature) -> Self { - Self(sig) + impl std::convert::From for $name { + fn from(hash: crate::dag::TransactionID) -> Self { + Self(hash) } } impl std::convert::From<&$name> for String { fn from(id: &$name) -> String { - let ser_val: u8 = match &id.0 { - crate::crypto::key::SignKeypairSignature::Ed25519(_) => 0, - }; - let mut bytes = Vec::from(id.as_ref()); - bytes.push(ser_val); - crate::util::ser::base64_encode(&bytes) + let bytes = id.deref().deref().deref(); + crate::util::ser::base64_encode(bytes) } } impl std::convert::TryFrom<&str> for $name { type Error = crate::error::Error; fn try_from(id_str: &str) -> std::result::Result { - let mut bytes = crate::util::ser::base64_decode(id_str.as_bytes())?; - let ser_val = bytes.pop().ok_or(crate::error::Error::SignatureMissing)?; - let id_sig = match ser_val { - _ => { - let bytes_arr: [u8; ed25519_dalek::SIGNATURE_LENGTH] = bytes.try_into() - .map_err(|_| crate::error::Error::BadLength)?; - let sig = ed25519_dalek::Signature::from(bytes_arr); - crate::crypto::key::SignKeypairSignature::Ed25519(crate::util::ser::Binary::new(sig.to_bytes())) - } - }; - Ok(Self(id_sig)) + let bytes = crate::util::ser::base64_decode(id_str.as_bytes())?; + let hash_bytes: [u8; crate::crypto::key::SHA512_LEN] = bytes.try_into() + .map_err(|_| crate::error::Error::BadLength)?; + let hash = crate::crypto::key::Sha512::from(hash_bytes); + Ok(Self(crate::dag::TransactionID::from(hash))) } } } @@ -243,7 +230,7 @@ impl FromStr for Date { } } -pub trait Public: Clone { +pub trait Public { /// Strip the private data from a object, returning only public data. fn strip_private(&self) -> Self; @@ -312,10 +299,10 @@ impl std::fmt::Display for Url { mod tests { use super::*; use crate::{ - crypto::key::{SignKeypairSignature}, + crypto::key::{Sha512}, + dag::TransactionID, util::ser::{self, Binary}, }; - use rand::prelude::*; use std::convert::{TryFrom, TryInto}; use std::ops::Deref; @@ -325,11 +312,9 @@ mod tests { TestID } - let sigbytes = vec![61, 47, 37, 255, 130, 49, 42, 60, 55, 247, 221, 146, 149, 13, 27, 227, 23, 228, 6, 170, 103, 177, 184, 3, 124, 102, 180, 148, 228, 67, 30, 140, 172, 59, 90, 94, 220, 123, 143, 239, 97, 164, 186, 213, 141, 217, 174, 43, 186, 16, 184, 236, 166, 130, 38, 5, 176, 33, 22, 5, 111, 171, 57, 2]; - let sigarr: [u8; 64] = sigbytes.try_into().unwrap(); - let sig = SignKeypairSignature::Ed25519(Binary::new(sigarr)); + let hash = Sha512::hash(b"get a job").unwrap(); - let id = TestID(sig); + let id = TestID(TransactionID::from(hash)); let string_id = String::try_from(&id).unwrap(); assert_eq!(&string_id, "PS8l_4IxKjw3992SlQ0b4xfkBqpnsbgDfGa0lORDHoysO1pe3HuP72GkutWN2a4ruhC47KaCJgWwIRYFb6s5AgA"); diff --git a/src/util/sign.rs b/src/util/sign.rs index d28835a..fa000d4 100644 --- a/src/util/sign.rs +++ b/src/util/sign.rs @@ -1,43 +1,6 @@ //! Includes some utilities helpful for generating signatures. -use crate::{ - util::Timestamp, -}; -use getset; -use rasn::{AsnType, Encode, Encoder, Tag, types::Class}; - -/// Attaches a serializable object to a date for signing. -/// -/// This is a one-way object used for comparing signatures, so never needs to be -/// deserialized. -#[derive(Debug, Clone, AsnType, getset::Getters, getset::MutGetters, getset::Setters)] -pub struct DateSigner<'a, 'b, T> { - /// The date we signed this value. - date: &'a Timestamp, - /// The value being signed. - value: &'b T, -} - -impl<'a, 'b, T: Encode> DateSigner<'a, 'b, T> { - /// Construct a new DateSigner - pub fn new(date: &'a Timestamp, value: &'b T) -> Self { - Self { - date, - value, - } - } -} - -impl<'a, 'b, T: Encode> Encode for DateSigner<'a, 'b, T> { - fn encode_with_tag(&self, encoder: &mut E, tag: Tag) -> Result<(), E::Error> { - encoder.encode_sequence(tag, |encoder| { - encoder.encode_explicit_prefix(Tag::new(Class::Context, 0), &self.date)?; - encoder.encode_explicit_prefix(Tag::new(Class::Context, 1), &self.value)?; - Ok(()) - })?; - Ok(()) - } -} +use rasn::{Encode}; /// A trait that allows an object to return a signable representation of itself. pub trait Signable { diff --git a/src/util/test.rs b/src/util/test.rs index c9cc5e7..ed158bd 100644 --- a/src/util/test.rs +++ b/src/util/test.rs @@ -1,9 +1,11 @@ use crate::{ crypto::key::{SecretKey, SignKeypair, CryptoKeypair}, + dag::Transactions, identity::{ identity::{IdentityID, Identity}, - keychain::{ExtendKeypair, AlphaKeypair, PolicyKeypair, PublishKeypair, RootKeypair, Key}, + keychain::{ExtendKeypair, AdminKey, AdminKeypair, Key}, }, + policy::{Capability, CapabilityPolicy, Participant, Policy}, util::Timestamp, }; use std::thread; @@ -15,13 +17,34 @@ pub(crate) fn sleep(millis: u64) { thread::sleep(Duration::from_millis(millis)); } +pub(crate) fn create_fake_identity(now: Timestamp) -> (SecretKey, Transactions, AdminKey) { + let transactions = Transactions::new(); + let master_key = SecretKey::new_xchacha20poly1305().unwrap(); + let sign = SignKeypair::new_ed25519(&master_key).unwrap(); + let admin = AdminKeypair::from(sign); + let admin_key = AdminKey::new(admin, "Alpha", None); + let capability = CapabilityPolicy::new( + "default".into(), + vec![Capability::Permissive], + Policy::MOfN { must_have: 1, participants: vec![admin_key.key().clone().into()] } + ); + let trans_id = transactions + .create_identity(now, vec![admin_key.clone()], vec![capability]).unwrap() + .sign(&master_key, &admin_key).unwrap(); + let transactions2 = transactions.push_transaction(trans_id).unwrap(); + (master_key, transactions2, admin_key) +} + pub(crate) fn setup_identity_with_subkeys() -> (SecretKey, Identity) { let master_key = SecretKey::new_xchacha20poly1305().unwrap(); - let alpha_keypair = AlphaKeypair::new_ed25519(&master_key).unwrap(); - let policy_keypair = PolicyKeypair::new_ed25519(&master_key).unwrap(); - let publish_keypair = PublishKeypair::new_ed25519(&master_key).unwrap(); - let root_keypair = RootKeypair::new_ed25519(&master_key).unwrap(); - let identity = Identity::create(IdentityID::random(), alpha_keypair, policy_keypair, publish_keypair, root_keypair, Timestamp::now()) + let admin_keypair = AdminKeypair::new_ed25519(&master_key).unwrap(); + let capability = CapabilityPolicy::new( + "Test default".into(), + vec![Capability::Permissive], + Policy::MOfN { must_have: 1, participants: vec![Participant::Key(admin_keypair.clone().into())] } + ); + let admin_key = AdminKey::new(admin_keypair, "Alpha", None); + let identity = Identity::create(IdentityID::random(), vec![admin_key], vec![capability], Timestamp::now()) .add_subkey(Key::new_sign(SignKeypair::new_ed25519(&master_key).unwrap()), "sign", None).unwrap() .add_subkey(Key::new_crypto(CryptoKeypair::new_curve25519xchacha20poly1305(&master_key).unwrap()), "cryptololol", None).unwrap(); (master_key, identity)