Skip to content

Commit f617532

Browse files
committed
added association log and signature
1 parent 96fcc6f commit f617532

File tree

3 files changed

+663
-0
lines changed

3 files changed

+663
-0
lines changed
+307
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
use super::hashes::generate_xid;
2+
use super::member::{Member, MemberIdentifier, MemberKind};
3+
use super::signature::{Signature, SignatureError, SignatureKind};
4+
use super::state::AssociationState;
5+
6+
use thiserror::Error;
7+
8+
// const ALLOWED_CREATE_ENTITY_ROLES: [EntityRole; 2] = [EntityRole::LegacyKey, EntityRole::Address];
9+
10+
#[derive(Debug, Error, PartialEq)]
11+
pub enum AssociationError {
12+
#[error("Error creating association {0}")]
13+
Generic(String),
14+
#[error("Multiple create operations detected")]
15+
MultipleCreate,
16+
#[error("XID not yet created")]
17+
NotCreated,
18+
#[error("Signature validation failed {0}")]
19+
Signature(#[from] SignatureError),
20+
#[error("Missing existing member")]
21+
MissingExistingMember,
22+
#[error("Legacy key is only allowed to be associated using a legacy signature with nonce 0")]
23+
LegacySignatureReuse,
24+
#[error("The new member identifier does not match the signer")]
25+
NewMemberIdSignatureMismatch,
26+
#[error("Signature not allowed for role {0:?} {1:?}")]
27+
SignatureNotAllowed(MemberKind, SignatureKind),
28+
#[error("Replay detected")]
29+
Replay,
30+
}
31+
32+
pub trait IdentityAction {
33+
fn update_state(
34+
&self,
35+
existing_state: Option<AssociationState>,
36+
) -> Result<AssociationState, AssociationError>;
37+
fn signatures(&self) -> Vec<Vec<u8>>;
38+
fn replay_check(&self, state: &AssociationState) -> Result<(), AssociationError> {
39+
let signatures = self.signatures();
40+
for signature in signatures {
41+
if state.has_seen(&signature) {
42+
return Err(AssociationError::Replay);
43+
}
44+
}
45+
46+
Ok(())
47+
}
48+
}
49+
50+
pub struct CreateInbox {
51+
pub nonce: u64,
52+
pub account_address: String,
53+
pub initial_address_signature: Box<dyn Signature>,
54+
}
55+
56+
impl IdentityAction for CreateInbox {
57+
fn update_state(
58+
&self,
59+
existing_state: Option<AssociationState>,
60+
) -> Result<AssociationState, AssociationError> {
61+
if existing_state.is_some() {
62+
return Err(AssociationError::MultipleCreate);
63+
}
64+
65+
let account_address = self.account_address.clone();
66+
let recovered_signer = self.initial_address_signature.recover_signer()?;
67+
if recovered_signer.ne(&MemberIdentifier::Address(account_address.clone())) {
68+
return Err(AssociationError::MissingExistingMember);
69+
}
70+
71+
Ok(AssociationState::new(account_address, self.nonce))
72+
}
73+
74+
fn signatures(&self) -> Vec<Vec<u8>> {
75+
vec![self.initial_address_signature.bytes()]
76+
}
77+
}
78+
79+
pub struct AddAssociation {
80+
pub client_timestamp_ns: u64,
81+
pub new_member_signature: Box<dyn Signature>,
82+
pub new_member_identifier: MemberIdentifier,
83+
pub existing_member_signature: Box<dyn Signature>,
84+
}
85+
86+
impl IdentityAction for AddAssociation {
87+
fn update_state(
88+
&self,
89+
maybe_existing_state: Option<AssociationState>,
90+
) -> Result<AssociationState, AssociationError> {
91+
let existing_state = maybe_existing_state.ok_or(AssociationError::NotCreated)?;
92+
self.replay_check(&existing_state)?;
93+
94+
let new_member_address = self.new_member_signature.recover_signer()?;
95+
if new_member_address.ne(&self.new_member_identifier) {
96+
return Err(AssociationError::NewMemberIdSignatureMismatch);
97+
}
98+
99+
let existing_member_identifier = self.existing_member_signature.recover_signer()?;
100+
let recovery_address = existing_state.recovery_address();
101+
102+
if new_member_address.ne(&self.new_member_identifier) {
103+
return Err(AssociationError::Generic(
104+
"new member identifier does not match signature".to_string(),
105+
));
106+
}
107+
108+
if new_member_address.ne(&self.new_member_identifier) {
109+
return Err(AssociationError::Generic(
110+
"new member identifier does not match signature".to_string(),
111+
));
112+
}
113+
114+
// You cannot add yourself
115+
if new_member_address == existing_member_identifier {
116+
return Err(AssociationError::Generic("tried to add self".to_string()));
117+
}
118+
119+
// Only allow LegacyDelegated signatures on XIDs with a nonce of 0
120+
// Otherwise the client should use the regular wallet signature to create
121+
if self.new_member_signature.signature_kind() == SignatureKind::LegacyDelegated {
122+
if existing_state
123+
.xid()
124+
.ne(&generate_xid(&existing_member_identifier.to_string(), &0))
125+
{
126+
return Err(AssociationError::LegacySignatureReuse);
127+
}
128+
}
129+
130+
// Make sure that the signature type lines up with the role
131+
if !allowed_signature_for_kind(
132+
&self.new_member_identifier.kind(),
133+
&self.new_member_signature.signature_kind(),
134+
) {
135+
return Err(AssociationError::SignatureNotAllowed(
136+
self.new_member_identifier.kind(),
137+
self.new_member_signature.signature_kind(),
138+
));
139+
}
140+
141+
let existing_member = existing_state.get(&existing_member_identifier);
142+
143+
let existing_entity_id = match existing_member {
144+
// If there is an existing member of the XID, use that member's ID
145+
Some(member) => member.identifier,
146+
None => {
147+
let recovery_identifier = MemberIdentifier::Address(recovery_address.clone());
148+
// Check if it is a signature from the recovery address, which is allowed to add members
149+
if existing_member_identifier.ne(&recovery_identifier) {
150+
return Err(AssociationError::MissingExistingMember);
151+
}
152+
// BUT, the recovery address has to be used with a real wallet signature, can't be delegated
153+
if self.existing_member_signature.signature_kind() == SignatureKind::LegacyDelegated
154+
{
155+
return Err(AssociationError::LegacySignatureReuse);
156+
}
157+
// If it is a real wallet signature, then it is allowed to add members
158+
recovery_identifier
159+
}
160+
};
161+
162+
let new_member = Member::new(new_member_address, Some(existing_entity_id));
163+
164+
println!("Adding new entity to state {:?}", &new_member);
165+
166+
Ok(existing_state.add(new_member))
167+
}
168+
169+
fn signatures(&self) -> Vec<Vec<u8>> {
170+
vec![
171+
self.existing_member_signature.bytes(),
172+
self.new_member_signature.bytes(),
173+
]
174+
}
175+
}
176+
177+
pub struct RevokeAssociation {
178+
pub client_timestamp_ns: u64,
179+
pub recovery_address_signature: Box<dyn Signature>,
180+
pub revoked_member: MemberIdentifier,
181+
}
182+
183+
impl IdentityAction for RevokeAssociation {
184+
fn update_state(
185+
&self,
186+
maybe_existing_state: Option<AssociationState>,
187+
) -> Result<AssociationState, AssociationError> {
188+
let existing_state = maybe_existing_state.ok_or(AssociationError::NotCreated)?;
189+
self.replay_check(&existing_state)?;
190+
191+
if self.recovery_address_signature.signature_kind() == SignatureKind::LegacyDelegated {
192+
return Err(AssociationError::SignatureNotAllowed(
193+
MemberKind::Address,
194+
SignatureKind::LegacyDelegated,
195+
));
196+
}
197+
// Don't need to check for replay here since revocation is idempotent
198+
let recovery_signer = self.recovery_address_signature.recover_signer()?;
199+
// Make sure there is a recovery address set on the state
200+
let state_recovery_address = existing_state.recovery_address();
201+
202+
// Ensure this message is signed by the recovery address
203+
if recovery_signer.ne(&MemberIdentifier::Address(state_recovery_address.clone())) {
204+
return Err(AssociationError::MissingExistingMember);
205+
}
206+
207+
let installations_to_remove: Vec<Member> = existing_state
208+
.members_by_parent(&self.revoked_member)
209+
.into_iter()
210+
// Only remove children if they are installations
211+
.filter(|child| child.kind() == MemberKind::Installation)
212+
.collect();
213+
214+
// Actually apply the revocation to the parent
215+
let new_state = existing_state.remove(&self.revoked_member);
216+
217+
Ok(installations_to_remove
218+
.iter()
219+
.fold(new_state, |state, installation| {
220+
state.remove(&installation.identifier)
221+
}))
222+
}
223+
224+
fn signatures(&self) -> Vec<Vec<u8>> {
225+
vec![self.recovery_address_signature.bytes()]
226+
}
227+
}
228+
229+
pub enum Action {
230+
CreateInbox(CreateInbox),
231+
AddAssociation(AddAssociation),
232+
RevokeAssociation(RevokeAssociation),
233+
}
234+
235+
impl IdentityAction for Action {
236+
fn update_state(
237+
&self,
238+
existing_state: Option<AssociationState>,
239+
) -> Result<AssociationState, AssociationError> {
240+
match self {
241+
Action::CreateInbox(event) => event.update_state(existing_state),
242+
Action::AddAssociation(event) => event.update_state(existing_state),
243+
Action::RevokeAssociation(event) => event.update_state(existing_state),
244+
}
245+
}
246+
247+
fn signatures(&self) -> Vec<Vec<u8>> {
248+
match self {
249+
Action::CreateInbox(event) => event.signatures(),
250+
Action::AddAssociation(event) => event.signatures(),
251+
Action::RevokeAssociation(event) => event.signatures(),
252+
}
253+
}
254+
}
255+
256+
pub struct IdentityUpdate {
257+
pub actions: Vec<Action>,
258+
}
259+
260+
impl IdentityUpdate {
261+
pub fn new(actions: Vec<Action>) -> Self {
262+
Self { actions }
263+
}
264+
}
265+
266+
impl IdentityAction for IdentityUpdate {
267+
fn update_state(
268+
&self,
269+
existing_state: Option<AssociationState>,
270+
) -> Result<AssociationState, AssociationError> {
271+
let mut state = existing_state.clone();
272+
for action in &self.actions {
273+
state = Some(action.update_state(state)?);
274+
}
275+
276+
let new_state = state.ok_or(AssociationError::NotCreated)?;
277+
278+
// After all the updates in the LogEntry have been processed, add the list of signatures to the state
279+
// so that the signatures can not be re-used in subsequent updates
280+
Ok(new_state.add_seen_signatures(self.signatures()))
281+
}
282+
283+
fn signatures(&self) -> Vec<Vec<u8>> {
284+
self.actions
285+
.iter()
286+
.flat_map(|action| action.signatures())
287+
.collect()
288+
}
289+
}
290+
291+
// Ensure that the type of signature matches the new entity's role.
292+
pub fn allowed_signature_for_kind(role: &MemberKind, signature_kind: &SignatureKind) -> bool {
293+
match role {
294+
MemberKind::Address => match signature_kind {
295+
SignatureKind::Erc191 => true,
296+
SignatureKind::Erc1271 => true,
297+
SignatureKind::InstallationKey => false,
298+
SignatureKind::LegacyDelegated => true,
299+
},
300+
MemberKind::Installation => match signature_kind {
301+
SignatureKind::Erc191 => false,
302+
SignatureKind::Erc1271 => false,
303+
SignatureKind::InstallationKey => true,
304+
SignatureKind::LegacyDelegated => false,
305+
},
306+
}
307+
}

0 commit comments

Comments
 (0)