From efadf74ea3628563d7bfe19698aeb5dbc363575b Mon Sep 17 00:00:00 2001 From: Andrew Danger Lyon Date: Sun, 23 Oct 2022 18:44:58 -0500 Subject: [PATCH] A checkin on stamp-protocol/tracker#31 Transaction IDs are onw a sha512 of the entry and allow multiple sigs. Policies are used for all tranactions (unimplemented, but the structure is there). Names for keys are no longer unique...keys are handled by id, and SecretKeys now have ids (their hmac). all private containers are PrivateWithHmac now, meaning their content can be verified AND their HMAC can be included in transactions. adding names to claims (again, not unique). this allows them to take the place of forward. removed forwards. they are incredibly stupid, because they duplicate claims in almost every way (except forwards had names, claims did not, which i fixed). now you can have a "Mastodon" claim that points to your mastodon account etc. restructing many container types, but mainly claims/stamps: ClaimContainer no longer exists, and claims/stamps no longer have signed content. instead, all signing and verification is done at the transaction level. this means a signed stamp is really just a normal transaction (which can be included both in the stamper or stampee's identity). similarly, publishing is no longer a separate process, but rather a new transaction type (that cannot be saved to the identity) which allows publishing to also be multisig. there's a ton of supporting shit for all the above, but that's the main stuff. a large amount of tests are broken, but the system compiles, and its time to check things in before i go off on another whim and rewire a bunch of stuff. --- .gitignore | 1 + src/crypto/key.rs | 119 +- src/crypto/message.rs | 14 +- src/crypto/sign.rs | 12 +- src/dag.rs | 2354 -------------------------------------- src/dag/mod.rs | 21 + src/dag/transaction.rs | 870 ++++++++++++++ src/dag/transactions.rs | 1669 +++++++++++++++++++++++++++ src/error.rs | 84 +- src/identity/claim.rs | 138 +-- src/identity/identity.rs | 739 +++++------- src/identity/keychain.rs | 488 +++----- src/identity/mod.rs | 411 +------ src/identity/stamp.rs | 188 +-- src/lib.rs | 39 +- src/policy.rs | 604 +++++----- src/private.rs | 106 +- src/util/mod.rs | 61 +- src/util/sign.rs | 39 +- src/util/test.rs | 35 +- 20 files changed, 3639 insertions(+), 4353 deletions(-) delete mode 100644 src/dag.rs create mode 100644 src/dag/mod.rs create mode 100644 src/dag/transaction.rs create mode 100644 src/dag/transactions.rs 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)