Sketch of an email design

Hello,

I’ve started looking into how a basic email or message-bus design could be built around the Register type, to start getting myself familiar with working with SAFE. After reading some of the safe_network source code and making little crates that use sn_api::Safe or sn_client::Client to play with Registers (against local test-net), I’ve come up with some questions about the SAFE Network design and some ideas about trying to use Registers for email. First I’ll just state my questions, then describe my idea, then get to issues where the idea leads to the questions.

Questions

  1. Will the Register API support private registers with write-only access for anyone or for select users?

  2. Will the Register API support changing a register’s permissions policy after it’s been created?

  3. Who will pay for writing an entry to a register - the owner of the register or the writing user? Will it be configurable, per register, to choose either way?

  4. How will denial-of-service attacks against a register be mitigated? How will heavy legit traffic on a register be supported?

Idea

An email address is an NRS name associated with a private register, the currently-active register where new emails are to be sent to. A sender (simply a SAFE user) must have write permission on such a register (which could have a simple single blanket permission for anyone to write). (Some kind of single shared mutable persistent data structure stored on the network is needed: 1) to avoid involving additional platforms, to achieve involving only SAFE; and 2) to enable the sending of emails from an unlimited amount of random new strangers to a single advertised address, similar to traditional email. The Register type (or something built on it) seems to be the only such data type provided by SAFE.)

An email is an entry in such a register. A sender sends by writing a new entry, and a recipient receives by getting the new entries.

With register entries each having a limited size (1 KiB), a larger email will have to be stored on the network as a separate file and its register entry will point to that. The details of how an email entry is structured and serialized are unimportant for now.

An NRS name enables changing the currently-active to different registers progressively, which enables dealing with the limited capacity of registers (max 64 Ki entries) becoming full as more emails are received. (Since registers, being Merkle-DAG CRDT, can only grow (even when “deleting” an entry), some way is needed to deal with this limited capacity while keeping the email address constant.) (And of course an NRS name is also human-friendly.)

A user’s app notices when their address’s currently-active register is full and then creates a new one and changes their NRS name to point to that, as needed.

When trying to send an email, a user’s app notices when the recipient’s currently-active register is full and cannot receive (cannot have another entry added) and so must wait and try to send later when the recipient changes their address’s NRS name to point to a new register.

Senders choose how an email (an entry) links to their previous emails (entries) in the DAG, which would seem to fit various forms of conversation threading, like: starting a new subject/thread (link to none), continuing a thread (link to single), forking multiple threads from one (link to single), and merging multiple threads into one (link to multiple). (Of course a back-and-forth conversation involving multiple addresses would require other kinds of links as well.)

(I’m only focusing on what I thought of for something similar to traditional email. I have different ideas for other applications, e.g. a different design with per-sender registers for some kind of message-bus where the set of senders is prearranged or introduced by some other means.)

Issues

  1. How to ensure privacy of a recipient’s register when untrusted senders can write to it? Only the owner (and additionally-permitted users, possibly) should be able to see all its entries. Each sender should only be able to know about their entries but not others’.

    If a sender must also have read permission in order to write, then ensuring privacy would be complicated by requiring some kind of extra layer of encryption of entries’ contents or something. Even if that could be figured out, that would still seem undesirable because then others could see how many entries there are, their DAG links, and maybe their approximate timings of being added, and could save all the entries to be possibly cracked in the distant future.

    If a sender can have write-only permission, i.e. they do not have read permission and cannot perform any read operations on these registers, then this would seem to have nice properties for ensuring privacy. Senders would only be able to know of their own entries (by recording a separate copy of them themselves). Only the recipient(s) would be able to see the entire DAG state, and senders never can and only ever know of their partial state but that seems ok because their write operations only need to be able to link to their own prior entries. I think this still satisfies the monotonic semilattice property required of a CRDT (it’s like each sender is perpetually partitioned and never gets updates from other replicas and only the recipient does).

    Support for such write-only permissions for registers is not provided by the current APIs. The internal types and functions for the permissions look like this could possibly be supported. However, the Client::write_to_register implementation calls Client::get_register which requires read permission, which makes it impossible to use a register created with a write-only policy. The reason it does this is to “get the causality info”, which I can see makes sense for other applications of the Register type. If a similar method was made that didn’t do get_register but instead took a Register as argument (which for my idea would be used to pass a sender’s partial state), this would seem to enable write-only applications. So I’d like to ask what’s been considered in these regards?

    Will the Register API support private registers with write-only access for anyone or for select users?

  2. How to dynamically prevent some users from sending to a register? An email-address owner might want to allow only specific users and block anyone else. It’d be nice to have this, I suppose, in addition to traditional spam filtering performed by a user’s app. We’d want the ability to dynamically change these permissions over time.

    The internal types and functions for the permissions look like this could maybe be supported, but RegisterCmd and the current APIs don’t provide a way to change the permissions after creating a register. I’d like to ask what’s been considered in these regards?

    Will the Register API support changing a register’s permissions policy after it’s been created?

    If not, I think the ability to dynamically change an email address’s permissions could still be achieved by changing the currently-active register to a new one with the desired different permissions.

  3. How to financially afford having an advertised email address that tons of random strangers can send to?

    If the register owner must pay for senders’ added entries, then I’d be concerned that some people won’t want to pay to provide such a contact unless it’s still cheap even with a continuously large amount of emails incoming.

    If the senders can be made to pay for adding entries (in addition to paying for separated files for larger emails, which is already the case of course), then this seems like a better option for most people, as well as being a spam deterrent. It’d also be nice to still have the option for the register owner to pay instead, so that more well-off organizations can foot the bill and avoid a deterrent to people contacting them. So I’d like to ask what’s been considered in these regards?

    Who will pay for writing an entry to a register - the owner of the register or the writing user? Will it be configurable, per register, to choose either way?

  4. How to deal with attackers trying to DoS an email address? How to support heavy legit traffic?

    A chunk, in contrast to a register, will be cached by many extra nodes, beyond its section, in proportion to the demand for getting it (as I’ve gathered) and so should be more resilient against DoS.

    But all attempts to write to a register must be routed to its section (I think) that has only a limited amount of nodes and where the amount of elders (possibly already under heavy load normally) is small. This seems potentially vulnerable to DoS. Even when a register is full, its section will still have to process incoming command messages to some extent to determine that commands to write to a full register should be rejected (I think). What if an attacker sends an endless flood of write commands to a particular register (email address) from very many different users they control? Similarly, what if a particular email address is very popular and has a very high rate of legit emails incoming?

    I surmise that reads of the current value of a register should not be cached by extra nodes (because its value can change), and so all such reads must also be routed to its section, and so this also poses similar issues with a single section handling heavy traffic.

    (I couldn’t find relevant answers by searching either of the forums (I found discussion about DoS related to other, some outdated, aspects). I could be misunderstanding how these aspects work, since I’m not very familiar.)

    How will denial-of-service attacks against a register be mitigated? How will heavy legit traffic on a register be supported?


Thanks for your time. Additional feedback is also welcome (including alternative ideas or saying that I’ve got it all wrong).

5 Likes

Access to a private Register is only allowed to the owner. You can have public Register with write-only permissions for anyone or selected users. See below my comment about encrypting content to make it private.

No, the Policy is set upon creation and cannot be changed afterwards.

I’m not sure that will help as an spammer can create any new key for sending. But spamming your inbox is not for free. Perhaps having one inbox for contacts only, and a second one for unknown/untrusted users. The latter may become more spammed but won’t hurt you as they need to pay for it, whilst you inbox for contacts should stay clean of spams.

The writing user.

The sender can encrypt the entry’s content, as well as the linked immutable file containing the email’s content, to the Register’s owner PK.

Correct, we need to expand the APIs to accept what permissions to set in the Policy when creating a Register.

Besides current state, which is as you noted about causality info, we should be able to do it. Again as you very well explain, for this scenario and type of applications writing to a Register should be still possible without reading it, specially because you are not interested in current state/entries of it. So it’s just a matter of providing some flexibility in the APIs for different type of apps.

4 Likes

Welcome @kcired !

This should be the same for any popular data. Whereby it can be cached along the route. It may mean there’s some delay in seeing updates, perhaps. But it should ensure the data remains available.

Registers are now stored at adults. They should be subject to the same (or similar) caching strategies as chunks (though this may affect new data availability on popular registers… perhaps we need lower TTL for register cache eg). It’s something we’ll need to consider more I think.


If such an API helps us be more flexible for applications atop Safe, then it’s probably a good move :+1:

The “select users” may well be possible when we get multisig setup (the key will be configuring thresholds for ownership, I think).

There may be space for flexibility here down the line. The flows around this will be fleshed out more as we get the DBC integration in.


Something else that may impact the above is the idea of using SafeIds (WebIds… but on safe). With one of these you could specify your inbox registers (or many of them) there, for users to find when they access your Id. I wonder what (if any) impact this could have on spam (if there are many inbox options in many sections).

I also wonder if inboxes registers might be derivable from a user’s PK eg. Such that registers dont exist until they are needed. Potentially giving a much wider name space (but searchable) for registers, as opposed to having to create each one and list it…

Just some :brain: :zap: there.

4 Likes

Thanks for the feedback! My responses will follow as separated posts.

(Looks like the order of my posts got reversed, due to the order of their being approved I’m guessing.)

1 Like

Sorry about that. Discord showed them in reverse order for approving, thats my excuse :wink:

You could edit each one and copy paste into new post in the order you want then delete the previous one.

1 Like

I don’t mind :slightly_smiling_face:. I just thought I should clarify since I’d referenced “follow” and “above”.

I’ll try recreating them as suggested.

1 Like

I was actually able to make a non-owner perform a write successfully to a private register, with both a read-write or a write-only policy:
https://github.com/DerickEddington/explore_safe_network/blob/bda1b1d19a7ea1e216a45ec2d795af7e0d136147/mbus/src/lib.rs#L111-L133

(To support write-only policy, it only required a tiny change to sn_client to add a Client::write_to_register_without_read method:
https://github.com/maidsafe/safe_network/compare/0.2.4-0.1.3-0.62.3-0.58.20-0.60.2-0.53.1...DerickEddington:7a980654904ff96c84eb2ec811675cd528b6fbe8)

If this is a bug (that a non-owner can access a private register), should I open an issue in your tracker?

Or is this something that could remain? I think I’d like to experiment with it.

1 Like

It looks like the purpose of public registers is that their data is simply always readable by everyone forever (like public files). That leaves private registers as the only way to completely prevent readability, and so it’d be nice if they were more flexible.

I know this (with public registers where everyone can read all entries) would probably be very secure, and I know that an additional layer of encryption like PGP could also be done. But it would still seem/feel better to me to have the amount of potential entities who can even try to crack your encrypted emails and analyze your inbox’s DAG be limited (by instead using private registers with permissions where only the owner can read entries) to only those few nodes that processed and saw your data, vs. the entire world being able to forever.

What I meant by “write-only” is that a non-owner cannot perform any read operations. But PublicPolicy::is_action_allowed always allows Action::Read regardless and does not check the permissions map in this case (and PublicPermissions::is_allowed always allows Action::Read), in contrast to PrivatePolicy (and PrivatePermissions). If it weren’t for that, it looks like PublicPolicy with permissions that do not include User::Anyone => Action::Read could achieve write-only.

Another reason I think private registers would be preferable is, IIUC, that enables a register to be destroyed, but public would require registers to be stored by the network for eternity. For applications like my email idea, the ability to destroy seems good because it enables an owner to remove this data from the network if they so desire for reasons of caution and/or to be a nice citizen by reducing the amount of data that nodes are responsible for.

To me, it seems more desirable for them to be able to allow access by non-owner users with the full flexibility that the policy permissions are capable of.

Why does PrivatePolicy::is_action_allowed, which can have permissions for non-owner users, not fallback to also checking for User::Anyone like PublicPolicy does? That prevents having a simple blanket permission for anyone, because, while User::Anyone can be included in the permissions map, only the requester: User is checked for and it looks like command-messages and query-messages cannot come from User::Anyone and so having that user in the permissions map is effectively pointless, for private registers. (I think messages cannot come from User::Anyone because it does not have a key-pair, and so such messages could not be signed as required for commands, and Client has a Keypair (as opposed to supporting User::Anyone) and signs queries as well as commands.)

But a register with permissions that only allow writes from specific users (i.e. User::Anyone => Action::Write is not in the permissions) would deny writes from any others. Isn’t such currently possible?

If, instead, the permissions were to deny only specific users and allow anyone else, then I’d see what you’re saying with being able to create new users to bypass such permissions, and that’s why I did not suggest that case.

That is an interesting idea that I’d also wondered about.

Thanks again!

That’s good to know and sounds helpful for reads as a DoS vector. A delay in seeing the current-value updates is a reasonable tradeoff.

But does that, or something else, help protect against a flood of writes as a DoS vector? Even with registers stored at adults, aren’t the smaller amount of elders still fairly involved in processing writes? Do nodes that are earlier in the routes to a section somehow throttle how much traffic they’ll forward to it and thus stop a flood coming from many directions?

Sounds intriguing. As shown above, I was actually already able to do write-only access for select users with the current safe_network code.

I’ll search for info about those.

I’d also wondered about multiple inbox registers for both spam and DoS resilience.

Do you mean deriving from the recipient’s PK (as opposed to a sender’s) so that a random new stranger who has not been introduced would be able to only know this PK, and a register could be created with an address (an xor-name that the creator can choose) derived from the PK?

Maybe the deriving of an address would also involve the current time, in which case the recipient would not need to be informed of the address. The recipient’s app would just check all registers at all such addresses, for each time interval of some agreed-upon duration, and after retrieving new emails (entries) it would then abandon (ideally destroy) these registers. For further spreading-out, say for DoS resilience, there could be multiple registers per each time interval whose addresses’ derivation additionally involves an index from a range.

But who would create these derived registers? If senders were supposed to, that would be a race condition and the first to create it could set permissions that prevent others from being able to send (write) to it and/or prevent the recipient from accessing it. If the recipient is supposed to create, then their app would have to be online continuously doing this, and it would not be “until they are needed” like you mentioned, and other users could (intentionally or not) steal these registers by creating them before a recipient does and thus prevent it from working. I suspect that all other global sources of information that could be used for address deriving, that both senders and recipients can know without being introduced, would also face this problem.

With many registers for a single logical inbox, the email app would have to be managing this complexity, and I’d vaguely wondered about that possibility.

Thank you also!

1 Like