-
Notifications
You must be signed in to change notification settings - Fork 39
feat: replacing hashchains with accounts #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
d6087aa
feat: replacing Hashchain with Account
distractedm1nd 5cdedc2
broken tests but progress
distractedm1nd 455c9d3
fixing tests
distractedm1nd a7f6d50
clippy
distractedm1nd 8650a06
rustdoc
distractedm1nd 5c45758
review fixes
distractedm1nd 9c85632
validation: review fixes
distractedm1nd 184e8c1
avoid double signing
distractedm1nd 85c7132
prism_serde
distractedm1nd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
use anyhow::{anyhow, Result}; | ||
use prism_keys::{Signature, SigningKey, VerifyingKey}; | ||
use prism_serde::raw_or_b64; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::{ | ||
operation::{Operation, ServiceChallenge}, | ||
transaction::Transaction, | ||
}; | ||
|
||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] | ||
pub struct SignedData(pub VerifyingKey, #[serde(with = "raw_or_b64")] pub Vec<u8>); | ||
|
||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Default)] | ||
/// Represents an account or service on prism, making up the values of our state | ||
/// tree. | ||
pub struct Account { | ||
/// The unique identifier for the account. | ||
id: String, | ||
|
||
/// The transaction nonce for the account. | ||
nonce: u64, | ||
|
||
/// The current set of valid keys for the account. Any of these keys can be | ||
/// used to sign transactions. | ||
valid_keys: Vec<VerifyingKey>, | ||
|
||
/// Arbitrary signed data associated with the account, used for bookkeeping | ||
/// externally signed data from keys that don't live on Prism. | ||
signed_data: Vec<SignedData>, | ||
|
||
/// The service challenge for the account, if it is a service. | ||
service_challenge: Option<ServiceChallenge>, | ||
} | ||
|
||
impl Account { | ||
pub fn id(&self) -> &str { | ||
&self.id | ||
} | ||
|
||
pub fn nonce(&self) -> u64 { | ||
self.nonce | ||
} | ||
|
||
pub fn valid_keys(&self) -> &[VerifyingKey] { | ||
&self.valid_keys | ||
} | ||
|
||
pub fn signed_data(&self) -> &[SignedData] { | ||
&self.signed_data | ||
} | ||
|
||
pub fn service_challenge(&self) -> Option<&ServiceChallenge> { | ||
self.service_challenge.as_ref() | ||
} | ||
|
||
/// Creates a [`Transaction`] that can be used to update or create the | ||
/// account. The transaction produced could be invalid, and will be | ||
/// validated before being processed. | ||
pub fn prepare_transaction( | ||
&self, | ||
account_id: String, | ||
operation: Operation, | ||
sk: &SigningKey, | ||
) -> Result<Transaction> { | ||
let vk = sk.verifying_key(); | ||
|
||
let mut tx = Transaction { | ||
id: account_id, | ||
nonce: self.nonce, | ||
operation, | ||
signature: Signature::Placeholder, | ||
vk, | ||
}; | ||
|
||
tx.sign(sk)?; | ||
|
||
Ok(tx) | ||
} | ||
|
||
/// Validates and processes an incoming [`Transaction`], updating the account state. | ||
pub fn process_transaction(&mut self, tx: &Transaction) -> Result<()> { | ||
self.validate_transaction(tx)?; | ||
self.process_operation(&tx.operation)?; | ||
self.nonce += 1; | ||
Ok(()) | ||
} | ||
distractedm1nd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// Validates a transaction against the current account state. Please note | ||
/// that the operation must be validated separately. | ||
fn validate_transaction(&self, tx: &Transaction) -> Result<()> { | ||
if tx.nonce != self.nonce { | ||
return Err(anyhow!( | ||
"Nonce does not match. {} != {}", | ||
tx.nonce, | ||
self.nonce | ||
)); | ||
} | ||
|
||
match tx.operation { | ||
Operation::CreateAccount { .. } | Operation::RegisterService { .. } => {} | ||
_ => { | ||
if tx.id != self.id { | ||
return Err(anyhow!("Transaction ID does not match account ID")); | ||
} | ||
if !self.valid_keys.contains(&tx.vk) { | ||
return Err(anyhow!("Invalid key")); | ||
} | ||
} | ||
} | ||
|
||
let msg = tx.get_signature_payload()?; | ||
tx.vk.verify_signature(&msg, &tx.signature)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Validates an operation against the current account state. | ||
fn validate_operation(&self, operation: &Operation) -> Result<()> { | ||
match operation { | ||
Operation::AddKey { key } => { | ||
if self.valid_keys.contains(key) { | ||
return Err(anyhow!("Key already exists")); | ||
} | ||
} | ||
Operation::RevokeKey { key } => { | ||
if !self.valid_keys.contains(key) { | ||
return Err(anyhow!("Key does not exist")); | ||
} | ||
} | ||
Operation::AddData { | ||
data, | ||
data_signature, | ||
} => { | ||
// we only need to do a single signature verification if the | ||
// user signs transaction and data with their own key | ||
if !self.valid_keys().contains(&data_signature.verifying_key) { | ||
data_signature | ||
.verifying_key | ||
.verify_signature(data, &data_signature.signature)?; | ||
} | ||
} | ||
Operation::CreateAccount { .. } | Operation::RegisterService { .. } => { | ||
if !self.is_empty() { | ||
return Err(anyhow!("Account already exists")); | ||
} | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Processes an operation, updating the account state. Should only be run | ||
/// in the context of a transaction. | ||
fn process_operation(&mut self, operation: &Operation) -> Result<()> { | ||
self.validate_operation(operation)?; | ||
|
||
match operation { | ||
Operation::AddKey { key } => { | ||
self.valid_keys.push(key.clone()); | ||
} | ||
Operation::RevokeKey { key } => { | ||
self.valid_keys.retain(|k| k != key); | ||
} | ||
Operation::AddData { | ||
data, | ||
data_signature, | ||
} => { | ||
self.signed_data.push(SignedData( | ||
data_signature.verifying_key.clone(), | ||
data.clone(), | ||
)); | ||
} | ||
Operation::CreateAccount { id, key, .. } => { | ||
self.id = id.clone(); | ||
self.valid_keys.push(key.clone()); | ||
} | ||
Operation::RegisterService { | ||
id, | ||
creation_gate, | ||
key, | ||
} => { | ||
self.id = id.clone(); | ||
self.valid_keys.push(key.clone()); | ||
self.service_challenge = Some(creation_gate.clone()); | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn is_empty(&self) -> bool { | ||
self.nonce == 0 | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.