Skip to content

Commit 6cc75a5

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

File tree

3 files changed

+669
-0
lines changed

3 files changed

+669
-0
lines changed
+313
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
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+
if self.initial_address_signature.signature_kind() == SignatureKind::LegacyDelegated
72+
&& self.nonce != 0
73+
{
74+
return Err(AssociationError::LegacySignatureReuse);
75+
}
76+
77+
Ok(AssociationState::new(account_address, self.nonce))
78+
}
79+
80+
fn signatures(&self) -> Vec<Vec<u8>> {
81+
vec![self.initial_address_signature.bytes()]
82+
}
83+
}
84+
85+
pub struct AddAssociation {
86+
pub client_timestamp_ns: u64,
87+
pub new_member_signature: Box<dyn Signature>,
88+
pub new_member_identifier: MemberIdentifier,
89+
pub existing_member_signature: Box<dyn Signature>,
90+
}
91+
92+
impl IdentityAction for AddAssociation {
93+
fn update_state(
94+
&self,
95+
maybe_existing_state: Option<AssociationState>,
96+
) -> Result<AssociationState, AssociationError> {
97+
let existing_state = maybe_existing_state.ok_or(AssociationError::NotCreated)?;
98+
self.replay_check(&existing_state)?;
99+
100+
let new_member_address = self.new_member_signature.recover_signer()?;
101+
if new_member_address.ne(&self.new_member_identifier) {
102+
return Err(AssociationError::NewMemberIdSignatureMismatch);
103+
}
104+
105+
let existing_member_identifier = self.existing_member_signature.recover_signer()?;
106+
let recovery_address = existing_state.recovery_address();
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+
if new_member_address.ne(&self.new_member_identifier) {
115+
return Err(AssociationError::Generic(
116+
"new member identifier does not match signature".to_string(),
117+
));
118+
}
119+
120+
// You cannot add yourself
121+
if new_member_address == existing_member_identifier {
122+
return Err(AssociationError::Generic("tried to add self".to_string()));
123+
}
124+
125+
// Only allow LegacyDelegated signatures on XIDs with a nonce of 0
126+
// Otherwise the client should use the regular wallet signature to create
127+
if self.new_member_signature.signature_kind() == SignatureKind::LegacyDelegated {
128+
if existing_state
129+
.xid()
130+
.ne(&generate_xid(&existing_member_identifier.to_string(), &0))
131+
{
132+
return Err(AssociationError::LegacySignatureReuse);
133+
}
134+
}
135+
136+
// Make sure that the signature type lines up with the role
137+
if !allowed_signature_for_kind(
138+
&self.new_member_identifier.kind(),
139+
&self.new_member_signature.signature_kind(),
140+
) {
141+
return Err(AssociationError::SignatureNotAllowed(
142+
self.new_member_identifier.kind(),
143+
self.new_member_signature.signature_kind(),
144+
));
145+
}
146+
147+
let existing_member = existing_state.get(&existing_member_identifier);
148+
149+
let existing_entity_id = match existing_member {
150+
// If there is an existing member of the XID, use that member's ID
151+
Some(member) => member.identifier,
152+
None => {
153+
let recovery_identifier = MemberIdentifier::Address(recovery_address.clone());
154+
// Check if it is a signature from the recovery address, which is allowed to add members
155+
if existing_member_identifier.ne(&recovery_identifier) {
156+
return Err(AssociationError::MissingExistingMember);
157+
}
158+
// BUT, the recovery address has to be used with a real wallet signature, can't be delegated
159+
if self.existing_member_signature.signature_kind() == SignatureKind::LegacyDelegated
160+
{
161+
return Err(AssociationError::LegacySignatureReuse);
162+
}
163+
// If it is a real wallet signature, then it is allowed to add members
164+
recovery_identifier
165+
}
166+
};
167+
168+
let new_member = Member::new(new_member_address, Some(existing_entity_id));
169+
170+
println!("Adding new entity to state {:?}", &new_member);
171+
172+
Ok(existing_state.add(new_member))
173+
}
174+
175+
fn signatures(&self) -> Vec<Vec<u8>> {
176+
vec![
177+
self.existing_member_signature.bytes(),
178+
self.new_member_signature.bytes(),
179+
]
180+
}
181+
}
182+
183+
pub struct RevokeAssociation {
184+
pub client_timestamp_ns: u64,
185+
pub recovery_address_signature: Box<dyn Signature>,
186+
pub revoked_member: MemberIdentifier,
187+
}
188+
189+
impl IdentityAction for RevokeAssociation {
190+
fn update_state(
191+
&self,
192+
maybe_existing_state: Option<AssociationState>,
193+
) -> Result<AssociationState, AssociationError> {
194+
let existing_state = maybe_existing_state.ok_or(AssociationError::NotCreated)?;
195+
self.replay_check(&existing_state)?;
196+
197+
if self.recovery_address_signature.signature_kind() == SignatureKind::LegacyDelegated {
198+
return Err(AssociationError::SignatureNotAllowed(
199+
MemberKind::Address,
200+
SignatureKind::LegacyDelegated,
201+
));
202+
}
203+
// Don't need to check for replay here since revocation is idempotent
204+
let recovery_signer = self.recovery_address_signature.recover_signer()?;
205+
// Make sure there is a recovery address set on the state
206+
let state_recovery_address = existing_state.recovery_address();
207+
208+
// Ensure this message is signed by the recovery address
209+
if recovery_signer.ne(&MemberIdentifier::Address(state_recovery_address.clone())) {
210+
return Err(AssociationError::MissingExistingMember);
211+
}
212+
213+
let installations_to_remove: Vec<Member> = existing_state
214+
.members_by_parent(&self.revoked_member)
215+
.into_iter()
216+
// Only remove children if they are installations
217+
.filter(|child| child.kind() == MemberKind::Installation)
218+
.collect();
219+
220+
// Actually apply the revocation to the parent
221+
let new_state = existing_state.remove(&self.revoked_member);
222+
223+
Ok(installations_to_remove
224+
.iter()
225+
.fold(new_state, |state, installation| {
226+
state.remove(&installation.identifier)
227+
}))
228+
}
229+
230+
fn signatures(&self) -> Vec<Vec<u8>> {
231+
vec![self.recovery_address_signature.bytes()]
232+
}
233+
}
234+
235+
pub enum Action {
236+
CreateInbox(CreateInbox),
237+
AddAssociation(AddAssociation),
238+
RevokeAssociation(RevokeAssociation),
239+
}
240+
241+
impl IdentityAction for Action {
242+
fn update_state(
243+
&self,
244+
existing_state: Option<AssociationState>,
245+
) -> Result<AssociationState, AssociationError> {
246+
match self {
247+
Action::CreateInbox(event) => event.update_state(existing_state),
248+
Action::AddAssociation(event) => event.update_state(existing_state),
249+
Action::RevokeAssociation(event) => event.update_state(existing_state),
250+
}
251+
}
252+
253+
fn signatures(&self) -> Vec<Vec<u8>> {
254+
match self {
255+
Action::CreateInbox(event) => event.signatures(),
256+
Action::AddAssociation(event) => event.signatures(),
257+
Action::RevokeAssociation(event) => event.signatures(),
258+
}
259+
}
260+
}
261+
262+
pub struct IdentityUpdate {
263+
pub actions: Vec<Action>,
264+
}
265+
266+
impl IdentityUpdate {
267+
pub fn new(actions: Vec<Action>) -> Self {
268+
Self { actions }
269+
}
270+
}
271+
272+
impl IdentityAction for IdentityUpdate {
273+
fn update_state(
274+
&self,
275+
existing_state: Option<AssociationState>,
276+
) -> Result<AssociationState, AssociationError> {
277+
let mut state = existing_state.clone();
278+
for action in &self.actions {
279+
state = Some(action.update_state(state)?);
280+
}
281+
282+
let new_state = state.ok_or(AssociationError::NotCreated)?;
283+
284+
// After all the updates in the LogEntry have been processed, add the list of signatures to the state
285+
// so that the signatures can not be re-used in subsequent updates
286+
Ok(new_state.add_seen_signatures(self.signatures()))
287+
}
288+
289+
fn signatures(&self) -> Vec<Vec<u8>> {
290+
self.actions
291+
.iter()
292+
.flat_map(|action| action.signatures())
293+
.collect()
294+
}
295+
}
296+
297+
// Ensure that the type of signature matches the new entity's role.
298+
pub fn allowed_signature_for_kind(role: &MemberKind, signature_kind: &SignatureKind) -> bool {
299+
match role {
300+
MemberKind::Address => match signature_kind {
301+
SignatureKind::Erc191 => true,
302+
SignatureKind::Erc1271 => true,
303+
SignatureKind::InstallationKey => false,
304+
SignatureKind::LegacyDelegated => true,
305+
},
306+
MemberKind::Installation => match signature_kind {
307+
SignatureKind::Erc191 => false,
308+
SignatureKind::Erc1271 => false,
309+
SignatureKind::InstallationKey => true,
310+
SignatureKind::LegacyDelegated => false,
311+
},
312+
}
313+
}

0 commit comments

Comments
 (0)